From ed795610beef6eaf49a9451b226ee399d1d68b1c Mon Sep 17 00:00:00 2001 From: Pavel Balashou Date: Thu, 10 Jul 2025 18:12:29 +0200 Subject: [PATCH] Revert " [#65747] Make /scim_v2/Schemas endpoint return required by OP schemas." This reverts commit b249fd36b4ebf2ff9f06a6048944beabbe5e1c6c. --- .../scim_v2/base_controller_actions.rb | 21 - app/controllers/scim_v2/groups_controller.rb | 50 ++- app/controllers/scim_v2/users_controller.rb | 50 ++- config/initializers/scimitar.rb | 4 +- lib/scimitar_schema_extension.rb | 42 +- spec/requests/scim_v2/groups_spec.rb | 308 ++++++++------- spec/requests/scim_v2/schemas_spec.rb | 94 +---- spec/requests/scim_v2/users_spec.rb | 359 ++++++++---------- 8 files changed, 383 insertions(+), 545 deletions(-) diff --git a/app/controllers/scim_v2/base_controller_actions.rb b/app/controllers/scim_v2/base_controller_actions.rb index e343384665b..b1778c0ce07 100644 --- a/app/controllers/scim_v2/base_controller_actions.rb +++ b/app/controllers/scim_v2/base_controller_actions.rb @@ -89,27 +89,6 @@ module ScimV2 all_possible_attributes - excluded_attributes - excluded_parents end - - def raise_result_errors_for_scim(result) - result.on_failure do |result| - uniqueness_error = result.errors.find { |e| e.type == :taken } - unauthorized_error = result.errors.find { |e| e.type == :error_unauthorized } - if uniqueness_error.present? - raise Scimitar::ErrorResponse.new( - status: 409, - scimType: "uniqueness", - detail: "Operation failed due to a uniqueness constraint: #{result.message}" - ) - elsif unauthorized_error.present? - raise Scimitar::ErrorResponse.new( - status: 403, - detail: "Action forbidden: insufficient permissions." - ) - else - raise result.message - end - end - end end end end diff --git a/app/controllers/scim_v2/groups_controller.rb b/app/controllers/scim_v2/groups_controller.rb index 685a10cb928..78389fddd36 100644 --- a/app/controllers/scim_v2/groups_controller.rb +++ b/app/controllers/scim_v2/groups_controller.rb @@ -40,15 +40,26 @@ module ScimV2 call = Groups::CreateService .new(user: User.current, model: group) .call(group.attributes) - raise_result_errors_for_scim(call) + .on_failure do |result| + uniqueness_error = result.errors.find { |e| e.type == :taken } + if uniqueness_error.present? + raise Scimitar::ErrorResponse.new( + status: 409, + scimType: "uniqueness", + detail: "Operation failed due to a uniqueness constraint: #{result.message}" + ) + else + raise result.message + end + end group = call.result + members = scim_resource.members if members.present? - raise_result_errors_for_scim( - Groups::AddUsersService - .new(group, current_user: User.system) - .call(ids: members.map(&:value), send_notifications: false) - ) + Groups::AddUsersService + .new(group, current_user: User.system) + .call(ids: members.map(&:value), send_notifications: false) + .on_failure { |call| raise call.message } end group.to_scim( @@ -64,11 +75,10 @@ module ScimV2 storage_class.transaction do group = storage_scope.find(group_id) group.from_scim!(scim_hash: scim_resource.as_json) - raise_result_errors_for_scim( - Groups::UpdateService - .new(user: User.current, model: group) - .call(user_ids: scim_resource.members.map(&:value)) - ) + Groups::UpdateService + .new(user: User.current, model: group) + .call(user_ids: scim_resource.members.map(&:value)) + .on_failure { |call| raise call.message } group.reload group.to_scim( location: url_for(action: :show, id: group.id), @@ -84,11 +94,10 @@ module ScimV2 group = storage_scope.find(group_id) group.from_scim_patch!(patch_hash: patch_hash) user_ids = group.scim_members.map(&:id) - raise_result_errors_for_scim( - Groups::UpdateService - .new(user: User.current, model: group) - .call(user_ids:) - ) + Groups::UpdateService + .new(user: User.current, model: group) + .call(user_ids:) + .on_failure { |call| raise call.message } group.reload group.to_scim( location: url_for(action: :show, id: group.id), @@ -101,11 +110,10 @@ module ScimV2 def destroy super do |group_id| group = storage_scope.find(group_id) - raise_result_errors_for_scim( - Groups::DeleteService - .new(user: User.current, model: group) - .call - ) + Groups::DeleteService + .new(user: User.current, model: group) + .call + .on_failure { |call| raise call.message } end end diff --git a/app/controllers/scim_v2/users_controller.rb b/app/controllers/scim_v2/users_controller.rb index 90eaa881760..aaeb30ebb59 100644 --- a/app/controllers/scim_v2/users_controller.rb +++ b/app/controllers/scim_v2/users_controller.rb @@ -40,7 +40,19 @@ module ScimV2 call = Users::CreateService .new(user: User.current, model: user) .call(user.attributes) - raise_result_errors_for_scim(call) + .on_failure do |result| + uniqueness_error = result.errors.find { |e| e.type == :taken } + if uniqueness_error.present? + raise Scimitar::ErrorResponse.new( + status: 409, + scimType: "uniqueness", + detail: "Operation failed due to a uniqueness constraint: #{result.message}" + ) + else + raise result.message + end + end + user = call.result user.to_scim( location: url_for(action: :show, id: user.id), @@ -55,11 +67,10 @@ module ScimV2 storage_class.transaction do user = storage_scope.find(user_id) user.from_scim!(scim_hash: scim_resource.as_json) - call = Users::UpdateService - .new(user: User.current, model: user) - .call - raise_result_errors_for_scim(call) - user = call.result + Users::UpdateService + .new(user: User.current, model: user) + .call + .on_failure { |call| raise call.message } user.to_scim( location: url_for(action: :show, id: user.id), include_attributes: @@ -73,11 +84,10 @@ module ScimV2 storage_class.transaction do user = storage_scope.find(user_id) user.from_scim_patch!(patch_hash: patch_hash) - call = Users::UpdateService - .new(user: User.current, model: user) - .call - raise_result_errors_for_scim(call) - user = call.result + Users::UpdateService + .new(user: User.current, model: user) + .call + .on_failure { |call| raise call.message } user.to_scim( location: url_for(action: :show, id: user.id), include_attributes: @@ -89,10 +99,20 @@ module ScimV2 def destroy super do |user_id| user = storage_scope.find(user_id) - call = Users::DeleteService - .new(user: User.current, model: user) - .call - raise_result_errors_for_scim(call) + Users::DeleteService + .new(user: User.current, model: user) + .call + .on_failure do |result| + unauthorized_error = result.errors.find { |e| e.type == :error_unauthorized } + if unauthorized_error.present? + raise Scimitar::ErrorResponse.new( + status: 403, + detail: "User can't be deleted due to permission absence." + ) + else + raise result.message + end + end end end diff --git a/config/initializers/scimitar.rb b/config/initializers/scimitar.rb index 5a03a0d9e0f..266285e1c8b 100644 --- a/config/initializers/scimitar.rb +++ b/config/initializers/scimitar.rb @@ -38,10 +38,10 @@ Rails.application.config.to_prepare do ) Scimitar::Schema::User.singleton_class.class_eval do - prepend ScimitarSchemaExtension::User + prepend ScimitarSchemaExtension end Scimitar::Schema::Group.singleton_class.class_eval do - prepend ScimitarSchemaExtension::Group + prepend ScimitarSchemaExtension end end diff --git a/lib/scimitar_schema_extension.rb b/lib/scimitar_schema_extension.rb index 66620f142a1..c94019ab766 100644 --- a/lib/scimitar_schema_extension.rb +++ b/lib/scimitar_schema_extension.rb @@ -28,43 +28,11 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class OpenProjectNameSchema < Scimitar::Schema::Base - def self.scim_attributes - @scim_attributes ||= [ - Scimitar::Schema::Attribute.new(name: "familyName", caseExact: true, type: "string", required: true), - Scimitar::Schema::Attribute.new(name: "givenName", caseExact: true, type: "string", required: true) - ] - end -end - -class OpenProjectNameComplexType < Scimitar::ComplexTypes::Base - set_schema OpenProjectNameSchema -end - module ScimitarSchemaExtension - module Group - def scim_attributes - [ - Scimitar::Schema::Attribute.new(name: "displayName", caseExact: true, type: "string", required: true), - Scimitar::Schema::Attribute.new(name: "members", multiValued: true, complexType: Scimitar::ComplexTypes::ReferenceMember, - mutability: "readWrite"), - Scimitar::Schema::Attribute.new(name: "externalId", type: "string", caseExact: true, required: true) - ] - end - end - - module User - def scim_attributes - [ - Scimitar::Schema::Attribute.new(name: "userName", caseExact: true, type: "string", uniqueness: "server", required: true), - Scimitar::Schema::Attribute.new(name: "name", caseExact: true, complexType: OpenProjectNameComplexType, required: true), - Scimitar::Schema::Attribute.new(name: "active", type: "boolean"), - Scimitar::Schema::Attribute.new(name: "emails", multiValued: true, complexType: Scimitar::ComplexTypes::Email, - required: true), - Scimitar::Schema::Attribute.new(name: "groups", multiValued: true, complexType: Scimitar::ComplexTypes::ReferenceGroup, - mutability: "readOnly"), - Scimitar::Schema::Attribute.new(name: "externalId", type: "string", caseExact: true, required: true) - ] - end + def scim_attributes + super + [Scimitar::Schema::Attribute.new(name: "externalId", + type: "string", + caseExact: true, + required: true)] end end diff --git a/spec/requests/scim_v2/groups_spec.rb b/spec/requests/scim_v2/groups_spec.rb index 14c8507fc18..88703e8f78d 100644 --- a/spec/requests/scim_v2/groups_spec.rb +++ b/spec/requests/scim_v2/groups_spec.rb @@ -61,26 +61,26 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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) + 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 @@ -88,29 +88,29 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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) + 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) + expect(response_body).to eq({ "Resources" => [], + "itemsPerPage" => 100, + "schemas" => ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], + "startIndex" => 1, + "totalResults" => 0 }) end end @@ -120,9 +120,8 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) expect(last_response).to have_http_status(401) end @@ -136,29 +135,29 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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"]) + 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).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 @@ -169,9 +168,8 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) expect(last_response).to have_http_status(401) end @@ -194,15 +192,15 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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"]) + 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 @@ -216,15 +214,15 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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"]) + 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 @@ -234,9 +232,8 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) expect(last_response).to have_http_status(401) end @@ -255,15 +252,15 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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"]) + 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"] }) perform_enqueued_jobs assert_performed_jobs 1 @@ -272,9 +269,9 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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" + { "detail" => "Resource \"#{group.id}\" not found", + "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "404" } ) end end @@ -285,9 +282,8 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) expect(last_response).to have_http_status(401) end @@ -315,15 +311,15 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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"]) + 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 end @@ -333,9 +329,8 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) expect(last_response).to have_http_status(401) end @@ -360,15 +355,15 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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"]) + 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 @@ -390,15 +385,15 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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"]) + 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 @@ -418,16 +413,16 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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"]) + 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 @@ -446,15 +441,15 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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"]) + 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 @@ -473,14 +468,14 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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"]) + 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 @@ -490,9 +485,8 @@ RSpec.describe "SCIM API Groups", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) end end diff --git a/spec/requests/scim_v2/schemas_spec.rb b/spec/requests/scim_v2/schemas_spec.rb index ffd097fb85a..f2b205176a6 100644 --- a/spec/requests/scim_v2/schemas_spec.rb +++ b/spec/requests/scim_v2/schemas_spec.rb @@ -42,93 +42,26 @@ RSpec.describe "SCIM API Schemas", with_ee: [:scim_api] do describe "GET /scim_v2/Schemas" do context "with the feature flag enabled", with_flag: { scim_api: true } do - # rubocop:disable Layout/LineLength 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"]) + 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" => true, "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" => "none", "returned" => "default", "name" => "externalId", "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" => true, "mutability" => "readWrite", "uniqueness" => "server", "returned" => "default", "name" => "userName", "type" => "string" }, - { "multiValued" => false, - "required" => true, - "caseExact" => true, - "mutability" => "readWrite", - "uniqueness" => "none", - "returned" => "default", - "type" => "complex", - "subAttributes" => [ - { "multiValued" => false, "required" => true, "caseExact" => true, "mutability" => "readWrite", "uniqueness" => "none", "returned" => "default", "name" => "familyName", "type" => "string" }, - { "multiValued" => false, "required" => true, "caseExact" => true, "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" => "none", "returned" => "default", "name" => "externalId", "type" => "string" } - ] - ) + external_id_schema = { "multiValued" => false, + "required" => true, + "caseExact" => true, + "mutability" => "readWrite", + "uniqueness" => "none", + "returned" => "default", + "name" => "externalId", + "type" => "string" } + expect(group_schema["attributes"]).to include(external_id_schema) + expect(user_schema["attributes"]).to include(external_id_schema) end - # rubocop:enable Layout/LineLength end context "with the feature flag disabled", with_flag: { scim_api: false } do @@ -137,9 +70,8 @@ RSpec.describe "SCIM API Schemas", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) end end diff --git a/spec/requests/scim_v2/users_spec.rb b/spec/requests/scim_v2/users_spec.rb index 12eec2793d3..821ae06a175 100644 --- a/spec/requests/scim_v2/users_spec.rb +++ b/spec/requests/scim_v2/users_spec.rb @@ -122,11 +122,11 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do get "/scim_v2/Users?filter=#{filter_with_nonexisting_rows}", {}, 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) + expect(response_body).to eq({ "Resources" => [], + "itemsPerPage" => 100, + "schemas" => ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], + "startIndex" => 1, + "totalResults" => 0 }) end it "filters results by externalId" do @@ -158,11 +158,11 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do get "/scim_v2/Users?filter=#{filter_with_nonexisting_rows}", {}, 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) + expect(response_body).to eq({ "Resources" => [], + "itemsPerPage" => 100, + "schemas" => ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], + "startIndex" => 1, + "totalResults" => 0 }) end end @@ -172,9 +172,8 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) expect(last_response).to have_http_status(401) end @@ -188,21 +187,21 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do get "/scim_v2/Users/#{user.id}", {}, headers response_body = JSON.parse(last_response.body) - expect(response_body).to eq("active" => true, - "emails" => [{ "primary" => true, - "type" => "work", - "value" => user.mail }], - "externalId" => external_user_id, - "groups" => [{ "value" => group.id.to_s }], - "id" => user.id.to_s, - "meta" => { "created" => user.created_at.iso8601, - "lastModified" => user.updated_at.iso8601, - "location" => "http://test.host/scim_v2/Users/#{user.id}", - "resourceType" => "User" }, - "name" => { "familyName" => user.lastname, - "givenName" => user.firstname }, - "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], - "userName" => user.login) + expect(response_body).to eq({ "active" => true, + "emails" => [{ "primary" => true, + "type" => "work", + "value" => user.mail }], + "externalId" => external_user_id, + "groups" => [{ "value" => group.id.to_s }], + "id" => user.id.to_s, + "meta" => { "created" => user.created_at.iso8601, + "lastModified" => user.updated_at.iso8601, + "location" => "http://test.host/scim_v2/Users/#{user.id}", + "resourceType" => "User" }, + "name" => { "familyName" => user.lastname, + "givenName" => user.firstname }, + "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], + "userName" => user.login }) end end @@ -212,9 +211,8 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) expect(last_response).to have_http_status(401) end @@ -249,71 +247,14 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do expect(last_response).to have_http_status(409) response_body = JSON.parse(last_response.body) expect(response_body).to eq( - "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], - "detail" => "Operation failed due to a uniqueness constraint: Username has already been taken.", - "status" => "409", - "scimType" => "uniqueness" + { "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "detail" => "Operation failed due to a uniqueness constraint: Username has already been taken.", + "status" => "409", + "scimType" => "uniqueness" } ) end end - it "responds with 400 when email is missing" do - group - request_body = { - "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], - "externalId" => external_user_id, - "userName" => user.login, - "name" => { - "givenName" => "John", - "familyName" => "Doe" - }, - "active" => true, - "emails" => [] - } - - post "/scim_v2/Users/", request_body.to_json, headers - - expect(last_response).to have_http_status(400) - response_body = JSON.parse(last_response.body) - expect(response_body).to eq( - "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], - "detail" => "Invalid resource: Emails is required.", - "status" => "400", - "scimType" => "invalidValue" - ) - end - - it "responds with 400 when familyName is missing" do - group - request_body = { - "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], - "externalId" => external_user_id, - "userName" => user.login, - "name" => { - "givenName" => "John" - }, - "active" => true, - "emails" => [ - { - "value" => "jdoe@example.com", - "type" => "work", - "primary" => true - } - ] - } - - post "/scim_v2/Users/", request_body.to_json, headers - - expect(last_response).to have_http_status(400) - response_body = JSON.parse(last_response.body) - expect(response_body).to eq( - "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], - "detail" => "Invalid resource: Name familyname is required.", - "status" => "400", - "scimType" => "invalidValue" - ) - end - it "creates user with provided data and excludes some attributes" do request_body = { "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], @@ -337,17 +278,17 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do response_body = JSON.parse(last_response.body) created_user = User.find_by(login: "jdoe") expect(created_user).to be_present - expect(response_body).to eq("active" => true, - "externalId" => external_user_id, - "groups" => [], - "id" => created_user.id.to_s, - "meta" => { "created" => created_user.created_at.iso8601, - "lastModified" => created_user.updated_at.iso8601, - "location" => "http://test.host/scim_v2/Users/#{created_user.id}", - "resourceType" => "User" }, - "name" => { "familyName" => "Doe" }, - "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], - "userName" => "jdoe") + expect(response_body).to eq({ "active" => true, + "externalId" => external_user_id, + "groups" => [], + "id" => created_user.id.to_s, + "meta" => { "created" => created_user.created_at.iso8601, + "lastModified" => created_user.updated_at.iso8601, + "location" => "http://test.host/scim_v2/Users/#{created_user.id}", + "resourceType" => "User" }, + "name" => { "familyName" => "Doe" }, + "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], + "userName" => "jdoe" }) end it "creates user with provided data" do @@ -373,21 +314,21 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do response_body = JSON.parse(last_response.body) created_user = User.find_by(login: "jdoe") expect(created_user).to be_present - expect(response_body).to eq("active" => true, - "emails" => [{ "primary" => true, - "type" => "work", - "value" => "jdoe@example.com" }], - "externalId" => external_user_id, - "groups" => [], - "id" => created_user.id.to_s, - "meta" => { "created" => created_user.created_at.iso8601, - "lastModified" => created_user.updated_at.iso8601, - "location" => "http://test.host/scim_v2/Users/#{created_user.id}", - "resourceType" => "User" }, - "name" => { "familyName" => "Doe", - "givenName" => "John" }, - "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], - "userName" => "jdoe") + expect(response_body).to eq({ "active" => true, + "emails" => [{ "primary" => true, + "type" => "work", + "value" => "jdoe@example.com" }], + "externalId" => external_user_id, + "groups" => [], + "id" => created_user.id.to_s, + "meta" => { "created" => created_user.created_at.iso8601, + "lastModified" => created_user.updated_at.iso8601, + "location" => "http://test.host/scim_v2/Users/#{created_user.id}", + "resourceType" => "User" }, + "name" => { "familyName" => "Doe", + "givenName" => "John" }, + "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], + "userName" => "jdoe" }) end it "creates user with any email type string provided" do @@ -411,21 +352,21 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do response_body = JSON.parse(last_response.body) created_user = User.find_by(login: "jdoe") - expect(response_body).to eq("active" => true, - "emails" => [{ "primary" => true, - "type" => "work", - "value" => "jdoe@example.com" }], - "externalId" => external_user_id, - "groups" => [], - "id" => created_user.id.to_s, - "meta" => { "created" => created_user.created_at.iso8601, - "lastModified" => created_user.updated_at.iso8601, - "location" => "http://test.host/scim_v2/Users/#{created_user.id}", - "resourceType" => "User" }, - "name" => { "familyName" => "Doe", - "givenName" => "John" }, - "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], - "userName" => "jdoe") + expect(response_body).to eq({ "active" => true, + "emails" => [{ "primary" => true, + "type" => "work", + "value" => "jdoe@example.com" }], + "externalId" => external_user_id, + "groups" => [], + "id" => created_user.id.to_s, + "meta" => { "created" => created_user.created_at.iso8601, + "lastModified" => created_user.updated_at.iso8601, + "location" => "http://test.host/scim_v2/Users/#{created_user.id}", + "resourceType" => "User" }, + "name" => { "familyName" => "Doe", + "givenName" => "John" }, + "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], + "userName" => "jdoe" }) end end @@ -435,9 +376,8 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) expect(last_response).to have_http_status(401) end @@ -458,21 +398,21 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do get "/scim_v2/Users/#{user.id}", "", headers response_body = JSON.parse(last_response.body) - expect(response_body).to eq("active" => false, - "emails" => [{ "primary" => true, - "type" => "work", - "value" => user.mail }], - "externalId" => external_user_id, - "groups" => [{ "value" => group.id.to_s }], - "id" => user.id.to_s, - "meta" => { "created" => user.created_at.iso8601, - "lastModified" => user.updated_at.iso8601, - "location" => "http://test.host/scim_v2/Users/#{user.id}", - "resourceType" => "User" }, - "name" => { "familyName" => user.lastname, - "givenName" => user.firstname }, - "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], - "userName" => user.login) + expect(response_body).to eq({ "active" => false, + "emails" => [{ "primary" => true, + "type" => "work", + "value" => user.mail }], + "externalId" => external_user_id, + "groups" => [{ "value" => group.id.to_s }], + "id" => user.id.to_s, + "meta" => { "created" => user.created_at.iso8601, + "lastModified" => user.updated_at.iso8601, + "location" => "http://test.host/scim_v2/Users/#{user.id}", + "resourceType" => "User" }, + "name" => { "familyName" => user.lastname, + "givenName" => user.firstname }, + "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], + "userName" => user.login }) perform_enqueued_jobs assert_performed_jobs 1 @@ -481,9 +421,9 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do response_body = JSON.parse(last_response.body) expect(response_body).to eq( - "detail" => "Resource \"#{user.id}\" not found", - "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], - "status" => "404" + { "detail" => "Resource \"#{user.id}\" not found", + "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "404" } ) end end @@ -494,9 +434,9 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do delete "/scim_v2/Users/#{user.id}", "", headers response_body = JSON.parse(last_response.body) - expect(response_body).to eq("schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], - "detail" => "Action forbidden: insufficient permissions.", - "status" => "403") + expect(response_body).to eq({ "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "detail" => "User can't be deleted due to permission absence.", + "status" => "403" }) expect(last_response).to have_http_status(403) end end @@ -508,9 +448,8 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) expect(last_response).to have_http_status(401) end @@ -546,21 +485,21 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do response_body = JSON.parse(last_response.body) user.reload - expect(response_body).to eq("active" => true, - "emails" => [{ "primary" => true, - "type" => "work", - "value" => request_body["emails"].first["value"] }], - "externalId" => new_external_user_id, - "groups" => [{ "value" => group.id.to_s }], - "id" => user.id.to_s, - "meta" => { "created" => user.created_at.iso8601, - "lastModified" => user.updated_at.iso8601, - "location" => "http://test.host/scim_v2/Users/#{user.id}", - "resourceType" => "User" }, - "name" => { "familyName" => request_body["name"]["familyName"], - "givenName" => request_body["name"]["givenName"] }, - "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], - "userName" => request_body["userName"]) + expect(response_body).to eq({ "active" => true, + "emails" => [{ "primary" => true, + "type" => "work", + "value" => request_body["emails"].first["value"] }], + "externalId" => new_external_user_id, + "groups" => [{ "value" => group.id.to_s }], + "id" => user.id.to_s, + "meta" => { "created" => user.created_at.iso8601, + "lastModified" => user.updated_at.iso8601, + "location" => "http://test.host/scim_v2/Users/#{user.id}", + "resourceType" => "User" }, + "name" => { "familyName" => request_body["name"]["familyName"], + "givenName" => request_body["name"]["givenName"] }, + "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], + "userName" => request_body["userName"] }) end end @@ -570,9 +509,8 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) expect(last_response).to have_http_status(401) end @@ -599,21 +537,21 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do response_body = JSON.parse(last_response.body) user.reload - expect(response_body).to eq("active" => true, - "emails" => [{ "primary" => true, - "type" => "work", - "value" => user.mail }], - "externalId" => new_external_user_id, - "groups" => [{ "value" => group.id.to_s }], - "id" => user.id.to_s, - "meta" => { "created" => user.created_at.iso8601, - "lastModified" => user.updated_at.iso8601, - "location" => "http://test.host/scim_v2/Users/#{user.id}", - "resourceType" => "User" }, - "name" => { "familyName" => user.lastname, - "givenName" => user.firstname }, - "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], - "userName" => user.login) + expect(response_body).to eq({ "active" => true, + "emails" => [{ "primary" => true, + "type" => "work", + "value" => user.mail }], + "externalId" => new_external_user_id, + "groups" => [{ "value" => group.id.to_s }], + "id" => user.id.to_s, + "meta" => { "created" => user.created_at.iso8601, + "lastModified" => user.updated_at.iso8601, + "location" => "http://test.host/scim_v2/Users/#{user.id}", + "resourceType" => "User" }, + "name" => { "familyName" => user.lastname, + "givenName" => user.firstname }, + "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], + "userName" => user.login }) end it "changes email value" do @@ -636,21 +574,21 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do response_body = JSON.parse(last_response.body) user.reload - expect(response_body).to eq("active" => true, - "emails" => [{ "primary" => true, - "type" => "work", - "value" => new_email_value }], - "externalId" => user.scim_external_id, - "groups" => [{ "value" => group.id.to_s }], - "id" => user.id.to_s, - "meta" => { "created" => user.created_at.iso8601, - "lastModified" => user.updated_at.iso8601, - "location" => "http://test.host/scim_v2/Users/#{user.id}", - "resourceType" => "User" }, - "name" => { "familyName" => user.lastname, - "givenName" => user.firstname }, - "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], - "userName" => user.login) + expect(response_body).to eq({ "active" => true, + "emails" => [{ "primary" => true, + "type" => "work", + "value" => new_email_value }], + "externalId" => user.scim_external_id, + "groups" => [{ "value" => group.id.to_s }], + "id" => user.id.to_s, + "meta" => { "created" => user.created_at.iso8601, + "lastModified" => user.updated_at.iso8601, + "location" => "http://test.host/scim_v2/Users/#{user.id}", + "resourceType" => "User" }, + "name" => { "familyName" => user.lastname, + "givenName" => user.firstname }, + "schemas" => ["urn:ietf:params:scim:schemas:core:2.0:User"], + "userName" => user.login }) end end @@ -660,9 +598,8 @@ RSpec.describe "SCIM API Users", with_ee: [:scim_api] do 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" + { "detail" => "Requires authentication", "schemas" => ["urn:ietf:params:scim:api:messages:2.0:Error"], + "status" => "401" } ) expect(last_response).to have_http_status(401) end