[ARVADOS] created: 1.3.0-1532-gfb7a09468

Git user git at public.curoverse.com
Tue Aug 20 19:59:01 UTC 2019


        at  fb7a094681db65daa2f28b50ca734dc27e9d6db7 (commit)


commit fb7a094681db65daa2f28b50ca734dc27e9d6db7
Author: Peter Amstutz <pamstutz at veritasgenetics.com>
Date:   Mon Aug 19 14:32:03 2019 -0400

    15558: Support login to user account by email address
    
    Can also login through aliases supplied as "alternate_emails".
    
    Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz at veritasgenetics.com>

diff --git a/services/api/app/controllers/user_sessions_controller.rb b/services/api/app/controllers/user_sessions_controller.rb
index ef0f88686..ca7ba4a40 100644
--- a/services/api/app/controllers/user_sessions_controller.rb
+++ b/services/api/app/controllers/user_sessions_controller.rb
@@ -15,68 +15,13 @@ class UserSessionsController < ApplicationController
   def create
     omniauth = request.env['omniauth.auth']
 
-    identity_url_ok = (omniauth['info']['identity_url'].length > 0) rescue false
-    unless identity_url_ok
-      # Whoa. This should never happen.
-      logger.error "UserSessionsController.create: omniauth object missing/invalid"
-      logger.error "omniauth: "+omniauth.pretty_inspect
-
+    begin
+      user = User.register omniauth['info']
+    rescue => e
+      Rails.logger.warn e
       return redirect_to login_failure_url
     end
 
-    # Only local users can create sessions, hence uuid_like_pattern
-    # here.
-    user = User.unscoped.where('identity_url = ? and uuid like ?',
-                               omniauth['info']['identity_url'],
-                               User.uuid_like_pattern).first
-    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',
-                 omniauth['info']['email'],
-                 User.uuid_like_pattern).each do |link|
-        if prefix = link.properties['identity_url_prefix']
-          if prefix == omniauth['info']['identity_url'][0..prefix.size-1]
-            user = User.find_by_uuid(link.head_uuid)
-            break if user
-          end
-        end
-      end
-    end
-
-    if not user
-      # New user registration
-      user = User.new(:email => omniauth['info']['email'],
-                      :first_name => omniauth['info']['first_name'],
-                      :last_name => omniauth['info']['last_name'],
-                      :identity_url => omniauth['info']['identity_url'],
-                      :is_active => Rails.configuration.Users.NewUsersAreActive,
-                      :owner_uuid => system_user_uuid)
-      if omniauth['info']['username']
-        user.set_initial_username(requested: omniauth['info']['username'])
-      end
-      act_as_system_user do
-        user.save or raise Exception.new(user.errors.messages)
-      end
-    else
-      user.email = omniauth['info']['email']
-      user.first_name = omniauth['info']['first_name']
-      user.last_name = omniauth['info']['last_name']
-      if user.identity_url.nil?
-        # First login to a pre-activated account
-        user.identity_url = omniauth['info']['identity_url']
-      end
-
-      while (uuid = user.redirect_to_user_uuid)
-        user = User.unscoped.where(uuid: uuid).first
-        if !user
-          raise Exception.new("identity_url #{omniauth['info']['identity_url']} redirects to nonexistent uuid #{uuid}")
-        end
-      end
-    end
-
     # For the benefit of functional and integration tests:
     @user = user
 
diff --git a/services/api/app/models/user.rb b/services/api/app/models/user.rb
index ee44812e0..6f2f92b93 100644
--- a/services/api/app/models/user.rb
+++ b/services/api/app/models/user.rb
@@ -327,6 +327,104 @@ class User < ArvadosModel
     end
   end
 
