When auto_admin_first_user is set to true (default false), the first
[arvados.git] / services / api / app / models / user.rb
index b15d441ce1d377e8660ca8f8fb0c4b34c410aa61..3b201b5babd90ee9244263737e499b8a381b35a6 100644 (file)
@@ -29,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}
@@ -70,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
@@ -104,12 +129,13 @@ class User < ArvadosModel
         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
-        # User or Group.
-        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
@@ -208,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 self.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
@@ -423,63 +451,45 @@ class User < ArvadosModel
 
   # Automatically setup new user during creation
   def auto_setup_new_user
-    blacklisted_usernames = Rails.configuration.auto_setup_name_blacklist.split(', ')
-
-    username = self.email.partition('@')[0] if self.email
-
-    if !Rails.configuration.auto_setup_new_users ||
-       !(/^[_.A-Za-z0-9][-\@_.A-Za-z0-9]*\$?$/.match(self.email)) ||
-       blacklisted_usernames.include?(username)
-      return true
-    else
-      username = derive_unique_username username
-      # setup user
-      setup_repo_vm_links(username, Rails.configuration.auto_setup_new_users_with_vm_uuid,
-                          Rails.configuration.default_openid_prefix)
+    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
 
-  # Derive repo name and vm username using the string before @ in user's email
-  # If a repo or vm login link with this username exists,
-  # generate unique string by appending a random number
+  # 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
-      # If repo and vm login link are not being created, no need to generate a unique username
-      vm_uuid = Rails.configuration.auto_setup_new_users_with_vm_uuid
-      if !vm_uuid && !Rails.configuration.auto_setup_new_users_with_repository
-        return username
-      end
-
-      # need a unique username
-      found_unique_username = false
-      while !found_unique_username
-        repo = Repository.where(name: username).first
-
-        if repo
-          username = username + SecureRandom.random_number(1000000).to_s
-        elsif vm_uuid
-          login_props = {"username" => username}
-
-          vm_login_perms = Link.where(head_uuid: vm_uuid,
-                                      link_class: 'permission',
-                                      name: 'can_login')
-          perm_exists = false
-          vm_login_perms.each do |perm|
-            if perm.properties['username'] == username
-              perm_exists = true
-              break
-            end
-          end
-
-          if perm_exists
-            username = username + SecureRandom.random_number(1000000).to_s
-          else
-            found_unique_username = true
-          end
-        else
-          found_unique_username = true
+    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
-    return username
+      username = username + SecureRandom.random_number(10).to_s
+    end
   end
 
   # Send notification if the user saved profile for the first time