|_. Argument |_. Type |_. Description |_. Location |_. Example |
{background:#ccffcc}.|uuid|string|The UUID of the User in question.|path||
|user|object||query||
+
+h3. update_uuid
+
+Change the UUID of an existing user, updating all database references accordingly.
+
+This method can only be used by an admin user. It should only be used when the affected user is idle. New references to the affected user that are established _while the update_uuid operation is in progress_ might not be migrated as expected.
+
+Arguments:
+
+table(table table-bordered table-condensed).
+|_. Argument |_. Type |_. Description |_. Location |_. Example |
+{background:#ccffcc}.|uuid|string|The current UUID of the user in question.|path|@zzzzz-tpzed-12345abcde12345@|
+{background:#ccffcc}.|new_uuid|string|The desired new UUID. It is an error to use a UUID belonging to an existing user.|query|@zzzzz-tpzed-abcde12345abcde@|
[:activate, :current, :system, :setup]
skip_before_filter :render_404_if_no_object, only:
[:activate, :current, :system, :setup]
- before_filter :admin_required, only: [:setup, :unsetup]
+ before_filter :admin_required, only: [:setup, :unsetup, :update_uuid]
def current
if current_user
show
end
+ # Change UUID to a new (unused) uuid and transfer all owned/linked
+ # objects accordingly.
+ def update_uuid
+ @object.update_uuid(new_uuid: params[:new_uuid])
+ show
+ end
+
protected
def self._setup_requires_parameters
}
end
+ def self._update_uuid_requires_parameters
+ {
+ new_uuid: {
+ type: 'string', required: true,
+ },
+ }
+ end
+
def apply_filters(model_class=nil)
return super if @read_users.any?(&:is_admin)
if params[:uuid] != current_user.andand.uuid
end
end
+ def update_uuid(new_uuid:)
+ if !current_user.andand.is_admin
+ raise PermissionDeniedError
+ end
+ if uuid == system_user_uuid || uuid == anonymous_user_uuid
+ raise "update_uuid cannot update system accounts"
+ end
+ if self.class != self.class.resource_class_for_uuid(new_uuid)
+ raise "invalid new_uuid #{new_uuid.inspect}"
+ end
+ transaction(requires_new: true) do
+ reload
+ old_uuid = self.uuid
+ self.uuid = new_uuid
+ save!(validate: false)
+ ActiveRecord::Base.descendants.reject(&:abstract_class?).each do |klass|
+ klass.columns.each do |col|
+ if col.name.end_with?('_uuid')
+ column = col.name.to_sym
+ klass.where(column => old_uuid).update_all(column => new_uuid)
+ end
+ end
+ end
+ end
+ end
+
protected
def ensure_ownership_path_leads_to_user
post 'activate', on: :member
post 'setup', on: :collection
post 'unsetup', on: :member
+ post 'update_uuid', on: :member
end
resources :virtual_machines do
get 'logins', on: :member
"user's writable_by should include its owner_uuid")
end
+ [
+ [:admin, true],
+ [:active, false],
+ ].each do |auth_user, expect_success|
+ test "update_uuid as #{auth_user}" do
+ authorize_with auth_user
+ orig_uuid = users(:active).uuid
+ post :update_uuid, {
+ id: orig_uuid,
+ new_uuid: 'zbbbb-tpzed-abcde12345abcde',
+ }
+ if expect_success
+ assert_response :success
+ assert_empty User.where(uuid: orig_uuid)
+ else
+ assert_response 403
+ assert_not_empty User.where(uuid: orig_uuid)
+ end
+ end
+ end
+
NON_ADMIN_USER_DATA = ["uuid", "kind", "is_active", "email", "first_name",
"last_name"].sort
end
end
+ [
+ [:active, 'zzzzz-borkd-abcde12345abcde'],
+ [:active, 'zzzzz-j7d0g-abcde12345abcde'],
+ [:active, 'zzzzz-tpzed-borkd'],
+ [:system_user, 'zzzzz-tpzed-abcde12345abcde'],
+ [:anonymous, 'zzzzz-tpzed-abcde12345abcde'],
+ ].each do |fixture, new_uuid|
+ test "disallow update_uuid #{fixture} -> #{new_uuid}" do
+ u = users(fixture)
+ orig_uuid = u.uuid
+ act_as_system_user do
+ assert_raises do
+ u.update_uuid(new_uuid: new_uuid)
+ end
+ end
+ # "Successfully aborted orig->new" outcome looks the same as
+ # "successfully updated new->orig".
+ assert_update_success(old_uuid: new_uuid,
+ new_uuid: orig_uuid,
+ expect_owned_objects: fixture == :active)
+ end
+ end
+
+ [:active, :spectator, :admin].each do |target|
+ test "update_uuid on #{target} as non-admin user" do
+ act_as_user users(:active) do
+ assert_raises(ArvadosModel::PermissionDeniedError) do
+ users(target).update_uuid(new_uuid: 'zzzzz-tpzed-abcde12345abcde')
+ end
+ end
+ end
+ end
+
+ test "update_uuid to existing uuid" do
+ u = users(:active)
+ orig_uuid = u.uuid
+ new_uuid = users(:admin).uuid
+ act_as_system_user do
+ assert_raises do
+ u.update_uuid(new_uuid: new_uuid)
+ end
+ end
+ u.reload
+ assert_equal u.uuid, orig_uuid
+ assert_not_empty Collection.where(owner_uuid: orig_uuid)
+ assert_not_empty Group.where(owner_uuid: orig_uuid)
+ end
+
+ [
+ [:active, 'zbbbb-tpzed-abcde12345abcde'],
+ [:active, 'zzzzz-tpzed-abcde12345abcde'],
+ [:admin, 'zbbbb-tpzed-abcde12345abcde'],
+ [:admin, 'zzzzz-tpzed-abcde12345abcde'],
+ ].each do |fixture, new_uuid|
+ test "update_uuid #{fixture} to unused uuid #{new_uuid}" do
+ u = users(fixture)
+ orig_uuid = u.uuid
+ act_as_system_user do
+ u.update_uuid(new_uuid: new_uuid)
+ end
+ assert_update_success(old_uuid: orig_uuid,
+ new_uuid: new_uuid,
+ expect_owned_objects: fixture == :active)
+ end
+ end
+
+ def assert_update_success(old_uuid:, new_uuid:, expect_owned_objects: true)
+ [[User, :uuid],
+ [Link, :head_uuid],
+ [Link, :tail_uuid],
+ [Group, :owner_uuid],
+ [Collection, :owner_uuid],
+ ].each do |klass, attr|
+ assert_empty klass.where(attr => old_uuid)
+ if klass == User || expect_owned_objects
+ assert_not_empty klass.where(attr => new_uuid)
+ end
+ end
+ end
end