X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/30e420e6cc547a12f2a775188f0429b3607da6c3..f05771f1bbaa93afbda43820af483727f4a2df3a:/services/api/app/models/user.rb diff --git a/services/api/app/models/user.rb b/services/api/app/models/user.rb index 677685d67a..3b201b5bab 100644 --- a/services/api/app/models/user.rb +++ b/services/api/app/models/user.rb @@ -12,7 +12,10 @@ class User < ArvadosModel before_update :prevent_inactive_admin before_create :check_auto_admin after_create :add_system_group_permission_link + after_create :auto_setup_new_user after_create :send_admin_notifications + after_update :send_profile_created_notification + has_many :authorized_keys, :foreign_key => :authorized_user_uuid, :primary_key => :uuid @@ -26,6 +29,7 @@ class User < ArvadosModel t.add :is_admin t.add :is_invited t.add :prefs + t.add :writable_by end ALL_PERMISSIONS = {read: true, write: true, manage: true} @@ -51,9 +55,13 @@ class User < ArvadosModel def can?(actions) return true if is_admin actions.each do |action, target| - target_uuid = target - if target.respond_to? :uuid - target_uuid = target.uuid + unless target.nil? + if target.respond_to? :uuid + target_uuid = target.uuid + else + target_uuid = target + target = ArvadosModel.find_by_uuid(target_uuid) + end end next if target_uuid == self.uuid next if (group_permissions[target_uuid] and @@ -63,6 +71,30 @@ class User < ArvadosModel next if (group_permissions[target.owner_uuid] and group_permissions[target.owner_uuid][action]) 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 + end + end return false end true @@ -75,23 +107,35 @@ class User < ArvadosModel # 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. + # + # The permission graph is built by repeatedly enumerating all + # permission links reachable from self.uuid, and then calling + # search_permissions def group_permissions Rails.cache.fetch "groups_for_user_#{self.uuid}" do 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 - Link.where('tail_uuid in (?) and link_class = ? and (head_uuid like ? or head_uuid like ?)', - lookup_uuids, + # 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 @@ -136,47 +180,35 @@ class User < ArvadosModel # delete user signatures, login, repo, and vm perms, and mark as inactive def unsetup # delete oid_login_perms for this user - oid_login_perms = Link.where(tail_uuid: self.email, - link_class: 'permission', - name: 'can_login') - oid_login_perms.each do |perm| - Link.delete perm - end + Link.destroy_all(tail_uuid: self.email, + link_class: 'permission', + name: 'can_login') # delete repo_perms for this user - repo_perms = Link.where(tail_uuid: self.uuid, - link_class: 'permission', - name: 'can_write') - repo_perms.each do |perm| - Link.delete perm - end + Link.destroy_all(tail_uuid: self.uuid, + link_class: 'permission', + name: 'can_manage') # delete vm_login_perms for this user - vm_login_perms = Link.where(tail_uuid: self.uuid, - link_class: 'permission', - name: 'can_login') - vm_login_perms.each do |perm| - Link.delete perm - end + Link.destroy_all(tail_uuid: self.uuid, + link_class: 'permission', + name: 'can_login') - # delete "All users' group read permissions for this user + # delete "All users" group read permissions for this user group = Group.where(name: 'All users').select do |g| g[:uuid].match /-f+$/ end.first - group_perms = Link.where(tail_uuid: self.uuid, - head_uuid: group[:uuid], - link_class: 'permission', - name: 'can_read') - group_perms.each do |perm| - Link.delete perm - end + Link.destroy_all(tail_uuid: self.uuid, + head_uuid: group[:uuid], + link_class: 'permission', + name: 'can_read') # delete any signatures by this user - signed_uuids = Link.where(link_class: 'signature', - tail_uuid: self.uuid) - signed_uuids.each do |sign| - Link.delete sign - end + Link.destroy_all(link_class: 'signature', + tail_uuid: self.uuid) + + # delete user preferences (including profile) + self.prefs = {} # mark the user as inactive self.is_active = false @@ -202,8 +234,10 @@ class User < ArvadosModel end def check_auto_admin - if User.where("uuid not like '%-000000000000000'").where(:is_admin => true).count == 0 and Rails.configuration.auto_admin_user - if current_user.email == Rails.configuration.auto_admin_user + if not self.uuid.end_with?('anonymouspublic') and + User.where("uuid not like '%-000000000000000'").where(:is_admin => true).count == 0 + if (Rails.configuration.auto_admin_user and self.email == Rails.configuration.auto_admin_user) or + Rails.configuration.auto_admin_first_user self.is_admin = true self.is_active = true end @@ -297,7 +331,7 @@ class User < ArvadosModel repo_perms = Link.where(tail_uuid: self.uuid, head_uuid: repo[:uuid], link_class: 'permission', - name: 'can_write') + name: 'can_manage') if repo_perms.any? logger.warn "User already has repository access " + repo_perms.collect { |p| p[:uuid] }.inspect @@ -312,7 +346,7 @@ class User < ArvadosModel repo_perm = Link.create(tail_uuid: self.uuid, head_uuid: repo[:uuid], link_class: 'permission', - name: 'can_write') + name: 'can_manage') logger.info { "repo permission: " + repo_perm[:uuid] } return repo_perm end @@ -414,4 +448,58 @@ class User < ArvadosModel AdminNotifier.new_inactive_user(self).deliver end end + + # Automatically setup new user during creation + def auto_setup_new_user + return true if !Rails.configuration.auto_setup_new_users + return true if !self.email + return true if self.uuid == system_user_uuid + return true if self.uuid == anonymous_user_uuid + + if Rails.configuration.auto_setup_new_users_with_vm_uuid || + Rails.configuration.auto_setup_new_users_with_repository + username = self.email.partition('@')[0] if self.email + return true if !username + + blacklisted_usernames = Rails.configuration.auto_setup_name_blacklist + if blacklisted_usernames.include?(username) + return true + elsif !(/^[a-zA-Z][-._a-zA-Z0-9]{0,30}[a-zA-Z0-9]$/.match(username)) + return true + else + return true if !(username = derive_unique_username username) + end + end + + # setup user + setup_repo_vm_links(username, + Rails.configuration.auto_setup_new_users_with_vm_uuid, + Rails.configuration.default_openid_prefix) + end + + # Find a username that starts with the given string and does not collide + # with any existing repository name or VM login name + def derive_unique_username username + while true + if Repository.where(name: username).empty? + login_collisions = Link.where(link_class: 'permission', + name: 'can_login').select do |perm| + perm.properties['username'] == username + end + return username if login_collisions.empty? + end + username = username + SecureRandom.random_number(10).to_s + end + 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 + end + end + end + end