Commit 87ce1f33 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch 'fix/include-external-users-in-user-search' into 'master'

Include external users in user search

See merge request gitlab-org/gitlab!53584
parents a68e88e7 2052d1d5
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
# active: boolean # active: boolean
# blocked: boolean # blocked: boolean
# external: boolean # external: boolean
# non_external: boolean
# without_projects: boolean # without_projects: boolean
# sort: string # sort: string
# id: integer # id: integer
...@@ -40,6 +41,7 @@ class UsersFinder ...@@ -40,6 +41,7 @@ class UsersFinder
users = by_active(users) users = by_active(users)
users = by_external_identity(users) users = by_external_identity(users)
users = by_external(users) users = by_external(users)
users = by_non_external(users)
users = by_2fa(users) users = by_2fa(users)
users = by_created_at(users) users = by_created_at(users)
users = by_without_projects(users) users = by_without_projects(users)
...@@ -97,13 +99,18 @@ class UsersFinder ...@@ -97,13 +99,18 @@ class UsersFinder
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def by_external(users) def by_external(users)
return users = users.where.not(external: true) unless current_user&.admin?
return users unless params[:external] return users unless params[:external]
users.external users.external
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def by_non_external(users)
return users unless params[:non_external]
users.non_external
end
def by_2fa(users) def by_2fa(users)
case params[:two_factor] case params[:two_factor]
when 'enabled' when 'enabled'
......
...@@ -360,6 +360,7 @@ class User < ApplicationRecord ...@@ -360,6 +360,7 @@ class User < ApplicationRecord
scope :blocked, -> { with_states(:blocked, :ldap_blocked) } scope :blocked, -> { with_states(:blocked, :ldap_blocked) }
scope :blocked_pending_approval, -> { with_states(:blocked_pending_approval) } scope :blocked_pending_approval, -> { with_states(:blocked_pending_approval) }
scope :external, -> { where(external: true) } scope :external, -> { where(external: true) }
scope :non_external, -> { where(external: false) }
scope :confirmed, -> { where.not(confirmed_at: nil) } scope :confirmed, -> { where.not(confirmed_at: nil) }
scope :active, -> { with_state(:active).non_internal } scope :active, -> { with_state(:active).non_internal }
scope :active_without_ghosts, -> { with_state(:active).without_ghosts } scope :active_without_ghosts, -> { with_state(:active).without_ghosts }
......
---
title: 'API: include external users in user search for non-admins'
merge_request: 53584
author: Jonas Wälter @wwwjon
type: changed
...@@ -53,6 +53,9 @@ For example: ...@@ -53,6 +53,9 @@ For example:
GET /users?username=jack_smith GET /users?username=jack_smith
``` ```
NOTE:
Username search is case insensitive.
In addition, you can filter users based on the states `blocked` and `active`. In addition, you can filter users based on the states `blocked` and `active`.
It does not support `active=false` or `blocked=false`. The list of billable users It does not support `active=false` or `blocked=false`. The list of billable users
is the total number of users minus the blocked users. is the total number of users minus the blocked users.
...@@ -65,6 +68,13 @@ GET /users?active=true ...@@ -65,6 +68,13 @@ GET /users?active=true
GET /users?blocked=true GET /users?blocked=true
``` ```
In addition, you can search for external users only with `external=true`.
It does not support `external=false`.
```plaintext
GET /users?external=true
```
GitLab supports bot users such as the [alert bot](../operations/incident_management/integrations.md) GitLab supports bot users such as the [alert bot](../operations/incident_management/integrations.md)
or the [support bot](../user/project/service_desk.md#support-bot-user). or the [support bot](../user/project/service_desk.md#support-bot-user).
You can exclude the following types of [internal users](../development/internal_users.md#internal-users) You can exclude the following types of [internal users](../development/internal_users.md#internal-users)
...@@ -80,8 +90,11 @@ However, this action does not exclude [project bot users](../user/project/settin ...@@ -80,8 +90,11 @@ However, this action does not exclude [project bot users](../user/project/settin
GET /users?exclude_internal=true GET /users?exclude_internal=true
``` ```
NOTE: In addition, to exclude external users from the users' list, you can use the parameter `exclude_external=true`.
Username search is case insensitive.
```plaintext
GET /users?exclude_external=true
```
### For admins ### For admins
...@@ -223,10 +236,6 @@ For example: ...@@ -223,10 +236,6 @@ For example:
GET /users?extern_uid=1234567&provider=github GET /users?extern_uid=1234567&provider=github
``` ```
Instance administrators can search for users who are external with: `/users?external=true`
You cannot search for external users if you are not an instance administrator.
You can search users by creation date time range with: You can search users by creation date time range with:
```plaintext ```plaintext
......
...@@ -13,13 +13,13 @@ RSpec.describe UsersFinder do ...@@ -13,13 +13,13 @@ RSpec.describe UsersFinder do
it 'returns ldap users by default' do it 'returns ldap users by default' do
users = described_class.new(normal_user).execute users = described_class.new(normal_user).execute
expect(users).to contain_exactly(normal_user, blocked_user, omniauth_user, ldap_user, internal_user, admin_user) expect(users).to contain_exactly(normal_user, blocked_user, omniauth_user, external_user, ldap_user, internal_user, admin_user)
end end
it 'returns only non-ldap users with skip_ldap: true' do it 'returns only non-ldap users with skip_ldap: true' do
users = described_class.new(normal_user, skip_ldap: true).execute users = described_class.new(normal_user, skip_ldap: true).execute
expect(users).to contain_exactly(normal_user, blocked_user, omniauth_user, internal_user, admin_user) expect(users).to contain_exactly(normal_user, blocked_user, omniauth_user, external_user, internal_user, admin_user)
end end
end end
end end
......
...@@ -82,6 +82,7 @@ module API ...@@ -82,6 +82,7 @@ module API
optional :search, type: String, desc: 'Search for a username' optional :search, type: String, desc: 'Search for a username'
optional :active, type: Boolean, default: false, desc: 'Filters only active users' optional :active, type: Boolean, default: false, desc: 'Filters only active users'
optional :external, type: Boolean, default: false, desc: 'Filters only external users' optional :external, type: Boolean, default: false, desc: 'Filters only external users'
optional :exclude_external, as: :non_external, type: Boolean, default: false, desc: 'Filters only non external users'
optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users' optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users'
optional :created_after, type: DateTime, desc: 'Return users created after the specified time' 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' optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
...@@ -97,7 +98,7 @@ module API ...@@ -97,7 +98,7 @@ module API
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
get feature_category: :users do get feature_category: :users do
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) authenticated_as_admin! if params[:extern_uid].present? && params[:provider].present?
unless current_user&.admin? unless current_user&.admin?
params.except!(:created_after, :created_before, :order_by, :sort, :two_factor, :without_projects) params.except!(:created_after, :created_before, :order_by, :sort, :two_factor, :without_projects)
......
...@@ -12,7 +12,7 @@ RSpec.describe UsersFinder do ...@@ -12,7 +12,7 @@ RSpec.describe UsersFinder do
it 'returns all users' do it 'returns all users' do
users = described_class.new(user).execute users = described_class.new(user).execute
expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user, internal_user, admin_user) expect(users).to contain_exactly(user, normal_user, blocked_user, external_user, omniauth_user, internal_user, admin_user)
end end
it 'filters by username' do it 'filters by username' do
...@@ -48,12 +48,18 @@ RSpec.describe UsersFinder do ...@@ -48,12 +48,18 @@ RSpec.describe UsersFinder do
it 'filters by active users' do it 'filters by active users' do
users = described_class.new(user, active: true).execute users = described_class.new(user, active: true).execute
expect(users).to contain_exactly(user, normal_user, omniauth_user, admin_user) expect(users).to contain_exactly(user, normal_user, external_user, omniauth_user, admin_user)
end end
it 'returns no external users' do it 'filters by external users' do
users = described_class.new(user, external: true).execute users = described_class.new(user, external: true).execute
expect(users).to contain_exactly(external_user)
end
it 'filters by non external users' do
users = described_class.new(user, non_external: true).execute
expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user, internal_user, admin_user) expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user, internal_user, admin_user)
end end
...@@ -71,7 +77,7 @@ RSpec.describe UsersFinder do ...@@ -71,7 +77,7 @@ RSpec.describe UsersFinder do
it 'filters by non internal users' do it 'filters by non internal users' do
users = described_class.new(user, non_internal: true).execute users = described_class.new(user, non_internal: true).execute
expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user, admin_user) expect(users).to contain_exactly(user, normal_user, external_user, blocked_user, omniauth_user, admin_user)
end end
it 'does not filter by custom attributes' do it 'does not filter by custom attributes' do
...@@ -80,18 +86,18 @@ RSpec.describe UsersFinder do ...@@ -80,18 +86,18 @@ RSpec.describe UsersFinder do
custom_attributes: { foo: 'bar' } custom_attributes: { foo: 'bar' }
).execute ).execute
expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user, internal_user, admin_user) expect(users).to contain_exactly(user, normal_user, blocked_user, external_user, omniauth_user, internal_user, admin_user)
end end
it 'orders returned results' do it 'orders returned results' do
users = described_class.new(user, sort: 'id_asc').execute users = described_class.new(user, sort: 'id_asc').execute
expect(users).to eq([normal_user, admin_user, blocked_user, omniauth_user, internal_user, user]) expect(users).to eq([normal_user, admin_user, blocked_user, external_user, omniauth_user, internal_user, user])
end end
it 'does not filter by admins' do it 'does not filter by admins' do
users = described_class.new(user, admins: true).execute users = described_class.new(user, admins: true).execute
expect(users).to contain_exactly(user, normal_user, admin_user, blocked_user, omniauth_user, internal_user) expect(users).to contain_exactly(user, normal_user, external_user, admin_user, blocked_user, omniauth_user, internal_user)
end end
end end
......
...@@ -320,6 +320,18 @@ RSpec.describe API::Users do ...@@ -320,6 +320,18 @@ RSpec.describe API::Users do
expect(json_response).to all(include('state' => /(blocked|ldap_blocked)/)) expect(json_response).to all(include('state' => /(blocked|ldap_blocked)/))
end end
it "returns an array of external users" do
create(:user)
external_user = create(:user, external: true)
get api("/users?external=true", user)
expect(response).to match_response_schema('public_api/v4/user/basics')
expect(response).to include_pagination_headers
expect(json_response.size).to eq(1)
expect(json_response[0]['id']).to eq(external_user.id)
end
it "returns one user" do it "returns one user" do
get api("/users?username=#{omniauth_user.username}", user) get api("/users?username=#{omniauth_user.username}", user)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment