# frozen_string_literal: true # == Schema Information # # Table name: users # # id :integer not null, primary key # firstname :string # surname :string # email :string(80) default(""), not null # created_at :datetime not null # updated_at :datetime not null # encrypted_password :string default("") # reset_password_token :string # reset_password_sent_at :datetime # remember_created_at :datetime # sign_in_count :integer default("0") # current_sign_in_at :datetime # last_sign_in_at :datetime # current_sign_in_ip :string # last_sign_in_ip :string # confirmation_token :string # confirmed_at :datetime # confirmation_sent_at :datetime # invitation_token :string # invitation_created_at :datetime # invitation_sent_at :datetime # invitation_accepted_at :datetime # other_organisation :string # dmponline3 :boolean # accept_terms :boolean # org_id :integer # api_token :string # invited_by_id :integer # invited_by_type :string # language_id :integer # recovery_email :string # active :boolean default("true") # department_id :integer # # Indexes # # users_email_key (email) UNIQUE # users_language_id_idx (language_id) # users_org_id_idx (org_id) # class User < ActiveRecord::Base include ConditionalUserMailer include ValidationMessages include ValidationValues extend UniqueRandom prepend Dmpopidor::Models::User ## # Devise # Include default devise modules. Others available are: # :token_authenticatable, :confirmable, # :lockable, :timeoutable and :omniauthable devise :invitable, :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable, omniauth_providers: [:shibboleth, :orcid] ## # User Notification Preferences serialize :prefs, Hash # ================ # = Associations = # ================ has_and_belongs_to_many :perms, join_table: :users_perms belongs_to :language belongs_to :org belongs_to :department, required: false has_one :pref has_many :answers has_many :notes has_many :exported_plans has_many :roles, dependent: :destroy has_many :plans, through: :roles has_many :user_identifiers has_many :identifier_schemes, through: :user_identifiers has_and_belongs_to_many :notifications, dependent: :destroy, join_table: "notification_acknowledgements" # =============== # = Validations = # =============== validates :active, inclusion: { in: BOOLEAN_VALUES, message: INCLUSION_MESSAGE } validates :firstname, presence: { message: PRESENCE_MESSAGE } validates :surname, presence: { message: PRESENCE_MESSAGE } validates :org, presence: { message: PRESENCE_MESSAGE } # ========== # = Scopes = # ========== default_scope { includes(:org, :perms) } # Retrieves all of the org_admins for the specified org scope :org_admins, -> (org_id) { joins(:perms).where("users.org_id = ? AND perms.name IN (?) AND " + "users.active = ?", org_id, ["grant_permissions", "modify_templates", "modify_guidance", "change_org_details"], true) } scope :search, -> (term) { search_pattern = "%#{term}%" # MySQL does not support standard string concatenation and since concat_ws # or concat functions do not exist for sqlite, we have to come up with this # conditional if ActiveRecord::Base.connection.adapter_name == "Mysql2" where("lower(concat_ws(' ', firstname, surname)) LIKE lower(?) OR " + "lower(email) LIKE lower(?)", search_pattern, search_pattern) else where("lower(firstname || ' ' || surname) LIKE lower(?) OR " + "email LIKE lower(?)", search_pattern, search_pattern) end } # ============= # = Callbacks = # ============= before_update :clear_other_organisation, :if => proc { org_id_changed? && org_id != Org.find_by(is_other: true).id } before_update :clear_department_id, if: :org_id_changed? after_update :delete_perms!, if: :org_id_changed?, unless: :can_change_org? after_update :remove_token!, if: :org_id_changed?, unless: :can_change_org? # ================= # = Class methods = # ================= ## # Load the user based on the scheme and id provided by the Omniauth call def self.from_omniauth(auth) joins(user_identifiers: :identifier_scheme) .where(user_identifiers: { identifier: auth.uid }, identifier_schemes: { name: auth.provider.downcase }).first end def self.to_csv(users) User::AtCsv.new(users).to_csv end # =========================== # = Public instance methods = # =========================== # This method uses Devise's built-in handling for inactive users # # Returns Boolean def active_for_authentication? super && active? end # EVALUATE CLASS AND INSTANCE METHODS BELOW # # What do they do? do they do it efficiently, and do we need them? # Determines the locale set for the user or the organisation he/she belongs # # Returns String # Returns nil def get_locale if !self.language.nil? self.language.abbreviation elsif !self.org.nil? self.org.get_locale else nil end end # Gives either the name of the user, or the email if name unspecified # # user_email - Use the email if there is no firstname or surname (defaults: true) # # Returns String def name(use_email = true) if (firstname.blank? && surname.blank?) || use_email then email else name = "#{firstname} #{surname}" name.strip end end # The user's identifier for the specified scheme name # # scheme - The identifier scheme name (e.g. ORCID) # # Returns UserIdentifier def identifier_for(scheme) user_identifiers.where(identifier_scheme: scheme).first end # Checks if the user is a super admin. If the user has any privelege which requires # them to see the super admin page then they are a super admin. # # Returns Boolean def can_super_admin? self.can_add_orgs? || self.can_grant_api_to_orgs? || self.can_change_org? end # Checks if the user is an organisation admin if the user has any privlege which # requires them to see the org-admin pages then they are an org admin. # # Returns Boolean def can_org_admin? self.can_grant_permissions? || self.can_modify_guidance? || self.can_modify_templates? || self.can_modify_org_details? || self.can_review_plans? end # Can the User add new organisations? # # Returns Boolean def can_add_orgs? perms.include? Perm.add_orgs end # Can the User change their organisation affiliations? # # Returns Boolean def can_change_org? perms.include? Perm.change_affiliation end # Can the User can grant their permissions to others? # # Returns Boolean def can_grant_permissions? perms.include? Perm.grant_permissions end # Can the User modify organisation templates? # # Returns Boolean def can_modify_templates? self.perms.include? Perm.modify_templates end # Can the User modify organisation guidance? # # Returns Boolean def can_modify_guidance? perms.include? Perm.modify_guidance end # Can the User use the API? # # Returns Boolean def can_use_api? perms.include? Perm.use_api end # Can the User modify their org's details? # # Returns Boolean def can_modify_org_details? perms.include? Perm.change_org_details end ## # Can the User grant the api to organisations? # # Returns Boolean def can_grant_api_to_orgs? perms.include? Perm.grant_api end ## # Can the user review their organisation's plans? # # Returns Boolean def can_review_plans? perms.include? Perm.review_plans end # Removes the api_token from the user # # Returns nil # Returns Boolean def remove_token! return if new_record? update_column(:api_token, nil) end # Generates a new token for the user unless the user already has a token. # # Returns nil # Returns Boolean def keep_or_generate_token! if api_token.nil? || api_token.empty? new_token = User.unique_random(field_name: 'api_token') update_column(:api_token, new_token) unless new_record? end end # The User's preferences for a given base key # # Returns Hash def get_preferences(key) defaults = Pref.default_settings[key.to_sym] || Pref.default_settings[key.to_s] if pref.present? existing = pref.settings[key.to_s].deep_symbolize_keys # Check for new preferences defaults.keys.each do |grp| defaults[grp].keys.each do |pref, v| # If the group isn't present in the saved values add all of it's preferences existing[grp] = defaults[grp] if existing[grp].nil? # If the preference isn't present in the saved values add the default existing[grp][pref] = defaults[grp][pref] if existing[grp][pref].nil? end end existing else defaults end end # Override devise_invitable email title def deliver_invitation(options = {}) super(options.merge(subject: d_('dmpopidor', '%{user_name} has shared a Data Management Plan with you in %{tool_name}') % { user_name: self.invited_by.name(false), tool_name: Rails.configuration.branding[:application][:name] })) end # Case insensitive search over User model # # field - The name of the field being queried # val - The String to search for, case insensitive. val is duck typed to check # whether or not downcase method exist. # # Returns ActiveRecord::Relation # Raises ArgumentError def self.where_case_insensitive(field, val) unless columns.map(&:name).include?(field.to_s) raise ArgumentError, "Field #{field} is not present on users table" end User.where("LOWER(#{field}) = :value", value: val.to_s.downcase) end # Acknowledge a Notification # # notification - Notification to acknowledge # # Returns ActiveRecord::Associations::CollectionProxy # Returns nil def acknowledge(notification) notifications << notification if notification.dismissable? end # remove personal data from the user account and save # leave account in-place, with org for statistics (until we refactor those) # # Returns boolean # SEE MODULE def archive self.firstname = 'Deleted' self.surname = 'User' self.email = User.unique_random(field_name: 'email', prefix: 'user_', suffix: Rails.configuration.branding[:application].fetch(:archived_accounts_email_suffix, '@example.org'), length: 5) self.recovery_email = nil self.api_token = nil self.encrypted_password = nil self.last_sign_in_ip = nil self.current_sign_in_ip = nil self.active = false return self.save end def merge(to_be_merged) # merge logic # => answers -> map id to_be_merged.answers.update_all(user_id: self.id) # => notes -> map id to_be_merged.notes.update_all(user_id: self.id) # => plans -> map on id roles to_be_merged.roles.update_all(user_id: self.id) # => prefs -> Keep's from self # => auths -> map onto keep id only if keep does not have the identifier to_be_merged.user_identifiers. where.not(identifier_scheme_id: self.identifier_scheme_ids) .update_all(user_id: self.id) # => ignore any perms the deleted user has to_be_merged.destroy end private # ============================ # = Private instance methods = # ============================ def delete_perms! perms.destroy_all end def clear_other_organisation self.other_organisation = nil end def clear_department_id self.department_id = nil end end