1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
5 class UserSessionsController < ApplicationController
6 before_action :require_auth_scope, :only => [ :destroy ]
8 skip_before_action :set_cors_headers
9 skip_before_action :find_object_by_uuid
10 skip_before_action :render_404_if_no_object
14 # create a new session
16 if !Rails.configuration.Login.LoginCluster.empty? and Rails.configuration.Login.LoginCluster != Rails.configuration.ClusterID
17 raise "Local login disabled when LoginCluster is set"
21 if params[:provider] == 'controller'
22 if request.headers['Authorization'] != 'Bearer ' + Rails.configuration.SystemRootToken
23 return send_error('Invalid authorization header', status: 401)
25 # arvados-controller verified the user and is passing auth_info
27 authinfo = SafeJSON.load(params[:auth_info])
28 max_expires_at = authinfo["expires_at"]
30 return send_error "Legacy code path no longer supported", status: 404
33 if !authinfo['user_uuid'].blank?
34 user = User.find_by_uuid(authinfo['user_uuid'])
36 Rails.logger.warn "Nonexistent user_uuid in authinfo #{authinfo.inspect}"
37 return redirect_to login_failure_url
41 user = User.register(authinfo)
43 Rails.logger.warn "User.register error #{e}"
44 Rails.logger.warn "authinfo was #{authinfo.inspect}"
45 return redirect_to login_failure_url
49 # For the benefit of functional and integration tests:
52 if user.uuid[0..4] != Rails.configuration.ClusterID
53 # Actually a remote user
54 # Send them to their home cluster's login
55 rh = Rails.configuration.RemoteClusters[user.uuid[0..4]]
56 remote, return_to_url = params[:return_to].split(',', 2)
57 @remotehomeurl = "#{rh.Scheme || "https"}://#{rh.Host}/login?remote=#{Rails.configuration.ClusterID}&return_to=#{return_to_url}"
62 # prevent ArvadosModel#before_create and _update from throwing
64 Thread.current[:user] = user
66 user.save or raise Exception.new(user.errors.messages)
68 # Give the authenticated user a cookie for direct API access
69 session[:user_id] = user.id
70 session[:api_client_uuid] = nil
71 session[:api_client_trusted] = true # full permission to see user's secrets
73 @redirect_to = root_path
74 if params.has_key?(:return_to)
75 # return_to param's format is 'remote,return_to_url'. This comes from login()
76 # encoding the remote=zbbbb parameter passed by a client asking for a salted
78 remote, return_to_url = params[:return_to].split(',', 2)
79 if remote !~ /^[0-9a-z]{5}$/ && remote != ""
80 return send_error 'Invalid remote cluster id', status: 400
82 remote = nil if remote == ''
83 return send_api_token_to(return_to_url, user, remote, max_expires_at)
85 redirect_to @redirect_to
88 # Omniauth failure callback
90 flash[:notice] = params[:message]
93 # logout - this gets intercepted by controller, so this is probably
94 # mostly dead code at this point.
96 session[:user_id] = nil
98 flash[:notice] = 'You have logged off'
99 return_to = params[:return_to] || root_url
100 redirect_to return_to
103 # login. Redirect to LoginCluster.
105 if params[:remote] !~ /^[0-9a-z]{5}$/ && !params[:remote].nil?
106 return send_error 'Invalid remote cluster id', status: 400
108 if current_user and params[:return_to]
109 # Already logged in; just need to send a token to the requesting
112 # FIXME: if current_user has never authorized this app before,
113 # ask for confirmation here!
115 return send_api_token_to(params[:return_to], current_user, params[:remote])
118 p << "auth_provider=#{CGI.escape(params[:auth_provider])}" if params[:auth_provider]
120 if !Rails.configuration.Login.LoginCluster.empty? and Rails.configuration.Login.LoginCluster != Rails.configuration.ClusterID
121 host = ApiClientAuthorization.remote_host(uuid_prefix: Rails.configuration.Login.LoginCluster)
123 raise "LoginCluster #{Rails.configuration.Login.LoginCluster} missing from RemoteClusters"
126 cluster = Rails.configuration.RemoteClusters[Rails.configuration.Login.LoginCluster]
127 if cluster and cluster['Scheme'] and !cluster['Scheme'].empty?
128 scheme = cluster['Scheme']
130 login_cluster = "#{scheme}://#{host}"
131 p << "remote=#{CGI.escape(params[:remote])}" if params[:remote]
132 p << "return_to=#{CGI.escape(params[:return_to])}" if params[:return_to]
133 redirect_to "#{login_cluster}/login?#{p.join('&')}"
135 return send_error "Legacy code path no longer supported", status: 404
139 def send_api_token_to(callback_url, user, remote=nil, token_expiration=nil)
140 # Give the API client a token for making API calls on behalf of
141 # the authenticated user
143 # Stub: automatically register all new API clients
144 api_client_url_prefix = callback_url.match(%r{^.*?://[^/]+})[0] + '/'
145 act_as_system_user do
146 @api_client = ApiClient.
147 find_or_create_by(url_prefix: api_client_url_prefix)
149 if Rails.configuration.Login.TokenLifetime > 0
150 if token_expiration == nil
151 token_expiration = db_current_time + Rails.configuration.Login.TokenLifetime
153 token_expiration = [token_expiration, db_current_time + Rails.configuration.Login.TokenLifetime].min
157 @api_client_auth = ApiClientAuthorization.
159 api_client: @api_client,
160 created_by_ip_address: remote_ip,
161 expires_at: token_expiration,
163 @api_client_auth.save!
165 if callback_url.index('?')
171 token = @api_client_auth.token
173 token = @api_client_auth.salted_token(remote: remote)
175 callback_url += 'api_token=' + token
176 redirect_to callback_url
179 def cross_origin_forbidden
180 send_error 'Forbidden', status: 403