BigW Consortium Gitlab

users.rb 19 KB
Newer Older
1
module API
2
  class Users < Grape::API
3
    include PaginationParams
4 5 6
    include APIGuard

    allow_access_with_scope :read_user, if: -> (request) { request.get? }
7

8
    resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
9 10 11 12
      before do
        authenticate_non_get!
      end

13
      helpers do
14
        def find_user(params)
15 16
          id = params[:user_id] || params[:id]
          User.find_by(id: id) || not_found!('User')
17 18
        end

19 20 21 22 23 24 25
        params :optional_attributes do
          optional :skype, type: String, desc: 'The Skype username'
          optional :linkedin, type: String, desc: 'The LinkedIn username'
          optional :twitter, type: String, desc: 'The Twitter username'
          optional :website_url, type: String, desc: 'The website of the user'
          optional :organization, type: String, desc: 'The organization of the user'
          optional :projects_limit, type: Integer, desc: 'The number of projects a user can create'
26
          optional :extern_uid, type: String, desc: 'The external authentication provider UID'
27 28 29 30 31
          optional :provider, type: String, desc: 'The external provider'
          optional :bio, type: String, desc: 'The biography of the user'
          optional :location, type: String, desc: 'The location of the user'
          optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator'
          optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups'
32
          optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed'
33
          optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
34
          optional :avatar, type: File, desc: 'Avatar image for user'
35 36 37 38 39 40 41 42
          all_or_none_of :extern_uid, :provider
        end
      end

      desc 'Get the list of users' do
        success Entities::UserBasic
      end
      params do
43
        # CE
44
        optional :username, type: String, desc: 'Get a single user with a specific username'
45 46
        optional :extern_uid, type: String, desc: 'Get a single user with a specific external authentication provider UID'
        optional :provider, type: String, desc: 'The external provider'
47 48 49 50
        optional :search, type: String, desc: 'Search for a username'
        optional :active, type: Boolean, default: false, desc: 'Filters only active users'
        optional :external, type: Boolean, default: false, desc: 'Filters only external users'
        optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users'
51 52
        optional :created_after, type: DateTime, desc: 'Return users created after the specified time'
        optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
53
        all_or_none_of :extern_uid, :provider
54

55
        use :pagination
56
      end
57
      get do
58 59
        authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)

James Lopez committed
60
        unless current_user&.admin?
61 62 63
          params.except!(:created_after, :created_before)
        end

64
        users = UsersFinder.new(current_user, params).execute
65

66
        authorized = can?(current_user, :read_users_list)
67

68 69 70 71 72 73
        # When `current_user` is not present, require that the `username`
        # parameter is passed, to prevent an unauthenticated user from accessing
        # a list of all the users on the GitLab instance. `UsersFinder` performs
        # an exact match on the `username` parameter, so we are guaranteed to
        # get either 0 or 1 `users` here.
        authorized &&= params[:username].present? if current_user.blank?
74

75 76 77
        forbidden!("Not authorized to access /api/v4/users") unless authorized

        entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
78
        present paginate(users), with: entity
79 80
      end

81
      desc 'Get a single user' do
82
        success Entities::User
83 84 85 86
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
      end
87
      get ":id" do
88
        user = User.find_by(id: params[:id])
89 90 91 92
        not_found!('User') unless user && can?(current_user, :read_user, user)

        opts = current_user&.admin? ? { with: Entities::UserWithAdmin } : {}
        present user, opts
93
      end
94

95
      desc 'Create a user. Available only for admins.' do
96
        success Entities::UserPublic
97 98 99
      end
      params do
        requires :email, type: String, desc: 'The email of the user'
100 101 102
        optional :password, type: String, desc: 'The password of the new user'
        optional :reset_password, type: Boolean, desc: 'Flag indicating the user will be sent a password reset token'
        at_least_one_of :password, :reset_password
103 104 105 106
        requires :name, type: String, desc: 'The name of the user'
        requires :username, type: String, desc: 'The username of the user'
        use :optional_attributes
      end
107 108
      post do
        authenticated_as_admin!
109

110
        params = declared_params(include_missing: false)
111
        user = ::Users::CreateService.new(current_user, params).execute(skip_authorization: true)
112

113
        if user.persisted?
114
          present user, with: Entities::UserPublic
115
        else
116 117 118
          conflict!('Email has already been taken') if User
              .where(email: user.email)
              .count > 0
119

120 121 122
          conflict!('Username has already been taken') if User
              .where(username: user.username)
              .count > 0
