mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Remove feature toggle for SCIM API
This has been part of OpenProject for a few releases already and there is no need to disable it anymore.
This commit is contained in:
@@ -53,10 +53,6 @@ OpenProject::FeatureDecisions.add :oidc_group_sync,
|
||||
description: "Allows to synchronize groups from OpenID Connect providers",
|
||||
force_active: true
|
||||
|
||||
OpenProject::FeatureDecisions.add :scim_api,
|
||||
description: "Enables SCIM API.",
|
||||
force_active: true
|
||||
|
||||
OpenProject::FeatureDecisions.add :beta_widgets,
|
||||
description: "Enables BETA versions of widgets."
|
||||
|
||||
|
||||
@@ -588,7 +588,7 @@ Redmine::MenuManager.map :admin_menu do |menu|
|
||||
|
||||
menu.push :scim_clients,
|
||||
{ controller: "/admin/scim_clients", action: "index" },
|
||||
if: ->(_) { User.current.admin? && OpenProject::FeatureDecisions.scim_api_active? },
|
||||
if: ->(_) { User.current.admin? },
|
||||
parent: :authentication,
|
||||
caption: ScimClient.model_name.human(count: 2),
|
||||
enterprise_feature: "scim_api"
|
||||
|
||||
@@ -43,9 +43,6 @@ Rails.application.config.to_prepare do
|
||||
)
|
||||
return handle_scim_error(error)
|
||||
end
|
||||
if !OpenProject::FeatureDecisions.scim_api_active?
|
||||
return handle_scim_error(Scimitar::AuthenticationError.new)
|
||||
end
|
||||
|
||||
warden = request.env["warden"]
|
||||
user = warden.authenticate(scope: :scim_v2)
|
||||
|
||||
@@ -38,7 +38,7 @@ RSpec.describe "SCIM API Authentication" do
|
||||
let(:scim_client) { create(:scim_client, authentication_method: :oauth2_token, auth_provider_id: oidc_provider.id) }
|
||||
|
||||
describe "GET /scim_v2/ServiceProviderConfig" do
|
||||
context "with the feature flag and enterprise enabled", with_ee: [:scim_api], with_flag: { scim_api: true } do
|
||||
context "with enterprise feature enabled", with_ee: [:scim_api] do
|
||||
context "with static token" do
|
||||
let(:oauth_access_token) { create(:oauth_access_token, resource_owner: service_account, scopes: ["scim_v2"]) }
|
||||
let!(:token) { oauth_access_token.plaintext_token }
|
||||
@@ -157,23 +157,7 @@ RSpec.describe "SCIM API Authentication" do
|
||||
end
|
||||
end
|
||||
|
||||
context "with the feature flag disabled", with_ee: [:scim_api], with_flag: { scim_api: false } do
|
||||
let(:oauth_access_token) { create(:oauth_access_token, resource_owner: service_account, scopes: ["scim_v2"]) }
|
||||
let!(:token) { oauth_access_token.plaintext_token }
|
||||
|
||||
it do
|
||||
get "/scim_v2/ServiceProviderConfig", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
{ "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "401" }
|
||||
)
|
||||
expect(last_response).to have_http_status(401)
|
||||
end
|
||||
end
|
||||
|
||||
context "with the enterprise feature missing", with_flag: { scim_api: true } do
|
||||
context "with the enterprise feature missing" do
|
||||
let(:oauth_access_token) { create(:oauth_access_token, resource_owner: service_account, scopes: ["scim_v2"]) }
|
||||
let!(:token) { oauth_access_token.plaintext_token }
|
||||
|
||||
|
||||
@@ -54,507 +54,412 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do
|
||||
end
|
||||
|
||||
describe "GET /scim_v2/Groups" do
|
||||
context "with the feature flag enabled", with_flag: { scim_api: true } do
|
||||
before { group }
|
||||
before { group }
|
||||
|
||||
it "responds with a list of groups excluding marked for deletion" do
|
||||
create(:group_marked_for_deletion)
|
||||
it "responds with a list of groups excluding marked for deletion" do
|
||||
create(:group_marked_for_deletion)
|
||||
|
||||
get "/scim_v2/Groups", {}, headers
|
||||
get "/scim_v2/Groups", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to match("Resources" => contain_exactly({ "displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"] }, { "displayName" => group_without_external_id.name,
|
||||
"id" => group_without_external_id.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group_without_external_id.id}",
|
||||
"created" => group_without_external_id.created_at.iso8601,
|
||||
"lastModified" => group_without_external_id.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"] }),
|
||||
"itemsPerPage" => 100,
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
"startIndex" => 1,
|
||||
"totalResults" => 2)
|
||||
end
|
||||
|
||||
it "filters results" do
|
||||
filter = ERB::Util.url_encode("displayName Eq \"#{group.name}\"")
|
||||
get "/scim_v2/Groups?filter=#{filter}", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq("Resources" => [{ "displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"] }],
|
||||
"itemsPerPage" => 100,
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
"startIndex" => 1,
|
||||
"totalResults" => 1)
|
||||
|
||||
filter = ERB::Util.url_encode('displayName Eq "NONEXISTENT GROUP NAME"')
|
||||
get "/scim_v2/Groups?filter=#{filter}", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq("Resources" => [],
|
||||
"itemsPerPage" => 100,
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
"startIndex" => 1,
|
||||
"totalResults" => 0)
|
||||
end
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to match("Resources" => contain_exactly({ "displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"] }, { "displayName" => group_without_external_id.name,
|
||||
"id" => group_without_external_id.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group_without_external_id.id}",
|
||||
"created" => group_without_external_id.created_at.iso8601,
|
||||
"lastModified" => group_without_external_id.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"] }),
|
||||
"itemsPerPage" => 100,
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
"startIndex" => 1,
|
||||
"totalResults" => 2)
|
||||
end
|
||||
|
||||
context "with the feature flag disabled", with_flag: { scim_api: false } do
|
||||
it do
|
||||
get "/scim_v2/Groups", {}, headers
|
||||
it "filters results" do
|
||||
filter = ERB::Util.url_encode("displayName Eq \"#{group.name}\"")
|
||||
get "/scim_v2/Groups?filter=#{filter}", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
"detail" => "Requires authentication",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "401"
|
||||
)
|
||||
expect(last_response).to have_http_status(401)
|
||||
end
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq("Resources" => [{ "displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"] }],
|
||||
"itemsPerPage" => 100,
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
"startIndex" => 1,
|
||||
"totalResults" => 1)
|
||||
|
||||
filter = ERB::Util.url_encode('displayName Eq "NONEXISTENT GROUP NAME"')
|
||||
get "/scim_v2/Groups?filter=#{filter}", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq("Resources" => [],
|
||||
"itemsPerPage" => 100,
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
|
||||
"startIndex" => 1,
|
||||
"totalResults" => 0)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /scim_v2/Groups/:id" do
|
||||
context "with the feature flag enabled", with_flag: { scim_api: true } do
|
||||
it "responds with specific group data" do
|
||||
group
|
||||
get "/scim_v2/Groups/#{group.id}", {}, headers
|
||||
it "responds with specific group data" do
|
||||
group
|
||||
get "/scim_v2/Groups/#{group.id}", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "excludes specified attributes" do
|
||||
get "/scim_v2/Groups/#{group.id}?excludedAttributes=members", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
expect(response_body["members"]).to be_nil
|
||||
end
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
context "with the feature flag disabled", with_flag: { scim_api: false } do
|
||||
it do
|
||||
get "/scim_v2/Groups/#{group.id}", {}, headers
|
||||
it "excludes specified attributes" do
|
||||
get "/scim_v2/Groups/#{group.id}?excludedAttributes=members", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
"detail" => "Requires authentication",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "401"
|
||||
)
|
||||
expect(last_response).to have_http_status(401)
|
||||
end
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
expect(response_body["members"]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /scim_v2/Groups/" do
|
||||
context "with the feature flag enabled", with_flag: { scim_api: true } do
|
||||
let(:group_name) { "Group 123" }
|
||||
let(:group_name) { "Group 123" }
|
||||
|
||||
it "creates a group with members" do
|
||||
user
|
||||
request_body = { "displayName" => group_name,
|
||||
"externalId" => external_group_id,
|
||||
"members" => [{ "value" => user.id.to_s,
|
||||
"$ref" => "/Users/#{user.id}" }],
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"] }
|
||||
expect do
|
||||
post "/scim_v2/Groups/", request_body.to_json, headers
|
||||
end.to change(Group, :count).by(1)
|
||||
it "creates a group with members" do
|
||||
user
|
||||
request_body = { "displayName" => group_name,
|
||||
"externalId" => external_group_id,
|
||||
"members" => [{ "value" => user.id.to_s,
|
||||
"$ref" => "/Users/#{user.id}" }],
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"] }
|
||||
expect do
|
||||
post "/scim_v2/Groups/", request_body.to_json, headers
|
||||
end.to change(Group, :count).by(1)
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group = Group.find_by(name: group_name)
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "creates group without members specified" do
|
||||
user
|
||||
request_body = { "displayName" => "Group 123",
|
||||
"externalId" => external_group_id,
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"] }
|
||||
expect do
|
||||
post "/scim_v2/Groups/", request_body.to_json, headers
|
||||
end.to change(Group, :count).by(1)
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group = Group.find_by(name: group_name)
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group = Group.find_by(name: group_name)
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
context "with the feature flag disabled", with_flag: { scim_api: false } do
|
||||
it do
|
||||
post "/scim_v2/Groups/", "", headers
|
||||
it "creates group without members specified" do
|
||||
user
|
||||
request_body = { "displayName" => "Group 123",
|
||||
"externalId" => external_group_id,
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"] }
|
||||
expect do
|
||||
post "/scim_v2/Groups/", request_body.to_json, headers
|
||||
end.to change(Group, :count).by(1)
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
"detail" => "Requires authentication",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "401"
|
||||
)
|
||||
expect(last_response).to have_http_status(401)
|
||||
end
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group = Group.find_by(name: group_name)
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
end
|
||||
|
||||
describe "DELETE /scim_v2/Groups/:id" do
|
||||
context "with the feature flag enabled", with_flag: { scim_api: true } do
|
||||
it "deletes specific group" do
|
||||
group
|
||||
delete "/scim_v2/Groups/#{group.id}", "", headers
|
||||
it "deletes specific group" do
|
||||
group
|
||||
delete "/scim_v2/Groups/#{group.id}", "", headers
|
||||
|
||||
expect(last_response.body).to eq("")
|
||||
expect(last_response).to have_http_status(204)
|
||||
expect(last_response.body).to eq("")
|
||||
expect(last_response).to have_http_status(204)
|
||||
|
||||
get "/scim_v2/Groups/#{group.id}", "", headers
|
||||
get "/scim_v2/Groups/#{group.id}", "", headers
|
||||
|
||||
expect(last_response).to have_http_status(404)
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
"detail" => "Resource \"#{group.id}\" not found",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "404"
|
||||
)
|
||||
expect(last_response).to have_http_status(404)
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
"detail" => "Resource \"#{group.id}\" not found",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "404"
|
||||
)
|
||||
|
||||
perform_enqueued_jobs
|
||||
assert_performed_jobs 1
|
||||
perform_enqueued_jobs
|
||||
assert_performed_jobs 1
|
||||
|
||||
get "/scim_v2/Groups/#{group.id}", "", headers
|
||||
get "/scim_v2/Groups/#{group.id}", "", headers
|
||||
|
||||
expect(last_response).to have_http_status(404)
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
"detail" => "Resource \"#{group.id}\" not found",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "404"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "with the feature flag disabled", with_flag: { scim_api: false } do
|
||||
it do
|
||||
delete "/scim_v2/Users/123", "", headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
"detail" => "Requires authentication",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "401"
|
||||
)
|
||||
expect(last_response).to have_http_status(401)
|
||||
end
|
||||
expect(last_response).to have_http_status(404)
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
"detail" => "Resource \"#{group.id}\" not found",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "404"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT /scim_v2/Groups/:id" do
|
||||
context "with the feature flag enabled", with_flag: { scim_api: true } do
|
||||
let(:new_external_group_id) { "new_idp_group_id_123asdqwe12345" }
|
||||
let(:new_external_group_id) { "new_idp_group_id_123asdqwe12345" }
|
||||
|
||||
before do
|
||||
admin
|
||||
group
|
||||
end
|
||||
|
||||
it "updates specific group by replacing it with newly provided data" do
|
||||
request_body = {
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||
"active" => true,
|
||||
"externalId" => new_external_group_id,
|
||||
"displayName" => group.name,
|
||||
"members" => [
|
||||
{ "value" => user.id.to_s },
|
||||
{ "value" => admin.id.to_s }
|
||||
]
|
||||
}
|
||||
|
||||
put "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to match("displayName" => group.name,
|
||||
"externalId" => new_external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"members" => contain_exactly({ "value" => user.id.to_s }, { "value" => admin.id.to_s }),
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "updates members if there is $ref field present for every member(Keycloak plugin adds it for example)" do
|
||||
request_body = {
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||
"externalId" => new_external_group_id,
|
||||
"displayName" => group.name,
|
||||
"members" => [
|
||||
{
|
||||
"value" => user.id.to_s,
|
||||
"$ref" => "/Users/#{user.id}"
|
||||
},
|
||||
{
|
||||
"value" => admin.id.to_s,
|
||||
"$ref" => "/Users/#{admin.id}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
put "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to match("displayName" => group.name,
|
||||
"externalId" => new_external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"members" => contain_exactly({ "value" => user.id.to_s }, { "value" => admin.id.to_s }),
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "updates members if there is no members field(Keycloak plugin sends memberless group request like that)" do
|
||||
request_body = {
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||
"externalId" => new_external_group_id,
|
||||
"displayName" => group.name
|
||||
}
|
||||
|
||||
put "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to match("displayName" => group.name,
|
||||
"externalId" => new_external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"members" => [],
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
before do
|
||||
admin
|
||||
group
|
||||
end
|
||||
|
||||
context "with the feature flag disabled", with_flag: { scim_api: false } do
|
||||
it do
|
||||
put "/scim_v2/Groups/123", "", headers
|
||||
it "updates specific group by replacing it with newly provided data" do
|
||||
request_body = {
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||
"active" => true,
|
||||
"externalId" => new_external_group_id,
|
||||
"displayName" => group.name,
|
||||
"members" => [
|
||||
{ "value" => user.id.to_s },
|
||||
{ "value" => admin.id.to_s }
|
||||
]
|
||||
}
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
"detail" => "Requires authentication",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "401"
|
||||
)
|
||||
expect(last_response).to have_http_status(401)
|
||||
end
|
||||
put "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to match("displayName" => group.name,
|
||||
"externalId" => new_external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"members" => contain_exactly({ "value" => user.id.to_s }, { "value" => admin.id.to_s }),
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "updates members if there is $ref field present for every member(Keycloak plugin adds it for example)" do
|
||||
request_body = {
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||
"externalId" => new_external_group_id,
|
||||
"displayName" => group.name,
|
||||
"members" => [
|
||||
{
|
||||
"value" => user.id.to_s,
|
||||
"$ref" => "/Users/#{user.id}"
|
||||
},
|
||||
{
|
||||
"value" => admin.id.to_s,
|
||||
"$ref" => "/Users/#{admin.id}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
put "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to match("displayName" => group.name,
|
||||
"externalId" => new_external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"members" => contain_exactly({ "value" => user.id.to_s }, { "value" => admin.id.to_s }),
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "updates members if there is no members field(Keycloak plugin sends memberless group request like that)" do
|
||||
request_body = {
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"],
|
||||
"externalId" => new_external_group_id,
|
||||
"displayName" => group.name
|
||||
}
|
||||
|
||||
put "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to match("displayName" => group.name,
|
||||
"externalId" => new_external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"members" => [],
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
end
|
||||
|
||||
describe "PATCH /scim_v2/Groups/:id" do
|
||||
context "with the feature flag enabled", with_flag: { scim_api: true } do
|
||||
it "supports external_id replacing" do
|
||||
group
|
||||
new_external_group_id = "new_idp_user_id_123asdqwe12345"
|
||||
request_body = {
|
||||
"schemas" =>
|
||||
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations" => [{
|
||||
"op" => "replace",
|
||||
"path" => "externalId",
|
||||
"value" => new_external_group_id
|
||||
}]
|
||||
}
|
||||
patch "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
it "supports external_id replacing" do
|
||||
group
|
||||
new_external_group_id = "new_idp_user_id_123asdqwe12345"
|
||||
request_body = {
|
||||
"schemas" =>
|
||||
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations" => [{
|
||||
"op" => "replace",
|
||||
"path" => "externalId",
|
||||
"value" => new_external_group_id
|
||||
}]
|
||||
}
|
||||
patch "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => new_external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "supports replacing of members" do
|
||||
group
|
||||
user2
|
||||
|
||||
request_body = {
|
||||
"schemas" =>
|
||||
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations" => [{
|
||||
"op" => "replace",
|
||||
"path" => "members",
|
||||
"value" => [{
|
||||
"value" => user2.id.to_s,
|
||||
"$ref" => "/Users/#{user2.id}"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
patch "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user2.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "supports adding of a member" do
|
||||
group
|
||||
user2
|
||||
|
||||
request_body = {
|
||||
"schemas" =>
|
||||
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations" => [{
|
||||
"op" => "add",
|
||||
"path" => "members",
|
||||
"value" => [{ "value" => user2.id.to_s }]
|
||||
}]
|
||||
}
|
||||
patch "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user1.id.to_s },
|
||||
{ "value" => user2.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "supports removal of a member" do
|
||||
group
|
||||
|
||||
request_body = {
|
||||
"schemas" =>
|
||||
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations" => [{
|
||||
"op" => "remove",
|
||||
"path" => "members",
|
||||
"value" => [{ "value" => user1.id.to_s }]
|
||||
}]
|
||||
}
|
||||
patch "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "supports removal of a member with exclusion of members list from the response" do
|
||||
group
|
||||
|
||||
request_body = {
|
||||
"schemas" =>
|
||||
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations" => [{
|
||||
"op" => "remove",
|
||||
"path" => "members",
|
||||
"value" => [{ "value" => user1.id.to_s }]
|
||||
}]
|
||||
}
|
||||
patch "/scim_v2/Groups/#{group.id}?excludedAttributes=members", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => new_external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
context "with the feature flag disabled", with_flag: { scim_api: false } do
|
||||
it do
|
||||
patch "/scim_v2/Groups/123", "", headers
|
||||
it "supports replacing of members" do
|
||||
group
|
||||
user2
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
"detail" => "Requires authentication",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "401"
|
||||
)
|
||||
end
|
||||
request_body = {
|
||||
"schemas" =>
|
||||
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations" => [{
|
||||
"op" => "replace",
|
||||
"path" => "members",
|
||||
"value" => [{
|
||||
"value" => user2.id.to_s,
|
||||
"$ref" => "/Users/#{user2.id}"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
patch "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user2.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "supports adding of a member" do
|
||||
group
|
||||
user2
|
||||
|
||||
request_body = {
|
||||
"schemas" =>
|
||||
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations" => [{
|
||||
"op" => "add",
|
||||
"path" => "members",
|
||||
"value" => [{ "value" => user2.id.to_s }]
|
||||
}]
|
||||
}
|
||||
patch "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [{ "value" => user1.id.to_s },
|
||||
{ "value" => user2.id.to_s }],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "supports removal of a member" do
|
||||
group
|
||||
|
||||
request_body = {
|
||||
"schemas" =>
|
||||
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations" => [{
|
||||
"op" => "remove",
|
||||
"path" => "members",
|
||||
"value" => [{ "value" => user1.id.to_s }]
|
||||
}]
|
||||
}
|
||||
patch "/scim_v2/Groups/#{group.id}", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"members" => [],
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
|
||||
it "supports removal of a member with exclusion of members list from the response" do
|
||||
group
|
||||
|
||||
request_body = {
|
||||
"schemas" =>
|
||||
["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
|
||||
"Operations" => [{
|
||||
"op" => "remove",
|
||||
"path" => "members",
|
||||
"value" => [{ "value" => user1.id.to_s }]
|
||||
}]
|
||||
}
|
||||
patch "/scim_v2/Groups/#{group.id}?excludedAttributes=members", request_body.to_json, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
group.reload
|
||||
expect(response_body).to eq("displayName" => group.name,
|
||||
"externalId" => external_group_id,
|
||||
"id" => group.id.to_s,
|
||||
"meta" => { "location" => "http://test.host/scim_v2/Groups/#{group.id}",
|
||||
"created" => group.created_at.iso8601,
|
||||
"lastModified" => group.updated_at.iso8601,
|
||||
"resourceType" => "Group" },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:Group"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -41,240 +41,225 @@ RSpec.describe "SCIM API Schemas", with_ee: [:scim_api] do
|
||||
before { token }
|
||||
|
||||
describe "GET /scim_v2/Schemas" do
|
||||
context "with the feature flag enabled", with_flag: { scim_api: true } do
|
||||
it "responds with supported schemas" do
|
||||
get "/scim_v2/Schemas", {}, headers
|
||||
it "responds with supported schemas" do
|
||||
get "/scim_v2/Schemas", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body["totalResults"]).to eq(2)
|
||||
expect(response_body["schemas"]).to eq(["urn:ietf:params:scim:api:messages:2.0:ListResponse"])
|
||||
group_schema = response_body["Resources"].find { |r| r["name"] == "Group" }
|
||||
user_schema = response_body["Resources"].find { |r| r["name"] == "User" }
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body["totalResults"]).to eq(2)
|
||||
expect(response_body["schemas"]).to eq(["urn:ietf:params:scim:api:messages:2.0:ListResponse"])
|
||||
group_schema = response_body["Resources"].find { |r| r["name"] == "Group" }
|
||||
user_schema = response_body["Resources"].find { |r| r["name"] == "User" }
|
||||
|
||||
expect(group_schema).to eq(
|
||||
"name" => "Group",
|
||||
"id" => "urn:ietf:params:scim:schemas:core:2.0:Group",
|
||||
"description" => "Represents a Group",
|
||||
"meta" => { "resourceType" => "Schema",
|
||||
"location" => "http://test.host/scim_v2/Schemas?name=urn%3Aietf%3Aparams%3Ascim%3Aschemas%3Acore%3A2.0%3AGroup" },
|
||||
"attributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "displayName",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => true,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"type" => "complex",
|
||||
"subAttributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "immutable",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "value",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "immutable",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "type",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "immutable",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "display",
|
||||
"type" => "string" }
|
||||
],
|
||||
"name" => "members" },
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => true,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "server",
|
||||
"returned" => "default",
|
||||
"name" => "externalId",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => true,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "server",
|
||||
"returned" => "default",
|
||||
"name" => "id",
|
||||
"type" => "string" }
|
||||
]
|
||||
)
|
||||
expect(user_schema).to eq(
|
||||
"name" => "User",
|
||||
"id" => "urn:ietf:params:scim:schemas:core:2.0:User",
|
||||
"description" => "Represents a User",
|
||||
"meta" => { "resourceType" => "Schema",
|
||||
"location" => "http://test.host/scim_v2/Schemas?name=urn%3Aietf%3Aparams%3Ascim%3Aschemas%3Acore%3A2.0%3AUser" },
|
||||
"attributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "server",
|
||||
"returned" => "default",
|
||||
"name" => "userName",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"type" => "complex",
|
||||
"subAttributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "familyName",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "givenName",
|
||||
"type" => "string" }
|
||||
],
|
||||
"name" => "name" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "active",
|
||||
"type" => "boolean" },
|
||||
{ "multiValued" => true,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"type" => "complex",
|
||||
"subAttributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "value",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "display",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "type",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "primary",
|
||||
"type" => "boolean" }
|
||||
],
|
||||
"name" => "emails" },
|
||||
{ "multiValued" => true,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"type" => "complex",
|
||||
"subAttributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "value",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "display",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "type",
|
||||
"type" => "string" }
|
||||
],
|
||||
"name" => "groups" },
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => true,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "server",
|
||||
"returned" => "default",
|
||||
"name" => "externalId",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => true,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "server",
|
||||
"returned" => "default",
|
||||
"name" => "id",
|
||||
"type" => "string" }
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context "with the feature flag disabled", with_flag: { scim_api: false } do
|
||||
it do
|
||||
get "/scim_v2/Schemas", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
"detail" => "Requires authentication",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "401"
|
||||
)
|
||||
end
|
||||
expect(group_schema).to eq(
|
||||
"name" => "Group",
|
||||
"id" => "urn:ietf:params:scim:schemas:core:2.0:Group",
|
||||
"description" => "Represents a Group",
|
||||
"meta" => { "resourceType" => "Schema",
|
||||
"location" => "http://test.host/scim_v2/Schemas?name=urn%3Aietf%3Aparams%3Ascim%3Aschemas%3Acore%3A2.0%3AGroup" },
|
||||
"attributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "displayName",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => true,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"type" => "complex",
|
||||
"subAttributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "immutable",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "value",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "immutable",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "type",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "immutable",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "display",
|
||||
"type" => "string" }
|
||||
],
|
||||
"name" => "members" },
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => true,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "server",
|
||||
"returned" => "default",
|
||||
"name" => "externalId",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => true,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "server",
|
||||
"returned" => "default",
|
||||
"name" => "id",
|
||||
"type" => "string" }
|
||||
]
|
||||
)
|
||||
expect(user_schema).to eq(
|
||||
"name" => "User",
|
||||
"id" => "urn:ietf:params:scim:schemas:core:2.0:User",
|
||||
"description" => "Represents a User",
|
||||
"meta" => { "resourceType" => "Schema",
|
||||
"location" => "http://test.host/scim_v2/Schemas?name=urn%3Aietf%3Aparams%3Ascim%3Aschemas%3Acore%3A2.0%3AUser" },
|
||||
"attributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "server",
|
||||
"returned" => "default",
|
||||
"name" => "userName",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"type" => "complex",
|
||||
"subAttributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "familyName",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "givenName",
|
||||
"type" => "string" }
|
||||
],
|
||||
"name" => "name" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "active",
|
||||
"type" => "boolean" },
|
||||
{ "multiValued" => true,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"type" => "complex",
|
||||
"subAttributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "value",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "display",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "type",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "primary",
|
||||
"type" => "boolean" }
|
||||
],
|
||||
"name" => "emails" },
|
||||
{ "multiValued" => true,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"type" => "complex",
|
||||
"subAttributes" => [
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "value",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "display",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => false,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "none",
|
||||
"returned" => "default",
|
||||
"name" => "type",
|
||||
"type" => "string" }
|
||||
],
|
||||
"name" => "groups" },
|
||||
{ "multiValued" => false,
|
||||
"required" => true,
|
||||
"caseExact" => true,
|
||||
"mutability" => "readWrite",
|
||||
"uniqueness" => "server",
|
||||
"returned" => "default",
|
||||
"name" => "externalId",
|
||||
"type" => "string" },
|
||||
{ "multiValued" => false,
|
||||
"required" => false,
|
||||
"caseExact" => true,
|
||||
"mutability" => "readOnly",
|
||||
"uniqueness" => "server",
|
||||
"returned" => "default",
|
||||
"name" => "id",
|
||||
"type" => "string" }
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -42,64 +42,48 @@ RSpec.describe "SCIM API ServiceProviderConfig" do
|
||||
|
||||
describe "GET /scim_v2/ServiceProviderConfig" do
|
||||
context "with enterprise token supporting scim_api", with_ee: [:scim_api] do
|
||||
context "with the feature flag enabled", with_flag: { scim_api: true } do
|
||||
it "responds with full ServiceProviderConfig information if authorization is correct" do
|
||||
it "responds with full ServiceProviderConfig information if authorization is correct" do
|
||||
get "/scim_v2/ServiceProviderConfig", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to include("authenticationSchemes" => [{ "description" => "Bearer Token can be obtained in 3 different ways(https://www.openproject.org/docs/system-admin-guide/authentication/scim/#step-3-choose-an-authentication-method)",
|
||||
|
||||
"name" => "OAuth Bearer Token",
|
||||
"type" => "oauthbearertoken" }],
|
||||
"bulk" => { "supported" => false },
|
||||
"changePassword" => { "supported" => false },
|
||||
"etag" => { "supported" => false },
|
||||
"filter" => { "maxResults" => 100,
|
||||
"supported" => true },
|
||||
"patch" => { "supported" => true },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
|
||||
"sort" => { "supported" => false })
|
||||
end
|
||||
|
||||
context "when authorization header contains an invalid token" do
|
||||
let(:token) { object_double(Doorkeeper::AccessToken.new, plaintext_token: "123123") }
|
||||
|
||||
it "responds with 401 Unauthorized" do
|
||||
get "/scim_v2/ServiceProviderConfig", {}, headers
|
||||
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to include("authenticationSchemes" => [{ "description" => "Bearer Token can be obtained in 3 different ways(https://www.openproject.org/docs/system-admin-guide/authentication/scim/#step-3-choose-an-authentication-method)",
|
||||
|
||||
"name" => "OAuth Bearer Token",
|
||||
"type" => "oauthbearertoken" }],
|
||||
"bulk" => { "supported" => false },
|
||||
"changePassword" => { "supported" => false },
|
||||
"etag" => { "supported" => false },
|
||||
"filter" => { "maxResults" => 100,
|
||||
"supported" => true },
|
||||
"patch" => { "supported" => true },
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"],
|
||||
"sort" => { "supported" => false })
|
||||
end
|
||||
|
||||
context "when authorization header contains an invalid token" do
|
||||
let(:token) { object_double(Doorkeeper::AccessToken.new, plaintext_token: "123123") }
|
||||
|
||||
it "responds with 401 Unauthorized" do
|
||||
get "/scim_v2/ServiceProviderConfig", {}, headers
|
||||
|
||||
expect(last_response).to have_http_status(401)
|
||||
expect(last_response.body).to eq("invalid_token")
|
||||
end
|
||||
end
|
||||
|
||||
context "when there is no authorization header at all" do
|
||||
let(:token) { object_double(Doorkeeper::AccessToken.new, plaintext_token: "123123") }
|
||||
|
||||
it "responds with limited ServiceProviderConfig information" do
|
||||
get "/scim_v2/ServiceProviderConfig", {}, { "CONTENT_TYPE" => "application/scim+json" }
|
||||
|
||||
expect(last_response).to have_http_status(200)
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body.keys).to eq(["meta", "schemas", "authenticationSchemes"])
|
||||
expect(response_body).to include("authenticationSchemes" => [{ "description" => "Bearer Token can be obtained in 3 different ways(https://www.openproject.org/docs/system-admin-guide/authentication/scim/#step-3-choose-an-authentication-method)",
|
||||
"name" => "OAuth Bearer Token",
|
||||
"type" => "oauthbearertoken" }],
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"])
|
||||
end
|
||||
expect(last_response).to have_http_status(401)
|
||||
expect(last_response.body).to eq("invalid_token")
|
||||
end
|
||||
end
|
||||
|
||||
context "with the feature flag disabled", with_flag: { scim_api: false } do
|
||||
it do
|
||||
get "/scim_v2/ServiceProviderConfig", {}, headers
|
||||
context "when there is no authorization header at all" do
|
||||
let(:token) { object_double(Doorkeeper::AccessToken.new, plaintext_token: "123123") }
|
||||
|
||||
it "responds with limited ServiceProviderConfig information" do
|
||||
get "/scim_v2/ServiceProviderConfig", {}, { "CONTENT_TYPE" => "application/scim+json" }
|
||||
|
||||
expect(last_response).to have_http_status(200)
|
||||
response_body = JSON.parse(last_response.body)
|
||||
expect(response_body).to eq(
|
||||
{ "detail" => "Requires authentication",
|
||||
"schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"],
|
||||
"status" => "401" }
|
||||
)
|
||||
expect(last_response).to have_http_status(401)
|
||||
expect(response_body.keys).to eq(["meta", "schemas", "authenticationSchemes"])
|
||||
expect(response_body).to include("authenticationSchemes" => [{ "description" => "Bearer Token can be obtained in 3 different ways(https://www.openproject.org/docs/system-admin-guide/authentication/scim/#step-3-choose-an-authentication-method)",
|
||||
"name" => "OAuth Bearer Token",
|
||||
"type" => "oauthbearertoken" }],
|
||||
"schemas" => ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+456
-552
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user