Commit 38d3e650 authored by James Edwards-Jones's avatar James Edwards-Jones Committed by Douglas Barbosa Alexandre

SCIM GET /Users supports requests without a filter

Allows all SCIM identities to be returned, paginated.

SCIM clients will sometimes send a request without a filter, using
pagination to limit results instead. Previously we would raise an error
when a filter wasn't provided, meaning we were unable to process those
requests.

Our initial approach was an MVC for Azure support, which required filter
by id. Filters are optional however, and we need to conform to the spec
more closely for Okta comaptibility.

See: https://tools.ietf.org/html/rfc7644#section-3.4.2.2
parent 2ffab3be
...@@ -21,7 +21,7 @@ Parameters: ...@@ -21,7 +21,7 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
|:----------|:--------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------| |:----------|:--------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------|
| `filter` | string | yes | A [filter](#available-filters) expression. | | `filter` | string | no | A [filter](#available-filters) expression. |
| `group_path` | string | yes | Full path to the group. | | `group_path` | string | yes | Full path to the group. |
| `startIndex` | integer | no | The 1-based index indicating where to start returning results from. A value of less than one will be interpreted as 1. | | `startIndex` | integer | no | The 1-based index indicating where to start returning results from. A value of less than one will be interpreted as 1. |
| `count` | integer | no | Desired maximum number of query results. | | `count` | integer | no | Desired maximum number of query results. |
......
...@@ -3,14 +3,40 @@ ...@@ -3,14 +3,40 @@
class ScimFinder class ScimFinder
attr_reader :saml_provider attr_reader :saml_provider
UnsupportedFilter = Class.new(StandardError)
def initialize(group) def initialize(group)
@saml_provider = group&.saml_provider @saml_provider = group&.saml_provider
end end
def search(params) def search(params)
return Identity.none unless saml_provider&.enabled? return Identity.none unless saml_provider&.enabled?
return saml_provider.identities if unfiltered?(params)
filter_identities(params)
end
private
def unfiltered?(params)
params[:filter].blank?
end
def filter_identities(params)
parser = EE::Gitlab::Scim::ParamsParser.new(params) parser = EE::Gitlab::Scim::ParamsParser.new(params)
if eq_filter_on_extern_uid?(parser)
by_extern_uid(parser)
else
raise UnsupportedFilter
end
end
def eq_filter_on_extern_uid?(parser)
parser.filter_operator == :eq && parser.filter_params[:extern_uid].present?
end
def by_extern_uid(parser)
Identity.where_group_saml_uid(saml_provider, parser.filter_params[:extern_uid]) Identity.where_group_saml_uid(saml_provider, parser.filter_params[:extern_uid])
end end
end end
---
title: SCIM GET /Users supports requests without a filter
merge_request: 19421
author:
type: changed
...@@ -95,8 +95,6 @@ module API ...@@ -95,8 +95,6 @@ module API
detail 'This feature was introduced in GitLab 11.10.' detail 'This feature was introduced in GitLab 11.10.'
end end
get do get do
scim_error!(message: 'Missing filter params') unless params[:filter]
group = find_and_authenticate_group!(params[:group]) group = find_and_authenticate_group!(params[:group])
results = ScimFinder.new(group).search(params) results = ScimFinder.new(group).search(params)
...@@ -106,6 +104,8 @@ module API ...@@ -106,6 +104,8 @@ module API
result_set = { resources: response_page, total_results: results.count, items_per_page: per_page(params[:count]), start_index: params[:startIndex] } result_set = { resources: response_page, total_results: results.count, items_per_page: per_page(params[:count]), start_index: params[:startIndex] }
present result_set, with: ::EE::Gitlab::Scim::Users present result_set, with: ::EE::Gitlab::Scim::Users
rescue ScimFinder::UnsupportedFilter
scim_error!(message: 'Unsupported Filter')
end end
desc 'Get a SAML user' do desc 'Get a SAML user' do
......
...@@ -30,6 +30,7 @@ describe ScimFinder do ...@@ -30,6 +30,7 @@ describe ScimFinder do
context 'with an eq filter' do context 'with an eq filter' do
let!(:identity) { create(:group_saml_identity, saml_provider: saml_provider) } let!(:identity) { create(:group_saml_identity, saml_provider: saml_provider) }
let!(:other_identity) { create(:group_saml_identity, saml_provider: saml_provider) }
it 'allows identity lookup by id/externalId' do it 'allows identity lookup by id/externalId' do
expect(finder.search(filter: "id eq #{identity.extern_uid}")).to be_a ActiveRecord::Relation expect(finder.search(filter: "id eq #{identity.extern_uid}")).to be_a ActiveRecord::Relation
...@@ -37,6 +38,20 @@ describe ScimFinder do ...@@ -37,6 +38,20 @@ describe ScimFinder do
expect(finder.search(filter: "externalId eq #{identity.extern_uid}").first).to eq identity expect(finder.search(filter: "externalId eq #{identity.extern_uid}").first).to eq identity
end end
end end
it 'returns all related identities if there is no filter' do
create_list(:group_saml_identity, 2, saml_provider: saml_provider)
expect(finder.search({}).count).to eq 2
end
it 'raises an error if the filter is unsupported' do
expect { finder.search(filter: 'id ne 1').count }.to raise_error(ScimFinder::UnsupportedFilter)
end
it 'raises an error if the attribute path is unsupported' do
expect { finder.search(filter: 'userName eq "name"').count }.to raise_error(ScimFinder::UnsupportedFilter)
end
end end
end end
end end
...@@ -23,13 +23,21 @@ describe API::Scim do ...@@ -23,13 +23,21 @@ describe API::Scim do
end end
end end
it 'responds with an error if there is no filter' do it 'responds with paginated users when there is no filter' do
get scim_api("scim/v2/groups/#{group.full_path}/Users") get scim_api("scim/v2/groups/#{group.full_path}/Users")
expect(response).to have_gitlab_http_status(200)
expect(json_response['Resources']).not_to be_empty
expect(json_response['totalResults']).to eq(Identity.count)
end
it 'responds with an error for unsupported filters' do
get scim_api("scim/v2/groups/#{group.full_path}/Users?filter=id ne \"#{identity.extern_uid}\"")
expect(response).to have_gitlab_http_status(412) expect(response).to have_gitlab_http_status(412)
end end
context 'existing user' do context 'existing user matches filter' do
it 'responds with 200' do it 'responds with 200' do
get scim_api("scim/v2/groups/#{group.full_path}/Users?filter=id eq \"#{identity.extern_uid}\"") get scim_api("scim/v2/groups/#{group.full_path}/Users?filter=id eq \"#{identity.extern_uid}\"")
...@@ -47,7 +55,7 @@ describe API::Scim do ...@@ -47,7 +55,7 @@ describe API::Scim do
end end
end end
context 'no user' do context 'no user matches filter' do
it 'responds with 200' do it 'responds with 200' do
get scim_api("scim/v2/groups/#{group.full_path}/Users?filter=id eq \"nonexistent\"") get scim_api("scim/v2/groups/#{group.full_path}/Users?filter=id eq \"nonexistent\"")
......
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