X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/3facf89bf048487ee718fe15d012b489f2d407b7..3a6b1a17f1b073e381b053b52e3cb0bb9c81d249:/services/api/app/models/user.rb diff --git a/services/api/app/models/user.rb b/services/api/app/models/user.rb index 8ec90f7e53..52d36ac577 100644 --- a/services/api/app/models/user.rb +++ b/services/api/app/models/user.rb @@ -21,12 +21,13 @@ class User < ArvadosModel 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 { username.nil? and username_changed? } - before_update :setup_on_activate + after_update :setup_on_activate before_create :check_auto_admin before_create :set_initial_username, :if => Proc.new { @@ -71,6 +72,8 @@ 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} @@ -86,6 +89,7 @@ class User < ArvadosModel VAL_FOR_PERM = {:read => 1, :write => 2, + :unfreeze => 3, :manage => 3} @@ -108,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 @@ -120,11 +123,19 @@ class User < ArvadosModel end next if target_uuid == self.uuid + 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 + target_owner_uuid = target.owner_uuid if target.respond_to? :owner_uuid user_uuids_subquery = USER_UUIDS_SUBQUERY_TEMPLATE % {user: "$1", perm_level: "$3"} - unless ActiveRecord::Base.connection. + if !is_admin && !ActiveRecord::Base.connection. exec_query(%{ SELECT 1 FROM #{PERMISSION_VIEW} WHERE user_uuid in (#{user_uuids_subquery}) and @@ -140,6 +151,23 @@ SELECT 1 FROM #{PERMISSION_VIEW} ).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 end true end @@ -161,6 +189,10 @@ SELECT 1 FROM #{PERMISSION_VIEW} 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 @@ -191,34 +223,80 @@ SELECT user_uuid, target_uuid, perm_level # and perm_hash[:write] are true if this user can read and write # objects owned by group_uuid. def group_permissions(level=1) - group_perms = {} - - user_uuids_subquery = USER_UUIDS_SUBQUERY_TEMPLATE % {user: "$1", perm_level: "$2"} + @group_perms ||= {} + if @group_perms.empty? + user_uuids_subquery = USER_UUIDS_SUBQUERY_TEMPLATE % {user: "$1", perm_level: 1} - ActiveRecord::Base.connection. - exec_query(%{ + ActiveRecord::Base.connection. + exec_query(%{ SELECT target_uuid, perm_level FROM #{PERMISSION_VIEW} - WHERE user_uuid in (#{user_uuids_subquery}) and perm_level >= $2 + 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], - [nil, level]]). - rows.each do |group_uuid, max_p_val| - group_perms[group_uuid] = PERMS_FOR_VAL[max_p_val.to_i] + # "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 + + 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 - group_perms end # create links - def setup(repo_name: nil, vm_uuid: nil) - repo_perm = create_user_repo_link repo_name - vm_login_perm = create_vm_login_permission_link(vm_uuid, username) if vm_uuid - 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 [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 @@ -251,11 +329,19 @@ SELECT target_uuid, perm_level 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 @@ -311,37 +397,6 @@ SELECT target_uuid, perm_level end end - def update_uuid(new_uuid:) - if !current_user.andand.is_admin - raise PermissionDeniedError - end - if uuid == system_user_uuid || uuid == anonymous_user_uuid - raise "update_uuid cannot update system accounts" - end - if self.class != self.class.resource_class_for_uuid(new_uuid) - raise "invalid new_uuid #{new_uuid.inspect}" - end - transaction(requires_new: true) do - reload - old_uuid = self.uuid - self.uuid = new_uuid - save!(validate: false) - change_all_uuid_refs(old_uuid: old_uuid, new_uuid: new_uuid) - ActiveRecord::Base.connection.exec_update %{ -update #{PERMISSION_VIEW} set user_uuid=$1 where user_uuid = $2 -}, - 'User.update_uuid.update_permissions_user_uuid', - [[nil, new_uuid], - [nil, old_uuid]] - ActiveRecord::Base.connection.exec_update %{ -update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 -}, - 'User.update_uuid.update_permissions_target_uuid', - [[nil, new_uuid], - [nil, old_uuid]] - 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). # @@ -536,6 +591,13 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 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| @@ -702,16 +764,26 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 login_perm end - # add the user to the 'All users' group - def create_user_group_link - return (Link.where(tail_uuid: self.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, 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 @@ -746,17 +818,6 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 # Automatically setup new user during creation def auto_setup_new_user setup - if username - create_vm_login_permission_link(Rails.configuration.Users.AutoSetupNewUsersWithVmUUID, - username) - repo_name = "#{username}/#{username}" - if Rails.configuration.Users.AutoSetupNewUsersWithRepository 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 end # Send notification if the user saved profile for the first time @@ -784,4 +845,10 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 repo.save! end end + + def identity_url_nil_if_empty + if identity_url == "" + self.identity_url = nil + end + end end