From 7d408c08ca1e3a43fc47781264b4addaa695c16e Mon Sep 17 00:00:00 2001 From: Aaron Delate Date: Thu, 26 Mar 2026 10:27:55 +0200 Subject: [PATCH 01/11] feat: client_scope_client for managing client scopes --- .../client/client_scope_client.rb | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 lib/keycloak-admin/client/client_scope_client.rb diff --git a/lib/keycloak-admin/client/client_scope_client.rb b/lib/keycloak-admin/client/client_scope_client.rb new file mode 100644 index 0000000..80584c7 --- /dev/null +++ b/lib/keycloak-admin/client/client_scope_client.rb @@ -0,0 +1,70 @@ +module KeycloakAdmin + class ClientScopeClient < Client + def initialize(configuration, realm_client) + super(configuration) + + raise ArgumentError.new("realm must be defined") unless realm_client.name_defined? + + @realm_client = realm_client + end + + def list + response = execute_http do + RestClient::Resource.new(client_scopes_url, @configuration.rest_client_options).get(headers) + end + + JSON.parse(response).map { |h| ClientScopeRepresentation.from_hash(h) } + end + + def get(client_scope_id) + response = execute_http do + RestClient::Resource.new(client_scopes_url(client_scope_id), @configuration.rest_client_options).get(headers) + end + + ClientScopeRepresentation.from_hash(JSON.parse(response)) + end + + def create!(client_scope_representation) + execute_http do + RestClient::Resource.new(client_scopes_url, @configuration.rest_client_options).post( + create_payload(client_scope_representation), headers + ) + end + + true + end + + def save(client_scope_representation) + execute_http do + RestClient::Resource.new(client_scopes_url(client_scope_representation.id), @configuration.rest_client_options).put( + create_payload(client_scope_representation), headers + ) + end + + true + end + + def search(name) + url = "#{client_scopes_url}?search=#{name}" + response = execute_http do + RestClient::Resource.new(url, @configuration.rest_client_options).get(headers) + end + + JSON.parse(response).map { |h| ClientScopeRepresentation.from_hash(h) } + end + + def delete(client_scope_id) + execute_http do + RestClient::Resource.new(client_scopes_url(client_scope_id), @configuration.rest_client_options).delete(headers) + end + + true + end + + def client_scopes_url(client_scope_id = nil) + base = "#{@realm_client.realm_admin_url}/client-scopes" + + client_scope_id ? "#{base}/#{client_scope_id}" : base + end + end +end From a4bff4734129e47fbbcaff96cfb0b11406812a41 Mon Sep 17 00:00:00 2001 From: Aaron Delate Date: Thu, 26 Mar 2026 10:28:20 +0200 Subject: [PATCH 02/11] refactor: change update to save to bring client in-line with other clients --- .../client/client_scope_protocol_mapper_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/keycloak-admin/client/client_scope_protocol_mapper_client.rb b/lib/keycloak-admin/client/client_scope_protocol_mapper_client.rb index fe51e7c..5288579 100644 --- a/lib/keycloak-admin/client/client_scope_protocol_mapper_client.rb +++ b/lib/keycloak-admin/client/client_scope_protocol_mapper_client.rb @@ -35,7 +35,7 @@ def create!(mapper_representation) true end - def update(mapper_representation) + def save(mapper_representation) execute_http do RestClient::Resource.new(protocol_mappers_url(mapper_representation.id), @configuration.rest_client_options).put( create_payload(mapper_representation), headers From ed895af663e957995bf9aed56462e3462cc2330a Mon Sep 17 00:00:00 2001 From: Aaron Delate Date: Thu, 26 Mar 2026 10:28:30 +0200 Subject: [PATCH 03/11] feat: client_scopes client for realm --- lib/keycloak-admin/client/realm_client.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/keycloak-admin/client/realm_client.rb b/lib/keycloak-admin/client/realm_client.rb index 57f32e1..6b71c56 100644 --- a/lib/keycloak-admin/client/realm_client.rb +++ b/lib/keycloak-admin/client/realm_client.rb @@ -103,6 +103,10 @@ def user(user_id) UserResource.new(@configuration, self, user_id) end + def client_scopes + ClientScopeClient.new(@configuration, self) + end + def client_scope_protocol_mappers(client_scope_id) ClientScopeProtocolMapperClient.new(@configuration, self, client_scope_id) end From 377ffde11a9b5174acaa2657d55981bd9cf4a9e3 Mon Sep 17 00:00:00 2001 From: Aaron Delate Date: Thu, 26 Mar 2026 10:28:43 +0200 Subject: [PATCH 04/11] feat: client_scope representation --- .../client_scope_representation.rb | 21 +++ .../client_scope_representation_spec.rb | 125 ++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 lib/keycloak-admin/representation/client_scope_representation.rb create mode 100644 spec/representation/client_scope_representation_spec.rb diff --git a/lib/keycloak-admin/representation/client_scope_representation.rb b/lib/keycloak-admin/representation/client_scope_representation.rb new file mode 100644 index 0000000..7b901f5 --- /dev/null +++ b/lib/keycloak-admin/representation/client_scope_representation.rb @@ -0,0 +1,21 @@ +module KeycloakAdmin + class ClientScopeRepresentation < Representation + attr_accessor :id, + :name, + :description, + :protocol, + :attributes, + :protocolMappers + + def self.from_hash(hash) + rep = new + rep.id = hash["id"] + rep.name = hash["name"] + rep.description = hash["description"] + rep.protocol = hash["protocol"] + rep.attributes = hash["attributes"] + rep.protocolMappers = (hash["protocolMappers"] || []).map { |m| ProtocolMapperRepresentation.from_hash(m) } + rep + end + end +end diff --git a/spec/representation/client_scope_representation_spec.rb b/spec/representation/client_scope_representation_spec.rb new file mode 100644 index 0000000..13412b2 --- /dev/null +++ b/spec/representation/client_scope_representation_spec.rb @@ -0,0 +1,125 @@ +# frozen_string_literal: true + +RSpec.describe KeycloakAdmin::ClientScopeRepresentation do + describe ".from_hash" do + context "with all fields" do + let(:hash) do + { + "id" => "valid-scope-id", + "name" => "my-scope", + "description" => "A test scope", + "protocol" => "openid-connect", + "attributes" => { + "display.on.consent.screen" => "true", + "include.in.token.scope" => "true" + }, + "protocolMappers" => [ + { + "id" => "mapper-id", + "name" => "my-claim", + "protocol" => "openid-connect", + "protocolMapper" => "oidc-hardcoded-claim-mapper", + "config" => { "claim.name" => "my_claim", "claim.value" => "bar" } + } + ] + } + end + + subject { described_class.from_hash(hash) } + + it "returns an instance of the class" do + expect(subject).to be_a described_class + end + + it "sets id" do + expect(subject.id).to eq "valid-scope-id" + end + + it "sets name" do + expect(subject.name).to eq "my-scope" + end + + it "sets description" do + expect(subject.description).to eq "A test scope" + end + + it "sets protocol" do + expect(subject.protocol).to eq "openid-connect" + end + + it "sets attributes" do + expect(subject.attributes).to eq( + "display.on.consent.screen" => "true", + "include.in.token.scope" => "true" + ) + end + + it "deserializes protocolMappers as ProtocolMapperRepresentation objects" do + expect(subject.protocolMappers.size).to eq 1 + expect(subject.protocolMappers.first).to be_a KeycloakAdmin::ProtocolMapperRepresentation + end + + it "sets the correct mapper attributes" do + expect(subject.protocolMappers.first).to have_attributes( + id: "mapper-id", + name: "my-claim", + protocol: "openid-connect", + protocolMapper: "oidc-hardcoded-claim-mapper" + ) + end + end + + context "without protocolMappers" do + subject { described_class.from_hash({ "id" => "valid-scope-id", "name" => "my-scope" }) } + + it "defaults protocolMappers to an empty array" do + expect(subject.protocolMappers).to eq [] + end + end + + context "with minimal fields" do + subject { described_class.from_hash({ "name" => "my-scope", "protocol" => "saml" }) } + + it "sets name" do + expect(subject.name).to eq "my-scope" + end + + it "sets protocol" do + expect(subject.protocol).to eq "saml" + end + + it "leaves id nil" do + expect(subject.id).to be_nil + end + + it "leaves description nil" do + expect(subject.description).to be_nil + end + + it "leaves attributes nil" do + expect(subject.attributes).to be_nil + end + end + end + + describe "#to_json" do + subject do + described_class.from_hash( + "id" => "valid-scope-id", + "name" => "my-scope", + "description" => "A test scope", + "protocol" => "openid-connect", + "attributes" => { "include.in.token.scope" => "true" } + ) + end + + it "serializes to JSON" do + parsed = JSON.parse(subject.to_json) + expect(parsed["id"]).to eq "valid-scope-id" + expect(parsed["name"]).to eq "my-scope" + expect(parsed["description"]).to eq "A test scope" + expect(parsed["protocol"]).to eq "openid-connect" + expect(parsed["attributes"]).to eq("include.in.token.scope" => "true") + end + end +end From 8d17042ac65480e17b6702e8564bc94d172f234a Mon Sep 17 00:00:00 2001 From: Aaron Delate Date: Thu, 26 Mar 2026 10:28:52 +0200 Subject: [PATCH 05/11] test: clients --- spec/client/client_scope_client_spec.rb | 188 ++++++++++++++++++ ...lient_scope_protocol_mapper_client_spec.rb | 6 +- 2 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 spec/client/client_scope_client_spec.rb diff --git a/spec/client/client_scope_client_spec.rb b/spec/client/client_scope_client_spec.rb new file mode 100644 index 0000000..7cf28fb --- /dev/null +++ b/spec/client/client_scope_client_spec.rb @@ -0,0 +1,188 @@ +RSpec.describe KeycloakAdmin::ClientScopeClient do + let(:realm_name) { "valid-realm" } + let(:client_scope_id) { "valid-scope-id" } + + let(:scope_json) do + <<~JSON + {"id":"valid-scope-id","name":"my-scope","description":"A test scope","protocol":"openid-connect","attributes":{"display.on.consent.screen":"true","include.in.token.scope":"true"}} + JSON + end + + let(:scope_with_mappers_json) do + <<~JSON + {"id":"valid-scope-id","name":"my-scope","description":"A test scope","protocol":"openid-connect","attributes":{},"protocolMappers":[{"id":"mapper-id","name":"my-claim","protocol":"openid-connect","protocolMapper":"oidc-hardcoded-claim-mapper","config":{"claim.name":"my_claim","claim.value":"bar","access.token.claim":"true"}}]} + JSON + end + + describe "#initialize" do + context "when realm_name is defined" do + it "does not raise any error" do + expect { KeycloakAdmin.realm(realm_name).client_scopes }.to_not raise_error + end + end + + context "when realm_name is not defined" do + it "raises an argument error" do + expect { KeycloakAdmin.realm(nil).client_scopes }.to raise_error(ArgumentError) + end + end + end + + describe "#list" do + before(:each) do + @client = KeycloakAdmin.realm(realm_name).client_scopes + stub_token_client + allow_any_instance_of(RestClient::Resource).to receive(:get).and_return stub_response + end + + context "with one scope" do + let(:stub_response) { "[#{scope_json}]" } + + it "returns one scope" do + expect(@client.list.size).to eq 1 + end + + it "returns the correct scope attributes" do + expect(@client.list.first).to have_attributes( + id: "valid-scope-id", + name: "my-scope", + description: "A test scope", + protocol: "openid-connect" + ) + end + + it "returns attributes map" do + expect(@client.list.first.attributes).to include( + "display.on.consent.screen" => "true", + "include.in.token.scope" => "true" + ) + end + end + + context "with multiple scopes" do + let(:second_scope_json) { '{"id":"other-scope-id","name":"other-scope","protocol":"openid-connect"}' } + let(:stub_response) { "[#{scope_json},#{second_scope_json}]" } + + it "returns two scopes" do + expect(@client.list.size).to eq 2 + end + + it "includes both scope names" do + expect(@client.list.map(&:name)).to include("my-scope", "other-scope") + end + end + end + + describe "#get" do + before(:each) do + @client = KeycloakAdmin.realm(realm_name).client_scopes + stub_token_client + allow_any_instance_of(RestClient::Resource).to receive(:get).and_return stub_response + end + + context "without protocol mappers" do + let(:stub_response) { scope_json } + + it "returns the correct id" do + expect(@client.get(client_scope_id).id).to eq "valid-scope-id" + end + + it "returns the correct name" do + expect(@client.get(client_scope_id).name).to eq "my-scope" + end + + it "returns the correct description" do + expect(@client.get(client_scope_id).description).to eq "A test scope" + end + + it "returns the correct protocol" do + expect(@client.get(client_scope_id).protocol).to eq "openid-connect" + end + + it "returns an empty protocolMappers list" do + expect(@client.get(client_scope_id).protocolMappers).to eq [] + end + end + + context "with protocol mappers" do + let(:stub_response) { scope_with_mappers_json } + + it "returns protocol mappers" do + expect(@client.get(client_scope_id).protocolMappers.size).to eq 1 + end + + it "returns the correct mapper name" do + expect(@client.get(client_scope_id).protocolMappers.first.name).to eq "my-claim" + end + end + end + + describe "#create!" do + before(:each) do + @client = KeycloakAdmin.realm(realm_name).client_scopes + stub_token_client + allow_any_instance_of(RestClient::Resource).to receive(:post).and_return "" + end + + let(:scope_representation) do + scope = KeycloakAdmin::ClientScopeRepresentation.new + scope.name = "my-scope" + scope.description = "A test scope" + scope.protocol = "openid-connect" + scope.attributes = { "display.on.consent.screen" => "true", "include.in.token.scope" => "true" } + scope + end + + it "creates successfully" do + expect(@client.create!(scope_representation)).to be true + end + end + + describe "#save" do + before(:each) do + @client = KeycloakAdmin.realm(realm_name).client_scopes + stub_token_client + allow_any_instance_of(RestClient::Resource).to receive(:put).and_return "" + end + + let(:scope_representation) { KeycloakAdmin::ClientScopeRepresentation.from_hash(JSON.parse(scope_json)) } + + it "calls put on the scope url" do + expect_any_instance_of(RestClient::Resource).to receive(:put).with(anything, anything) + @client.save(scope_representation) + end + + it "returns true" do + expect(@client.save(scope_representation)).to be true + end + end + + describe "#delete" do + before(:each) do + @client = KeycloakAdmin.realm(realm_name).client_scopes + stub_token_client + allow_any_instance_of(RestClient::Resource).to receive(:delete).and_return "" + end + + it "returns true" do + expect(@client.delete(client_scope_id)).to eq true + end + end + + describe "#client_scopes_url" do + let(:client) { KeycloakAdmin.realm(realm_name).client_scopes } + let(:base_url) { "http://auth.service.io/auth/admin/realms/valid-realm/client-scopes" } + + context "without a client_scope_id" do + it "returns the base url" do + expect(client.client_scopes_url).to eq base_url + end + end + + context "with a client_scope_id" do + it "returns the url with client_scope_id appended" do + expect(client.client_scopes_url(client_scope_id)).to eq "#{base_url}/valid-scope-id" + end + end + end +end diff --git a/spec/client/client_scope_protocol_mapper_client_spec.rb b/spec/client/client_scope_protocol_mapper_client_spec.rb index 296188f..fed7a49 100644 --- a/spec/client/client_scope_protocol_mapper_client_spec.rb +++ b/spec/client/client_scope_protocol_mapper_client_spec.rb @@ -173,7 +173,7 @@ end end - describe "#update" do + describe "#save" do before(:each) do @client = KeycloakAdmin.realm(realm_name).client_scope_protocol_mappers(client_scope_id) stub_token_client @@ -185,7 +185,7 @@ it "calls put on the mapper url" do expect_any_instance_of(RestClient::Resource).to receive(:put).with(anything, anything) - @client.update(mapper_representation) + @client.save(mapper_representation) end end @@ -194,7 +194,7 @@ it "calls put on the mapper url" do expect_any_instance_of(RestClient::Resource).to receive(:put).with(anything, anything) - @client.update(mapper_representation) + @client.save(mapper_representation) end end end From 750a7f8d73284a310e203753b76372c6a3f7920f Mon Sep 17 00:00:00 2001 From: Aaron Delate Date: Thu, 26 Mar 2026 10:29:02 +0200 Subject: [PATCH 06/11] chore: add client and representations to gem --- lib/keycloak-admin.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/keycloak-admin.rb b/lib/keycloak-admin.rb index c28f57f..635141d 100644 --- a/lib/keycloak-admin.rb +++ b/lib/keycloak-admin.rb @@ -16,6 +16,7 @@ require_relative "keycloak-admin/client/configurable_token_client" require_relative "keycloak-admin/client/attack_detection_client" require_relative "keycloak-admin/client/client_authz_scope_client" +require_relative "keycloak-admin/client/client_scope_client" require_relative "keycloak-admin/client/client_scope_protocol_mapper_client" require_relative "keycloak-admin/client/client_authz_resource_client" require_relative "keycloak-admin/client/client_authz_policy_client" @@ -40,6 +41,7 @@ require_relative "keycloak-admin/representation/identity_provider_representation" require_relative "keycloak-admin/representation/attack_detection_representation" require_relative "keycloak-admin/representation/session_representation" +require_relative "keycloak-admin/representation/client_scope_representation" require_relative "keycloak-admin/representation/client_authz_scope_representation" require_relative "keycloak-admin/representation/client_authz_resource_representation" require_relative "keycloak-admin/representation/client_authz_policy_representation" From d05bc5a0787459ef695be993e28a28f0b5a4d8fd Mon Sep 17 00:00:00 2001 From: Aaron Delate Date: Thu, 26 Mar 2026 10:36:34 +0200 Subject: [PATCH 07/11] feat: add search to client_scope_client --- .../client/client_scope_client.rb | 7 +--- spec/client/client_scope_client_spec.rb | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/lib/keycloak-admin/client/client_scope_client.rb b/lib/keycloak-admin/client/client_scope_client.rb index 80584c7..70c12ba 100644 --- a/lib/keycloak-admin/client/client_scope_client.rb +++ b/lib/keycloak-admin/client/client_scope_client.rb @@ -45,12 +45,7 @@ def save(client_scope_representation) end def search(name) - url = "#{client_scopes_url}?search=#{name}" - response = execute_http do - RestClient::Resource.new(url, @configuration.rest_client_options).get(headers) - end - - JSON.parse(response).map { |h| ClientScopeRepresentation.from_hash(h) } + list.select { |scope| scope&.name&.include?(name) } end def delete(client_scope_id) diff --git a/spec/client/client_scope_client_spec.rb b/spec/client/client_scope_client_spec.rb index 7cf28fb..0b916da 100644 --- a/spec/client/client_scope_client_spec.rb +++ b/spec/client/client_scope_client_spec.rb @@ -157,6 +157,38 @@ end end + describe "#search" do + let(:second_scope_json) { '{"id":"other-scope-id","name":"other-scope","protocol":"openid-connect"}' } + + before(:each) do + @client = KeycloakAdmin.realm(realm_name).client_scopes + stub_token_client + allow_any_instance_of(RestClient::Resource).to receive(:get).and_return "[#{scope_json},#{second_scope_json}]" + end + + context "when the name matches one scope" do + it "returns only the matching scope" do + expect(@client.search("my-scope").size).to eq 1 + end + + it "returns the correct scope" do + expect(@client.search("my-scope").first).to have_attributes(id: "valid-scope-id", name: "my-scope") + end + end + + context "when the name is a partial match" do + it "returns all scopes containing the substring" do + expect(@client.search("scope").size).to eq 2 + end + end + + context "when no scope matches" do + it "returns an empty array" do + expect(@client.search("unknown")).to eq [] + end + end + end + describe "#delete" do before(:each) do @client = KeycloakAdmin.realm(realm_name).client_scopes From be57ffe24964c01fd4a4b2a829544182ec943f70 Mon Sep 17 00:00:00 2001 From: Aaron Delate Date: Thu, 26 Mar 2026 10:38:23 +0200 Subject: [PATCH 08/11] docs: document the client_scope_client --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a81d8a7..51c3183 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,8 @@ All options have a default value. However, all of them can be changed in your in * Execute actions emails * Send forgot passsword mail * Client Authorization, create, update, get, delete Resource, Scope, Policy, Permission, Policy Enforcer -* Get list of protocol mappers for a client scope, create/update/get/delete a protocol mapper +* Get list of client scopes, create/save/get/delete/search a client scope +* Get list of protocol mappers for a client scope, create/save/get/delete a protocol mapper * Get list of organizations, create/update/get/delete an organization * Get list of members of an organization, add/remove members * Invite new or existing users to an organization @@ -758,6 +759,64 @@ KeycloakAdmin.realm("realm_a").authz_permissions(client.id, 'scope').delete(scop KeycloakAdmin.realm("realm_a").authz_permissions(client.id, 'resource').delete(resource_permission.id) ``` +### Manage Client Scopes + +### List all client scopes in a realm + +Returns an array of `KeycloakAdmin::ClientScopeRepresentation`. + +```ruby +KeycloakAdmin.realm("a_realm").client_scopes.list +``` + +### Get a client scope by its id + +Returns an instance of `KeycloakAdmin::ClientScopeRepresentation`. + +```ruby +client_scope_id = "7686af34-204c-4515-8122-78d19febbf6e" +KeycloakAdmin.realm("a_realm").client_scopes.get(client_scope_id) +``` + +### Search for client scopes by name + +Returns an array of `KeycloakAdmin::ClientScopeRepresentation` whose names contain the given substring. + +```ruby +KeycloakAdmin.realm("a_realm").client_scopes.search("my-scope") +``` + +### Create a client scope + +Takes `scope_representation` of type `KeycloakAdmin::ClientScopeRepresentation`. Returns `true` on success. + +```ruby +scope = KeycloakAdmin::ClientScopeRepresentation.new +scope.name = "my-scope" +scope.description = "My custom scope" +scope.protocol = "openid-connect" +scope.attributes = { "display.on.consent.screen" => "true", "include.in.token.scope" => "true" } +KeycloakAdmin.realm("a_realm").client_scopes.create!(scope) +``` + +### Save (update) a client scope + +Takes `scope_representation` of type `KeycloakAdmin::ClientScopeRepresentation` (must include its `id`). Returns `true` on success. + +```ruby +client_scope_id = "7686af34-204c-4515-8122-78d19febbf6e" +scope = KeycloakAdmin.realm("a_realm").client_scopes.get(client_scope_id) +scope.description = "Updated description" +KeycloakAdmin.realm("a_realm").client_scopes.save(scope) +``` + +### Delete a client scope + +```ruby +client_scope_id = "7686af34-204c-4515-8122-78d19febbf6e" +KeycloakAdmin.realm("a_realm").client_scopes.delete(client_scope_id) +``` + ### Manage Protocol Mappers for a Client Scope Protocol mappers allow you to transform tokens and assertions. The following operations are available on the protocol mappers of a given client scope. @@ -795,7 +854,7 @@ mapper.config = { "user.attribute" => "locale", "claim.name" => "locale", KeycloakAdmin.realm("a_realm").client_scope_protocol_mappers(client_scope_id).create!(mapper) ``` -### Update a protocol mapper for a client scope +### Save (update) a protocol mapper for a client scope Takes `mapper_representation` of type `KeycloakAdmin::ProtocolMapperRepresentation` (must include its `id`). Returns `true` on success. @@ -803,7 +862,7 @@ Takes `mapper_representation` of type `KeycloakAdmin::ProtocolMapperRepresentati client_scope_id = "7686af34-204c-4515-8122-78d19febbf6e" mapper = KeycloakAdmin.realm("a_realm").client_scope_protocol_mappers(client_scope_id).get(mapper_id) mapper.config["claim.name"] = "updated_claim" -KeycloakAdmin.realm("a_realm").client_scope_protocol_mappers(client_scope_id).update(mapper) +KeycloakAdmin.realm("a_realm").client_scope_protocol_mappers(client_scope_id).save(mapper) ``` ### Delete a protocol mapper from a client scope From e8a09ca2c003a6f79d85bae44b101d75c64c14fb Mon Sep 17 00:00:00 2001 From: Aaron Delate Date: Thu, 26 Mar 2026 10:44:04 +0200 Subject: [PATCH 09/11] fix: casing of proto_mappers in rep --- .../representation/client_scope_representation.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/keycloak-admin/representation/client_scope_representation.rb b/lib/keycloak-admin/representation/client_scope_representation.rb index 7b901f5..93c4544 100644 --- a/lib/keycloak-admin/representation/client_scope_representation.rb +++ b/lib/keycloak-admin/representation/client_scope_representation.rb @@ -8,13 +8,13 @@ class ClientScopeRepresentation < Representation :protocolMappers def self.from_hash(hash) - rep = new - rep.id = hash["id"] - rep.name = hash["name"] - rep.description = hash["description"] - rep.protocol = hash["protocol"] - rep.attributes = hash["attributes"] - rep.protocolMappers = (hash["protocolMappers"] || []).map { |m| ProtocolMapperRepresentation.from_hash(m) } + rep = new + rep.id = hash["id"] + rep.name = hash["name"] + rep.description = hash["description"] + rep.protocol = hash["protocol"] + rep.attributes = hash["attributes"] + rep.protocol_mappers = (hash["protocolMappers"] || []).map { |m| ProtocolMapperRepresentation.from_hash(m) } rep end end From 43868f6c1cf84dc5cf5c429f5dcabbc9f30ec0cf Mon Sep 17 00:00:00 2001 From: Aaron Delate Date: Thu, 26 Mar 2026 13:43:48 +0200 Subject: [PATCH 10/11] revert: casing of protocolMappers --- .../representation/client_scope_representation.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/keycloak-admin/representation/client_scope_representation.rb b/lib/keycloak-admin/representation/client_scope_representation.rb index 93c4544..d3ea58f 100644 --- a/lib/keycloak-admin/representation/client_scope_representation.rb +++ b/lib/keycloak-admin/representation/client_scope_representation.rb @@ -8,13 +8,13 @@ class ClientScopeRepresentation < Representation :protocolMappers def self.from_hash(hash) - rep = new - rep.id = hash["id"] - rep.name = hash["name"] - rep.description = hash["description"] - rep.protocol = hash["protocol"] - rep.attributes = hash["attributes"] - rep.protocol_mappers = (hash["protocolMappers"] || []).map { |m| ProtocolMapperRepresentation.from_hash(m) } + rep = new + rep.id = hash["id"] + rep.name = hash["name"] + rep.description = hash["description"] + rep.protocol = hash["protocol"] + rep.attributes = hash["attributes"] + rep.protocolMappers = (hash["protocolMappers"] || []).map { |m| ProtocolMapperRepresentation.from_hash(m) } rep end end From 8995b041bdbc762da74828e57042ce69faa64d90 Mon Sep 17 00:00:00 2001 From: Aaron Delate Date: Thu, 26 Mar 2026 14:16:52 +0200 Subject: [PATCH 11/11] refactor: use snake_case for protocol_mappers --- .../client_scope_representation.rb | 16 ++++++++-------- spec/client/client_scope_client_spec.rb | 6 +++--- .../client_scope_representation_spec.rb | 8 ++++---- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/keycloak-admin/representation/client_scope_representation.rb b/lib/keycloak-admin/representation/client_scope_representation.rb index d3ea58f..53b8aaa 100644 --- a/lib/keycloak-admin/representation/client_scope_representation.rb +++ b/lib/keycloak-admin/representation/client_scope_representation.rb @@ -5,16 +5,16 @@ class ClientScopeRepresentation < Representation :description, :protocol, :attributes, - :protocolMappers + :protocol_mappers def self.from_hash(hash) - rep = new - rep.id = hash["id"] - rep.name = hash["name"] - rep.description = hash["description"] - rep.protocol = hash["protocol"] - rep.attributes = hash["attributes"] - rep.protocolMappers = (hash["protocolMappers"] || []).map { |m| ProtocolMapperRepresentation.from_hash(m) } + rep = new + rep.id = hash["id"] + rep.name = hash["name"] + rep.description = hash["description"] + rep.protocol = hash["protocol"] + rep.attributes = hash["attributes"] + rep.protocol_mappers = (hash["protocolMappers"] || []).map { |m| ProtocolMapperRepresentation.from_hash(m) } rep end end diff --git a/spec/client/client_scope_client_spec.rb b/spec/client/client_scope_client_spec.rb index 0b916da..d992d4a 100644 --- a/spec/client/client_scope_client_spec.rb +++ b/spec/client/client_scope_client_spec.rb @@ -100,7 +100,7 @@ end it "returns an empty protocolMappers list" do - expect(@client.get(client_scope_id).protocolMappers).to eq [] + expect(@client.get(client_scope_id).protocol_mappers).to eq [] end end @@ -108,11 +108,11 @@ let(:stub_response) { scope_with_mappers_json } it "returns protocol mappers" do - expect(@client.get(client_scope_id).protocolMappers.size).to eq 1 + expect(@client.get(client_scope_id).protocol_mappers.size).to eq 1 end it "returns the correct mapper name" do - expect(@client.get(client_scope_id).protocolMappers.first.name).to eq "my-claim" + expect(@client.get(client_scope_id).protocol_mappers.first.name).to eq "my-claim" end end end diff --git a/spec/representation/client_scope_representation_spec.rb b/spec/representation/client_scope_representation_spec.rb index 13412b2..afa0e49 100644 --- a/spec/representation/client_scope_representation_spec.rb +++ b/spec/representation/client_scope_representation_spec.rb @@ -55,12 +55,12 @@ end it "deserializes protocolMappers as ProtocolMapperRepresentation objects" do - expect(subject.protocolMappers.size).to eq 1 - expect(subject.protocolMappers.first).to be_a KeycloakAdmin::ProtocolMapperRepresentation + expect(subject.protocol_mappers.size).to eq 1 + expect(subject.protocol_mappers.first).to be_a KeycloakAdmin::ProtocolMapperRepresentation end it "sets the correct mapper attributes" do - expect(subject.protocolMappers.first).to have_attributes( + expect(subject.protocol_mappers.first).to have_attributes( id: "mapper-id", name: "my-claim", protocol: "openid-connect", @@ -73,7 +73,7 @@ subject { described_class.from_hash({ "id" => "valid-scope-id", "name" => "my-scope" }) } it "defaults protocolMappers to an empty array" do - expect(subject.protocolMappers).to eq [] + expect(subject.protocol_mappers).to eq [] end end