1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
5 class Arvados::V1::UsersController < ApplicationController
6 accept_attribute_as_json :prefs, Hash
7 accept_param_as_json :updates
9 skip_before_action :find_object_by_uuid, only:
10 [:activate, :current, :system, :setup, :merge, :batch_update]
11 skip_before_action :render_404_if_no_object, only:
12 [:activate, :current, :system, :setup, :merge, :batch_update]
13 before_action :admin_required, only: [:setup, :unsetup, :batch_update]
15 # Internal API used by controller to update local cache of user
16 # records from LoginCluster.
19 # update_remote_user takes a row lock on the User record, so sort
20 # the keys so we always lock them in the same order.
21 sorted = params[:updates].keys.sort
23 attrs = params[:updates][uuid]
25 u = User.update_remote_user nullify_attrs(attrs)
33 def self._current_method_description
34 "Return the user record associated with the API token authorizing this request."
39 @object = current_user
42 send_error("Not logged in", status: 401)
46 def self._system_method_description
47 "Return this cluster's system (\"root\") user record."
55 def self._activate_method_description
56 "Set the `is_active` flag on a user record."
60 if params[:id] and params[:id].match(/\D/)
61 params[:uuid] = params.delete :id
63 if current_user.andand.is_admin && params[:uuid]
64 @object = User.find_by_uuid params[:uuid]
66 @object = current_user
68 if not @object.is_active
69 if @object.uuid[0..4] == Rails.configuration.Login.LoginCluster &&
70 @object.uuid[0..4] != Rails.configuration.ClusterID
71 logger.warn "Local user #{@object.uuid} called users#activate but only LoginCluster can do that"
72 raise ArgumentError.new "cannot activate user #{@object.uuid} here, only the #{@object.uuid[0..4]} cluster can do that"
73 elsif not (current_user.is_admin or @object.is_invited)
74 logger.warn "User #{@object.uuid} called users.activate " +
76 raise ArgumentError.new "Cannot activate without being invited."
79 required_uuids = Link.where("owner_uuid = ? and link_class = ? and name = ? and tail_uuid = ? and head_uuid like ?",
84 Collection.uuid_like_pattern).
86 signed_uuids = Link.where(owner_uuid: system_user_uuid,
87 link_class: 'signature',
89 tail_uuid: @object.uuid,
90 head_uuid: required_uuids).
92 todo_uuids = required_uuids - signed_uuids
94 @object.update is_active: true
95 logger.info "User #{@object.uuid} activated"
97 logger.warn "User #{@object.uuid} called users.activate " +
98 "before signing agreements #{todo_uuids.inspect}"
99 raise ArvadosModel::PermissionDeniedError.new \
100 "Cannot activate without user agreements #{todo_uuids.inspect}."
107 def self._setup_method_description
108 "Convenience method to \"fully\" set up a user record with a virtual machine login and notification email."
111 # create user object and all the needed links
114 @object = User.find_by_uuid(params[:uuid])
116 return render_404_if_no_object
118 elsif !params[:user] || params[:user].empty?
119 raise ArgumentError.new "Required uuid or user"
120 elsif !params[:user]['email']
121 raise ArgumentError.new "Require user email"
123 @object = model_class.create! resource_attrs
126 @response = @object.setup(vm_uuid: params[:vm_uuid],
127 send_notification_email: params[:send_notification_email])
129 send_json kind: "arvados#HashList", items: @response.as_api_response(nil)
132 def self._unsetup_method_description
133 "Unset a user's active flag and delete associated records."
136 # delete user agreements, vm, repository, login links; set state to inactive
138 reload_object_before_update
143 def self._merge_method_description
144 "Transfer ownership of one user's data to another."
148 if (params[:old_user_uuid] || params[:new_user_uuid])
149 if !current_user.andand.is_admin
150 return send_error("Must be admin to use old_user_uuid/new_user_uuid", status: 403)
152 if !params[:old_user_uuid] || !params[:new_user_uuid]
153 return send_error("Must supply both old_user_uuid and new_user_uuid", status: 422)
155 new_user = User.find_by_uuid(params[:new_user_uuid])
157 return send_error("User in new_user_uuid not found", status: 422)
159 @object = User.find_by_uuid(params[:old_user_uuid])
161 return send_error("User in old_user_uuid not found", status: 422)
164 if Thread.current[:api_client_authorization].scopes != ['all']
165 return send_error("cannot merge with a scoped token", status: 403)
168 new_auth = ApiClientAuthorization.validate(token: params[:new_user_token])
170 return send_error("invalid new_user_token", status: 401)
173 if new_auth.user.uuid[0..4] == Rails.configuration.ClusterID
174 if new_auth.scopes != ['all']
175 return send_error("supplied new_user_token has restricted scope", status: 403)
178 new_user = new_auth.user
179 @object = current_user
182 if @object.uuid == new_user.uuid
183 return send_error("cannot merge user to self", status: 422)
186 if !params[:new_owner_uuid]
187 return send_error("missing new_owner_uuid", status: 422)
190 if !new_user.can?(write: params[:new_owner_uuid])
191 return send_error("cannot move objects into supplied new_owner_uuid: new user does not have write permission", status: 403)
194 act_as_system_user do
195 @object.merge(new_owner_uuid: params[:new_owner_uuid],
196 new_user_uuid: new_user.uuid,
197 redirect_to_new_user: params[:redirect_to_new_user])
204 def self._merge_requires_parameters
209 description: "UUID of the user or group that will take ownership of data owned by the old user.",
214 description: "Valid API token for the user receiving ownership. If you use this option, it takes ownership of data owned by the user making the request.",
216 redirect_to_new_user: {
220 description: "If true, authorization attempts for the old user will be redirected to the new user.",
225 description: "UUID of the user whose ownership is being transferred to `new_owner_uuid`. You must be an admin to use this option.",
230 description: "UUID of the user receiving ownership. You must be an admin to use this option.",
235 def self._setup_requires_parameters
240 description: "UUID of an existing user record to set up."
245 description: "Attributes of a new user record to set up.",
250 description: "This parameter is obsolete and ignored.",
255 description: "If given, setup creates a login link to allow this user to access the Arvados virtual machine with this UUID.",
257 send_notification_email: {
261 description: "If true, send an email to the user notifying them they can now access this Arvados cluster.",
266 def self._update_requires_parameters
272 description: "If true, do not try to update the user on any other clusters in the federation,
273 only the cluster that received the request.
274 You must be an administrator to use this flag.",
279 def apply_filters(model_class=nil)
280 return super if @read_users.any?(&:is_admin)
281 if params[:uuid] != current_user.andand.uuid
282 # Non-admin index/show returns very basic information about readable users.
283 safe_attrs = ["uuid", "is_active", "is_admin", "is_invited", "email", "first_name", "last_name", "username", "can_write", "can_manage", "kind"]
285 @select = @select & safe_attrs
289 @filters += [['is_active', '=', true]]
291 # This gets called from within find_object_by_uuid.
292 # find_object_by_uuid stores the original value of @select in
293 # @preserve_select, edits the value of @select, calls
294 # find_objects_for_index, then restores @select from the value
295 # of @preserve_select. So if we want our updated value of
296 # @select here to stick, we have to set @preserve_select.
297 @preserve_select = @select
301 def nullable_attributes
302 super + [:email, :first_name, :last_name, :username]