omniauth = request.env['omniauth.auth']
- identity_url_ok = (omniauth['info']['identity_url'].length > 0) rescue false
- unless identity_url_ok
- # Whoa. This should never happen.
- logger.error "UserSessionsController.create: omniauth object missing/invalid"
- logger.error "omniauth: "+omniauth.pretty_inspect
-
+ begin
+ user = User.register omniauth['info']
+ rescue => e
+ Rails.logger.warn e
return redirect_to login_failure_url
end
- # Only local users can create sessions, hence uuid_like_pattern
- # here.
- user = User.unscoped.where('identity_url = ? and uuid like ?',
- omniauth['info']['identity_url'],
- User.uuid_like_pattern).first
- if not user
- # Check for permission to log in to an existing User record with
- # a different identity_url
- Link.where("link_class = ? and name = ? and tail_uuid = ? and head_uuid like ?",
- 'permission',
- 'can_login',
- omniauth['info']['email'],
- User.uuid_like_pattern).each do |link|
- if prefix = link.properties['identity_url_prefix']
- if prefix == omniauth['info']['identity_url'][0..prefix.size-1]
- user = User.find_by_uuid(link.head_uuid)
- break if user
- end
- end
- end
- end
-
- if not user
- # New user registration
- user = User.new(:email => omniauth['info']['email'],
- :first_name => omniauth['info']['first_name'],
- :last_name => omniauth['info']['last_name'],
- :identity_url => omniauth['info']['identity_url'],
- :is_active => Rails.configuration.Users.NewUsersAreActive,
- :owner_uuid => system_user_uuid)
- if omniauth['info']['username']
- user.set_initial_username(requested: omniauth['info']['username'])
- end
- act_as_system_user do
- user.save or raise Exception.new(user.errors.messages)
- end
- else
- user.email = omniauth['info']['email']
- user.first_name = omniauth['info']['first_name']
- user.last_name = omniauth['info']['last_name']
- if user.identity_url.nil?
- # First login to a pre-activated account
- user.identity_url = omniauth['info']['identity_url']
- end
-
- while (uuid = user.redirect_to_user_uuid)
- user = User.unscoped.where(uuid: uuid).first
- if !user
- raise Exception.new("identity_url #{omniauth['info']['identity_url']} redirects to nonexistent uuid #{uuid}")
- end
- end
- end
-
# For the benefit of functional and integration tests:
@user = user
end
end
+ def redirects_to
+ user = self
+ redirects = 0
+ while (uuid = user.redirect_to_user_uuid)
+ user = User.unscoped.find_by_uuid(uuid)
+ if !user
+ raise Exception.new("user uuid #{user.uuid} redirects to nonexistent uuid #{uuid}")
+ end
+ redirects += 1
+ if redirects > 15
+ raise "Starting from #{self.uuid} redirect_to_user_uuid exceeded maximum number of redirects"
+ end
+ end
+ user
+ end
+
+ def self.register info
+ # login info expected fields, all can be optional but at minimum
+ # must supply either 'identity_url' or 'email'
+ #
+ # email
+ # first_name
+ # last_name
+ # username
+ # alternate_emails
+ # identity_url
+
+ info = info.with_indifferent_access
+
+ primary_user = nil
+
+ # local database
+ identity_url = info['identity_url']
+
+ if identity_url && identity_url.length > 0
+ # Only local users can create sessions, hence uuid_like_pattern
+ # here.
+ user = User.unscoped.where('identity_url = ? and uuid like ?',
+ identity_url,
+ User.uuid_like_pattern).first
+ primary_user = user.redirects_to if user
+ end
+
+ if !primary_user
+ # identity url is unset or didn't find matching record.
+ emails = [info['email']] + (info['alternate_emails'] || [])
+ emails.select! {|em| !em.nil? && !em.empty?}
+
+ User.unscoped.where('email in (?) and uuid like ?',
+ emails,
+ User.uuid_like_pattern).each do |user|
+ if !primary_user
+ primary_user = user.redirects_to
+ elsif primary_user.uuid != user.redirects_to.uuid
+ raise "Ambigious email address, directs to both #{primary_user.uuid} and #{user.redirects_to.uuid}"
+ end
+ end
+ end
+
+ if !primary_user
+ # New user registration
+ primary_user = User.new(:owner_uuid => system_user_uuid,
+ :is_admin => false,
+ :is_active => Rails.configuration.Users.NewUsersAreActive)
+
+ primary_user.set_initial_username(requested: info['username']) if info['username']
+ primary_user.identity_url = info['identity_url'] if identity_url
+ end
+
+ primary_user.email = info['email'] if info['email']
+ primary_user.first_name = info['first_name'] if info['first_name']
+ primary_user.last_name = info['last_name'] if info['last_name']
+
+ if (!primary_user.email or primary_user.email.empty?) and (!primary_user.identity_url or primary_user.identity_url.empty?)
+ raise "Must have supply at least one of 'email' or 'identity_url' to User.register"
+ end
+
+ act_as_system_user do
+ primary_user.save!
+ end
+
+ primary_user
+ end
+
protected
def change_all_uuid_refs(old_uuid:, new_uuid:)
end
def permission_to_update
- if username_changed? || redirect_to_user_uuid_changed?
+ if username_changed? || redirect_to_user_uuid_changed? || email_changed?
current_user.andand.is_admin
else
# users must be able to update themselves (even if they are
--- /dev/null
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
+class EnforceUniqueIdentityUrl < ActiveRecord::Migration[5.0]
+ def change
+ add_index :users, [:identity_url], :unique => true
+ end
+end
CREATE INDEX index_users_on_created_at ON public.users USING btree (created_at);
+--
+-- Name: index_users_on_identity_url; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE UNIQUE INDEX index_users_on_identity_url ON public.users USING btree (identity_url);
+
+
--
-- Name: index_users_on_modified_at; Type: INDEX; Schema: public; Owner: -
--
('20190422144631'),
('20190523180148'),
('20190808145904'),
-('20190809135453');
+('20190809135453'),
+('20190905151603');
email: zbbbb-active-user@arvados.local
first_name: Active
last_name: User
- identity_url: https://active-user.openid.local
+ identity_url: https://federated-active-user.openid.local
is_active: true
is_admin: false
username: federatedactive
email: spectator@arvados.local
first_name: Spect
last_name: Ator
- identity_url: https://spectator.openid.local
+ identity_url: https://container_runtime_token_user.openid.local
is_active: true
is_admin: false
username: containerruntimetokenuser
email: jobber@arvados.local
first_name: Job
last_name: Er
- identity_url: https://spectator.openid.local
+ identity_url: https://job_reader.openid.local
is_active: true
is_admin: false
username: jobber
email: fuse@arvados.local
first_name: FUSE
last_name: User
- identity_url: https://fuse.openid.local
+ identity_url: https://permission_perftest.openid.local
is_active: true
is_admin: false
username: perftest
res.status = @stub_status
res.body = @stub_content.is_a?(String) ? @stub_content : @stub_content.to_json
end
- srv.mount_proc '/arvados/v1/users/register' do |req, res|
- res.status = @stub_status
- res.body = @stub_content.is_a?(String) ? @stub_content : @stub_content.to_json
- end
Thread.new do
srv.start
end
end
test 'pre-activate remote user' do
+ @stub_content = {
+ uuid: 'zbbbb-tpzed-000000000001234',
+ email: 'foo@example.com',
+ username: 'barney',
+ is_admin: true,
+ is_active: true,
+ }
+
post '/arvados/v1/users',
params: {
"user" => {
- "uuid" => "zbbbb-tpzed-000000000000000",
+ "uuid" => "zbbbb-tpzed-000000000001234",
"email" => 'foo@example.com',
"username" => 'barney',
- "is_active" => true
+ "is_active" => true,
+ "is_admin" => false
}
},
headers: {'HTTP_AUTHORIZATION' => "OAuth2 #{api_token(:admin)}"}
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 'zbbbb-tpzed-000000000001234', 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 'remote user inactive without pre-activation' do
+ @stub_content = {
+ uuid: 'zbbbb-tpzed-000000000001234',
+ email: 'foo@example.com',
+ username: 'barney',
+ is_admin: true,
+ is_active: true,
+ }
+
+ get '/arvados/v1/users/current',
+ params: {format: 'json'},
+ headers: auth(remote: 'zbbbb')
+ assert_response :success
+ assert_equal 'zbbbb-tpzed-000000000001234', 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']
+ end
+
test "validate unsalted v2 token for remote cluster zbbbb" do
auth = api_client_authorizations(:active)
token = "v2/#{auth.uuid}/#{auth.api_token}"
assert_response(:success)
assert_equal(project_uuid, json_response['owner_uuid'])
end
+
+ test 'pre-activate user' do
+ post '/arvados/v1/users',
+ params: {
+ "user" => {
+ "email" => 'foo@example.com',
+ "is_active" => true,
+ "username" => "barney"
+ }
+ },
+ headers: {'HTTP_AUTHORIZATION' => "OAuth2 #{api_token(:admin)}"}
+ assert_response :success
+ rp = json_response
+ assert_not_nil rp["uuid"]
+ assert_not_nil rp["is_active"]
+ assert_nil rp["is_admin"]
+
+ get "/arvados/v1/users/#{rp['uuid']}",
+ params: {format: 'json'},
+ headers: auth(:admin)
+ assert_response :success
+ assert_equal rp["uuid"], json_response['uuid']
+ assert_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
+
end
end
end
end
+
+ test "lookup user by email" do
+ u = User.register({"email" => "active-user@arvados.local", "identity_url" => "different-identity-url"})
+ active = User.find_by_uuid(users(:active).uuid)
+ assert_equal active.uuid, u.uuid
+ assert_equal "active-user@arvados.local", active.email
+ # identity_url is not updated
+ assert_equal "https://active-user.openid.local", active.identity_url
+ end
+
+ test "lookup user by alternate email" do
+ # register method will find that active-user@arvados.local already
+ # exists and return existing 'active' user.
+ u = User.register({"email" => "user@parent-company.com",
+ "alternate_emails" => ["active-user@arvados.local"],
+ "identity_url" => "different-identity-url"})
+ active = User.find_by_uuid(users(:active).uuid)
+ assert_equal active.uuid, u.uuid
+
+ # email should be updated
+ assert_equal "user@parent-company.com", active.email
+
+ # identity_url is not updated
+ assert_equal "https://active-user.openid.local", active.identity_url
+ end
+
+ test "register new user" do
+ u = User.register({"email" => "never-before-seen-user@arvados.local",
+ "identity_url" => "different-identity-url",
+ "first_name" => "Robert",
+ "last_name" => "Baratheon",
+ "username" => "bobby"})
+ nbs = User.find_by_uuid(u.uuid)
+ assert_equal nbs.uuid, u.uuid
+ assert_equal "different-identity-url", nbs.identity_url
+ assert_equal "never-before-seen-user@arvados.local", nbs.email
+ assert_equal false, nbs.is_admin
+ assert_equal false , nbs.is_active
+ assert_equal "bobby", nbs.username
+ assert_equal "Robert", nbs.first_name
+ assert_equal "Baratheon", nbs.last_name
+ end
+
+ test "fail when email address is ambigious" do
+ User.register({"email" => "active-user@arvados.local"})
+ u = User.register({"email" => "never-before-seen-user@arvados.local"})
+ u.email = "active-user@arvados.local"
+ act_as_system_user do
+ u.save!
+ end
+ assert_raises do
+ User.register({"email" => "active-user@arvados.local"})
+ end
+ end
+
+ test "fail lookup without identifiers" do
+ assert_raises do
+ User.register({"first_name" => "Robert", "last_name" => "Baratheon"})
+ end
+ assert_raises do
+ User.register({"first_name" => "Robert", "last_name" => "Baratheon", "identity_url" => "", "email" => ""})
+ end
+ end
+
+ test "user can update name" do
+ set_user_from_auth :active
+ user = users(:active)
+ user.first_name = "MyNewName"
+ assert user.save
+ end
+
+ test "user cannot update email" do
+ set_user_from_auth :active
+ user = users(:active)
+ user.email = "new-name@example.com"
+ assert_not_allowed { user.save }
+ end
+
+ test "admin can update email" do
+ set_user_from_auth :admin
+ user = users(:active)
+ user.email = "new-name@example.com"
+ assert user.save
+ end
+
end