Merge branch 'master' into 15558-alternate-email-addresses
[arvados.git] / services / api / app / controllers / user_sessions_controller.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 class UserSessionsController < ApplicationController
6   before_action :require_auth_scope, :only => [ :destroy ]
7
8   skip_before_action :set_cors_headers
9   skip_before_action :find_object_by_uuid
10   skip_before_action :render_404_if_no_object
11
12   respond_to :html
13
14   # omniauth callback method
15   def create
16     omniauth = request.env['omniauth.auth']
17
18     begin
19       user = User.register omniauth['info']
20     rescue => e
21       Rails.logger.warn e
22       return redirect_to login_failure_url
23     end
24
25     # For the benefit of functional and integration tests:
26     @user = user
27
28     if user.uuid[0..4] != Rails.configuration.ClusterID
29       # Actually a remote user
30       # Send them to their home cluster's login
31       rh = Rails.configuration.RemoteClusters[user.uuid[0..4]]
32       remote, return_to_url = params[:return_to].split(',', 2)
33       @remotehomeurl = "#{rh.Scheme || "https"}://#{rh.Host}/login?remote=#{Rails.configuration.ClusterID}&return_to=#{return_to_url}"
34       render
35       return
36     end
37
38     # prevent ArvadosModel#before_create and _update from throwing
39     # "unauthorized":
40     Thread.current[:user] = user
41
42     user.save or raise Exception.new(user.errors.messages)
43
44     omniauth.delete('extra')
45
46     # Give the authenticated user a cookie for direct API access
47     session[:user_id] = user.id
48     session[:api_client_uuid] = nil
49     session[:api_client_trusted] = true # full permission to see user's secrets
50
51     @redirect_to = root_path
52     if params.has_key?(:return_to)
53       # return_to param's format is 'remote,return_to_url'. This comes from login()
54       # encoding the remote=zbbbb parameter passed by a client asking for a salted
55       # token.
56       remote, return_to_url = params[:return_to].split(',', 2)
57       if remote !~ /^[0-9a-z]{5}$/ && remote != ""
58         return send_error 'Invalid remote cluster id', status: 400
59       end
60       remote = nil if remote == ''
61       return send_api_token_to(return_to_url, user, remote)
62     end
63     redirect_to @redirect_to
64   end
65
66   # Omniauth failure callback
67   def failure
68     flash[:notice] = params[:message]
69   end
70
71   # logout - Clear our rack session BUT essentially redirect to the provider
72   # to clean up the Devise session from there too !
73   def logout
74     session[:user_id] = nil
75
76     flash[:notice] = 'You have logged off'
77     return_to = params[:return_to] || root_url
78     redirect_to "#{Rails.configuration.Services.SSO.ExternalURL}/users/sign_out?redirect_uri=#{CGI.escape return_to}"
79   end
80
81   # login - Just bounce to /auth/joshid. The only purpose of this function is
82   # to save the return_to parameter (if it exists; see the application
83   # controller). /auth/joshid bypasses the application controller.
84   def login
85     if params[:remote] !~ /^[0-9a-z]{5}$/ && !params[:remote].nil?
86       return send_error 'Invalid remote cluster id', status: 400
87     end
88     if current_user and params[:return_to]
89       # Already logged in; just need to send a token to the requesting
90       # API client.
91       #
92       # FIXME: if current_user has never authorized this app before,
93       # ask for confirmation here!
94
95       return send_api_token_to(params[:return_to], current_user, params[:remote])
96     end
97     p = []
98     p << "auth_provider=#{CGI.escape(params[:auth_provider])}" if params[:auth_provider]
99     if params[:return_to]
100       # Encode remote param inside callback's return_to, so that we'll get it on
101       # create() after login.
102       remote_param = params[:remote].nil? ? '' : params[:remote]
103       p << "return_to=#{CGI.escape(remote_param + ',' + params[:return_to])}"
104     end
105     redirect_to "/auth/joshid?#{p.join('&')}"
106   end
107
108   def send_api_token_to(callback_url, user, remote=nil)
109     # Give the API client a token for making API calls on behalf of
110     # the authenticated user
111
112     # Stub: automatically register all new API clients
113     api_client_url_prefix = callback_url.match(%r{^.*?://[^/]+})[0] + '/'
114     act_as_system_user do
115       @api_client = ApiClient.
116         find_or_create_by(url_prefix: api_client_url_prefix)
117     end
118
119     @api_client_auth = ApiClientAuthorization.
120       new(user: user,
121           api_client: @api_client,
122           created_by_ip_address: remote_ip,
123           scopes: ["all"])
124     @api_client_auth.save!
125
126     if callback_url.index('?')
127       callback_url += '&'
128     else
129       callback_url += '?'
130     end
131     if remote.nil?
132       token = @api_client_auth.token
133     else
134       token = @api_client_auth.salted_token(remote: remote)
135     end
136     callback_url += 'api_token=' + token
137     redirect_to callback_url
138   end
139
140   def cross_origin_forbidden
141     send_error 'Forbidden', status: 403
142   end
143 end