+  def self.register info
+    # login info should have fields:
+    #   email
+    #   first_name
+    #   last_name
+    #   username
+    #   alternate_emails
+    #   identity_url
+
+    primary_user = nil
+
+    # local database
+    identity_url = info['identity_url']
+
+    if identity_url && identity_url.length > 0
+      # Only local users can create sessions, hence uuid_like_pattern
+      # here.
+      user = User.unscoped.where('identity_url = ? and uuid like ?',
+                                 info['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
+    end
+
+    if !primary_user and info['email'] and !info['email'].empty?
+      # identity url is unset or didn't find anything.
+      emails = [info['email']] + (info['alternate_emails'] || [])
+      emails.select! {|em| !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
+      end
+    end
+
+    if !primary_user
+      # New user registration
+      primary_user = User.new(:owner_uuid => system_user_uuid,
+                              :is_admin => false,
+                              :is_active => Rails.configuration.Users.NewUsersAreActive)
+
+      primary_user.set_initial_username(requested: info['username']) if info['username']
+    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']
+
+    act_as_system_user do
+      primary_user.save!
+    end
+
+    return primary_user
+
+  end
+
   protected
 
   def change_all_uuid_refs(old_uuid:, new_uuid:)
diff --git a/services/api/test/integration/remote_user_test.rb b/services/api/test/integration/remote_user_test.rb
index 90a558653..259d7f554 100644
--- a/services/api/test/integration/remote_user_test.rb
+++ b/services/api/test/integration/remote_user_test.rb
@@ -33,39 +33,54 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
 
     @controller = Arvados::V1::UsersController.new
     ready = Thread::Queue.new
-    srv = WEBrick::HTTPServer.new(
-      Port: 0,
-      Logger: WEBrick::Log.new(
-        Rails.root.join("log", "webrick.log").to_s,
-        WEBrick::Log::INFO),
-      AccessLog: [[File.open(Rails.root.join(
-                              "log", "webrick_access.log").to_s, 'a+'),
-                   WEBrick::AccessLog::COMBINED_LOG_FORMAT]],
-      SSLEnable: true,
-      SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
-      SSLPrivateKey: OpenSSL::PKey::RSA.new(
-        File.open(Rails.root.join("tmp", "self-signed.key")).read),
-      SSLCertificate: OpenSSL::X509::Certificate.new(
-        File.open(Rails.root.join("tmp", "self-signed.pem")).read),
-      SSLCertName: [["CN", WEBrick::Utils::getservername]],
-      StartCallback: lambda { ready.push(true) })
-    srv.mount_proc '/discovery/v1/apis/arvados/v1/rest' do |req, res|
-      Rails.cache.delete 'arvados_v1_rest_discovery'
-      res.body = Arvados::V1::SchemaController.new.send(:discovery_doc).to_json
-    end
-    srv.mount_proc '/arvados/v1/users/current' do |req, res|
-      res.status = @stub_status
-      res.body = @stub_content.is_a?(String) ? @stub_content : @stub_content.to_json
-    end
-    Thread.new do
-      srv.start
+
+    @remote_server = []
+    @remote_host = []
+
+    ['zbbbb', 'zbork'].each do |clusterid|
+      srv = WEBrick::HTTPServer.new(
+        Port: 0,
+        Logger: WEBrick::Log.new(
+          Rails.root.join("log", "webrick.log").to_s,
+          WEBrick::Log::INFO),
+        AccessLog: [[File.open(Rails.root.join(
+                                 "log", "webrick_access.log").to_s, 'a+'),
+                     WEBrick::AccessLog::COMBINED_LOG_FORMAT]],
+        SSLEnable: true,
+        SSLVerifyClient: OpenSSL::SSL::VERIFY_NONE,
+        SSLPrivateKey: OpenSSL::PKey::RSA.new(
+          File.open(Rails.root.join("tmp", "self-signed.key")).read),
+        SSLCertificate: OpenSSL::X509::Certificate.new(
+          File.open(Rails.root.join("tmp", "self-signed.pem")).read),
+        SSLCertName: [["CN", WEBrick::Utils::getservername]],
+        StartCallback: lambda { ready.push(true) })
+      srv.mount_proc '/discovery/v1/apis/arvados/v1/rest' do |req, res|
+        Rails.cache.delete 'arvados_v1_rest_discovery'
+        res.body = Arvados::V1::SchemaController.new.send(:discovery_doc).to_json
+      end
+      srv.mount_proc '/arvados/v1/users/current' do |req, res|
+        if clusterid == 'zbbbb' and req.header['authorization'][0][10..14] == 'zbork'
+          # asking zbbbb about zbork should yield an error, zbbbb doesn't trust zbork
+          res.status = 401
+          return
+        end
+        res.status = @stub_status
+        res.body = @stub_content.is_a?(String) ? @stub_content : @stub_content.to_json
+      end
+      srv.mount_proc '/arvados/v1/users/register' do |req, res|
+        res.status = @stub_status
+        res.body = @stub_content.is_a?(String) ? @stub_content : @stub_content.to_json
+      end
+      Thread.new do
+        srv.start
+      end
+      ready.pop
+      @remote_server << srv
+      @remote_host << "127.0.0.1:#{srv.config[:Port]}"
     end
-    ready.pop
-    @remote_server = srv
-    @remote_host = "127.0.0.1:#{srv.config[:Port]}"
-    Rails.configuration.RemoteClusters = Rails.configuration.RemoteClusters.merge({zbbbb: ActiveSupport::InheritableOptions.new({Host: @remote_host}),
-                                                                                   zbork: ActiveSupport::InheritableOptions.new({Host: @remote_host})})
-    Arvados::V1::SchemaController.any_instance.stubs(:root_url).returns "https://#{@remote_host}"
+    Rails.configuration.RemoteClusters = Rails.configuration.RemoteClusters.merge({zbbbb: ActiveSupport::InheritableOptions.new({Host: @remote_host[0]}),
+                                                                                   zbork: ActiveSupport::InheritableOptions.new({Host: @remote_host[1]})})
+    Arvados::V1::SchemaController.any_instance.stubs(:root_url).returns "https://#{@remote_host[0]}"
     @stub_status = 200
     @stub_content = {
       uuid: 'zbbbb-tpzed-000000000000000',
@@ -77,7 +92,9 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
   end
 
   teardown do
-    @remote_server.andand.stop
+    @remote_server.each do |srv|
+      srv.stop
+    end
   end
 
   test 'authenticate with remote token' do
@@ -148,7 +165,7 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
     assert_equal 'foo', json_response['username']
   end
 
-  test 'authenticate with remote token from misbhehaving remote cluster' do
+  test 'authenticate with remote token from misbehaving remote cluster' do
     get '/arvados/v1/users/current',
       params: {format: 'json'},
       headers: auth(remote: 'zbork')

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list