123 124

          render_validation_error!(user)
125 126
        end
      end
127

128
      desc 'Update a user. Available only for admins.' do
129
        success Entities::UserPublic
130 131 132 133 134 135 136 137 138
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
        optional :email, type: String, desc: 'The email of the user'
        optional :password, type: String, desc: 'The password of the new user'
        optional :name, type: String, desc: 'The name of the user'
        optional :username, type: String, desc: 'The username of the user'
        use :optional_attributes
      end
139 140
      put ":id" do
        authenticated_as_admin!
141

142
        user = User.find_by(id: params.delete(:id))
143
        not_found!('User') unless user
144

145
        conflict!('Email has already been taken') if params[:email] &&
146 147
            User.where(email: params[:email])
                .where.not(id: user.id).count > 0
148

149
        conflict!('Username has already been taken') if params[:username] &&
150 151
            User.where(username: params[:username])
                .where.not(id: user.id).count > 0
152

153 154
        user_params = declared_params(include_missing: false)
        identity_attrs = user_params.slice(:provider, :extern_uid)
155

156 157
        if identity_attrs.any?
          identity = user.identities.find_by(provider: identity_attrs[:provider])
158

159 160 161 162 163 164 165 166
          if identity
            identity.update_attributes(identity_attrs)
          else
            identity = user.identities.build(identity_attrs)
            identity.save
          end
        end

167
        user_params[:password_expires_at] = Time.now if user_params[:password].present?
168

169
        result = ::Users::UpdateService.new(user, user_params.except(:extern_uid, :provider)).execute
170 171

        if result[:status] == :success
172
          present user, with: Entities::UserPublic
173
        else
174
          render_validation_error!(user)
175 176 177
        end
      end

178 179 180 181 182 183 184 185
      desc 'Add an SSH key to a specified user. Available only for admins.' do
        success Entities::SSHKey
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
        requires :key, type: String, desc: 'The new SSH key'
        requires :title, type: String, desc: 'The title of the new SSH key'
      end
Angus MacArthur committed
186 187
      post ":id/keys" do
        authenticated_as_admin!
188

189 190 191 192 193
        user = User.find_by(id: params.delete(:id))
        not_found!('User') unless user

        key = user.keys.new(declared_params(include_missing: false))

Angus MacArthur committed
194 195 196
        if key.save
          present key, with: Entities::SSHKey
        else
197
          render_validation_error!(key)
Angus MacArthur committed
198 199 200
        end
      end

201 202 203 204 205
      desc 'Get the SSH keys of a specified user. Available only for admins.' do
        success Entities::SSHKey
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
206
        use :pagination
207 208
      end
      get ':id/keys' do
209
        authenticated_as_admin!
210 211

        user = User.find_by(id: params[:id])
212 213
        not_found!('User') unless user

214
        present paginate(user.keys), with: Entities::SSHKey
215 216
      end

217 218 219 220 221 222 223 224
      desc 'Delete an existing SSH key from a specified user. Available only for admins.' do
        success Entities::SSHKey
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
        requires :key_id, type: Integer, desc: 'The ID of the SSH key'
      end
      delete ':id/keys/:key_id' do
225
        authenticated_as_admin!
226 227

        user = User.find_by(id: params[:id])
228 229
        not_found!('User') unless user

230 231 232
        key = user.keys.find_by(id: params[:key_id])
        not_found!('Key') unless key

Dmitriy Zaporozhets committed
233
        status 204
234
        key.destroy
235 236
      end

237 238 239 240 241 242 243
      desc 'Add an email address to a specified user. Available only for admins.' do
        success Entities::Email
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
        requires :email, type: String, desc: 'The email of the user'
      end
244 245 246
      post ":id/emails" do
        authenticated_as_admin!

247 248 249
        user = User.find_by(id: params.delete(:id))
        not_found!('User') unless user

James Lopez committed
250
        email = Emails::CreateService.new(user, declared_params(include_missing: false)).execute
James Lopez committed
251 252

        if email.errors.blank?
253 254 255 256 257 258 259
          NotificationService.new.new_email(email)
          present email, with: Entities::Email
        else
          render_validation_error!(email)
        end
      end

260 261 262 263 264
      desc 'Get the emails addresses of a specified user. Available only for admins.' do
        success Entities::Email
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
265
        use :pagination
266 267
      end
      get ':id/emails' do
268
        authenticated_as_admin!
269
        user = User.find_by(id: params[:id])
