diff --git a/lib/puppet/provider/keycloak_client_protocol_mapper/kcadm.rb b/lib/puppet/provider/keycloak_client_protocol_mapper/kcadm.rb index b67452a8..f29602db 100644 --- a/lib/puppet/provider/keycloak_client_protocol_mapper/kcadm.rb +++ b/lib/puppet/provider/keycloak_client_protocol_mapper/kcadm.rb @@ -48,6 +48,9 @@ def self.instances protocol_mapper[:claim_name] = d['config']['claim.name'] protocol_mapper[:json_type_label] = d['config']['jsonType.label'] end + if protocol_mapper[:type] == 'oidc-usermodel-client-role-mapper' + protocol_mapper[:usermodel_client_role_mapping_client_id] = d['config']['usermodel.clientRoleMapping.clientId'] + end if protocol_mapper[:type] == 'oidc-group-membership-mapper' protocol_mapper[:full_path] = d['config']['full.path'] end @@ -71,6 +74,7 @@ def self.instances if ['saml-role-list-mapper'].include?(protocol_mapper[:type]) || protocol_mapper[:type] =~ %r{script-.+} protocol_mapper[:single] = d['config']['single'].to_s.to_sym end + protocol_mapper[:multivalued] = d['config']['multivalued'].to_s.to_sym if d['config']['multivalued'] protocol_mappers << new(protocol_mapper) end end @@ -112,6 +116,9 @@ def create if resource[:type] == 'oidc-group-membership-mapper' && resource[:full_path] data[:config][:'full.path'] = resource[:full_path] end + if resource[:type] == 'oidc-usermodel-client-role-mapper' && resource[:usermodel_client_role_mapping_client_id] + data[:config][:'usermodel.clientRoleMapping.clientId'] = resource[:usermodel_client_role_mapping_client_id] + end if (['saml-user-property-mapper'].include?(resource[:type]) || (resource[:protocol] == 'saml' && resource[:type] =~ %r{script-.+})) && resource[:friendly_name] data[:config][:'friendly.name'] = resource[:friendly_name] end @@ -132,6 +139,9 @@ def create if (['saml-role-list-mapper'].include?(resource[:type]) || (resource[:protocol] == 'saml' && resource[:type] =~ %r{script-.+})) && resource[:single] data[:config][:single] = resource[:single].to_s end + if resource[:multivalued] + data[:config][:multivalued] = resource[:multivalued].to_s + end t = Tempfile.new('keycloak_protocol_mapper') t.write(JSON.pretty_generate(data)) @@ -194,6 +204,9 @@ def flush if resource[:type] == 'oidc-group-membership-mapper' && resource[:full_path] config[:'full.path'] = resource[:full_path] end + if resource[:type] == 'oidc-usermodel-client-role-mapper' && resource[:usermodel_client_role_mapping_client_id] + config[:'usermodel.clientRoleMapping.clientId'] = resource[:usermodel_client_role_mapping_client_id] + end if (['saml-user-property-mapper'].include?(resource[:type]) || (resource[:protocol] == 'saml' && resource[:type] =~ %r{script-.+})) && resource[:friendly_name] config[:'friendly.name'] = resource[:friendly_name] end @@ -214,6 +227,9 @@ def flush if (['saml-role-list-mapper'].include?(resource[:type]) || (resource[:protocol] == 'saml' && resource[:type] =~ %r{script-.+})) && resource[:single] config[:single] = resource[:single].to_s end + if resource[:multivalued] + config[:multivalued] = resource[:multivalued].to_s + end data[:config] = config unless config.empty? t = Tempfile.new('keycloak_protocol_mapper') diff --git a/lib/puppet/type/keycloak_client_protocol_mapper.rb b/lib/puppet/type/keycloak_client_protocol_mapper.rb index 6819ae36..139d0e20 100644 --- a/lib/puppet/type/keycloak_client_protocol_mapper.rb +++ b/lib/puppet/type/keycloak_client_protocol_mapper.rb @@ -187,6 +187,10 @@ end end + newproperty(:usermodel_client_role_mapping_client_id) do + desc 'usermodel.clientRoleMapping.clientId for `type` `oidc-usermodel-client-role-mapper`' + end + newproperty(:single, boolean: true) do desc 'single. Default to `false` for `type` `saml-role-list-mapper`.' newvalues(:true, :false) @@ -199,6 +203,11 @@ end end + newproperty(:multivalued, boolean: true) do + desc 'multivalued' + newvalues(:true, :false) + end + newproperty(:included_client_audience) do desc 'included.client.audience Required for `type` of `oidc-audience-mapper`' end @@ -264,5 +273,8 @@ def self.title_patterns if self[:type] == 'oidc-audience-mapper' && self[:included_client_audience].nil? raise Puppet::Error, 'included_client_audience is required for oidc-audience-mapper' end + if self[:usermodel_client_role_mapping_client_id] && self[:type] != 'oidc-usermodel-client-role-mapper' + raise Puppet::Error, 'usermodel_client_role_mapping_client_id is only valid for type=oidc-usermodel-client-role-mapper' + end end end diff --git a/spec/acceptance/7_client_protocol_mapper_spec.rb b/spec/acceptance/7_client_protocol_mapper_spec.rb index 61c2da1a..9e34578f 100644 --- a/spec/acceptance/7_client_protocol_mapper_spec.rb +++ b/spec/acceptance/7_client_protocol_mapper_spec.rb @@ -29,6 +29,12 @@ class { 'keycloak': } type => 'oidc-audience-mapper', included_client_audience => 'foo', } + keycloak_client_protocol_mapper { 'role mapper for test.foo.bar on test': + type => 'oidc-usermodel-client-role-mapper', + claim_name => 'permissions', + multivalued => true, + usermodel_client_role_mapping_client_id => 'test.foo.bar', + } PUPPET_PP apply_manifest(pp, catch_failures: true) @@ -77,6 +83,17 @@ class { 'keycloak': } expect(mapper['config']['included.client.audience']).to eq('foo') end end + + it 'has created protocol mapper type=oidc-usermodel-client-role-mapper' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar/protocol-mappers/models -r test' do + data = JSON.parse(stdout) + mapper = data.select { |d| d['name'] == 'role mapper' }[0] + expect(mapper['protocolMapper']).to eq('oidc-usermodel-client-role-mapper') + expect(mapper['config']['claim.name']).to eq('permissions') + expect(mapper['config']['multivalued']).to eq('true') + expect(mapper['config']['usermodel.clientRoleMapping.clientId']).to eq('test.foo.bar') + end + end end context 'when updates protocol_mapper' do @@ -108,6 +125,12 @@ class { 'keycloak': } included_client_audience => 'foo', id_token_claim => false, } + keycloak_client_protocol_mapper { 'role mapper for test.foo.bar on test': + type => 'oidc-usermodel-client-role-mapper', + claim_name => 'permissions', + multivalued => false, + usermodel_client_role_mapping_client_id => 'test.foo', + } PUPPET_PP apply_manifest(pp, catch_failures: true) @@ -156,5 +179,16 @@ class { 'keycloak': } expect(mapper['config']['included.client.audience']).to eq('foo') end end + + it 'has updated protocol mapper type=oidc-usermodel-client-role-mapper' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar/protocol-mappers/models -r test' do + data = JSON.parse(stdout) + mapper = data.select { |d| d['name'] == 'role mapper' }[0] + expect(mapper['protocolMapper']).to eq('oidc-usermodel-client-role-mapper') + expect(mapper['config']['claim.name']).to eq('permissions') + expect(mapper['config']['multivalued']).to eq('false') + expect(mapper['config']['usermodel.clientRoleMapping.clientId']).to eq('test.foo') + end + end end end diff --git a/spec/unit/puppet/type/keycloak_client_protocol_mapper_spec.rb b/spec/unit/puppet/type/keycloak_client_protocol_mapper_spec.rb index c939a1a7..42be10e8 100644 --- a/spec/unit/puppet/type/keycloak_client_protocol_mapper_spec.rb +++ b/spec/unit/puppet/type/keycloak_client_protocol_mapper_spec.rb @@ -302,6 +302,20 @@ }.to raise_error(%r{foo}) end + it 'accepts usermodel_client_role_mapping_client_id' do + config[:usermodel_client_role_mapping_client_id] = 'foo' + config[:type] = 'oidc-usermodel-client-role-mapper' + expect(resource[:usermodel_client_role_mapping_client_id]).to eq('foo') + end + + it 'errors when usermodel_client_role_mapping_client_id used with wrong type' do + config[:usermodel_client_role_mapping_client_id] = 'foo' + config[:type] = 'saml-role-list-mapper' + expect { + resource + }.to raise_error(Puppet::Error) + end + it 'accepts value for single' do config[:protocol] = 'saml' config[:type] = 'saml-role-list-mapper' @@ -335,6 +349,27 @@ }.to raise_error(%r{foo}) end + it 'accepts value for multivalued' do + config[:multivalued] = false + expect(resource[:multivalued]).to eq(:false) + end + + it 'accepts value for multivalued string' do + config[:multivalued] = 'false' + expect(resource[:multivalued]).to eq(:false) + end + + it 'has default for multivalued' do + expect(resource[:multivalued]).to be_nil + end + + it 'does not accept invalid value for multivalued' do + config[:single] = 'foo' + expect { + resource + }.to raise_error(%r{foo}) + end + it 'accepts script' do config[:protocol] = 'saml' config[:type] = 'script-foo.js'