Merge branch '8784-dir-listings'
[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_filter :require_auth_scope, :only => [ :destroy ]
7
8   skip_before_filter :set_cors_headers
9   skip_before_filter :find_object_by_uuid
10   skip_before_filter :render_404_if_no_object
11
12   respond_to :html
13
14   # omniauth callback method
15   def create
16     omniauth = env['omniauth.auth']
17
18     identity_url_ok = (omniauth['info']['identity_url'].length > 0) rescue false
19     unless identity_url_ok
20       # Whoa. This should never happen.
21       logger.error "UserSessionsController.create: omniauth object missing/invalid"
22       logger.error "omniauth: "+omniauth.pretty_inspect
23
24       return redirect_to login_failure_url
25     end
26
27     user = User.find_by_identity_url(omniauth['info']['identity_url'])
28     if not user
29       # Check for permission to log in to an existing User record with
30       # a different identity_url
31       Link.where("link_class = ? and name = ? and tail_uuid = ? and head_uuid like ?",
32                  'permission',
33                  'can_login',
34                  omniauth['info']['email'],
35                  User.uuid_like_pattern).each do |link|
36         if prefix = link.properties['identity_url_prefix']
37           if prefix == omniauth['info']['identity_url'][0..prefix.size-1]
38             user = User.find_by_uuid(link.head_uuid)
39             break if user
40           end
41         end
42       end
43     end
44     if not user
45       # New user registration
46       user = User.new(:email => omniauth['info']['email'],
47                       :first_name => omniauth['info']['first_name'],
48                       :last_name => omniauth['info']['last_name'],
49                       :identity_url => omniauth['info']['identity_url'],
50                       :is_active => Rails.configuration.new_users_are_active,
51                       :owner_uuid => system_user_uuid)
52       if omniauth['info']['username']
53         user.set_initial_username(requested: omniauth['info']['username'])
54       end
55       act_as_system_user do
56         user.save or raise Exception.new(user.errors.messages)
57       end
58     else
59       user.email = omniauth['info']['email']
60       user.first_name = omniauth['info']['first_name']
61       user.last_name = omniauth['info']['last_name']
62       if user.identity_url.nil?
63         # First login to a pre-activated account
64         user.identity_url = omniauth['info']['identity_url']
65       end
66     end
67
68     # For the benefit of functional and integration tests:
69     @user = user
70
71     # prevent ArvadosModel#before_create and _update from throwing
72     # "unauthorized":
73     Thread.current[:user] = user
74
75     user.save or raise Exception.new(user.errors.messages)
76
77     omniauth.delete('extra')
78
79     # Give the authenticated user a cookie for direct API access
80     session[:user_id] = user.id
81     session[:api_client_uuid] = nil
82     session[:api_client_trusted] = true # full permission to see user's secrets
83
84     @redirect_to = root_path
85     if params.has_key?(:return_to)
86       return send_api_token_to(params[:return_to], user)
87     end
88     redirect_to @redirect_to
89   end
90
91   # Omniauth failure callback
92   def failure
93     flash[:notice] = params[:message]
94   end
95
96   # logout - Clear our rack session BUT essentially redirect to the provider
97   # to clean up the Devise session from there too !
98   def logout
99     session[:user_id] = nil
100
101     flash[:notice] = 'You have logged off'
102     return_to = params[:return_to] || root_url
103     redirect_to "#{Rails.configuration.sso_provider_url}/users/sign_out?redirect_uri=#{CGI.escape return_to}"
104   end
105
106   # login - Just bounce to /auth/joshid. The only purpose of this function is
107   # to save the return_to parameter (if it exists; see the application
108   # controller). /auth/joshid bypasses the application controller.
109   def login
110     auth_provider = if params[:auth_provider] then "auth_provider=#{CGI.escape(params[:auth_provider])}" else "" end
111
112     if current_user and params[:return_to]
113       # Already logged in; just need to send a token to the requesting
114       # API client.
115       #
116       # FIXME: if current_user has never authorized this app before,
117       # ask for confirmation here!
118
119       send_api_token_to(params[:return_to], current_user)
120     elsif params[:return_to]
121       redirect_to "/auth/joshid?return_to=#{CGI.escape(params[:return_to])}&#{auth_provider}"
122     else
123       redirect_to "/auth/joshid?#{auth_provider}"
124     end
125   end
126
127   def send_api_token_to(callback_url, user)
128     # Give the API client a token for making API calls on behalf of
129     # the authenticated user
130
131     # Stub: automatically register all new API clients
132     api_client_url_prefix = callback_url.match(%r{^.*?://[^/]+})[0] + '/'
133     act_as_system_user do
134       @api_client = ApiClient.
135         find_or_create_by(url_prefix: api_client_url_prefix)
136     end
137
138     api_client_auth = ApiClientAuthorization.
139       new(user: user,
140           api_client: @api_client,
141           created_by_ip_address: remote_ip,
142           scopes: ["all"])
143     api_client_auth.save!
144
145     if callback_url.index('?')
146       callback_url += '&'
147     else
148       callback_url += '?'
149     end
150     callback_url += 'api_token=' + api_client_auth.api_token
151     redirect_to callback_url
152   end
153
154   def cross_origin_forbidden
155     send_error 'Forbidden', status: 403
156   end
157 end