From 3ac64827a94d1ef42c4941a4a7890cef5ef03e8a Mon Sep 17 00:00:00 2001
From: Jiovanni Castillo <jcastillo@gitlab.com>
Date: Tue, 18 Jul 2023 05:31:00 +0000
Subject: [PATCH] Add SCIM as a provider to the Users API search

Changelog: added
EE: true
---
 app/finders/users_finder.rb        |  6 ++----
 doc/api/users.md                   |  6 ++++++
 ee/app/finders/ee/users_finder.rb  |  8 ++++++++
 ee/app/models/ee/user.rb           |  2 ++
 ee/spec/requests/api/users_spec.rb | 24 ++++++++++++++++++++++++
 5 files changed, 42 insertions(+), 4 deletions(-)

diff --git a/app/finders/users_finder.rb b/app/finders/users_finder.rb
index 13c4aae5b25c..88ba635e20b2 100644
--- a/app/finders/users_finder.rb
+++ b/app/finders/users_finder.rb
@@ -99,13 +99,11 @@ def by_active(users)
     users.active
   end
 
-  # rubocop: disable CodeReuse/ActiveRecord
   def by_external_identity(users)
-    return users unless current_user&.can_admin_all_resources? && params[:extern_uid] && params[:provider]
+    return users unless params[:extern_uid] && params[:provider]
 
-    users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
+    users.by_provider_and_extern_uid(params[:provider], params[:extern_uid])
   end
-  # rubocop: enable CodeReuse/ActiveRecord
 
   # rubocop: disable CodeReuse/ActiveRecord
   def by_external(users)
diff --git a/doc/api/users.md b/doc/api/users.md
index cd9c7bb578d3..d413ed361e47 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -285,6 +285,12 @@ For example:
 GET /users?extern_uid=1234567&provider=github
 ```
 
+Users on [GitLab Premium or Ultimate](https://about.gitlab.com/pricing/) have the `scim` provider available:
+
+```plaintext
+GET /users?extern_uid=1234567&provider=scim
+```
+
 You can search users by creation date time range with:
 
 ```plaintext
diff --git a/ee/app/finders/ee/users_finder.rb b/ee/app/finders/ee/users_finder.rb
index 82d9375a519f..1f3180c48d3c 100644
--- a/ee/app/finders/ee/users_finder.rb
+++ b/ee/app/finders/ee/users_finder.rb
@@ -22,5 +22,13 @@ def by_saml_provider_id(users)
 
       users.limit_to_saml_provider(saml_provider_id)
     end
+
+    override :by_external_identity
+    def by_external_identity(users)
+      return users unless params[:extern_uid] && params[:provider]
+      return super unless params[:provider] == "scim"
+
+      users.with_scim_identities_by_extern_uid(params[:extern_uid])
+    end
   end
 end
diff --git a/ee/app/models/ee/user.rb b/ee/app/models/ee/user.rb
index 849c62d2000b..3e7ef356cc82 100644
--- a/ee/app/models/ee/user.rb
+++ b/ee/app/models/ee/user.rb
@@ -136,6 +136,8 @@ module User
         where(id: ::PersonalAccessToken.with_invalid_expires_at(expiration_date).select(:user_id))
       end
 
+      scope :with_scim_identities_by_extern_uid, ->(extern_uid) { joins(:scim_identities).merge(ScimIdentity.with_extern_uid(extern_uid)) }
+
       accepts_nested_attributes_for :namespace
       accepts_nested_attributes_for :custom_attributes
 
diff --git a/ee/spec/requests/api/users_spec.rb b/ee/spec/requests/api/users_spec.rb
index 0ce8ec8aa2dc..a3f2df0593ac 100644
--- a/ee/spec/requests/api/users_spec.rb
+++ b/ee/spec/requests/api/users_spec.rb
@@ -486,4 +486,28 @@
       end
     end
   end
+
+  describe 'GET /api/users?extern_uid=:extern_uid&provider=scim' do
+    context 'querying users by SCIM identity as an admin' do
+      let(:instance_scim_user) { create(:user) }
+      let!(:instance_scim_identity) { create(:scim_identity, user: instance_scim_user, extern_uid: 'test_uid') }
+
+      let(:group) { create(:group) }
+      let(:group_scim_user) { create(:user) }
+      let!(:group_scim_identity) { create(:scim_identity, user: group_scim_user, group: group, extern_uid: 'test_uid') }
+      let(:group_scim_user_2) { create(:user) }
+      let!(:group_scim_identity_2) { create(:scim_identity, user: group_scim_user_2, group: group, extern_uid: 'test_uid_2') }
+
+      it 'returns only users for the extern_uid' do
+        non_scim_user = create(:user)
+
+        get api("/users", admin, admin_mode: true), params: { extern_uid: 'test_uid', provider: 'scim' }
+
+        expect(json_response.map { |u| u['id'] }).to include(instance_scim_user.id)
+        expect(json_response.map { |u| u['id'] }).to include(group_scim_user.id)
+        expect(json_response.map { |u| u['id'] }).not_to include(group_scim_user_2.id)
+        expect(json_response.map { |u| u['id'] }).not_to include(non_scim_user.id)
+      end
+    end
+  end
 end
-- 
GitLab