Merge branch 'master' into 15558-alternate-email-addresses
[arvados.git] / services / api / app / models / user.rb
index 5f94db0bb2abc4be8a404f007b47e5c57c97ce95..8ab8ea1d85b0146f692993a8cd1fb6199addc8fc 100644 (file)
@@ -327,8 +327,26 @@ class User < ArvadosModel
     end
   end
 
+  def redirects_to
+    user = self
+    redirects = 0
+    while (uuid = user.redirect_to_user_uuid)
+      user = User.unscoped.find_by_uuid(uuid)
+      if !user
+        raise Exception.new("user uuid #{user.uuid} redirects to nonexistent uuid #{uuid}")
+      end
+      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 should have fields:
+    # login info expected fields, all can be optional but at minimum
+    # must supply either 'identity_url' or 'email'
+    #
     #   email
     #   first_name
     #   last_name
@@ -336,6 +354,8 @@ class User < ArvadosModel
     #   alternate_emails
     #   identity_url
 
+    info = info.with_indifferent_access
+
     primary_user = nil
 
     # local database
@@ -345,61 +365,24 @@ class User < ArvadosModel
       # Only local users can create sessions, hence uuid_like_pattern
       # here.
       user = User.unscoped.where('identity_url = ? and uuid like ?',
-                                 info['identity_url'],
+                                 identity_url,
                                  User.uuid_like_pattern).first
-      if user
-        while (uuid = user.redirect_to_user_uuid)
-          user = User.unscoped.find_by_uuid(uuid)
-          if !user
-            raise Exception.new("user uuid #{user.uuid} redirects to nonexistent uuid #{uuid}")
-          end
-        end
-        primary_user = user
-      end
-
-      # Don't think this is necessary if admin can just create a user
-      # record with the desired email.
-      #
-      # if not user
-      #   # Check for permission to log in to an existing User record with
-      #   # a different identity_url
-      #   Link.where("link_class = ? and name = ? and tail_uuid = ? and head_uuid like ?",
-      #              'permission',
-      #              'can_login',
-      #              info['email'],
-      #              User.uuid_like_pattern).each do |link|
-      #     if prefix = link.properties['identity_url_prefix']
-      #       if prefix == info['identity_url'][0..prefix.size-1]
-      #         user = User.find_by_uuid(link.head_uuid)
-      #         break if user
-      #       end
-      #     end
-      #   end
-      # end
+      primary_user = user.redirects_to if user
     end
 
     if !primary_user
-      # identity url is unset or didn't find anything.
+      # identity url is unset or didn't find matching record.
       emails = [info['email']] + (info['alternate_emails'] || [])
       emails.select! {|em| !em.nil? && !em.empty?}
-      emails.each do |em|
-        # Go through each email address, try to find a user record
-        # corresponding to one of the addresses supplied.
-
-        user = User.unscoped.where('email = ? and uuid like ?',
-                                   em,
-                                   User.uuid_like_pattern).first
-        next if !user
-
-        while (uuid = user.redirect_to_user_uuid)
-          user = User.unscoped.find_by_uuid(uuid)
-          if !user
-            raise Exception.new("user uuid #{user.uuid} redirects to nonexistent uuid #{uuid}")
-          end
-        end
 
-        primary_user = user
-        break
+      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 "Ambigious email address, directs to both #{primary_user.uuid} and #{user.redirects_to.uuid}"
+        end
       end
     end
 
@@ -410,12 +393,16 @@ class User < ArvadosModel
                               :is_active => Rails.configuration.Users.NewUsersAreActive)
 
       primary_user.set_initial_username(requested: info['username']) if info['username']
+      primary_user.identity_url = info['identity_url'] if identity_url
     end
 
-    primary_user.email = info['email']
-    primary_user.identity_url = info['identity_url']
-    primary_user.first_name = info['first_name']
-    primary_user.last_name = info['last_name']
+    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!
@@ -442,7 +429,7 @@ class User < ArvadosModel
   end
 
   def permission_to_update
-    if username_changed? || redirect_to_user_uuid_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