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 # omniauth callback method
16 if !Rails.configuration.Login.LoginCluster.empty? and Rails.configuration.Login.LoginCluster != Rails.configuration.ClusterID
17 raise "Local login disabled when LoginCluster is set"
20 omniauth = request.env['omniauth.auth']
22 identity_url_ok = (omniauth['info']['identity_url'].length > 0) rescue false
23 unless identity_url_ok
24 # Whoa. This should never happen.
25 logger.error "UserSessionsController.create: omniauth object missing/invalid"
26 logger.error "omniauth: "+omniauth.pretty_inspect
28 return redirect_to login_failure_url
31 # Only local users can create sessions, hence uuid_like_pattern
33 user = User.unscoped.where('identity_url = ? and uuid like ?',
34 omniauth['info']['identity_url'],
35 User.uuid_like_pattern).first
37 # Check for permission to log in to an existing User record with
38 # a different identity_url
39 Link.where("link_class = ? and name = ? and tail_uuid = ? and head_uuid like ?",
42 omniauth['info']['email'],
43 User.uuid_like_pattern).each do |link|
44 if prefix = link.properties['identity_url_prefix']
45 if prefix == omniauth['info']['identity_url'][0..prefix.size-1]
46 user = User.find_by_uuid(link.head_uuid)
54 # New user registration
55 user = User.new(:email => omniauth['info']['email'],
56 :first_name => omniauth['info']['first_name'],
57 :last_name => omniauth['info']['last_name'],
58 :identity_url => omniauth['info']['identity_url'],
59 :is_active => Rails.configuration.Users.NewUsersAreActive,
60 :owner_uuid => system_user_uuid)
61 if omniauth['info']['username']
62 user.set_initial_username(requested: omniauth['info']['username'])
65 user.save or raise Exception.new(user.errors.messages)
68 user.email = omniauth['info']['email']
69 user.first_name = omniauth['info']['first_name']
70 user.last_name = omniauth['info']['last_name']
71 if user.identity_url.nil?
72 # First login to a pre-activated account
73 user.identity_url = omniauth['info']['identity_url']
76 while (uuid = user.redirect_to_user_uuid)
77 user = User.unscoped.where(uuid: uuid).first
79 raise Exception.new("identity_url #{omniauth['info']['identity_url']} redirects to nonexistent uuid #{uuid}")
84 # For the benefit of functional and integration tests:
87 if user.uuid[0..4] != Rails.configuration.ClusterID
88 # Actually a remote user
89 # Send them to their home cluster's login
90 rh = Rails.configuration.RemoteClusters[user.uuid[0..4]]
91 remote, return_to_url = params[:return_to].split(',', 2)
92 @remotehomeurl = "#{rh.Scheme || "https"}://#{rh.Host}/login?remote=#{Rails.configuration.ClusterID}&return_to=#{return_to_url}"
97 # prevent ArvadosModel#before_create and _update from throwing
99 Thread.current[:user] = user
101 user.save or raise Exception.new(user.errors.messages)
103 omniauth.delete('extra')
105 # Give the authenticated user a cookie for direct API access
106 session[:user_id] = user.id
107 session[:api_client_uuid] = nil
108 session[:api_client_trusted] = true # full permission to see user's secrets
110 @redirect_to = root_path
111 if params.has_key?(:return_to)
112 # return_to param's format is 'remote,return_to_url'. This comes from login()
113 # encoding the remote=zbbbb parameter passed by a client asking for a salted
115 remote, return_to_url = params[:return_to].split(',', 2)
116 if remote !~ /^[0-9a-z]{5}$/ && remote != ""
117 return send_error 'Invalid remote cluster id', status: 400
119 remote = nil if remote == ''
120 return send_api_token_to(return_to_url, user, remote)
122 redirect_to @redirect_to
125 # Omniauth failure callback
127 flash[:notice] = params[:message]
130 # logout - Clear our rack session BUT essentially redirect to the provider
131 # to clean up the Devise session from there too !
133 session[:user_id] = nil
135 flash[:notice] = 'You have logged off'
136 return_to = params[:return_to] || root_url
137 redirect_to "#{Rails.configuration.Services.SSO.ExternalURL}/users/sign_out?redirect_uri=#{CGI.escape return_to}"
140 # login - Just bounce to /auth/joshid. The only purpose of this function is
141 # to save the return_to parameter (if it exists; see the application
142 # controller). /auth/joshid bypasses the application controller.
144 if params[:remote] !~ /^[0-9a-z]{5}$/ && !params[:remote].nil?
145 return send_error 'Invalid remote cluster id', status: 400
147 if current_user and params[:return_to]
148 # Already logged in; just need to send a token to the requesting
151 # FIXME: if current_user has never authorized this app before,
152 # ask for confirmation here!
154 return send_api_token_to(params[:return_to], current_user, params[:remote])
157 p << "auth_provider=#{CGI.escape(params[:auth_provider])}" if params[:auth_provider]
159 if !Rails.configuration.Login.LoginCluster.empty? and Rails.configuration.Login.LoginCluster != Rails.configuration.ClusterID
160 cluster = Rails.configuration.RemoteClusters[Rails.configuration.Login.LoginCluster]
162 raise "LoginCluster #{Rails.configuration.Login.LoginCluster} missing from RemoteClusters"
165 if cluster['Scheme'] and !cluster['Scheme'].empty?
166 scheme = cluster['Scheme']
168 if !cluster['Host'] or cluster['Host'].empty?
169 raise "LoginCluster #{Rails.configuration.Login.LoginCluster} missing 'Host' in RemoteClusters"
171 login_cluster = "#{scheme}://#{cluster['Host']}"
172 p << "remote=#{CGI.escape(params[:remote])}" if params[:remote]
173 p << "return_to=#{CGI.escape(params[:return_to])}" if params[:return_to]
174 redirect_to "#{login_cluster}/login?#{p.join('&')}"
176 if params[:return_to]
177 # Encode remote param inside callback's return_to, so that we'll get it on
178 # create() after login.
179 remote_param = params[:remote].nil? ? '' : params[:remote]
180 p << "return_to=#{CGI.escape(remote_param + ',' + params[:return_to])}"
182 redirect_to "/auth/joshid?#{p.join('&')}"
186 def send_api_token_to(callback_url, user, remote=nil)
187 # Give the API client a token for making API calls on behalf of
188 # the authenticated user
190 # Stub: automatically register all new API clients
191 api_client_url_prefix = callback_url.match(%r{^.*?://[^/]+})[0] + '/'
192 act_as_system_user do
193 @api_client = ApiClient.
194 find_or_create_by(url_prefix: api_client_url_prefix)
197 @api_client_auth = ApiClientAuthorization.
199 api_client: @api_client,
200 created_by_ip_address: remote_ip,
202 @api_client_auth.save!
204 if callback_url.index('?')
210 token = @api_client_auth.token
212 token = @api_client_auth.salted_token(remote: remote)
214 callback_url += 'api_token=' + token
215 redirect_to callback_url
218 def cross_origin_forbidden
219 send_error 'Forbidden', status: 403