Implement review comments for !12445 from @godfat and @rymai.
- Use `GlobalPolicy` to authorize the users that a non-authenticated user can fetch from `/api/v4/users`. We allow access if the `Gitlab::VisibilityLevel::PUBLIC` visibility level is not restricted. - Further, as before, `/api/v4/users` is only accessible to unauthenticated users if the `username` parameter is passed. - Turn off `authenticate!` for the `/api/v4/users` endpoint by matching on the actual route + method, rather than the description. - Change the type of `current_user` check in `UsersFinder` to be more compatible with EE.
This commit is contained in:
parent
c39e4ccfb7
commit
3c88a7869b
7 changed files with 26 additions and 32 deletions
|
@ -27,11 +27,8 @@ class UsersFinder
|
||||||
users = by_search(users)
|
users = by_search(users)
|
||||||
users = by_blocked(users)
|
users = by_blocked(users)
|
||||||
users = by_active(users)
|
users = by_active(users)
|
||||||
|
|
||||||
if current_user
|
|
||||||
users = by_external_identity(users)
|
users = by_external_identity(users)
|
||||||
users = by_external(users)
|
users = by_external(users)
|
||||||
end
|
|
||||||
|
|
||||||
users
|
users
|
||||||
end
|
end
|
||||||
|
@ -63,13 +60,13 @@ class UsersFinder
|
||||||
end
|
end
|
||||||
|
|
||||||
def by_external_identity(users)
|
def by_external_identity(users)
|
||||||
return users unless current_user.admin? && params[:extern_uid] && params[:provider]
|
return users unless current_user&.admin? && params[:extern_uid] && params[:provider]
|
||||||
|
|
||||||
users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
|
users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
|
||||||
end
|
end
|
||||||
|
|
||||||
def by_external(users)
|
def by_external(users)
|
||||||
return users = users.where.not(external: true) unless current_user.admin?
|
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
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
class BasePolicy
|
class BasePolicy
|
||||||
|
include Gitlab::CurrentSettings
|
||||||
|
|
||||||
class RuleSet
|
class RuleSet
|
||||||
attr_reader :can_set, :cannot_set
|
attr_reader :can_set, :cannot_set
|
||||||
def initialize(can_set, cannot_set)
|
def initialize(can_set, cannot_set)
|
||||||
|
@ -124,4 +126,8 @@ class BasePolicy
|
||||||
yield
|
yield
|
||||||
@rule_set
|
@rule_set
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def restricted_public_level?
|
||||||
|
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
class GlobalPolicy < BasePolicy
|
class GlobalPolicy < BasePolicy
|
||||||
def rules
|
def rules
|
||||||
|
can! :read_users_list unless restricted_public_level?
|
||||||
|
|
||||||
return unless @user
|
return unless @user
|
||||||
|
|
||||||
can! :create_group if @user.can_create_group
|
can! :create_group if @user.can_create_group
|
||||||
can! :read_users_list
|
|
||||||
|
|
||||||
unless @user.blocked? || @user.internal?
|
unless @user.blocked? || @user.internal?
|
||||||
can! :log_in unless @user.access_locked?
|
can! :log_in unless @user.access_locked?
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
class UserPolicy < BasePolicy
|
class UserPolicy < BasePolicy
|
||||||
include Gitlab::CurrentSettings
|
|
||||||
|
|
||||||
def rules
|
def rules
|
||||||
can! :read_user if @user || !restricted_public_level?
|
can! :read_user if @user || !restricted_public_level?
|
||||||
|
|
||||||
|
@ -12,8 +10,4 @@ class UserPolicy < BasePolicy
|
||||||
cannot! :destroy_user if @subject.ghost?
|
cannot! :destroy_user if @subject.ghost?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def restricted_public_level?
|
|
||||||
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -410,8 +410,8 @@ module API
|
||||||
|
|
||||||
# Does the current route match the route identified by
|
# Does the current route match the route identified by
|
||||||
# `description`?
|
# `description`?
|
||||||
def route_matches_description?(description)
|
def request_matches_route?(method, route)
|
||||||
options.dig(:route_options, :description) == description
|
request.request_method == method && request.path == route
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ module API
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow_access_with_scope :read_user if request.get?
|
allow_access_with_scope :read_user if request.get?
|
||||||
authenticate! unless route_matches_description?("Get the list of users")
|
authenticate! unless request_matches_route?('GET', '/api/v4/users')
|
||||||
end
|
end
|
||||||
|
|
||||||
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
|
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
|
||||||
|
@ -55,22 +55,18 @@ module API
|
||||||
|
|
||||||
users = UsersFinder.new(current_user, params).execute
|
users = UsersFinder.new(current_user, params).execute
|
||||||
|
|
||||||
authorized =
|
authorized = can?(current_user, :read_users_list)
|
||||||
if current_user
|
|
||||||
can?(current_user, :read_users_list)
|
|
||||||
else
|
|
||||||
# When `current_user` is not present, require that the `username`
|
# When `current_user` is not present, require that the `username`
|
||||||
# parameter is passed, to prevent an unauthenticated user from accessing
|
# parameter is passed, to prevent an unauthenticated user from accessing
|
||||||
# a list of all the users on the GitLab instance. `UsersFinder` performs
|
# a list of all the users on the GitLab instance. `UsersFinder` performs
|
||||||
# an exact match on the `username` parameter, so we are guaranteed to
|
# an exact match on the `username` parameter, so we are guaranteed to
|
||||||
# get either 0 or 1 `users` here.
|
# get either 0 or 1 `users` here.
|
||||||
params[:username].present? &&
|
authorized &&= params[:username].present? if current_user.blank?
|
||||||
users.all? { |user| can?(current_user, :read_user, user) }
|
|
||||||
end
|
|
||||||
|
|
||||||
render_api_error!("Not authorized.", 403) unless authorized
|
forbidden!("Not authorized to access /api/v4/users") unless authorized
|
||||||
|
|
||||||
entity = current_user.try(:admin?) ? Entities::UserWithAdmin : Entities::UserBasic
|
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
|
||||||
present paginate(users), with: entity
|
present paginate(users), with: entity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ describe API::Users do
|
||||||
it "returns authorization error when the `username` parameter refers to an inaccessible user" do
|
it "returns authorization error when the `username` parameter refers to an inaccessible user" do
|
||||||
user = create(:user)
|
user = create(:user)
|
||||||
|
|
||||||
expect(Ability).to receive(:allowed?).with(nil, :read_user, user).and_return(false)
|
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
|
||||||
|
|
||||||
get api("/users"), username: user.username
|
get api("/users"), username: user.username
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue