Merge branch '14988-wb-rails5-upgrade'
[arvados.git] / services / api / test / integration / remote_user_test.rb
index 1201d44f26a39b247b4012a6f7052c41e3435376..90a55865397cefb6ce7e0829eca626a21734c2fc 100644 (file)
@@ -8,9 +8,14 @@ require 'test_helper'
 require 'helpers/users_test_helper'
 
 class RemoteUsersTest < ActionDispatch::IntegrationTest
+  include DbCurrentTime
+
+  def salted_active_token(remote:)
+    salt_token(fixture: :active, remote: remote).sub('/zzzzz-', '/'+remote+'-')
+  end
+
   def auth(remote:)
-    token = salt_token(fixture: :active, remote: remote)
-    token.sub!('/zzzzz-', '/'+remote+'-')
+    token = salted_active_token(remote: remote)
     {"HTTP_AUTHORIZATION" => "Bearer #{token}"}
   end
 
@@ -22,6 +27,10 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
   # Test cases can override the stub's default response to
   # .../users/current by changing @stub_status and @stub_content.
   setup do
+    clnt = HTTPClient.new
+    clnt.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
+    HTTPClient.stubs(:new).returns clnt
+
     @controller = Arvados::V1::UsersController.new
     ready = Thread::Queue.new
     srv = WEBrick::HTTPServer.new(
@@ -54,12 +63,14 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
     ready.pop
     @remote_server = srv
     @remote_host = "127.0.0.1:#{srv.config[:Port]}"
-    Rails.configuration.remote_hosts['zbbbb'] = @remote_host
-    Rails.configuration.remote_hosts['zbork'] = @remote_host
+    Rails.configuration.RemoteClusters = Rails.configuration.RemoteClusters.merge({zbbbb: ActiveSupport::InheritableOptions.new({Host: @remote_host}),
+                                                                                   zbork: ActiveSupport::InheritableOptions.new({Host: @remote_host})})
     Arvados::V1::SchemaController.any_instance.stubs(:root_url).returns "https://#{@remote_host}"
     @stub_status = 200
     @stub_content = {
       uuid: 'zbbbb-tpzed-000000000000000',
+      email: 'foo@example.com',
+      username: 'barney',
       is_admin: true,
       is_active: true,
     }
@@ -70,14 +81,77 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
   end
 
   test 'authenticate with remote token' do
-    get '/arvados/v1/users/current', {}, auth(remote: 'zbbbb')
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
     assert_response :success
     assert_equal 'zbbbb-tpzed-000000000000000', json_response['uuid']
     assert_equal false, json_response['is_admin']
+    assert_equal false, json_response['is_active']
+    assert_equal 'foo@example.com', json_response['email']
+    assert_equal 'barney', json_response['username']
+
+    # revoke original token
+    @stub_status = 401
+
+    # re-authorize before cache expires
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
+    assert_response :success
+
+    # simulate cache expiry
+    ApiClientAuthorization.where(
+      uuid: salted_active_token(remote: 'zbbbb').split('/')[1]).
+      update_all(expires_at: db_current_time - 1.minute)
+
+    # re-authorize after cache expires
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
+    assert_response 401
+
+    # simulate cached token indicating wrong user (e.g., local user
+    # entry was migrated out of the way taking the cached token with
+    # it, or authorizing cluster reassigned auth to a different user)
+    ApiClientAuthorization.where(
+      uuid: salted_active_token(remote: 'zbbbb').split('/')[1]).
+      update_all(user_id: users(:active).id)
+
+    # revive original token and re-authorize
+    @stub_status = 200
+    @stub_content[:username] = 'blarney'
+    @stub_content[:email] = 'blarney@example.com'
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
+    assert_response :success
+    assert_equal 'barney', json_response['username'], 'local username should not change once assigned'
+    assert_equal 'blarney@example.com', json_response['email']
+  end
+
+  test 'authenticate with remote token, remote username conflicts with local' do
+    @stub_content[:username] = 'active'
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
+    assert_response :success
+    assert_equal 'active2', json_response['username']
+  end
+
+  test 'authenticate with remote token, remote username is nil' do
+    @stub_content.delete :username
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
+    assert_response :success
+    assert_equal 'foo', json_response['username']
   end
 
   test 'authenticate with remote token from misbhehaving remote cluster' do
-    get '/arvados/v1/users/current', {}, auth(remote: 'zbork')
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbork')
     assert_response 401
   end
 
@@ -86,23 +160,61 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
     @stub_content = {
       error: 'not authorized',
     }
-    get '/arvados/v1/users/current', {}, auth(remote: 'zbbbb')
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
     assert_response 401
   end
 
+  ['v2',
+   'v2/',
+   'v2//',
+   'v2///',
+   "v2/'; delete from users where 1=1; commit; select '/lol",
+   'v2/foo/bar',
+   'v2/zzzzz-gj3su-077z32aux8dg2s1',
+   'v2/zzzzz-gj3su-077z32aux8dg2s1/',
+   'v2/3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi',
+   'v2/3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi/zzzzz-gj3su-077z32aux8dg2s1',
+   'v2//3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi',
+   'v8/zzzzz-gj3su-077z32aux8dg2s1/3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi',
+   '/zzzzz-gj3su-077z32aux8dg2s1/3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi',
+   '"v2/zzzzz-gj3su-077z32aux8dg2s1/3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi"',
+   '/',
+   '//',
+   '///',
+  ].each do |token|
+    test "authenticate with malformed remote token #{token}" do
+      get '/arvados/v1/users/current',
+        params: {format: 'json'},
+        headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"}
+      assert_response 401
+    end
+  end
+
+  test "ignore extra fields in remote token" do
+    token = salted_active_token(remote: 'zbbbb') + '/foo/bar/baz/*'
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"}
+    assert_response :success
+  end
+
   test 'remote api server is not an api server' do
     @stub_status = 200
     @stub_content = '<html>bad</html>'
-    get '/arvados/v1/users/current', {}, auth(remote: 'zbbbb')
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
     assert_response 401
   end
 
   ['zbbbb', 'z0000'].each do |token_valid_for|
     test "validate #{token_valid_for}-salted token for remote cluster zbbbb" do
       salted_token = salt_token(fixture: :active, remote: token_valid_for)
-      get '/arvados/v1/users/current', {format: 'json', remote: 'zbbbb'}, {
-            "HTTP_AUTHORIZATION" => "Bearer #{salted_token}"
-          }
+      get '/arvados/v1/users/current',
+        params: {format: 'json', remote: 'zbbbb'},
+        headers: {"HTTP_AUTHORIZATION" => "Bearer #{salted_token}"}
       if token_valid_for == 'zbbbb'
         assert_response 200
         assert_equal(users(:active).uuid, json_response['uuid'])
@@ -111,4 +223,95 @@ class RemoteUsersTest < ActionDispatch::IntegrationTest
       end
     end
   end
+
+  test "list readable groups with salted token" do
+    salted_token = salt_token(fixture: :active, remote: 'zbbbb')
+    get '/arvados/v1/groups',
+      params: {
+        format: 'json',
+        remote: 'zbbbb',
+        limit: 10000,
+      },
+      headers: {"HTTP_AUTHORIZATION" => "Bearer #{salted_token}"}
+    assert_response 200
+    group_uuids = json_response['items'].collect { |i| i['uuid'] }
+    assert_includes(group_uuids, 'zzzzz-j7d0g-fffffffffffffff')
+    refute_includes(group_uuids, 'zzzzz-j7d0g-000000000000000')
+    assert_includes(group_uuids, groups(:aproject).uuid)
+    refute_includes(group_uuids, groups(:trashed_project).uuid)
+    refute_includes(group_uuids, groups(:testusergroup_admins).uuid)
+  end
+
+  test 'auto-activate user from trusted cluster' do
+    Rails.configuration.RemoteClusters['zbbbb'].ActivateUsers = true
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
+    assert_response :success
+    assert_equal 'zbbbb-tpzed-000000000000000', json_response['uuid']
+    assert_equal false, json_response['is_admin']
+    assert_equal true, json_response['is_active']
+    assert_equal 'foo@example.com', json_response['email']
+    assert_equal 'barney', json_response['username']
+  end
+
+  test 'pre-activate remote user' do
+    post '/arvados/v1/users',
+      params: {
+        "user" => {
+          "uuid" => "zbbbb-tpzed-000000000000000",
+          "email" => 'foo@example.com',
+          "username" => 'barney',
+          "is_active" => true
+        }
+      },
+      headers: {'HTTP_AUTHORIZATION' => "OAuth2 #{api_token(:admin)}"}
+    assert_response :success
+
+    get '/arvados/v1/users/current',
+      params: {format: 'json'},
+      headers: auth(remote: 'zbbbb')
+    assert_response :success
+    assert_equal 'zbbbb-tpzed-000000000000000', json_response['uuid']
+    assert_equal nil, json_response['is_admin']
+    assert_equal true, json_response['is_active']
+    assert_equal 'foo@example.com', json_response['email']
+    assert_equal 'barney', json_response['username']
+  end
+
+  test "validate unsalted v2 token for remote cluster zbbbb" do
+    auth = api_client_authorizations(:active)
+    token = "v2/#{auth.uuid}/#{auth.api_token}"
+    get '/arvados/v1/users/current',
+      params: {format: 'json', remote: 'zbbbb'},
+      headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"}
+    assert_response :success
+    assert_equal(users(:active).uuid, json_response['uuid'])
+  end
+
+  test 'container request with runtime_token' do
+    [["valid local", "v2/#{api_client_authorizations(:active).uuid}/#{api_client_authorizations(:active).api_token}"],
+     ["valid remote", "v2/zbbbb-gj3su-000000000000000/abc"],
+     ["invalid local", "v2/#{api_client_authorizations(:active).uuid}/fakefakefake"],
+     ["invalid remote", "v2/zbork-gj3su-000000000000000/abc"],
+    ].each do |label, runtime_token|
+      post '/arvados/v1/container_requests',
+        params: {
+          "container_request" => {
+            "command" => ["echo"],
+            "container_image" => "xyz",
+            "output_path" => "/",
+            "cwd" => "/",
+            "runtime_token" => runtime_token
+          }
+        },
+        headers: {"HTTP_AUTHORIZATION" => "Bearer #{api_client_authorizations(:active).api_token}"}
+      if label.include? "invalid"
+        assert_response 422
+      else
+        assert_response :success
+      end
+    end
+  end
+
 end