BigW Consortium Gitlab

users.rb 23.8 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
      include CustomAttributesEndpoints

11 12 13 14
      before do
        authenticate_non_get!
      end

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

21 22 23 24 25 26 27
        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'
28
          optional :extern_uid, type: String, desc: 'The external authentication provider UID'
29 30 31 32 33
          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'
34
          optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed'
35
          optional :external, type: Boolean, desc: 'Flag indicating the user is an external user'
36
          optional :avatar, type: File, desc: 'Avatar image for user'
37 38 39 40 41 42 43 44
          all_or_none_of :extern_uid, :provider
        end
      end

      desc 'Get the list of users' do
        success Entities::UserBasic
      end
      params do
45
        # CE
46
        optional :username, type: String, desc: 'Get a single user with a specific username'
47 48
        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'
49 50 51 52
        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'
53 54
        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'
55
        all_or_none_of :extern_uid, :provider
56

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

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

66
        users = UsersFinder.new(current_user, params).execute
67

68
        authorized = can?(current_user, :read_users_list)
69

70 71 72 73 74 75
        # 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?
76

77 78 79
        forbidden!("Not authorized to access /api/v4/users") unless authorized

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

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

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

97
      desc 'Create a user. Available only for admins.' do
98
        success Entities::UserPublic
99 100 101
      end
      params do
        requires :email, type: String, desc: 'The email of the user'
102 103 104
        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
105 106 107 108
        requires :name, type: String, desc: 'The name of the user'
        requires :username, type: String, desc: 'The username of the user'
        use :optional_attributes
      end
109 110
      post do
        authenticated_as_admin!
111

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

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

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

          render_validation_error!(user)
127 128
        end
      end
129

130
      desc 'Update a user. Available only for admins.' do
131
        success Entities::UserPublic
132 133 134 135 136 137 138 139 140
      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
141 142
      put ":id" do
        authenticated_as_admin!
143

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

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

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

155 156
        user_params = declared_params(include_missing: false)
        identity_attrs = user_params.slice(:provider, :extern_uid)
157

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

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

169
        user_params[:password_expires_at] = Time.now if user_params[:password].present?
170

171
        result = ::Users::UpdateService.new(current_user, user_params.except(:extern_uid, :provider).merge(user: user)).execute
172 173

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

180 181 182 183 184 185 186 187
      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
188 189
      post ":id/keys" do
        authenticated_as_admin!
190

191 192 193 194 195
        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
196 197 198
        if key.save
          present key, with: Entities::SSHKey
        else
199
          render_validation_error!(key)
Angus MacArthur committed
200 201 202
        end
      end

203 204 205 206 207
      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'
208
        use :pagination
209 210
      end
      get ':id/keys' do
211
        authenticated_as_admin!
212 213

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

216
        present paginate(user.keys), with: Entities::SSHKey
217 218
      end

219 220 221 222 223 224 225 226
      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
227
        authenticated_as_admin!
228 229

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

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

235
        destroy_conditionally!(key)
236 237
      end

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
      desc 'Add a GPG key to a specified user. Available only for admins.' do
        detail 'This feature was added in GitLab 10.0'
        success Entities::GPGKey
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
        requires :key, type: String, desc: 'The new GPG key'
      end
      post ':id/gpg_keys' do
        authenticated_as_admin!

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

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

        if key.save
          present key, with: Entities::GPGKey
        else
          render_validation_error!(key)
        end
      end

      desc 'Get the GPG keys of a specified user. Available only for admins.' do
        detail 'This feature was added in GitLab 10.0'
        success Entities::GPGKey
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
        use :pagination
      end
      get ':id/gpg_keys' do
        authenticated_as_admin!

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

        present paginate(user.gpg_keys), with: Entities::GPGKey
      end

      desc 'Delete an existing GPG key from a specified user. Available only for admins.' do
        detail 'This feature was added in GitLab 10.0'
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
        requires :key_id, type: Integer, desc: 'The ID of the GPG key'
      end
      delete ':id/gpg_keys/:key_id' do
        authenticated_as_admin!

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

        key = user.gpg_keys.find_by(id: params[:key_id])
        not_found!('GPG Key') unless key

        status 204
        key.destroy
      end

      desc 'Revokes an existing GPG key from a specified user. Available only for admins.' do
        detail 'This feature was added in GitLab 10.0'
      end
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
        requires :key_id, type: Integer, desc: 'The ID of the GPG key'
      end
      post ':id/gpg_keys/:key_id/revoke' do
        authenticated_as_admin!

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

        key = user.gpg_keys.find_by(id: params[:key_id])
        not_found!('GPG Key') unless key

        key.revoke
        status :accepted
      end

318 319 320 321 322 323 324
      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
