X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/e04c307f011f5ce3b42dff062e1394256f604037..61ee61895a33008c70e5a294407cf55efc19622c:/services/api/app/models/user.rb?ds=sidebyside diff --git a/services/api/app/models/user.rb b/services/api/app/models/user.rb index 6f30b27a95..bbdd9c2843 100644 --- a/services/api/app/models/user.rb +++ b/services/api/app/models/user.rb @@ -21,8 +21,10 @@ 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 :prevent_nonadmin_system_root before_update :verify_repositories_empty, :if => Proc.new { username.nil? and username_changed? } @@ -71,6 +73,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 +90,7 @@ class User < ArvadosModel VAL_FOR_PERM = {:read => 1, :write => 2, + :unfreeze => 3, :manage => 3} @@ -108,7 +113,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 +124,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 +152,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 +190,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,25 +224,35 @@ 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 @@ -220,8 +263,9 @@ SELECT target_uuid, perm_level name: 'can_read').empty? # Add can_read link from this user to "all users" which makes this - # user "invited" - group_perm = create_user_group_link + # 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? @@ -251,38 +295,44 @@ SELECT target_uuid, perm_level 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 def unsetup + if self.uuid == system_user_uuid + raise "System root user cannot be deactivated" + end + # delete oid_login_perms for this user # - # note: these permission links are obsolete, they have no effect - # on anything and they are not created for new users. + # note: these permission links are obsolete anyway: 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').destroy_all - - # delete repo_perms for this user - Link.where(tail_uuid: self.uuid, - link_class: 'permission', - name: 'can_manage').destroy_all - - # delete vm_login_perms for this user - Link.where(tail_uuid: self.uuid, - link_class: 'permission', - name: 'can_login').destroy_all + link_class: 'permission', + name: 'can_login').destroy_all - # delete "All users" group read permissions for this user + # Delete all sharing permissions so (a) the user doesn't + # automatically regain access to anything if re-setup in future, + # (b) the user doesn't appear in "currently shared with" lists + # shown to other users. + # + # Notably this includes the can_read -> "all users" group + # permission. Link.where(tail_uuid: self.uuid, - head_uuid: all_users_group_uuid, - link_class: 'permission', - name: 'can_read').destroy_all + link_class: 'permission').destroy_all # delete any signatures by this user Link.where(link_class: 'signature', - tail_uuid: self.uuid).destroy_all + 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 = {} @@ -290,9 +340,15 @@ SELECT target_uuid, perm_level # 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 + # Called from ArvadosModel + def set_default_owner + self.owner_uuid = system_user_uuid + end + def must_unsetup_to_deactivate if !self.new_record? && self.uuid[0..4] == Rails.configuration.Login.LoginCluster && @@ -319,8 +375,7 @@ SELECT target_uuid, perm_level # if Link.where(tail_uuid: self.uuid, head_uuid: all_users_group_uuid, - link_class: 'permission', - name: 'can_read').any? + link_class: 'permission').any? errors.add :is_active, "cannot be set to false directly, use the 'Deactivate' button on Workbench, or the 'unsetup' API call" end end @@ -345,37 +400,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). # @@ -570,6 +594,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| @@ -674,6 +705,13 @@ update #{PERMISSION_VIEW} set target_uuid=$1 where target_uuid = $2 true end + def prevent_nonadmin_system_root + if self.uuid == system_user_uuid and self.is_admin_changed? and !self.is_admin + raise "System root user cannot be non-admin" + end + true + end + def search_permissions(start, graph, merged={}, upstream_mask=nil, upstream_path={}) nextpaths = graph[start] return merged if !nextpaths @@ -736,16 +774,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_write').first || Link.create(tail_uuid: self.uuid, head_uuid: all_users_group_uuid, link_class: 'permission', - name: 'can_read')) + name: 'can_write')] + 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 @@ -807,4 +855,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