Merge branch '20640-computed-permissions-api'
[arvados.git] / services / api / test / integration / user_sessions_test.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'test_helper'
6
7 class UserSessionsApiTest < ActionDispatch::IntegrationTest
8   # remote prefix & return url packed into the return_to param passed around
9   # between API and SSO provider.
10   def client_url(remote: nil)
11     url = ',https://controller.api.client.invalid'
12     url = "#{remote}#{url}" unless remote.nil?
13     url
14   end
15
16   def mock_auth_with(email: nil, username: nil, identity_url: nil, remote: nil, expected_response: :redirect)
17     mock = {
18         'identity_url' => 'https://edward.example.com',
19         'name' => 'Edward Example',
20         'first_name' => 'Edward',
21         'last_name' => 'Example',
22     }
23     mock['email'] = email unless email.nil?
24     mock['username'] = username unless username.nil?
25     mock['identity_url'] = identity_url unless identity_url.nil?
26     post('/auth/controller/callback',
27       params: {return_to: client_url(remote: remote), :auth_info => SafeJSON.dump(mock)},
28       headers: {'Authorization' => 'Bearer ' + Rails.configuration.SystemRootToken})
29
30     errors = {
31       :redirect => 'Did not redirect to client with token',
32       400 => 'Did not return Bad Request error',
33     }
34     assert_response expected_response, errors[expected_response]
35   end
36
37   test 'assign username from sso' do
38     mock_auth_with(email: 'foo@example.com', username: 'bar')
39     u = assigns(:user)
40     assert_equal 'bar', u.username
41   end
42
43   test 'no assign username from sso' do
44     mock_auth_with(email: 'foo@example.com')
45     u = assigns(:user)
46     assert_equal 'foo', u.username
47   end
48
49   test 'existing user login' do
50     mock_auth_with(identity_url: "https://active-user.openid.local")
51     u = assigns(:user)
52     assert_equal users(:active).uuid, u.uuid
53   end
54
55   test 'user redirect_to_user_uuid' do
56     mock_auth_with(identity_url: "https://redirects-to-active-user.openid.local")
57     u = assigns(:user)
58     assert_equal users(:active).uuid, u.uuid
59   end
60
61   test 'user double redirect_to_user_uuid' do
62     mock_auth_with(identity_url: "https://double-redirects-to-active-user.openid.local")
63     u = assigns(:user)
64     assert_equal users(:active).uuid, u.uuid
65   end
66
67   test 'create new user during omniauth callback' do
68     mock_auth_with(email: 'edward@example.com')
69     assert_equal(0, @response.redirect_url.index(client_url.split(',', 2)[1]),
70                  'Redirected to wrong address after succesful login: was ' +
71                  @response.redirect_url + ', expected ' + client_url.split(',', 2)[1] + '[...]')
72     assert_not_nil(@response.redirect_url.index('api_token='),
73                    'Expected api_token in query string of redirect url ' +
74                    @response.redirect_url)
75   end
76
77   test 'issue salted token from omniauth callback with remote param' do
78     mock_auth_with(email: 'edward@example.com', remote: 'zbbbb')
79     api_client_auth = assigns(:api_client_auth)
80     assert_not_nil api_client_auth
81     assert_includes(@response.redirect_url, 'api_token=' + api_client_auth.salted_token(remote: 'zbbbb'))
82   end
83
84   test 'error out from omniauth callback with invalid remote param' do
85     mock_auth_with(email: 'edward@example.com', remote: 'invalid_cluster_id', expected_response: 400)
86   end
87
88   # Test various combinations of auto_setup configuration and email
89   # address provided during a new user's first session setup.
90   [{result: :nope, email: nil, cfg: {auto: true, vm: true}},
91    {result: :yup, email: nil, cfg: {auto: true}},
92    {result: :nope, email: '@example.com', cfg: {auto: true, vm: true}},
93    {result: :yup, email: '@example.com', cfg: {auto: true}},
94    {result: :nope, email: 'root@', cfg: {auto: true, vm: true}},
95    {result: :nope, email: 'root@', cfg: {auto: true}},
96    {result: :nope, email: 'gitolite@', cfg: {auto: true}},
97    {result: :nope, email: '*_*@', cfg: {auto: true, vm: true}},
98    {result: :yup, email: 'toor@', cfg: {auto: true, vm: true, repo: true}},
99    {result: :yup, email: 'foo@', cfg: {auto: true, vm: true},
100      uniqprefix: 'foo'},
101    {result: :yup, email: 'foo@', cfg: {auto: true},
102      uniqprefix: 'foo'},
103    {result: :yup, email: 'auto_setup_vm_login@', cfg: {auto: true},
104      uniqprefix: 'auto_setup_vm_login'},
105    ].each do |testcase|
106     test "user auto-activate #{testcase.inspect}" do
107       # Configure auto_setup behavior according to testcase[:cfg]
108       Rails.configuration.Users.NewUsersAreActive = false
109       Rails.configuration.Users.AutoSetupNewUsers = testcase[:cfg][:auto]
110       Rails.configuration.Users.AutoSetupNewUsersWithVmUUID =
111         (testcase[:cfg][:vm] ? virtual_machines(:testvm).uuid : "")
112
113       mock_auth_with(email: testcase[:email])
114       u = assigns(:user)
115       vm_links = Link.where('link_class=? and tail_uuid=? and head_uuid like ?',
116                             'permission', u.uuid,
117                             '%-' + VirtualMachine.uuid_prefix + '-%')
118       case u[:result]
119       when :nope
120         assert_equal false, u.is_invited, "should not have been set up"
121         assert_empty vm_links, "should not have VM login permission"
122       when :yup
123         assert_equal true, u.is_invited
124         if testcase[:cfg][:vm]
125           assert_equal 1, vm_links.count, "wrong number of VM perm links"
126         else
127           assert_empty vm_links, "should not have VM login permission"
128         end
129       end
130       if (prefix = testcase[:uniqprefix])
131         # This email address conflicts with a test fixture. Make sure
132         # every VM login got digits added to make it unique.
133         vm_links.collect { |link| link.properties['username'] }.each do |name|
134           r = name.match(/^(.{#{prefix.length}})(\d+)$/)
135           assert_not_nil r, "#{name.inspect} does not match {prefix}\\d+"
136           assert_equal(prefix, r[1],
137                        "#{name.inspect} was not {#{prefix.inspect} plus digits}")
138         end
139       end
140     end
141   end
142 end