325 326 327
      post ":id/emails" do
        authenticated_as_admin!

328 329 330
        user = User.find_by(id: params.delete(:id))
        not_found!('User') unless user

James Lopez committed
331
        email = Emails::CreateService.new(current_user, declared_params(include_missing: false).merge(user: user)).execute
James Lopez committed
332 333

        if email.errors.blank?
334 335 336 337 338 339
          present email, with: Entities::Email
        else
          render_validation_error!(email)
        end
      end

340 341 342 343 344
      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'
345
        use :pagination
346 347
      end
      get ':id/emails' do
348
        authenticated_as_admin!
349
        user = User.find_by(id: params[:id])
350 351
        not_found!('User') unless user

352
        present paginate(user.emails), with: Entities::Email
353 354
      end

355 356 357 358 359 360 361 362
      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
363
        authenticated_as_admin!
364
        user = User.find_by(id: params[:id])
365 366
        not_found!('User') unless user

367 368
        email = user.emails.find_by(id: params[:email_id])
        not_found!('Email') unless email
369

370
        destroy_conditionally!(email) do |email|
371
          Emails::DestroyService.new(current_user, user: user).execute(email)
372
        end
373 374
      end

375 376 377 378 379
      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'
380
        optional :hard_delete, type: Boolean, desc: "Whether to remove a user's contributions"
381
      end
382 383
      delete ":id" do
        authenticated_as_admin!
384

skv committed
385
        user = User.find_by(id: params[:id])
386
        not_found!('User') unless user
387

388 389 390
        destroy_conditionally!(user) do
          user.delete_async(deleted_by: current_user, params: params)
        end
391
      end
392

393 394 395 396
      desc 'Block a user. Available only for admins.'
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
      end
397
      post ':id/block' do
398 399
        authenticated_as_admin!
        user = User.find_by(id: params[:id])
400
        not_found!('User') unless user
401

402
        if !user.ldap_blocked?
403 404
          user.block
        else
405
          forbidden!('LDAP blocked users cannot be modified by the API')
406 407 408
        end
      end

409 410 411 412
      desc 'Unblock a user. Available only for admins.'
      params do
        requires :id, type: Integer, desc: 'The ID of the user'
      end
413
      post ':id/unblock' do
414 415
        authenticated_as_admin!
        user = User.find_by(id: params[:id])
416
        not_found!('User') unless user
417

418
        if user.ldap_blocked?
419
          forbidden!('LDAP blocked users cannot be unblocked by the API')
Gabriel Mazetto committed
420 421
        else
          user.activate
422 423
        end
      end
424

425
      params do
426 427 428 429 430 431
        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 = {})
432
              user = find_user_by_id(params)
433 434 435 436 437 438
              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
439
          end
440

441 442 443
          before { authenticated_as_admin! }

          desc 'Retrieve impersonation tokens. Available only for admins.' do
444
            detail 'This feature was introduced in GitLab 9.0'
445
            success Entities::ImpersonationToken
446 447
          end
          params do
448
            use :pagination
449
            optional :state, type: String, default: 'all', values: %w[all active inactive], desc: 'Filters (all|active|inactive) impersonation_tokens'
450
          end
451
          get { present paginate(finder(declared_params(include_missing: false)).execute), with: Entities::ImpersonationToken }
452

453
          desc 'Create a impersonation token. Available only for admins.' do
454
            detail 'This feature was introduced in GitLab 9.0'
455
            success Entities::ImpersonationToken
456 457
          end
          params do
458 459 460
            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'
461 462
          end
          post do
463
            impersonation_token = finder.build(declared_params(include_missing: false))
464

465 466
            if impersonation_token.save
              present impersonation_token, with: Entities::ImpersonationToken
467
            else
468
              render_validation_error!(impersonation_token)
469 470
            end
          end
471

472
          desc 'Retrieve impersonation token. Available only for admins.' do
473
            detail 'This feature was introduced in GitLab 9.0'
474
            success Entities::ImpersonationToken
475 476
          end
          params do
477
            requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
478
          end
479 480
          get ':impersonation_token_id' do
            present find_impersonation_token, with: Entities::ImpersonationToken
481
          end
482

483
          desc 'Revoke a impersonation token. Available only for admins.' do
484 485 486
            detail 'This feature was introduced in GitLab 9.0'
          end
          params do
487
            requires :impersonation_token_id, type: Integer, desc: 'The ID of the impersonation token'
488
          end
489
          delete ':impersonation_token_id' do
490 491 492 493 494
            token = find_impersonation_token

            destroy_conditionally!(token) do
              token.revoke!
            end
495 496
          end
        end
497
      end
498 499
    end

500
    resource :user do