270 271
        not_found!('User') unless user

272
        present paginate(user.emails), with: Entities::Email
273 274
      end

275 276 277 278 279 280 281 282
      desc 'Delete an email address of a specified user. Available only for admins.' do
        success Entities::Email
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
        requires :email_id, type: Integer, desc: 'The ID of the email'
      end
      delete ':id/emails/:email_id' do
283
        authenticated_as_admin!
284
        user = User.find_by(id: params[:id])
285 286
        not_found!('User') unless user

287 288
        email = user.emails.find_by(id: params[:email_id])
        not_found!('Email') unless email
289

James Lopez committed
290
        Emails::DestroyService.new(user, email: email.email).execute
291 292
      end

293 294 295 296 297
      desc 'Delete a user. Available only for admins.' do
        success Entities::Email
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
298
        optional :hard_delete, type: Boolean, desc: "Whether to remove a user's contributions"
299
      end
300 301
      delete ":id" do
        authenticated_as_admin!
skv committed
302
        user = User.find_by(id: params[:id])
303
        not_found!('User') unless user
304

Dmitriy Zaporozhets committed
305
        status 204
306
        user.delete_async(deleted_by: current_user, params: params)
307
      end
308

309 310 311 312
      desc 'Block a user. Available only for admins.'
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
      end
313
      post ':id/block' do
314 315
        authenticated_as_admin!
        user = User.find_by(id: params[:id])
316
        not_found!('User') unless user
317

318
        if !user.ldap_blocked?
319 320
          user.block
        else
321
          forbidden!('LDAP blocked users cannot be modified by the API')
322 323 324
        end
      end

325 326 327 328
      desc 'Unblock a user. Available only for admins.'
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
      end
329
      post ':id/unblock' do
330 331
        authenticated_as_admin!
        user = User.find_by(id: params[:id])
332
        not_found!('User') unless user
333

334
        if user.ldap_blocked?
335
          forbidden!('LDAP blocked users cannot be unblocked by the API')
Gabriel Mazetto committed
336 337
        else
          user.activate
338 339
        end
      end
340

341
      params do
342 343 344 345 346 347 348 349 350 351 352 353 354
        requires :user_id, type: Integer, desc: 'The ID of the user'
      end
      segment ':user_id' do
        resource :impersonation_tokens do
          helpers do
            def finder(options = {})
              user = find_user(params)
              PersonalAccessTokensFinder.new({ user: user, impersonation: true }.merge(options))
            end

            def find_impersonation_token
              finder.find_by(id: declared_params[:impersonation_token_id]) || not_found!('Impersonation Token')
            end
355
          end
356

357 358 359
          before { authenticated_as_admin! }

          desc 'Retrieve impersonation tokens. Available only for admins.' do
360
            detail 'This feature was introduced in GitLab 9.0'
361
            success Entities::ImpersonationToken
362 363
          end
          params do
364
            use :pagination
365
            optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) impersonation_tokens'
366
          end
367
          get { present paginate(finder(declared_params(include_missing: false)).execute), with: Entities::ImpersonationToken }
368

369
          desc 'Create a impersonation token. Available only for admins.' do
370
            detail 'This feature was introduced in GitLab 9.0'
371
            success Entities::ImpersonationToken
372 373
          end
          params do
374 375 376
            requires :name, type: String, desc: 'The name of the impersonation token'
            optional :expires_at, type: Date, desc: 'The expiration date in the format YEAR-MONTH-DAY of the impersonation token'
            optional :scopes, type: Array, desc: 'The array of scopes of the impersonation token'
377 378
          end
          post do
379
            impersonation_token = finder.build(declared_params(include_missing: false))
380

381 382
            if impersonation_token.save
              present impersonation_token, with: Entities::ImpersonationToken
383
            else
384
              render_validation_error!(impersonation_token)
385 386
            end
          end
387

388
          desc 'Retrieve impersonation token. Available only for admins.' do
389
            detail 'This feature was introduced in GitLab 9.0'
390
            success Entities::ImpersonationToken
391 392
          end
          params do
393
            requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
394
          end
395 396
          get ':impersonation_token_id' do
            present find_impersonation_token, with: Entities::ImpersonationToken
397
          end
398

399
          desc 'Revoke a impersonation token. Available only for admins.' do
400 401 402
            detail 'This feature was introduced in GitLab 9.0'
          end
          params do
403
            requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
404
          end
405
          delete ':impersonation_token_id' do
