X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/5d00ecb0932f86e4d2aced3d9258b96522ef38bd..7c3e13d4876a8e37feffee3cdaebc44b20b7b61b:/services/api/app/controllers/user_sessions_controller.rb diff --git a/services/api/app/controllers/user_sessions_controller.rb b/services/api/app/controllers/user_sessions_controller.rb index c5507045c4..49af414310 100644 --- a/services/api/app/controllers/user_sessions_controller.rb +++ b/services/api/app/controllers/user_sessions_controller.rb @@ -1,15 +1,23 @@ +# Copyright (C) The Arvados Authors. All rights reserved. +# +# SPDX-License-Identifier: AGPL-3.0 + class UserSessionsController < ApplicationController - before_filter :require_auth_scope, :only => [ :destroy ] + before_action :require_auth_scope, :only => [ :destroy ] - skip_before_filter :set_cors_headers - skip_before_filter :find_object_by_uuid - skip_before_filter :render_404_if_no_object + skip_before_action :set_cors_headers + skip_before_action :find_object_by_uuid + skip_before_action :render_404_if_no_object respond_to :html # omniauth callback method def create - omniauth = env['omniauth.auth'] + if !Rails.configuration.Login.LoginCluster.empty? and Rails.configuration.Login.LoginCluster != Rails.configuration.ClusterID + raise "Local login disabled when LoginCluster is set" + end + + omniauth = request.env['omniauth.auth'] identity_url_ok = (omniauth['info']['identity_url'].length > 0) rescue false unless identity_url_ok @@ -20,7 +28,11 @@ class UserSessionsController < ApplicationController return redirect_to login_failure_url end - user = User.find_by_identity_url(omniauth['info']['identity_url']) + # 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 @@ -37,13 +49,14 @@ class UserSessionsController < ApplicationController 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.new_users_are_active, + :is_active => Rails.configuration.Users.NewUsersAreActive, :owner_uuid => system_user_uuid) if omniauth['info']['username'] user.set_initial_username(requested: omniauth['info']['username']) @@ -59,11 +72,28 @@ class UserSessionsController < ApplicationController # 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 + if user.uuid[0..4] != Rails.configuration.ClusterID + # Actually a remote user + # Send them to their home cluster's login + rh = Rails.configuration.RemoteClusters[user.uuid[0..4]] + remote, return_to_url = params[:return_to].split(',', 2) + @remotehomeurl = "#{rh.Scheme || "https"}://#{rh.Host}/login?remote=#{Rails.configuration.ClusterID}&return_to=#{return_to_url}" + render + return + end + # prevent ArvadosModel#before_create and _update from throwing # "unauthorized": Thread.current[:user] = user @@ -79,7 +109,15 @@ class UserSessionsController < ApplicationController @redirect_to = root_path if params.has_key?(:return_to) - return send_api_token_to(params[:return_to], user) + # return_to param's format is 'remote,return_to_url'. This comes from login() + # encoding the remote=zbbbb parameter passed by a client asking for a salted + # token. + remote, return_to_url = params[:return_to].split(',', 2) + if remote !~ /^[0-9a-z]{5}$/ && remote != "" + return send_error 'Invalid remote cluster id', status: 400 + end + remote = nil if remote == '' + return send_api_token_to(return_to_url, user, remote) end redirect_to @redirect_to end @@ -96,15 +134,16 @@ class UserSessionsController < ApplicationController flash[:notice] = 'You have logged off' return_to = params[:return_to] || root_url - redirect_to "#{Rails.configuration.sso_provider_url}/users/sign_out?redirect_uri=#{CGI.escape return_to}" + redirect_to "#{Rails.configuration.Services.SSO.ExternalURL}/users/sign_out?redirect_uri=#{CGI.escape return_to}" end # login - Just bounce to /auth/joshid. The only purpose of this function is # to save the return_to parameter (if it exists; see the application # controller). /auth/joshid bypasses the application controller. def login - auth_provider = if params[:auth_provider] then "auth_provider=#{CGI.escape(params[:auth_provider])}" else "" end - + if params[:remote] !~ /^[0-9a-z]{5}$/ && !params[:remote].nil? + return send_error 'Invalid remote cluster id', status: 400 + end if current_user and params[:return_to] # Already logged in; just need to send a token to the requesting # API client. @@ -112,15 +151,37 @@ class UserSessionsController < ApplicationController # FIXME: if current_user has never authorized this app before, # ask for confirmation here! - send_api_token_to(params[:return_to], current_user) - elsif params[:return_to] - redirect_to "/auth/joshid?return_to=#{CGI.escape(params[:return_to])}&#{auth_provider}" + return send_api_token_to(params[:return_to], current_user, params[:remote]) + end + p = [] + p << "auth_provider=#{CGI.escape(params[:auth_provider])}" if params[:auth_provider] + + if !Rails.configuration.Login.LoginCluster.empty? and Rails.configuration.Login.LoginCluster != Rails.configuration.ClusterID + host = ApiClientAuthorization.remote_host(uuid_prefix: Rails.configuration.Login.LoginCluster) + if not host + raise "LoginCluster #{Rails.configuration.Login.LoginCluster} missing from RemoteClusters" + end + scheme = "https" + cluster = Rails.configuration.RemoteClusters[Rails.configuration.Login.LoginCluster] + if cluster and cluster['Scheme'] and !cluster['Scheme'].empty? + scheme = cluster['Scheme'] + end + login_cluster = "#{scheme}://#{host}" + p << "remote=#{CGI.escape(params[:remote])}" if params[:remote] + p << "return_to=#{CGI.escape(params[:return_to])}" if params[:return_to] + redirect_to "#{login_cluster}/login?#{p.join('&')}" else - redirect_to "/auth/joshid?#{auth_provider}" + if params[:return_to] + # Encode remote param inside callback's return_to, so that we'll get it on + # create() after login. + remote_param = params[:remote].nil? ? '' : params[:remote] + p << "return_to=#{CGI.escape(remote_param + ',' + params[:return_to])}" + end + redirect_to "/auth/joshid?#{p.join('&')}" end end - def send_api_token_to(callback_url, user) + def send_api_token_to(callback_url, user, remote=nil) # Give the API client a token for making API calls on behalf of # the authenticated user @@ -131,19 +192,24 @@ class UserSessionsController < ApplicationController find_or_create_by(url_prefix: api_client_url_prefix) end - api_client_auth = ApiClientAuthorization. + @api_client_auth = ApiClientAuthorization. new(user: user, api_client: @api_client, created_by_ip_address: remote_ip, scopes: ["all"]) - api_client_auth.save! + @api_client_auth.save! if callback_url.index('?') callback_url += '&' else callback_url += '?' end - callback_url += 'api_token=' + api_client_auth.api_token + if remote.nil? + token = @api_client_auth.token + else + token = @api_client_auth.salted_token(remote: remote) + end + callback_url += 'api_token=' + token redirect_to callback_url end