X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/0e5198142fdba0ce2af6eb2852d45dff46ffb2e2..3a6b1a17f1b073e381b053b52e3cb0bb9c81d249:/services/api/app/models/user.rb diff --git a/services/api/app/models/user.rb b/services/api/app/models/user.rb index 9363cc4f02..52d36ac577 100644 --- a/services/api/app/models/user.rb +++ b/services/api/app/models/user.rb @@ -1,3 +1,7 @@ +# Copyright (C) The Arvados Authors. All rights reserved. +# +# SPDX-License-Identifier: AGPL-3.0 + require 'can_be_an_owner' class User < ArvadosModel @@ -5,42 +9,57 @@ class User < ArvadosModel include KindAndEtag include CommonApiTemplate include CanBeAnOwner + extend CurrentApiClient serialize :prefs, Hash has_many :api_client_authorizations validates(:username, format: { - with: /^[A-Za-z][A-Za-z0-9]*$/, + with: /\A[A-Za-z][A-Za-z0-9]*\z/, message: "must begin with a letter and contain only alphanumerics", }, uniqueness: true, allow_nil: true) + validate :must_unsetup_to_deactivate + validate :identity_url_nil_if_empty before_update :prevent_privilege_escalation before_update :prevent_inactive_admin - before_update :verify_repositories_empty, :if => Proc.new { |user| - user.username.nil? and user.username_changed? + before_update :verify_repositories_empty, :if => Proc.new { + username.nil? and username_changed? } + after_update :setup_on_activate + before_create :check_auto_admin - before_create :set_initial_username, :if => Proc.new { |user| - user.username.nil? and user.email + before_create :set_initial_username, :if => Proc.new { + username.nil? and email } + after_create :after_ownership_change + after_create :setup_on_activate after_create :add_system_group_permission_link - after_create :auto_setup_new_user, :if => Proc.new { |user| - Rails.configuration.auto_setup_new_users and - (user.uuid != system_user_uuid) and - (user.uuid != anonymous_user_uuid) + after_create :auto_setup_new_user, :if => Proc.new { + Rails.configuration.Users.AutoSetupNewUsers and + (uuid != system_user_uuid) and + (uuid != anonymous_user_uuid) and + (uuid[0..4] == Rails.configuration.ClusterID) } after_create :send_admin_notifications + + before_update :before_ownership_change + after_update :after_ownership_change after_update :send_profile_created_notification - after_update :sync_repository_names, :if => Proc.new { |user| - (user.uuid != system_user_uuid) and - user.username_changed? and - (not user.username_was.nil?) + after_update :sync_repository_names, :if => Proc.new { + (uuid != system_user_uuid) and + saved_change_to_username? and + (not username_before_last_save.nil?) } + before_destroy :clear_permissions + after_destroy :remove_self_from_permissions has_many :authorized_keys, :foreign_key => :authorized_user_uuid, :primary_key => :uuid has_many :repositories, foreign_key: :owner_uuid, primary_key: :uuid + default_scope { where('redirect_to_user_uuid is null') } + api_accessible :user, extend: :common do |t| t.add :email t.add :username @@ -53,22 +72,39 @@ class User < ArvadosModel t.add :is_invited t.add :prefs t.add :writable_by + t.add :can_write + t.add :can_manage end ALL_PERMISSIONS = {read: true, write: true, manage: true} + # Map numeric permission levels (see lib/create_permission_view.sql) + # back to read/write/manage flags. + PERMS_FOR_VAL = + [{}, + {read: true}, + {read: true, write: true}, + {read: true, write: true, manage: true}] + + VAL_FOR_PERM = + {:read => 1, + :write => 2, + :unfreeze => 3, + :manage => 3} + + def full_name "#{first_name} #{last_name}".strip end def is_invited !!(self.is_active || - Rails.configuration.new_users_are_active || - self.groups_i_can(:read).select { |x| x.match /-f+$/ }.first) + Rails.configuration.Users.NewUsersAreActive || + self.groups_i_can(:read).select { |x| x.match(/-f+$/) }.first) end def groups_i_can(verb) - my_groups = self.group_permissions.select { |uuid, mask| mask[verb] }.keys + my_groups = self.group_permissions(VAL_FOR_PERM[verb]).keys if verb == :read my_groups << anonymous_group_uuid end @@ -76,7 +112,6 @@ class User < ArvadosModel end def can?(actions) - return true if is_admin actions.each do |action, target| unless target.nil? if target.respond_to? :uuid @@ -87,180 +122,262 @@ class User < ArvadosModel end end next if target_uuid == self.uuid - next if (group_permissions[target_uuid] and - group_permissions[target_uuid][action]) - if target.respond_to? :owner_uuid - next if target.owner_uuid == self.uuid - next if (group_permissions[target.owner_uuid] and - group_permissions[target.owner_uuid][action]) + + if action == :write && target && !target.new_record? && + target.respond_to?(:frozen_by_uuid) && + target.frozen_by_uuid_was + # Just an optimization to skip the PERMISSION_VIEW and + # FrozenGroup queries below + return false end - sufficient_perms = case action - when :manage - ['can_manage'] - when :write - ['can_manage', 'can_write'] - when :read - ['can_manage', 'can_write', 'can_read'] - else - # (Skip this kind of permission opportunity - # if action is an unknown permission type) - end - if sufficient_perms - # Check permission links with head_uuid pointing directly at - # the target object. If target is a Group, this is redundant - # and will fail except [a] if permission caching is broken or - # [b] during a race condition, where a permission link has - # *just* been added. - if Link.where(link_class: 'permission', - name: sufficient_perms, - tail_uuid: groups_i_can(action) + [self.uuid], - head_uuid: target_uuid).any? - next + + target_owner_uuid = target.owner_uuid if target.respond_to? :owner_uuid + + user_uuids_subquery = USER_UUIDS_SUBQUERY_TEMPLATE % {user: "$1", perm_level: "$3"} + + if !is_admin && !ActiveRecord::Base.connection. + exec_query(%{ +SELECT 1 FROM #{PERMISSION_VIEW} + WHERE user_uuid in (#{user_uuids_subquery}) and + ((target_uuid = $2 and perm_level >= $3) + or (target_uuid = $4 and perm_level >= $3 and traverse_owned)) +}, + # "name" arg is a query label that appears in logs: + "user_can_query", + [[nil, self.uuid], + [nil, target_uuid], + [nil, VAL_FOR_PERM[action]], + [nil, target_owner_uuid]] + ).any? + return false + end + + if action == :write + if FrozenGroup.where(uuid: [target_uuid, target_owner_uuid]).any? + # self or parent is frozen + return false + end + elsif action == :unfreeze + # "unfreeze" permission means "can write, but only if + # explicitly un-freezing at the same time" (see + # ArvadosModel#ensure_owner_uuid_is_permitted). If the + # permission query above passed the permission level of + # :unfreeze (which is the same as :manage), and the parent + # isn't also frozen, then un-freeze is allowed. + if FrozenGroup.where(uuid: target_owner_uuid).any? + return false end end - return false end true end - def self.invalidate_permissions_cache(timestamp=nil) - if Rails.configuration.async_permissions_update - timestamp = DbCurrentTime::db_current_time.to_i if timestamp.nil? - connection.execute "NOTIFY invalidate_permissions_cache, '#{timestamp}'" - else - Rails.cache.delete_matched(/^groups_for_user_/) + def before_ownership_change + if owner_uuid_changed? and !self.owner_uuid_was.nil? + MaterializedPermission.where(user_uuid: owner_uuid_was, target_uuid: uuid).delete_all + update_permissions self.owner_uuid_was, self.uuid, REVOKE_PERM end end - # Return a hash of {group_uuid: perm_hash} where perm_hash[:read] - # and perm_hash[:write] are true if this user can read and write - # objects owned by group_uuid. + def after_ownership_change + if saved_change_to_owner_uuid? + update_permissions self.owner_uuid, self.uuid, CAN_MANAGE_PERM + end + end + + def clear_permissions + MaterializedPermission.where("user_uuid = ? and target_uuid != ?", uuid, uuid).delete_all + end + + def forget_cached_group_perms + @group_perms = nil + end + + def remove_self_from_permissions + MaterializedPermission.where("target_uuid = ?", uuid).delete_all + check_permissions_against_full_refresh + end + + # Return a hash of {user_uuid: group_perms} # - # The permission graph is built by repeatedly enumerating all - # permission links reachable from self.uuid, and then calling - # search_permissions - def calculate_group_permissions - permissions_from = {} - todo = {self.uuid => true} - done = {} - # Build the equivalence class of permissions starting with - # self.uuid. On each iteration of this loop, todo contains - # the next set of uuids in the permission equivalence class - # to evaluate. - while !todo.empty? - lookup_uuids = todo.keys - lookup_uuids.each do |uuid| done[uuid] = true end - todo = {} - newgroups = [] - # include all groups owned by the current set of uuids. - Group.where('owner_uuid in (?)', lookup_uuids).each do |group| - newgroups << [group.owner_uuid, group.uuid, 'can_manage'] - end - # add any permission links from the current lookup_uuids to a Group. - Link.where('link_class = ? and tail_uuid in (?) and ' \ - '(head_uuid like ? or (name = ? and head_uuid like ?))', - 'permission', - lookup_uuids, - Group.uuid_like_pattern, - 'can_manage', - User.uuid_like_pattern).each do |link| - newgroups << [link.tail_uuid, link.head_uuid, link.name] - end - newgroups.each do |tail_uuid, head_uuid, perm_name| - unless done.has_key? head_uuid - todo[head_uuid] = true - end - link_permissions = {} - case perm_name - when 'can_read' - link_permissions = {read:true} - when 'can_write' - link_permissions = {read:true,write:true} - when 'can_manage' - link_permissions = ALL_PERMISSIONS - end - permissions_from[tail_uuid] ||= {} - permissions_from[tail_uuid][head_uuid] ||= {} - link_permissions.each do |k,v| - permissions_from[tail_uuid][head_uuid][k] ||= v - end - end - end - perms = search_permissions(self.uuid, permissions_from) - Rails.cache.write "groups_for_user_#{self.uuid}", perms - perms + # note: this does not account for permissions that a user gains by + # having can_manage on another user. + def self.all_group_permissions + all_perms = {} + ActiveRecord::Base.connection. + exec_query(%{ +SELECT user_uuid, target_uuid, perm_level + FROM #{PERMISSION_VIEW} + WHERE traverse_owned +}, + # "name" arg is a query label that appears in logs: + "all_group_permissions"). + rows.each do |user_uuid, group_uuid, max_p_val| + all_perms[user_uuid] ||= {} + all_perms[user_uuid][group_uuid] = PERMS_FOR_VAL[max_p_val.to_i] + end + all_perms end # Return a hash of {group_uuid: perm_hash} where perm_hash[:read] # and perm_hash[:write] are true if this user can read and write # objects owned by group_uuid. - def group_permissions - r = Rails.cache.read "groups_for_user_#{self.uuid}" - if r.nil? - if Rails.configuration.async_permissions_update - while r.nil? - sleep(0.1) - r = Rails.cache.read "groups_for_user_#{self.uuid}" - end - else - r = calculate_group_permissions + def group_permissions(level=1) + @group_perms ||= {} + if @group_perms.empty? + user_uuids_subquery = USER_UUIDS_SUBQUERY_TEMPLATE % {user: "$1", perm_level: 1} + + ActiveRecord::Base.connection. + exec_query(%{ +SELECT target_uuid, perm_level + FROM #{PERMISSION_VIEW} + WHERE user_uuid in (#{user_uuids_subquery}) and perm_level >= 1 +}, + # "name" arg is a query label that appears in logs: + "User.group_permissions", + # "binds" arg is an array of [col_id, value] for '$1' vars: + [[nil, uuid]]). + rows.each do |group_uuid, max_p_val| + @group_perms[group_uuid] = PERMS_FOR_VAL[max_p_val.to_i] end end - r - end - def self.setup(user, openid_prefix, repo_name=nil, vm_uuid=nil) - return user.setup_repo_vm_links(repo_name, vm_uuid, openid_prefix) + case level + when 1 + @group_perms + when 2 + @group_perms.select {|k,v| v[:write] } + when 3 + @group_perms.select {|k,v| v[:manage] } + else + raise "level must be 1, 2 or 3" + end end # create links - def setup_repo_vm_links(repo_name, vm_uuid, openid_prefix) - oid_login_perm = create_oid_login_perm openid_prefix - repo_perm = create_user_repo_link repo_name - vm_login_perm = create_vm_login_permission_link vm_uuid, username - group_perm = create_user_group_link + def setup(repo_name: nil, vm_uuid: nil, send_notification_email: nil) + newly_invited = Link.where(tail_uuid: self.uuid, + head_uuid: all_users_group_uuid, + link_class: 'permission', + name: 'can_read').empty? + + # Add can_read link from this user to "all users" which makes this + # user "invited", and (depending on config) a link in the opposite + # direction which makes this user visible to other users. + group_perms = add_to_all_users_group + + # Add git repo + repo_perm = if (!repo_name.nil? || Rails.configuration.Users.AutoSetupNewUsersWithRepository) and !username.nil? + repo_name ||= "#{username}/#{username}" + create_user_repo_link repo_name + end + + # Add virtual machine + if vm_uuid.nil? and !Rails.configuration.Users.AutoSetupNewUsersWithVmUUID.empty? + vm_uuid = Rails.configuration.Users.AutoSetupNewUsersWithVmUUID + end + + vm_login_perm = if vm_uuid && username + create_vm_login_permission_link(vm_uuid, username) + end + + # Send welcome email + if send_notification_email.nil? + send_notification_email = Rails.configuration.Mail.SendUserSetupNotificationEmail + end + + if newly_invited and send_notification_email and !Rails.configuration.Users.UserSetupMailText.empty? + begin + UserNotifier.account_is_setup(self).deliver_now + rescue => e + logger.warn "Failed to send email to #{self.email}: #{e}" + end + end - return [oid_login_perm, repo_perm, vm_login_perm, group_perm, self].compact + forget_cached_group_perms + + return [repo_perm, vm_login_perm, *group_perms, self].compact end # delete user signatures, login, repo, and vm perms, and mark as inactive def unsetup # delete oid_login_perms for this user - Link.destroy_all(tail_uuid: self.email, + # + # note: these permission links are obsolete, they have no effect + # on anything and they are not created for new users. + Link.where(tail_uuid: self.email, link_class: 'permission', - name: 'can_login') + name: 'can_login').destroy_all # delete repo_perms for this user - Link.destroy_all(tail_uuid: self.uuid, + Link.where(tail_uuid: self.uuid, link_class: 'permission', - name: 'can_manage') + name: 'can_manage').destroy_all # delete vm_login_perms for this user - Link.destroy_all(tail_uuid: self.uuid, + Link.where(tail_uuid: self.uuid, link_class: 'permission', - name: 'can_login') + name: 'can_login').destroy_all # delete "All users" group read permissions for this user - group = Group.where(name: 'All users').select do |g| - g[:uuid].match /-f+$/ - end.first - Link.destroy_all(tail_uuid: self.uuid, - head_uuid: group[:uuid], + Link.where(tail_uuid: self.uuid, + head_uuid: all_users_group_uuid, link_class: 'permission', - name: 'can_read') + name: 'can_read').destroy_all # delete any signatures by this user - Link.destroy_all(link_class: 'signature', - tail_uuid: self.uuid) + Link.where(link_class: 'signature', + tail_uuid: self.uuid).destroy_all + + # delete tokens for this user + ApiClientAuthorization.where(user_id: self.id).destroy_all + # delete ssh keys for this user + AuthorizedKey.where(owner_uuid: self.uuid).destroy_all + AuthorizedKey.where(authorized_user_uuid: self.uuid).destroy_all # delete user preferences (including profile) self.prefs = {} # mark the user as inactive + self.is_admin = false # can't be admin and inactive self.is_active = false + forget_cached_group_perms self.save! end + def must_unsetup_to_deactivate + if !self.new_record? && + self.uuid[0..4] == Rails.configuration.Login.LoginCluster && + self.uuid[0..4] != Rails.configuration.ClusterID + # OK to update our local record to whatever the LoginCluster + # reports, because self-activate is not allowed. + return + elsif self.is_active_changed? && + self.is_active_was && + !self.is_active + + # When a user is set up, they are added to the "All users" + # group. A user that is part of the "All users" group is + # allowed to self-activate. + # + # It doesn't make sense to deactivate a user (set is_active = + # false) without first removing them from the "All users" group, + # because they would be able to immediately reactivate + # themselves. + # + # The 'unsetup' method removes the user from the "All users" + # group (and also sets is_active = false) so send a message + # explaining the correct way to deactivate a user. + # + if Link.where(tail_uuid: self.uuid, + head_uuid: all_users_group_uuid, + link_class: 'permission', + name: 'can_read').any? + errors.add :is_active, "cannot be set to false directly, use the 'Deactivate' button on Workbench, or the 'unsetup' API call" + end + end + end + def set_initial_username(requested: false) if !requested.is_a?(String) || requested.empty? email_parts = email.partition("@") @@ -280,14 +397,224 @@ class User < ArvadosModel end end + # Move this user's (i.e., self's) owned items to new_owner_uuid and + # new_user_uuid (for things normally owned directly by the user). + # + # If redirect_auth is true, also reassign auth tokens and ssh keys, + # and redirect this account to redirect_to_user_uuid, i.e., when a + # caller authenticates to this account in the future, the account + # redirect_to_user_uuid account will be used instead. + # + # current_user must have admin privileges, i.e., the caller is + # responsible for checking permission to do this. + def merge(new_owner_uuid:, new_user_uuid:, redirect_to_new_user:) + raise PermissionDeniedError if !current_user.andand.is_admin + raise "Missing new_owner_uuid" if !new_owner_uuid + raise "Missing new_user_uuid" if !new_user_uuid + transaction(requires_new: true) do + reload + raise "cannot merge an already merged user" if self.redirect_to_user_uuid + + new_user = User.where(uuid: new_user_uuid).first + raise "user does not exist" if !new_user + raise "cannot merge to an already merged user" if new_user.redirect_to_user_uuid + + self.clear_permissions + new_user.clear_permissions + + # If 'self' is a remote user, don't transfer authorizations + # (i.e. ability to access the account) to the new user, because + # that gives the remote site the ability to access the 'new' + # user account that takes over the 'self' account. + # + # If 'self' is a local user, it is okay to transfer + # authorizations, even if the 'new' user is a remote account, + # because the remote site does not gain the ability to access an + # account it could not before. + + if redirect_to_new_user and self.uuid[0..4] == Rails.configuration.ClusterID + # Existing API tokens and ssh keys are updated to authenticate + # to the new user. + ApiClientAuthorization. + where(user_id: id). + update_all(user_id: new_user.id) + + user_updates = [ + [AuthorizedKey, :owner_uuid], + [AuthorizedKey, :authorized_user_uuid], + [Link, :owner_uuid], + [Link, :tail_uuid], + [Link, :head_uuid], + ] + else + # Destroy API tokens and ssh keys associated with the old + # user. + ApiClientAuthorization.where(user_id: id).destroy_all + AuthorizedKey.where(owner_uuid: uuid).destroy_all + AuthorizedKey.where(authorized_user_uuid: uuid).destroy_all + user_updates = [ + [Link, :owner_uuid], + [Link, :tail_uuid] + ] + end + + # References to the old user UUID in the context of a user ID + # (rather than a "home project" in the project hierarchy) are + # updated to point to the new user. + user_updates.each do |klass, column| + klass.where(column => uuid).update_all(column => new_user.uuid) + end + + # Need to update repository names to new username + if username + old_repo_name_re = /^#{Regexp.escape(username)}\// + Repository.where(:owner_uuid => uuid).each do |repo| + repo.owner_uuid = new_user.uuid + repo_name_sub = "#{new_user.username}/" + name = repo.name.sub(old_repo_name_re, repo_name_sub) + while (conflict = Repository.where(:name => name).first) != nil + repo_name_sub += "migrated" + name = repo.name.sub(old_repo_name_re, repo_name_sub) + end + repo.name = name + repo.save! + end + end + + # References to the merged user's "home project" are updated to + # point to new_owner_uuid. + ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |klass| + next if [ApiClientAuthorization, + AuthorizedKey, + Link, + Log, + Repository].include?(klass) + next if !klass.columns.collect(&:name).include?('owner_uuid') + klass.where(owner_uuid: uuid).update_all(owner_uuid: new_owner_uuid) + end + + if redirect_to_new_user + update_attributes!(redirect_to_user_uuid: new_user.uuid, username: nil) + end + skip_check_permissions_against_full_refresh do + update_permissions self.uuid, self.uuid, CAN_MANAGE_PERM + update_permissions new_user.uuid, new_user.uuid, CAN_MANAGE_PERM + update_permissions new_user.owner_uuid, new_user.uuid, CAN_MANAGE_PERM + end + update_permissions self.owner_uuid, self.uuid, CAN_MANAGE_PERM + end + end + + def redirects_to + user = self + redirects = 0 + while (uuid = user.redirect_to_user_uuid) + break if uuid.empty? + nextuser = User.unscoped.find_by_uuid(uuid) + if !nextuser + raise Exception.new("user uuid #{user.uuid} redirects to nonexistent uuid '#{uuid}'") + end + user = nextuser + redirects += 1 + if redirects > 15 + raise "Starting from #{self.uuid} redirect_to_user_uuid exceeded maximum number of redirects" + end + end + user + end + + def self.register info + # login info expected fields, all can be optional but at minimum + # must supply either 'identity_url' or 'email' + # + # email + # first_name + # last_name + # username + # alternate_emails + # identity_url + + primary_user = nil + + # local database + identity_url = info['identity_url'] + + if identity_url && identity_url.length > 0 + # Only local users can create sessions, hence uuid_like_pattern + # here. + user = User.unscoped.where('identity_url = ? and uuid like ?', + identity_url, + User.uuid_like_pattern).first + primary_user = user.redirects_to if user + end + + if !primary_user + # identity url is unset or didn't find matching record. + emails = [info['email']] + (info['alternate_emails'] || []) + emails.select! {|em| !em.nil? && !em.empty?} + + User.unscoped.where('email in (?) and uuid like ?', + emails, + User.uuid_like_pattern).each do |user| + if !primary_user + primary_user = user.redirects_to + elsif primary_user.uuid != user.redirects_to.uuid + raise "Ambiguous email address, directs to both #{primary_user.uuid} and #{user.redirects_to.uuid}" + end + end + end + + if !primary_user + # New user registration + primary_user = User.new(:owner_uuid => system_user_uuid, + :is_admin => false, + :is_active => Rails.configuration.Users.NewUsersAreActive) + + primary_user.set_initial_username(requested: info['username']) if info['username'] && !info['username'].blank? + primary_user.identity_url = info['identity_url'] if identity_url + end + + primary_user.email = info['email'] if info['email'] + primary_user.first_name = info['first_name'] if info['first_name'] + primary_user.last_name = info['last_name'] if info['last_name'] + + if (!primary_user.email or primary_user.email.empty?) and (!primary_user.identity_url or primary_user.identity_url.empty?) + raise "Must have supply at least one of 'email' or 'identity_url' to User.register" + end + + act_as_system_user do + primary_user.save! + end + + primary_user + end + protected + def self.attributes_required_columns + super.merge( + 'can_write' => ['owner_uuid', 'uuid'], + 'can_manage' => ['owner_uuid', 'uuid'], + ) + end + + def change_all_uuid_refs(old_uuid:, new_uuid:) + ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |klass| + klass.columns.each do |col| + if col.name.end_with?('_uuid') + column = col.name.to_sym + klass.where(column => old_uuid).update_all(column => new_uuid) + end + end + end + end + def ensure_ownership_path_leads_to_user true end def permission_to_update - if username_changed? + if username_changed? || redirect_to_user_uuid_changed? || email_changed? current_user.andand.is_admin else # users must be able to update themselves (even if they are @@ -298,16 +625,17 @@ class User < ArvadosModel def permission_to_create current_user.andand.is_admin or - (self == current_user and - self.is_active == Rails.configuration.new_users_are_active) + (self == current_user && + self.redirect_to_user_uuid.nil? && + self.is_active == Rails.configuration.Users.NewUsersAreActive) end def check_auto_admin return if self.uuid.end_with?('anonymouspublic') if (User.where("email = ?",self.email).where(:is_admin => true).count == 0 and - Rails.configuration.auto_admin_user and self.email == Rails.configuration.auto_admin_user) or + !Rails.configuration.Users.AutoAdminUserWithEmail.empty? and self.email == Rails.configuration.Users["AutoAdminUserWithEmail"]) or (User.where("uuid not like '%-000000000000000'").where(:is_admin => true).count == 0 and - Rails.configuration.auto_admin_first_user) + Rails.configuration.Users.AutoAdminFirstUser) self.is_admin = true self.is_active = true end @@ -322,13 +650,13 @@ class User < ArvadosModel quoted_name = self.class.connection.quote_string(basename) next_username = basename next_suffix = 1 - while Rails.configuration.auto_setup_name_blacklist.include?(next_username) + while Rails.configuration.Users.AutoSetupUsernameBlacklist[next_username] next_suffix += 1 next_username = "%s%i" % [basename, next_suffix] end 0.upto(6).each do |suffix_len| pattern = "%s%s" % [quoted_name, "_" * suffix_len] - self.class. + self.class.unscoped. where("username like '#{pattern}'"). select(:username). order('username asc'). @@ -351,7 +679,7 @@ class User < ArvadosModel end if self.is_active_changed? if self.is_active != self.is_active_was - logger.warn "User #{current_user.uuid} tried to change is_active from #{self.is_admin_was} to #{self.is_admin} for #{self.uuid}" + logger.warn "User #{current_user.uuid} tried to change is_active from #{self.is_active_was} to #{self.is_active} for #{self.uuid}" self.is_active = self.is_active_was end end @@ -391,30 +719,6 @@ class User < ArvadosModel merged end - def create_oid_login_perm (openid_prefix) - login_perm_props = { "identity_url_prefix" => openid_prefix} - - # Check oid_login_perm - oid_login_perms = Link.where(tail_uuid: self.email, - link_class: 'permission', - name: 'can_login').where("head_uuid = ?", self.uuid) - - if !oid_login_perms.any? - # create openid login permission - oid_login_perm = Link.create(link_class: 'permission', - name: 'can_login', - tail_uuid: self.email, - head_uuid: self.uuid, - properties: login_perm_props - ) - logger.info { "openid login permission: " + oid_login_perm[:uuid] } - else - oid_login_perm = oid_login_perms.first - end - - return oid_login_perm - end - def create_user_repo_link(repo_name) # repo_name is optional if not repo_name @@ -434,15 +738,12 @@ class User < ArvadosModel # create login permission for the given vm_uuid, if it does not already exist def create_vm_login_permission_link(vm_uuid, repo_name) # vm uuid is optional - if vm_uuid - vm = VirtualMachine.where(uuid: vm_uuid).first + return if vm_uuid == "" - if not vm - logger.warn "Could not find virtual machine for #{vm_uuid.inspect}" - raise "No vm found for #{vm_uuid}" - end - else - return + vm = VirtualMachine.where(uuid: vm_uuid).first + if !vm + logger.warn "Could not find virtual machine for #{vm_uuid.inspect}" + raise "No vm found for #{vm_uuid}" end logger.info { "vm uuid: " + vm[:uuid] } @@ -463,16 +764,26 @@ class User < ArvadosModel login_perm end - # add the user to the 'All users' group - def create_user_group_link - return (Link.where(tail_uuid: self.uuid, - head_uuid: all_users_group[:uuid], + def add_to_all_users_group + resp = [Link.where(tail_uuid: self.uuid, + head_uuid: all_users_group_uuid, link_class: 'permission', - name: 'can_read').first or + name: 'can_read').first || Link.create(tail_uuid: self.uuid, - head_uuid: all_users_group[:uuid], + head_uuid: all_users_group_uuid, link_class: 'permission', - name: 'can_read')) + name: 'can_read')] + if Rails.configuration.Users.ActivatedUsersAreVisibleToOthers + resp += [Link.where(tail_uuid: all_users_group_uuid, + head_uuid: self.uuid, + link_class: 'permission', + name: 'can_read').first || + Link.create(tail_uuid: all_users_group_uuid, + head_uuid: self.uuid, + link_class: 'permission', + name: 'can_read')] + end + return resp end # Give the special "System group" permission to manage this user and @@ -489,34 +800,32 @@ class User < ArvadosModel # Send admin notifications def send_admin_notifications - AdminNotifier.new_user(self).deliver + AdminNotifier.new_user(self).deliver_now if not self.is_active then - AdminNotifier.new_inactive_user(self).deliver + AdminNotifier.new_inactive_user(self).deliver_now + end + end + + # Automatically setup if is_active flag turns on + def setup_on_activate + return if [system_user_uuid, anonymous_user_uuid].include?(self.uuid) + if is_active && + (new_record? || saved_change_to_is_active? || will_save_change_to_is_active?) + setup end end # Automatically setup new user during creation def auto_setup_new_user - setup_repo_vm_links(nil, nil, Rails.configuration.default_openid_prefix) - if username - create_vm_login_permission_link(Rails.configuration.auto_setup_new_users_with_vm_uuid, - username) - repo_name = "#{username}/#{username}" - if Rails.configuration.auto_setup_new_users_with_repository and - Repository.where(name: repo_name).first.nil? - repo = Repository.create!(name: repo_name, owner_uuid: uuid) - Link.create!(tail_uuid: uuid, head_uuid: repo.uuid, - link_class: "permission", name: "can_manage") - end - end + setup end # Send notification if the user saved profile for the first time def send_profile_created_notification - if self.prefs_changed? - if self.prefs_was.andand.empty? || !self.prefs_was.andand['profile'] - profile_notification_address = Rails.configuration.user_profile_notification_address - ProfileNotifier.profile_created(self, profile_notification_address).deliver if profile_notification_address + if saved_change_to_prefs? + if prefs_before_last_save.andand.empty? || !prefs_before_last_save.andand['profile'] + profile_notification_address = Rails.configuration.Users.UserProfileNotificationAddress + ProfileNotifier.profile_created(self, profile_notification_address).deliver_now if profile_notification_address and !profile_notification_address.empty? end end end @@ -524,16 +833,22 @@ class User < ArvadosModel def verify_repositories_empty unless repositories.first.nil? errors.add(:username, "can't be unset when the user owns repositories") - false + throw(:abort) end end def sync_repository_names - old_name_re = /^#{Regexp.escape(username_was)}\// + old_name_re = /^#{Regexp.escape(username_before_last_save)}\// name_sub = "#{username}/" repositories.find_each do |repo| repo.name = repo.name.sub(old_name_re, name_sub) repo.save! end end + + def identity_url_nil_if_empty + if identity_url == "" + self.identity_url = nil + end + end end