Dmitriy Zaporozhets committed
406
            status 204
407
            find_impersonation_token.revoke!
408 409
          end
        end
410
      end
411 412
    end

413
    resource :user do
414 415 416 417
      before do
        authenticate!
      end

418
      desc 'Get the currently authenticated user' do
419
        success Entities::UserPublic
420
      end
421
      get do
422 423 424 425 426 427 428 429 430 431
        entity =
          if sudo?
            Entities::UserWithPrivateDetails
          elsif current_user.admin?
            Entities::UserWithAdmin
          else
            Entities::UserPublic
          end

        present current_user, with: entity
432 433
      end

434 435 436
      desc "Get the currently authenticated user's SSH keys" do
        success Entities::SSHKey
      end
437 438 439
      params do
        use :pagination
      end
440
      get "keys" do
441
        present paginate(current_user.keys), with: Entities::SSHKey
442 443
      end

444 445 446 447 448 449 450 451 452 453
      desc 'Get a single key owned by currently authenticated user' do
        success Entities::SSHKey
      end
      params do
        requires :key_id, type: Integer, desc: 'The ID of the SSH key'
      end
      get "keys/:key_id" do
        key = current_user.keys.find_by(id: params[:key_id])
        not_found!('Key') unless key

454 455 456
        present key, with: Entities::SSHKey
      end

457 458 459 460 461 462 463
      desc 'Add a new SSH key to the currently authenticated user' do
        success Entities::SSHKey
      end
      params do
        requires :key, type: String, desc: 'The new SSH key'
        requires :title, type: String, desc: 'The title of the new SSH key'
      end
464
      post "keys" do
465
        key = current_user.keys.new(declared_params)
466

467 468 469
        if key.save
          present key, with: Entities::SSHKey
        else
470
          render_validation_error!(key)
471 472 473
        end
      end

474 475 476 477 478 479 480 481 482 483
      desc 'Delete an SSH key from the currently authenticated user' do
        success Entities::SSHKey
      end
      params do
        requires :key_id, type: Integer, desc: 'The ID of the SSH key'
      end
      delete "keys/:key_id" do
        key = current_user.keys.find_by(id: params[:key_id])
        not_found!('Key') unless key

Dmitriy Zaporozhets committed
484
        status 204
485
        key.destroy
486
      end
487

488 489 490
      desc "Get the currently authenticated user's email addresses" do
        success Entities::Email
      end
491 492 493
      params do
        use :pagination
      end
494
      get "emails" do
495
        present paginate(current_user.emails), with: Entities::Email
496 497
      end

498 499 500 501 502 503 504 505 506 507
      desc 'Get a single email address owned by the currently authenticated user' do
        success Entities::Email
      end
      params do
        requires :email_id, type: Integer, desc: 'The ID of the email'
      end
      get "emails/:email_id" do
        email = current_user.emails.find_by(id: params[:email_id])
        not_found!('Email') unless email

508 509 510
        present email, with: Entities::Email
      end

511 512 513 514 515 516
      desc 'Add new email address to the currently authenticated user' do
        success Entities::Email
      end
      params do
        requires :email, type: String, desc: 'The new email'
      end
517
      post "emails" do
James Lopez committed
518
        email = Emails::CreateService.new(current_user, declared_params).execute
519

James Lopez committed
520
        if email.errors.blank?
521 522 523 524 525 526 527
          NotificationService.new.new_email(email)
          present email, with: Entities::Email
        else
          render_validation_error!(email)
        end
      end

528 529 530 531 532 533 534
      desc 'Delete an email address from the currently authenticated user'
      params do
        requires :email_id, type: Integer, desc: 'The ID of the email'
      end
      delete "emails/:email_id" do
        email = current_user.emails.find_by(id: params[:email_id])
        not_found!('Email') unless email
535

Dmitriy Zaporozhets committed
536
        status 204
James Lopez committed
537
        Emails::DestroyService.new(current_user, email: email.email).execute
538
      end
539 540 541

      desc 'Get a list of user activities'
      params do
542
        optional :from, type: DateTime, default: 6.months.ago, desc: 'Date string in the format YEAR-MONTH-DAY'
543 544
        use :pagination
      end
545
      get "activities" do
546 547
        authenticated_as_admin!

548 549 550
        activities = User
          .where(User.arel_table[:last_activity_on].gteq(params[:from]))
          .reorder(last_activity_on: :asc)
551

552
        present paginate(activities), with: Entities::UserActivity
553
      end
554 555 556
    end
  end
end