501 502 503 504
      before do
        authenticate!
      end

505
      desc 'Get the currently authenticated user' do
506
        success Entities::UserPublic
507
      end
508
      get do
509 510 511 512 513 514 515 516 517 518
        entity =
          if sudo?
            Entities::UserWithPrivateDetails
          elsif current_user.admin?
            Entities::UserWithAdmin
          else
            Entities::UserPublic
          end

        present current_user, with: entity
519 520
      end

521 522 523
      desc "Get the currently authenticated user's SSH keys" do
        success Entities::SSHKey
      end
524 525 526
      params do
        use :pagination
      end
527
      get "keys" do
528
        present paginate(current_user.keys), with: Entities::SSHKey
529 530
      end

531 532 533 534 535 536 537 538 539 540
      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

541 542 543
        present key, with: Entities::SSHKey
      end

544 545 546 547 548 549 550
      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
551
      post "keys" do
552
        key = current_user.keys.new(declared_params)
553

554 555 556
        if key.save
          present key, with: Entities::SSHKey
        else
557
          render_validation_error!(key)
558 559 560
        end
      end

561 562 563 564 565 566 567 568 569 570
      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

571
        destroy_conditionally!(key)
572
      end
573

574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
      desc "Get the currently authenticated user's GPG keys" do
        detail 'This feature was added in GitLab 10.0'
        success Entities::GPGKey
      end
      params do
        use :pagination
      end
      get 'gpg_keys' do
        present paginate(current_user.gpg_keys), with: Entities::GPGKey
      end

      desc 'Get a single GPG key owned by currently authenticated user' do
        detail 'This feature was added in GitLab 10.0'
        success Entities::GPGKey
      end
      params do
        requires :key_id, type: Integer, desc: 'The ID of the GPG key'
      end
      get 'gpg_keys/:key_id' do
        key = current_user.gpg_keys.find_by(id: params[:key_id])
        not_found!('GPG Key') unless key

        present key, with: Entities::GPGKey
      end

      desc 'Add a new GPG key to the currently authenticated user' do
        detail 'This feature was added in GitLab 10.0'
        success Entities::GPGKey
      end
      params do
        requires :key, type: String, desc: 'The new GPG key'
      end
      post 'gpg_keys' do
        key = current_user.gpg_keys.new(declared_params)

        if key.save
          present key, with: Entities::GPGKey
        else
          render_validation_error!(key)
        end
      end

      desc 'Revoke a GPG key owned by currently authenticated user' do
        detail 'This feature was added in GitLab 10.0'
      end
      params do
        requires :key_id, type: Integer, desc: 'The ID of the GPG key'
      end
      post 'gpg_keys/:key_id/revoke' do
        key = current_user.gpg_keys.find_by(id: params[:key_id])
        not_found!('GPG Key') unless key

        key.revoke
        status :accepted
      end

      desc 'Delete a GPG key from the currently authenticated user' do
        detail 'This feature was added in GitLab 10.0'
      end
      params do
        requires :key_id, type: Integer, desc: 'The ID of the SSH key'
      end
      delete 'gpg_keys/:key_id' do
        key = current_user.gpg_keys.find_by(id: params[:key_id])
        not_found!('GPG Key') unless key

        status 204
        key.destroy
      end

644 645 646
      desc "Get the currently authenticated user's email addresses" do
        success Entities::Email
      end
647 648 649
      params do
        use :pagination
      end
650
      get "emails" do
651
        present paginate(current_user.emails), with: Entities::Email
652 653
      end

654 655 656 657 658 659 660 661 662 663
      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

664 665 666
        present email, with: Entities::Email
      end

667 668 669 670 671 672
      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
673
      post "emails" do
James Lopez committed
674
        email = Emails::CreateService.new(current_user, declared_params.merge(user: current_user)).execute
675

James Lopez committed
676
        if email.errors.blank?
677 678 679 680 681 682
          present email, with: Entities::Email
        else
          render_validation_error!(email)
        end
      end

683 684 685 686 687 688 689
      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
690

691
        destroy_conditionally!(email) do |email|
692
          Emails::DestroyService.new(current_user, user: current_user).execute(email)
693
        end
694
      end
695 696 697

      desc 'Get a list of user activities'
      params do
698
        optional :from, type: DateTime, default: 6.months.ago, desc: 'Date string in the format YEAR-MONTH-DAY'
699 700
        use :pagination
      end
701
      get "activities" do
702 703
        authenticated_as_admin!

704 705 706
        activities = User
          .where(User.arel_table[:last_activity_on].gteq(params[:from]))
          .reorder(last_activity_on: :asc)
707

708
        present paginate(activities), with: Entities::UserActivity
709
      end
710 711 712
    end
  end
end