diff --git a/lib/puppet/provider/keycloak_client_protocol_mapper/kcadm.rb b/lib/puppet/provider/keycloak_client_protocol_mapper/kcadm.rb index 174c9b5..d1ba8f5 100644 --- a/lib/puppet/provider/keycloak_client_protocol_mapper/kcadm.rb +++ b/lib/puppet/provider/keycloak_client_protocol_mapper/kcadm.rb @@ -1,223 +1,238 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'keycloak_api')) Puppet::Type.type(:keycloak_client_protocol_mapper).provide(:kcadm, parent: Puppet::Provider::KeycloakAPI) do desc '' mk_resource_methods def self.attribute_nameformat_map { uri: 'URI Reference', basic: 'Basic', unspecified: 'Unspecified', } end def self.get_attribute_nameformat(v) attribute_nameformat_map[v.to_sym] end def self.get_attribute_nameformat_reverse(v) attribute_nameformat_map.invert[v] end def self.instances protocol_mappers = [] realms.each do |realm| clients_output = kcadm('get', 'clients', realm) clients_data = JSON.parse(clients_output) clients_data.each do |client| client_id = client['id'] data = client['protocolMappers'] || [] data.each do |d| protocol_mapper = {} protocol_mapper[:ensure] = :present protocol_mapper[:id] = d['id'] protocol_mapper[:realm] = realm protocol_mapper[:client] = client_id protocol_mapper[:resource_name] = d['name'] protocol_mapper[:protocol] = d['protocol'] protocol_mapper[:name] = "#{protocol_mapper[:resource_name]} for #{protocol_mapper[:client]} on #{protocol_mapper[:realm]}" protocol_mapper[:type] = d['protocolMapper'] if protocol_mapper[:type] == 'oidc-usermodel-property-mapper' || protocol_mapper[:type] == 'saml-user-property-mapper' protocol_mapper[:user_attribute] = d['config']['user.attribute'] end if ['oidc-usermodel-property-mapper', 'oidc-group-membership-mapper'].include?(protocol_mapper[:type]) protocol_mapper[:claim_name] = d['config']['claim.name'] protocol_mapper[:json_type_label] = d['config']['jsonType.label'] end if protocol_mapper[:type] == 'oidc-group-membership-mapper' protocol_mapper[:full_path] = d['config']['full.path'] end if ['saml-user-property-mapper', 'saml-javascript-mapper'].include?(protocol_mapper[:type]) protocol_mapper[:friendly_name] = d['config']['friendly.name'] end if protocol_mapper[:type] == 'saml-javascript-mapper' protocol_mapper[:script] = d['config']['Script'] end if protocol_mapper[:protocol] == 'openid-connect' protocol_mapper[:id_token_claim] = d['config']['id.token.claim'] protocol_mapper[:access_token_claim] = d['config']['access.token.claim'] + end + unless ['oidc-audience-mapper'].include?(protocol_mapper[:type]) protocol_mapper[:userinfo_token_claim] = d['config']['userinfo.token.claim'] end + if protocol_mapper[:type] == 'oidc-audience-mapper' + protocol_mapper[:included_client_audience] = d['config']['included.client.audience'] + end if protocol_mapper[:protocol] == 'saml' protocol_mapper[:attribute_name] = d['config']['attribute.name'] protocol_mapper[:attribute_nameformat] = get_attribute_nameformat_reverse(d['config']['attribute.nameformat']) end if ['saml-role-list-mapper', 'saml-javascript-mapper'].include?(protocol_mapper[:type]) protocol_mapper[:single] = d['config']['single'].to_s.to_sym end protocol_mappers << new(protocol_mapper) end end end protocol_mappers end def self.prefetch(resources) protocol_mappers = instances resources.keys.each do |name| provider = protocol_mappers.find do |c| c.resource_name == resources[name][:resource_name] && c.realm == resources[name][:realm] && c.client == resources[name][:client] end next unless provider resources[name].provider = provider end end def create raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? raise(Puppet::Error, "Client is mandatory for #{resource.type} #{resource.name}") if resource[:client].nil? data = {} data[:id] = resource[:id] || name_uuid(resource[:name]) data[:name] = resource[:resource_name] data[:protocol] = resource[:protocol] data[:protocolMapper] = resource[:type] data[:config] = {} if resource[:type] == 'oidc-usermodel-property-mapper' || resource[:type] == 'saml-user-property-mapper' data[:config][:'user.attribute'] = resource[:user_attribute] if resource[:user_attribute] end if ['oidc-usermodel-property-mapper', 'oidc-group-membership-mapper'].include?(resource[:type]) data[:config][:'claim.name'] = resource[:claim_name] if resource[:claim_name] data[:config][:'jsonType.label'] = resource[:json_type_label] if resource[:json_type_label] end if resource[:type] == 'oidc-group-membership-mapper' data[:config][:'full.path'] = resource[:full_path] if resource[:full_path] end if ['saml-user-property-mapper', 'saml-javascript-mapper'].include?(resource[:type]) data[:config][:'friendly.name'] = resource[:friendly_name] if resource[:friendly_name] end if resource[:type] == 'saml-javascript-mapper' data[:config][:Script] = resource[:script] end if resource[:protocol] == 'openid-connect' data[:config][:'id.token.claim'] = resource[:id_token_claim] if resource[:id_token_claim] data[:config][:'access.token.claim'] = resource[:access_token_claim] if resource[:access_token_claim] + end + unless ['oidc-audience-mapper'].include?(resource[:type]) data[:config][:'userinfo.token.claim'] = resource[:userinfo_token_claim] if resource[:userinfo_token_claim] end + if resource[:type] == 'oidc-audience-mapper' + data[:config][:'included.client.audience'] = resource[:included_client_audience] if resource[:included_client_audience] + end if resource[:protocol] == 'saml' data[:config][:'attribute.name'] = resource[:attribute_name] if resource[:attribute_name] data[:config][:'attribute.nameformat'] = self.class.get_attribute_nameformat(resource[:attribute_nameformat]) if resource[:attribute_nameformat] end if ['saml-role-list-mapper', 'saml-javascript-mapper'].include?(resource[:type]) data[:config][:single] = resource[:single].to_s if resource[:single] end t = Tempfile.new('keycloak_protocol_mapper') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin kcadm('create', "clients/#{resource[:client]}/protocol-mappers/models", resource[:realm], t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm create protocol-mapper failed\nError message: #{e.message}" end @property_hash[:ensure] = :present end def destroy raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? raise(Puppet::Error, "Client is mandatory for #{resource.type} #{resource.name}") if resource[:client].nil? begin kcadm('delete', "clients/#{resource[:client]}/protocol-mappers/models/#{id}", resource[:realm]) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realm failed\nError message: #{e.message}" end @property_hash.clear end def exists? @property_hash[:ensure] == :present end def initialize(value = {}) super(value) @property_flush = {} end type_properties.each do |prop| define_method "#{prop}=".to_sym do |value| @property_flush[prop] = value end end def flush unless @property_flush.empty? raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? raise(Puppet::Error, "Client is mandatory for #{resource.type} #{resource.name}") if resource[:client].nil? data = {} data[:id] = id data[:name] = resource[:resource_name] data[:protocol] = resource[:protocol] data[:protocolMapper] = resource[:type] config = {} if resource[:type] == 'oidc-usermodel-property-mapper' || resource[:type] == 'saml-user-property-mapper' config[:'user.attribute'] = resource[:user_attribute] if resource[:user_attribute] end if ['oidc-usermodel-property-mapper', 'oidc-group-membership-mapper'].include?(resource[:type]) config[:'claim.name'] = resource[:claim_name] if resource[:claim_name] config[:'jsonType.label'] = resource[:json_type_label] if resource[:json_type_label] end if resource[:type] == 'oidc-group-membership-mapper' config[:'full.path'] = resource[:full_path] if resource[:full_path] end if ['saml-user-property-mapper', 'saml-javascript-mapper'].include?(resource[:type]) config[:'friendly.name'] = resource[:friendly_name] if resource[:friendly_name] end if resource[:type] == 'saml-javascript-mapper' config[:Script] = resource[:script] end if resource[:protocol] == 'openid-connect' config[:'id.token.claim'] = resource[:id_token_claim] if resource[:id_token_claim] config[:'access.token.claim'] = resource[:access_token_claim] if resource[:access_token_claim] + end + unless ['oidc-audience-mapper'].include?(resource[:type]) config[:'userinfo.token.claim'] = resource[:userinfo_token_claim] if resource[:userinfo_token_claim] end + if resource[:type] == 'oidc-audience-mapper' + config[:'included.client.audience'] = resource[:included_client_audience] if resource[:included_client_audience] + end if resource[:protocol] == 'saml' config[:'attribute.name'] = resource[:attribute_name] if resource[:attribute_name] config[:'attribute.nameformat'] = self.class.get_attribute_nameformat(resource[:attribute_nameformat]) if resource[:attribute_nameformat] end if ['saml-role-list-mapper', 'saml-javascript-mapper'].include?(resource[:type]) config[:single] = resource[:single].to_s if resource[:single] end data[:config] = config unless config.empty? t = Tempfile.new('keycloak_protocol_mapper') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin kcadm('update', "clients/#{resource[:client]}/protocol-mappers/models/#{id}", resource[:realm], t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update component failed\nError message: #{e.message}" end end # Collect the resources again once they've been changed (that way `puppet # resource` will show the correct values after changes have been made). @property_hash = resource.to_hash end end diff --git a/lib/puppet/provider/keycloak_protocol_mapper/kcadm.rb b/lib/puppet/provider/keycloak_protocol_mapper/kcadm.rb index 03e0c9c..398e92e 100644 --- a/lib/puppet/provider/keycloak_protocol_mapper/kcadm.rb +++ b/lib/puppet/provider/keycloak_protocol_mapper/kcadm.rb @@ -1,223 +1,238 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'keycloak_api')) Puppet::Type.type(:keycloak_protocol_mapper).provide(:kcadm, parent: Puppet::Provider::KeycloakAPI) do desc '' mk_resource_methods def self.attribute_nameformat_map { uri: 'URI Reference', basic: 'Basic', unspecified: 'Unspecified', } end def self.get_attribute_nameformat(v) attribute_nameformat_map[v.to_sym] end def self.get_attribute_nameformat_reverse(v) attribute_nameformat_map.invert[v] end def self.instances protocol_mappers = [] realms.each do |realm| client_scopes_output = kcadm('get', 'client-scopes', realm) client_scope_data = JSON.parse(client_scopes_output) client_scope_data.each do |client_scope| client_scope_id = client_scope['id'] data = client_scope['protocolMappers'] || [] data.each do |d| protocol_mapper = {} protocol_mapper[:ensure] = :present protocol_mapper[:id] = d['id'] protocol_mapper[:realm] = realm protocol_mapper[:client_scope] = client_scope_id protocol_mapper[:resource_name] = d['name'] protocol_mapper[:protocol] = d['protocol'] protocol_mapper[:name] = "#{protocol_mapper[:resource_name]} for #{protocol_mapper[:client_scope]} on #{protocol_mapper[:realm]}" protocol_mapper[:type] = d['protocolMapper'] if protocol_mapper[:type] == 'oidc-usermodel-property-mapper' || protocol_mapper[:type] == 'saml-user-property-mapper' protocol_mapper[:user_attribute] = d['config']['user.attribute'] end if ['oidc-usermodel-property-mapper', 'oidc-group-membership-mapper'].include?(protocol_mapper[:type]) protocol_mapper[:claim_name] = d['config']['claim.name'] protocol_mapper[:json_type_label] = d['config']['jsonType.label'] end if protocol_mapper[:type] == 'oidc-group-membership-mapper' protocol_mapper[:full_path] = d['config']['full.path'] end if ['saml-user-property-mapper', 'saml-javascript-mapper'].include?(protocol_mapper[:type]) protocol_mapper[:friendly_name] = d['config']['friendly.name'] end if protocol_mapper[:type] == 'saml-javascript-mapper' protocol_mapper[:script] = d['config']['Script'] end if protocol_mapper[:protocol] == 'openid-connect' protocol_mapper[:id_token_claim] = d['config']['id.token.claim'] protocol_mapper[:access_token_claim] = d['config']['access.token.claim'] + end + unless ['oidc-audience-mapper'].include?(protocol_mapper[:type]) protocol_mapper[:userinfo_token_claim] = d['config']['userinfo.token.claim'] end + if protocol_mapper[:type] == 'oidc-audience-mapper' + protocol_mapper[:included_client_audience] = d['config']['included.client.audience'] + end if protocol_mapper[:protocol] == 'saml' protocol_mapper[:attribute_name] = d['config']['attribute.name'] protocol_mapper[:attribute_nameformat] = get_attribute_nameformat_reverse(d['config']['attribute.nameformat']) end if ['saml-role-list-mapper', 'saml-javascript-mapper'].include?(protocol_mapper[:type]) protocol_mapper[:single] = d['config']['single'].to_s.to_sym end protocol_mappers << new(protocol_mapper) end end end protocol_mappers end def self.prefetch(resources) protocol_mappers = instances resources.keys.each do |name| provider = protocol_mappers.find do |c| c.resource_name == resources[name][:resource_name] && c.realm == resources[name][:realm] && c.client_scope == resources[name][:client_scope] end next unless provider resources[name].provider = provider end end def create raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? raise(Puppet::Error, "Client scope is mandatory for #{resource.type} #{resource.name}") if resource[:client_scope].nil? data = {} data[:id] = resource[:id] || name_uuid(resource[:name]) data[:name] = resource[:resource_name] data[:protocol] = resource[:protocol] data[:protocolMapper] = resource[:type] data[:config] = {} if resource[:type] == 'oidc-usermodel-property-mapper' || resource[:type] == 'saml-user-property-mapper' data[:config][:'user.attribute'] = resource[:user_attribute] if resource[:user_attribute] end if ['oidc-usermodel-property-mapper', 'oidc-group-membership-mapper'].include?(resource[:type]) data[:config][:'claim.name'] = resource[:claim_name] if resource[:claim_name] data[:config][:'jsonType.label'] = resource[:json_type_label] if resource[:json_type_label] end if resource[:type] == 'oidc-group-membership-mapper' data[:config][:'full.path'] = resource[:full_path] if resource[:full_path] end if ['saml-user-property-mapper', 'saml-javascript-mapper'].include?(resource[:type]) data[:config][:'friendly.name'] = resource[:friendly_name] if resource[:friendly_name] end if resource[:type] == 'saml-javascript-mapper' data[:config][:Script] = resource[:script] end if resource[:protocol] == 'openid-connect' data[:config][:'id.token.claim'] = resource[:id_token_claim] if resource[:id_token_claim] data[:config][:'access.token.claim'] = resource[:access_token_claim] if resource[:access_token_claim] + end + unless ['oidc-audience-mapper'].include?(resource[:type]) data[:config][:'userinfo.token.claim'] = resource[:userinfo_token_claim] if resource[:userinfo_token_claim] end + if resource[:type] == 'oidc-audience-mapper' + data[:config][:'included.client.audience'] = resource[:included_client_audience] if resource[:included_client_audience] + end if resource[:protocol] == 'saml' data[:config][:'attribute.name'] = resource[:attribute_name] if resource[:attribute_name] data[:config][:'attribute.nameformat'] = self.class.get_attribute_nameformat(resource[:attribute_nameformat]) if resource[:attribute_nameformat] end if ['saml-role-list-mapper', 'saml-javascript-mapper'].include?(resource[:type]) data[:config][:single] = resource[:single].to_s if resource[:single] end t = Tempfile.new('keycloak_protocol_mapper') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin kcadm('create', "client-scopes/#{resource[:client_scope]}/protocol-mappers/models", resource[:realm], t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm create protocol-mapper failed\nError message: #{e.message}" end @property_hash[:ensure] = :present end def destroy raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? raise(Puppet::Error, "Client scope is mandatory for #{resource.type} #{resource.name}") if resource[:client_scope].nil? begin kcadm('delete', "client-scopes/#{resource[:client_scope]}/protocol-mappers/models/#{id}", resource[:realm]) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realm failed\nError message: #{e.message}" end @property_hash.clear end def exists? @property_hash[:ensure] == :present end def initialize(value = {}) super(value) @property_flush = {} end type_properties.each do |prop| define_method "#{prop}=".to_sym do |value| @property_flush[prop] = value end end def flush unless @property_flush.empty? raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? raise(Puppet::Error, "Client scope is mandatory for #{resource.type} #{resource.name}") if resource[:client_scope].nil? data = {} data[:id] = @property_hash[:id] data[:name] = resource[:resource_name] data[:protocol] = resource[:protocol] data[:protocolMapper] = resource[:type] config = {} if resource[:type] == 'oidc-usermodel-property-mapper' || resource[:type] == 'saml-user-property-mapper' config[:'user.attribute'] = resource[:user_attribute] if resource[:user_attribute] end if ['oidc-usermodel-property-mapper', 'oidc-group-membership-mapper'].include?(resource[:type]) config[:'claim.name'] = resource[:claim_name] if resource[:claim_name] config[:'jsonType.label'] = resource[:json_type_label] if resource[:json_type_label] end if resource[:type] == 'oidc-group-membership-mapper' config[:'full.path'] = resource[:full_path] if resource[:full_path] end if ['saml-user-property-mapper', 'saml-javascript-mapper'].include?(resource[:type]) config[:'friendly.name'] = resource[:friendly_name] if resource[:friendly_name] end if resource[:type] == 'saml-javascript-mapper' config[:Script] = resource[:script] end if resource[:protocol] == 'openid-connect' config[:'id.token.claim'] = resource[:id_token_claim] if resource[:id_token_claim] config[:'access.token.claim'] = resource[:access_token_claim] if resource[:access_token_claim] + end + unless ['oidc-audience-mapper'].include?(resource[:type]) config[:'userinfo.token.claim'] = resource[:userinfo_token_claim] if resource[:userinfo_token_claim] end + if resource[:type] == 'oidc-audience-mapper' + config[:'included.client.audience'] = resource[:included_client_audience] if resource[:included_client_audience] + end if resource[:protocol] == 'saml' config[:'attribute.name'] = resource[:attribute_name] if resource[:attribute_name] config[:'attribute.nameformat'] = self.class.get_attribute_nameformat(resource[:attribute_nameformat]) if resource[:attribute_nameformat] end if ['saml-role-list-mapper', 'saml-javascript-mapper'].include?(resource[:type]) config[:single] = resource[:single].to_s if resource[:single] end data[:config] = config unless config.empty? t = Tempfile.new('keycloak_protocol_mapper') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin kcadm('update', "client-scopes/#{resource[:client_scope]}/protocol-mappers/models/#{id}", resource[:realm], t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update component failed\nError message: #{e.message}" end end # Collect the resources again once they've been changed (that way `puppet # resource` will show the correct values after changes have been made). @property_hash = resource.to_hash end end diff --git a/lib/puppet/type/keycloak_client_protocol_mapper.rb b/lib/puppet/type/keycloak_client_protocol_mapper.rb index 7c20f2d..3f6d4e2 100644 --- a/lib/puppet/type/keycloak_client_protocol_mapper.rb +++ b/lib/puppet/type/keycloak_client_protocol_mapper.rb @@ -1,259 +1,267 @@ require_relative '../provider/keycloak_api' require_relative '../../puppet_x/keycloak/type' require_relative '../../puppet_x/keycloak/array_property' Puppet::Type.newtype(:keycloak_client_protocol_mapper) do desc <<-DESC Manage Keycloak protocol mappers @example Add email protocol mapper to test.example.com client in realm test keycloak_client_protocol_mapper { "email for test.example.com on test": claim_name => 'email', user_attribute => 'email', } DESC extend PuppetX::Keycloak::Type add_autorequires ensurable newparam(:name, namevar: true) do desc 'The protocol mapper name' end newparam(:id) do desc 'Id.' end newparam(:resource_name, namevar: true) do desc 'The protocol mapper name. Defaults to `name`.' defaultto do @resource[:name] end end newparam(:client, namevar: true) do desc 'client' end newparam(:realm, namevar: true) do desc 'realm' end newproperty(:protocol) do desc 'protocol' defaultto('openid-connect') newvalues('openid-connect', 'saml') munge { |v| v } end newparam(:type) do desc <<-DESC protocolMapper. Default is `oidc-usermodel-property-mapper` for `protocol` `openid-connect` and `saml-user-property-mapper` for `protocol` `saml`. DESC newvalues( 'oidc-usermodel-property-mapper', 'oidc-full-name-mapper', 'oidc-group-membership-mapper', + 'oidc-audience-mapper', 'saml-user-property-mapper', 'saml-role-list-mapper', 'saml-javascript-mapper', ) defaultto do if @resource[:protocol] == 'openid-connect' 'oidc-usermodel-property-mapper' elsif @resource[:protocol] == 'saml' 'saml-user-property-mapper' else nil end end munge { |v| v } end newproperty(:user_attribute) do desc 'user.attribute. Default to `resource_name` for `type` `oidc-usermodel-property-mapper` or `saml-user-property-mapper`' defaultto do if @resource[:type] == 'oidc-usermodel-property-mapper' || @resource[:type] == 'saml-user-property-mapper' @resource[:resource_name] else nil end end end newproperty(:json_type_label) do desc 'json.type.label. Default to `String` for `type` `oidc-usermodel-property-mapper` and `oidc-group-membership-mapper`.' defaultto do if ['oidc-usermodel-property-mapper', 'oidc-group-membership-mapper'].include?(@resource[:type]) 'String' else nil end end end newproperty(:full_path, boolean: true) do desc 'full.path. Default to `false` for `type` `oidc-group-membership-mapper`.' newvalues(:true, :false) defaultto do if @resource[:type] == 'oidc-group-membership-mapper' :false else nil end end end newproperty(:friendly_name) do desc 'friendly.name. Default to `resource_name` for `type` `saml-user-property-mapper`.' defaultto do if @resource[:type] == 'saml-user-property-mapper' @resource[:resource_name] else nil end end end newproperty(:attribute_name) do desc 'attribute.name Default to `resource_name` for `type` `saml-user-property-mapper`.' defaultto do if @resource[:type] == 'saml-user-property-mapper' @resource[:resource_name] else nil end end end newproperty(:claim_name) do desc 'claim.name' end newproperty(:id_token_claim, boolean: true) do desc 'id.token.claim. Default to `true` for `protocol` `openid-connect`.' newvalues(:true, :false) defaultto do if @resource['protocol'] == 'openid-connect' :true else nil end end end newproperty(:access_token_claim, boolean: true) do desc 'access.token.claim. Default to `true` for `protocol` `openid-connect`.' newvalues(:true, :false) defaultto do if @resource['protocol'] == 'openid-connect' :true else nil end end end newproperty(:userinfo_token_claim, boolean: true) do - desc 'userinfo.token.claim. Default to `true` for `protocol` `openid-connect`.' + desc 'userinfo.token.claim. Default to `true` for `protocol` `openid-connect` except `type` of `oidc-audience-mapper`.' newvalues(:true, :false) defaultto do - if @resource['protocol'] == 'openid-connect' + if @resource['protocol'] == 'openid-connect' && @resource['type'] != 'oidc-audience-mapper' :true else nil end end end newproperty(:attribute_nameformat) do desc 'attribute.nameformat' validate do |v| unless [:basic, :uri, :unspecified].include?(v.downcase.to_sym) raise ArgumentError, 'attribute_nameformat must be one of basic, uri, or unspecified' end end munge do |v| v.downcase.to_sym end end newproperty(:single, boolean: true) do desc 'single. Default to `false` for `type` `saml-role-list-mapper`.' newvalues(:true, :false) defaultto do if ['saml-role-list-mapper', 'saml-javascript-mapper'].include?(@resource['type']) :false else nil end end end newproperty(:script) do desc <<-EOS Script, only valid for `type` of `saml-javascript-mapper`' Array values will be joined with newlines. Strings will be kept unchanged. EOS end + newproperty(:included_client_audience) do + desc 'included.client.audience Required for `type` of `oidc-audience-mapper`' + end + autorequire(:keycloak_client) do requires = [] catalog.resources.each do |resource| next unless resource.class.to_s == 'Puppet::Type::Keycloak_client' if resource[:client_id] == self[:client] requires << resource.name end end requires end def self.title_patterns [ [ %r{^((.+) for (\S+) on (\S+))$}, [ [:name], [:resource_name], [:client], [:realm], ], ], [ %r{(.*)}, [ [:name], ], ], ] end validate do - if self[:protocol] == 'openid-connect' && !['oidc-usermodel-property-mapper', 'oidc-full-name-mapper', 'oidc-group-membership-mapper'].include?(self[:type]) + if self[:protocol] == 'openid-connect' && !['oidc-usermodel-property-mapper', 'oidc-full-name-mapper', 'oidc-group-membership-mapper', 'oidc-audience-mapper'].include?(self[:type]) raise Puppet::Error, "type #{self[:type]} is not valid for protocol openid-connect" end if self[:protocol] == 'saml' && !['saml-user-property-mapper', 'saml-role-list-mapper', 'saml-javascript-mapper'].include?(self[:type]) raise Puppet::Error, "type #{self[:type]} is not valid for protocol saml" end if self[:friendly_name] && !['saml-user-property-mapper', 'saml-javascript-mapper'].include?(self[:type]) raise Puppet::Error, "friendly_name is not valid for type #{self[:type]}" end if self[:attribute_name] && self[:protocol] != 'saml' raise Puppet::Error, "attribute_name is not valid for type #{self[:type]}" end if self[:attribute_nameformat] && self[:protocol] != 'saml' raise Puppet::Error, "attribute_nameformat is not valid for protocol #{self[:protocol]}" end if self[:single] && !['saml-role-list-mapper', 'saml-javascript-mapper'].include?(self[:type]) raise Puppet::Error, "single is not valid for type #{self[:type]}" end if self[:type] == 'saml-javascript-mapper' && self[:script].nil? raise Puppet::Error, 'script is required for saml-javascript-mapper' end + if self[:type] == 'oidc-audience-mapper' && self[:included_client_audience].nil? + raise Puppet::Error, 'included_client_audience is required for oidc-audience-mapper' + end end end diff --git a/lib/puppet/type/keycloak_protocol_mapper.rb b/lib/puppet/type/keycloak_protocol_mapper.rb index 6cd9a41..d59f4f5 100644 --- a/lib/puppet/type/keycloak_protocol_mapper.rb +++ b/lib/puppet/type/keycloak_protocol_mapper.rb @@ -1,259 +1,267 @@ require_relative '../provider/keycloak_api' require_relative '../../puppet_x/keycloak/type' require_relative '../../puppet_x/keycloak/array_property' Puppet::Type.newtype(:keycloak_protocol_mapper) do desc <<-DESC Manage Keycloak client scope protocol mappers @example Add email protocol mapper to oidc-client client scope in realm test keycloak_protocol_mapper { "email for oidc-clients on test": claim_name => 'email', user_attribute => 'email', } DESC extend PuppetX::Keycloak::Type add_autorequires ensurable newparam(:name, namevar: true) do desc 'The protocol mapper name' end newparam(:id) do desc 'Id.' end newparam(:resource_name, namevar: true) do desc 'The protocol mapper name. Defaults to `name`.' defaultto do @resource[:name] end end newparam(:client_scope, namevar: true) do desc 'client scope' end newparam(:realm, namevar: true) do desc 'realm' end newproperty(:protocol) do desc 'protocol' defaultto('openid-connect') newvalues('openid-connect', 'saml') munge { |v| v } end newparam(:type) do desc <<-DESC protocolMapper. Default is `oidc-usermodel-property-mapper` for `protocol` `openid-connect` and `saml-user-property-mapper` for `protocol` `saml`. DESC newvalues( 'oidc-usermodel-property-mapper', 'oidc-full-name-mapper', 'oidc-group-membership-mapper', + 'oidc-audience-mapper', 'saml-user-property-mapper', 'saml-role-list-mapper', 'saml-javascript-mapper', ) defaultto do if @resource[:protocol] == 'openid-connect' 'oidc-usermodel-property-mapper' elsif @resource[:protocol] == 'saml' 'saml-user-property-mapper' else nil end end munge { |v| v } end newproperty(:user_attribute) do desc 'user.attribute. Default to `resource_name` for `type` `oidc-usermodel-property-mapper` or `saml-user-property-mapper`' defaultto do if @resource[:type] == 'oidc-usermodel-property-mapper' || @resource[:type] == 'saml-user-property-mapper' @resource[:resource_name] else nil end end end newproperty(:json_type_label) do desc 'json.type.label. Default to `String` for `type` `oidc-usermodel-property-mapper` and `oidc-group-membership-mapper`.' defaultto do if ['oidc-usermodel-property-mapper', 'oidc-group-membership-mapper'].include?(@resource[:type]) 'String' else nil end end end newproperty(:full_path, boolean: true) do desc 'full.path. Default to `false` for `type` `oidc-group-membership-mapper`.' newvalues(:true, :false) defaultto do if @resource[:type] == 'oidc-group-membership-mapper' :false else nil end end end newproperty(:friendly_name) do desc 'friendly.name. Default to `resource_name` for `type` `saml-user-property-mapper`.' defaultto do if @resource[:type] == 'saml-user-property-mapper' @resource[:resource_name] else nil end end end newproperty(:attribute_name) do desc 'attribute.name Default to `resource_name` for `type` `saml-user-property-mapper`.' defaultto do if @resource[:type] == 'saml-user-property-mapper' @resource[:resource_name] else nil end end end newproperty(:claim_name) do desc 'claim.name' end newproperty(:id_token_claim, boolean: true) do desc 'id.token.claim. Default to `true` for `protocol` `openid-connect`.' newvalues(:true, :false) defaultto do if @resource['protocol'] == 'openid-connect' :true else nil end end end newproperty(:access_token_claim, boolean: true) do desc 'access.token.claim. Default to `true` for `protocol` `openid-connect`.' newvalues(:true, :false) defaultto do if @resource['protocol'] == 'openid-connect' :true else nil end end end newproperty(:userinfo_token_claim, boolean: true) do - desc 'userinfo.token.claim. Default to `true` for `protocol` `openid-connect`.' + desc 'userinfo.token.claim. Default to `true` for `protocol` `openid-connect` except `type` of `oidc-audience-mapper`.' newvalues(:true, :false) defaultto do - if @resource['protocol'] == 'openid-connect' + if @resource['protocol'] == 'openid-connect' && @resource['type'] != 'oidc-audience-mapper' :true else nil end end end newproperty(:attribute_nameformat) do desc 'attribute.nameformat' validate do |v| unless [:basic, :uri, :unspecified].include?(v.downcase.to_sym) raise ArgumentError, 'attribute_nameformat must be one of basic, uri, or unspecified' end end munge do |v| v.downcase.to_sym end end newproperty(:single, boolean: true) do desc 'single. Default to `false` for `type` `saml-role-list-mapper` or `saml-javascript-mapper`.' newvalues(:true, :false) defaultto do if ['saml-role-list-mapper', 'saml-javascript-mapper'].include?(@resource['type']) :false else nil end end end newproperty(:script) do desc <<-EOS Script, only valid for `type` of `saml-javascript-mapper`' Array values will be joined with newlines. Strings will be kept unchanged. EOS end + newproperty(:included_client_audience) do + desc 'included.client.audience Required for `type` of `oidc-audience-mapper`' + end + autorequire(:keycloak_client_scope) do requires = [] catalog.resources.each do |resource| next unless resource.class.to_s == 'Puppet::Type::Keycloak_client_scope' if resource[:resource_name] == self[:client_scope] requires << resource.name end end requires end def self.title_patterns [ [ %r{^((.+) for (\S+) on (\S+))$}, [ [:name], [:resource_name], [:client_scope], [:realm], ], ], [ %r{(.*)}, [ [:name], ], ], ] end validate do - if self[:protocol] == 'openid-connect' && !['oidc-usermodel-property-mapper', 'oidc-full-name-mapper', 'oidc-group-membership-mapper'].include?(self[:type]) + if self[:protocol] == 'openid-connect' && !['oidc-usermodel-property-mapper', 'oidc-full-name-mapper', 'oidc-group-membership-mapper', 'oidc-audience-mapper'].include?(self[:type]) raise Puppet::Error, "type #{self[:type]} is not valid for protocol openid-connect" end if self[:protocol] == 'saml' && !['saml-user-property-mapper', 'saml-role-list-mapper', 'saml-javascript-mapper'].include?(self[:type]) raise Puppet::Error, "type #{self[:type]} is not valid for protocol saml" end if self[:friendly_name] && !['saml-user-property-mapper', 'saml-javascript-mapper'].include?(self[:type]) raise Puppet::Error, "friendly_name is not valid for type #{self[:type]}" end if self[:attribute_name] && self[:protocol] != 'saml' raise Puppet::Error, "attribute_name is not valid for type #{self[:type]}" end if self[:attribute_nameformat] && self[:protocol] != 'saml' raise Puppet::Error, "attribute_nameformat is not valid for protocol #{self[:protocol]}" end if self[:single] && !['saml-role-list-mapper', 'saml-javascript-mapper'].include?(self[:type]) raise Puppet::Error, "single is not valid for type #{self[:type]}" end if self[:type] == 'saml-javascript-mapper' && self[:script].nil? raise Puppet::Error, 'script is required for saml-javascript-mapper' end + if self[:type] == 'oidc-audience-mapper' && self[:included_client_audience].nil? + raise Puppet::Error, 'included_client_audience is required for oidc-audience-mapper' + end end end diff --git a/spec/acceptance/6_protocol_mapper_spec.rb b/spec/acceptance/6_protocol_mapper_spec.rb index 113304c..3e69162 100644 --- a/spec/acceptance/6_protocol_mapper_spec.rb +++ b/spec/acceptance/6_protocol_mapper_spec.rb @@ -1,219 +1,250 @@ require 'spec_helper_acceptance' describe 'keycloak_protocol_mapper type:', if: RSpec.configuration.keycloak_full do context 'creates protocol_mapper' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test': ensure => 'present' } keycloak_client_scope { 'oidc on test': ensure => 'present', } keycloak_protocol_mapper { "username for oidc on test": claim_name => 'preferred_username', user_attribute => 'username', } keycloak_protocol_mapper { "full name for oidc on test": type => 'oidc-full-name-mapper', userinfo_token_claim => false, } keycloak_protocol_mapper { "groups for oidc on test": type => 'oidc-group-membership-mapper', claim_name => 'groups', } + keycloak_protocol_mapper { "foo for oidc on test": + type => 'oidc-audience-mapper', + included_client_audience => 'foo', + } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has created a client scope' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/oidc -r test' do data = JSON.parse(stdout) expect(data['name']).to eq('oidc') expect(data['protocol']).to eq('openid-connect') end end it 'has created protocol mapper username' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/oidc/protocol-mappers/models -r test' do data = JSON.parse(stdout) mapper = data.select { |d| d['name'] == 'username' }[0] expect(mapper['config']['claim.name']).to eq('preferred_username') expect(mapper['config']['user.attribute']).to eq('username') expect(mapper['config']['userinfo.token.claim']).to eq('true') end end it 'has created protocol mapper full name' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/oidc/protocol-mappers/models -r test' do data = JSON.parse(stdout) mapper = data.select { |d| d['name'] == 'full name' }[0] expect(mapper['protocolMapper']).to eq('oidc-full-name-mapper') expect(mapper['config']['userinfo.token.claim']).to eq('false') end end it 'has created protocol mapper groups' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/oidc/protocol-mappers/models -r test' do data = JSON.parse(stdout) mapper = data.select { |d| d['name'] == 'groups' }[0] expect(mapper['protocolMapper']).to eq('oidc-group-membership-mapper') expect(mapper['config']['full.path']).to eq('false') expect(mapper['config']['id.token.claim']).to eq('true') expect(mapper['config']['access.token.claim']).to eq('true') expect(mapper['config']['userinfo.token.claim']).to eq('true') expect(mapper['config']['claim.name']).to eq('groups') end end + + it 'has created protocol mapper audience' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/oidc/protocol-mappers/models -r test' do + data = JSON.parse(stdout) + mapper = data.select { |d| d['name'] == 'foo' }[0] + expect(mapper['protocolMapper']).to eq('oidc-audience-mapper') + expect(mapper['config']['id.token.claim']).to eq('true') + expect(mapper['config']['access.token.claim']).to eq('true') + expect(mapper['config']['included.client.audience']).to eq('foo') + end + end end context 'updates protocol_mapper' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test': ensure => 'present' } keycloak_client_scope { 'oidc on test': ensure => 'present', } keycloak_protocol_mapper { "username for oidc on test": claim_name => 'preferred_username', user_attribute => 'username', userinfo_token_claim => false, } keycloak_protocol_mapper { "full name for oidc on test": type => 'oidc-full-name-mapper', userinfo_token_claim => true, } keycloak_protocol_mapper { "groups for oidc on test": type => 'oidc-group-membership-mapper', claim_name => 'groups', full_path => true, } + keycloak_protocol_mapper { "foo for oidc on test": + type => 'oidc-audience-mapper', + included_client_audience => 'foo', + id_token_claim => false, + } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has updated protocol mapper username' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/oidc/protocol-mappers/models -r test' do data = JSON.parse(stdout) mapper = data.select { |d| d['name'] == 'username' }[0] expect(mapper['config']['claim.name']).to eq('preferred_username') expect(mapper['config']['user.attribute']).to eq('username') expect(mapper['config']['userinfo.token.claim']).to eq('false') end end it 'has updated protocol mapper full name' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/oidc/protocol-mappers/models -r test' do data = JSON.parse(stdout) mapper = data.select { |d| d['name'] == 'full name' }[0] expect(mapper['protocolMapper']).to eq('oidc-full-name-mapper') expect(mapper['config']['userinfo.token.claim']).to eq('true') end end it 'has updated protocol mapper groups' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/oidc/protocol-mappers/models -r test' do data = JSON.parse(stdout) mapper = data.select { |d| d['name'] == 'groups' }[0] expect(mapper['protocolMapper']).to eq('oidc-group-membership-mapper') expect(mapper['config']['full.path']).to eq('true') expect(mapper['config']['id.token.claim']).to eq('true') expect(mapper['config']['access.token.claim']).to eq('true') expect(mapper['config']['userinfo.token.claim']).to eq('true') expect(mapper['config']['claim.name']).to eq('groups') end end + + it 'has updated protocol mapper audience' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/oidc/protocol-mappers/models -r test' do + data = JSON.parse(stdout) + mapper = data.select { |d| d['name'] == 'foo' }[0] + expect(mapper['protocolMapper']).to eq('oidc-audience-mapper') + expect(mapper['config']['id.token.claim']).to eq('false') + expect(mapper['config']['access.token.claim']).to eq('true') + expect(mapper['config']['included.client.audience']).to eq('foo') + end + end end context 'creates saml protocol_mapper' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test': ensure => 'present' } keycloak_client_scope { 'saml on test': ensure => 'present', protocol => 'saml', } keycloak_protocol_mapper { "email for saml on test": protocol => 'saml', type => 'saml-user-property-mapper', user_attribute => 'email', friendly_name => 'email', attribute_name => 'email', } keycloak_protocol_mapper { "firstName for saml on test": protocol => 'saml', type => 'saml-user-property-mapper', user_attribute => 'firstName', friendly_name => 'firstName', attribute_name => 'firstName', } keycloak_protocol_mapper { "displayName for saml on test": protocol => 'saml', type => 'saml-javascript-mapper', script => "var foo = 'bar';\\nfoo;", friendly_name => 'displayName', attribute_name => 'displayName', } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has created a client scope' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/saml -r test' do data = JSON.parse(stdout) expect(data['name']).to eq('saml') expect(data['protocol']).to eq('saml') end end it 'has created protocol mapper email' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/saml/protocol-mappers/models -r test' do data = JSON.parse(stdout) mapper = data.select { |d| d['name'] == 'email' }[0] expect(mapper['protocolMapper']).to eq('saml-user-property-mapper') expect(mapper['config']['attribute.name']).to eq('email') expect(mapper['config']['user.attribute']).to eq('email') expect(mapper['config']['friendly.name']).to eq('email') end end it 'has created protocol mapper firstName' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/saml/protocol-mappers/models -r test' do data = JSON.parse(stdout) mapper = data.select { |d| d['name'] == 'firstName' }[0] expect(mapper['protocolMapper']).to eq('saml-user-property-mapper') expect(mapper['config']['attribute.name']).to eq('firstName') expect(mapper['config']['user.attribute']).to eq('firstName') expect(mapper['config']['friendly.name']).to eq('firstName') end end it 'has created protocol mapper displayName' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get client-scopes/saml/protocol-mappers/models -r test' do data = JSON.parse(stdout) mapper = data.select { |d| d['name'] == 'displayName' }[0] expect(mapper['protocolMapper']).to eq('saml-javascript-mapper') expect(mapper['config']['attribute.name']).to eq('displayName') expect(mapper['config']['Script']).to match(%r{^var foo = 'bar';$}) expect(mapper['config']['Script']).to match(%r{^foo;$}) expect(mapper['config']['friendly.name']).to eq('displayName') end end end end diff --git a/spec/acceptance/7_client_protocol_mapper_spec.rb b/spec/acceptance/7_client_protocol_mapper_spec.rb index fb07dec..f428067 100644 --- a/spec/acceptance/7_client_protocol_mapper_spec.rb +++ b/spec/acceptance/7_client_protocol_mapper_spec.rb @@ -1,133 +1,164 @@ require 'spec_helper_acceptance' describe 'keycloak_client_protocol_mapper type:', if: RSpec.configuration.keycloak_full do context 'creates protocol_mapper' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test': ensure => 'present' } keycloak_client { 'test.foo.bar': realm => 'test', redirect_uris => ['https://test.foo.bar/test1'], secret => 'foobar', } keycloak_client_protocol_mapper { "username for test.foo.bar on test": claim_name => 'preferred_username', user_attribute => 'username', } keycloak_client_protocol_mapper { "full name for test.foo.bar on test": type => 'oidc-full-name-mapper', userinfo_token_claim => false, } keycloak_client_protocol_mapper { "groups for test.foo.bar on test": type => 'oidc-group-membership-mapper', claim_name => 'groups', } + keycloak_client_protocol_mapper { "foo for test.foo.bar on test": + type => 'oidc-audience-mapper', + included_client_audience => 'foo', + } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has created client protocol mapper username' 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'] == 'username' }[0] expect(mapper['config']['claim.name']).to eq('preferred_username') expect(mapper['config']['user.attribute']).to eq('username') expect(mapper['config']['userinfo.token.claim']).to eq('true') end end it 'has created protocol mapper full name' 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'] == 'full name' }[0] expect(mapper['protocolMapper']).to eq('oidc-full-name-mapper') expect(mapper['config']['userinfo.token.claim']).to eq('false') end end it 'has created protocol mapper groups' 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'] == 'groups' }[0] expect(mapper['protocolMapper']).to eq('oidc-group-membership-mapper') expect(mapper['config']['full.path']).to eq('false') expect(mapper['config']['id.token.claim']).to eq('true') expect(mapper['config']['access.token.claim']).to eq('true') expect(mapper['config']['userinfo.token.claim']).to eq('true') expect(mapper['config']['claim.name']).to eq('groups') end end + + it 'has created protocol mapper audience' 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'] == 'foo' }[0] + expect(mapper['protocolMapper']).to eq('oidc-audience-mapper') + expect(mapper['config']['id.token.claim']).to eq('true') + expect(mapper['config']['access.token.claim']).to eq('true') + expect(mapper['config']['included.client.audience']).to eq('foo') + end + end end context 'updates protocol_mapper' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test': ensure => 'present' } keycloak_client { 'test.foo.bar': realm => 'test', redirect_uris => ['https://test.foo.bar/test1'], secret => 'foobar', } keycloak_client_protocol_mapper { "username for test.foo.bar on test": claim_name => 'preferred_username', user_attribute => 'username', userinfo_token_claim => false, } keycloak_client_protocol_mapper { "full name for test.foo.bar on test": type => 'oidc-full-name-mapper', userinfo_token_claim => true, } keycloak_client_protocol_mapper { "groups for test.foo.bar on test": type => 'oidc-group-membership-mapper', claim_name => 'groups', full_path => true, } + keycloak_client_protocol_mapper { "foo for test.foo.bar on test": + type => 'oidc-audience-mapper', + included_client_audience => 'foo', + id_token_claim => false, + } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has updated protocol mapper username' 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'] == 'username' }[0] expect(mapper['config']['claim.name']).to eq('preferred_username') expect(mapper['config']['user.attribute']).to eq('username') expect(mapper['config']['userinfo.token.claim']).to eq('false') end end it 'has updated protocol mapper full name' 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'] == 'full name' }[0] expect(mapper['protocolMapper']).to eq('oidc-full-name-mapper') expect(mapper['config']['userinfo.token.claim']).to eq('true') end end it 'has updated protocol mapper groups' 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'] == 'groups' }[0] expect(mapper['protocolMapper']).to eq('oidc-group-membership-mapper') expect(mapper['config']['full.path']).to eq('true') expect(mapper['config']['id.token.claim']).to eq('true') expect(mapper['config']['access.token.claim']).to eq('true') expect(mapper['config']['userinfo.token.claim']).to eq('true') expect(mapper['config']['claim.name']).to eq('groups') end end + + it 'has updated protocol mapper audience' 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'] == 'foo' }[0] + expect(mapper['protocolMapper']).to eq('oidc-audience-mapper') + expect(mapper['config']['id.token.claim']).to eq('false') + expect(mapper['config']['access.token.claim']).to eq('true') + expect(mapper['config']['included.client.audience']).to eq('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 74f70c5..ebb22e2 100644 --- a/spec/unit/puppet/type/keycloak_client_protocol_mapper_spec.rb +++ b/spec/unit/puppet/type/keycloak_client_protocol_mapper_spec.rb @@ -1,386 +1,397 @@ require 'spec_helper' describe Puppet::Type.type(:keycloak_client_protocol_mapper) do let(:default_config) do { name: 'foo', realm: 'test', client: 'test.example.com', } end let(:config) do default_config end let(:resource) do described_class.new(config) end it 'adds to catalog without raising an error' do catalog = Puppet::Resource::Catalog.new expect { catalog.add_resource resource }.not_to raise_error end it 'has a name' do expect(resource[:name]).to eq('foo') end it 'has resource_name default to name' do expect(resource[:resource_name]).to eq('foo') end it 'does not have id default' do expect(resource[:id]).to be_nil end it 'has realm' do expect(resource[:realm]).to eq('test') end it 'handles componsite name' do component = described_class.new(name: 'foo for test.example.com on test') expect(component[:name]).to eq('foo for test.example.com on test') expect(component[:resource_name]).to eq('foo') expect(component[:client]).to eq('test.example.com') expect(component[:realm]).to eq('test') end it 'handles componsite name with space' do component = described_class.new(name: 'full name for test.example.com on test') expect(component[:name]).to eq('full name for test.example.com on test') expect(component[:resource_name]).to eq('full name') expect(component[:client]).to eq('test.example.com') expect(component[:realm]).to eq('test') end it 'defaults to protocol=openid-connect' do expect(resource[:protocol]).to eq('openid-connect') end it 'does not allow invalid protocol' do config[:protocol] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'defaults to type=oidc-usermodel-property-mapper' do expect(resource[:type]).to eq('oidc-usermodel-property-mapper') end it 'allows valid type' do config[:type] = 'oidc-full-name-mapper' expect(resource[:type]).to eq('oidc-full-name-mapper') end it 'allows valid saml type' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' expect(resource[:type]).to eq('saml-user-property-mapper') end it 'does not allow invalid type' do config[:type] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'has user_attribute be nil for full-name-ldap-mapper' do config[:type] = 'oidc-full-name-mapper' expect(resource[:user_attribute]).to be_nil end it 'has user_attribute default to name for oidc-usermodel-property-mapper' do config[:type] = 'oidc-usermodel-property-mapper' expect(resource[:user_attribute]).to eq('foo') end it 'has user_attribute default to name for saml-user-property-mapper' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' expect(resource[:user_attribute]).to eq('foo') end it 'has json_type_label be nil for full-name-ldap-mapper' do config[:type] = 'oidc-full-name-mapper' expect(resource[:json_type_label]).to be_nil end it 'has json_type_label default to String for oidc-usermodel-property-mapper' do config[:type] = 'oidc-usermodel-property-mapper' expect(resource[:json_type_label]).to eq('String') end it 'has friendly_name as nil' do expect(resource[:friendly_name]).to be_nil end it 'defaults friend_name for saml' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' expect(resource[:friendly_name]).to eq('foo') end it 'allows valid friendly_name' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' config[:friendly_name] = 'email' expect(resource[:friendly_name]).to eq('email') end it 'has attribute_name as nil' do expect(resource[:attribute_name]).to be_nil end it 'defaults attribute_name for saml' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' expect(resource[:attribute_name]).to eq('foo') end it 'allows valid attribute_name' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' config[:attribute_name] = 'email' expect(resource[:attribute_name]).to eq('email') end it 'defaults for id_token_claim' do expect(resource[:id_token_claim]).to eq(:true) end it 'does not default id_token_claim for saml' do config[:protocol] = 'saml' expect(resource[:id_token_claim]).to be_nil end it 'accepts true for id_token_claim' do config[:id_token_claim] = true expect(resource[:id_token_claim]).to eq(:true) end it 'accepts true for id_token_claim as string' do config[:id_token_claim] = 'true' expect(resource[:id_token_claim]).to eq(:true) end it 'accepts false for id_token_claim' do config[:id_token_claim] = false expect(resource[:id_token_claim]).to eq(:false) end it 'accepts false for id_token_claim as string' do config[:id_token_claim] = 'false' expect(resource[:id_token_claim]).to eq(:false) end it 'does not accept strings for id_token_claim' do config[:id_token_claim] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'defaults for access_token_claim' do expect(resource[:access_token_claim]).to eq(:true) end it 'does not default access_token_claim for saml' do config[:protocol] = 'saml' expect(resource[:access_token_claim]).to be_nil end it 'accepts true for access_token_claim' do config[:access_token_claim] = true expect(resource[:access_token_claim]).to eq(:true) config[:access_token_claim] = 'true' expect(resource[:access_token_claim]).to eq(:true) end it 'accepts false for access_token_claim' do config[:access_token_claim] = false expect(resource[:access_token_claim]).to eq(:false) config[:access_token_claim] = 'false' expect(resource[:access_token_claim]).to eq(:false) end it 'does not accept strings for access_token_claim' do config[:access_token_claim] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'defaults for userinfo_token_claim' do expect(resource[:userinfo_token_claim]).to eq(:true) end it 'does not default userinfo_token_claim for saml' do config[:protocol] = 'saml' expect(resource[:userinfo_token_claim]).to be_nil end it 'accepts true for userinfo_token_claim' do config[:userinfo_token_claim] = true expect(resource[:userinfo_token_claim]).to eq(:true) end it 'accepts true for userinfo_token_claim as string' do config[:userinfo_token_claim] = 'true' expect(resource[:userinfo_token_claim]).to eq(:true) end it 'accepts false for userinfo_token_claim' do config[:userinfo_token_claim] = false expect(resource[:userinfo_token_claim]).to eq(:false) end it 'accepts false for userinfo_token_claim as string' do config[:userinfo_token_claim] = 'false' expect(resource[:userinfo_token_claim]).to eq(:false) end it 'does not accept strings for userinfo_token_claim' do config[:userinfo_token_claim] = 'foo' expect { resource }.to raise_error(%r{foo}) end defaults = {} describe 'basic properties' do # Test basic properties [ :claim_name, ].each do |p| it "should accept a #{p}" do config[p] = 'foo' expect(resource[p]).to eq('foo') end next unless defaults[p] it "should have default for #{p}" do expect(resource[p]).to eq(defaults[p]) end end end describe 'full_path' do it 'defaults to nil for non groups' do expect(resource[:full_path]).to be_nil end it 'defaults to false for groups' do config[:type] = 'oidc-group-membership-mapper' expect(resource[:full_path]).to eq(:false) end end it 'accepts Basic for attribute_nameformat' do config[:protocol] = 'saml' config[:attribute_nameformat] = 'Basic' expect(resource[:attribute_nameformat]).to eq(:basic) end it 'accepts uri for attribute_nameformat' do config[:protocol] = 'saml' config[:attribute_nameformat] = 'uri' expect(resource[:attribute_nameformat]).to eq(:uri) end it 'does not accept invalid value for attribute_nameformat' do config[:protocol] = 'saml' config[:attribute_nameformat] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'accepts value for single' do config[:protocol] = 'saml' config[:type] = 'saml-role-list-mapper' config[:single] = false expect(resource[:single]).to eq(:false) end it 'accepts value for single string' do config[:protocol] = 'saml' config[:type] = 'saml-role-list-mapper' config[:single] = 'false' expect(resource[:single]).to eq(:false) end it 'has default for single' do expect(resource[:single]).to be_nil end it 'has default for single and saml-role-list-mapper' do config[:protocol] = 'saml' config[:type] = 'saml-role-list-mapper' expect(resource[:single]).to eq(:false) end it 'does not accept invalid value for single' do config[:protocol] = 'saml' config[:type] = 'saml-role-list-mapper' config[:single] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'accepts value for script' do config[:protocol] = 'saml' config[:type] = 'saml-javascript-mapper' config[:script] = 'foobar' expect(resource[:script]).to eq('foobar') end it 'accepts value with newline for script' do config[:protocol] = 'saml' config[:type] = 'saml-javascript-mapper' config[:script] = 'foobar\nbaz' expect(resource[:script]).to eq('foobar\nbaz') end + it 'accepts value for included_client_audience' do + config[:type] = 'oidc-audience-mapper' + config[:included_client_audience] = 'foo' + expect(resource[:included_client_audience]).to eq('foo') + end + + it 'requires included_client_audience for oidc-audience-mapper' do + config[:type] = 'oidc-audience-mapper' + expect { resource }.to raise_error(%r{included_client_audience}) + end + it 'autorequires keycloak_conn_validator' do keycloak_conn_validator = Puppet::Type.type(:keycloak_conn_validator).new(name: 'keycloak') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_conn_validator rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_conn_validator.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires kcadm-wrapper.sh' do file = Puppet::Type.type(:file).new(name: 'kcadm-wrapper.sh', path: '/opt/keycloak/bin/kcadm-wrapper.sh') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource file rel = resource.autorequire[0] expect(rel.source.ref).to eq(file.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires keycloak_realm' do keycloak_realm = Puppet::Type.type(:keycloak_realm).new(name: 'test') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_realm rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_realm.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires keycloak_client' do keycloak_client = Puppet::Type.type(:keycloak_client).new(name: 'test.example.com', realm: 'test') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_client rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_client.ref) expect(rel.target.ref).to eq(resource.ref) end end diff --git a/spec/unit/puppet/type/keycloak_protocol_mapper_spec.rb b/spec/unit/puppet/type/keycloak_protocol_mapper_spec.rb index e1ea397..e4e963e 100644 --- a/spec/unit/puppet/type/keycloak_protocol_mapper_spec.rb +++ b/spec/unit/puppet/type/keycloak_protocol_mapper_spec.rb @@ -1,386 +1,397 @@ require 'spec_helper' describe Puppet::Type.type(:keycloak_protocol_mapper) do let(:default_config) do { name: 'foo', realm: 'test', client_scope: 'oidc', } end let(:config) do default_config end let(:resource) do described_class.new(config) end it 'adds to catalog without raising an error' do catalog = Puppet::Resource::Catalog.new expect { catalog.add_resource resource }.not_to raise_error end it 'has a name' do expect(resource[:name]).to eq('foo') end it 'has resource_name default to name' do expect(resource[:resource_name]).to eq('foo') end it 'does not have id default' do expect(resource[:id]).to be_nil end it 'has realm' do expect(resource[:realm]).to eq('test') end it 'handles componsite name' do component = described_class.new(name: 'foo for oidc on test') expect(component[:name]).to eq('foo for oidc on test') expect(component[:resource_name]).to eq('foo') expect(component[:client_scope]).to eq('oidc') expect(component[:realm]).to eq('test') end it 'handles componsite name with space' do component = described_class.new(name: 'full name for oidc on test') expect(component[:name]).to eq('full name for oidc on test') expect(component[:resource_name]).to eq('full name') expect(component[:client_scope]).to eq('oidc') expect(component[:realm]).to eq('test') end it 'defaults to protocol=openid-connect' do expect(resource[:protocol]).to eq('openid-connect') end it 'does not allow invalid protocol' do config[:protocol] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'defaults to type=oidc-usermodel-property-mapper' do expect(resource[:type]).to eq('oidc-usermodel-property-mapper') end it 'allows valid type' do config[:type] = 'oidc-full-name-mapper' expect(resource[:type]).to eq('oidc-full-name-mapper') end it 'allows valid saml type' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' expect(resource[:type]).to eq('saml-user-property-mapper') end it 'does not allow invalid type' do config[:type] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'has user_attribute be nil for full-name-ldap-mapper' do config[:type] = 'oidc-full-name-mapper' expect(resource[:user_attribute]).to be_nil end it 'has user_attribute default to name for oidc-usermodel-property-mapper' do config[:type] = 'oidc-usermodel-property-mapper' expect(resource[:user_attribute]).to eq('foo') end it 'has user_attribute default to name for saml-user-property-mapper' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' expect(resource[:user_attribute]).to eq('foo') end it 'has json_type_label be nil for full-name-ldap-mapper' do config[:type] = 'oidc-full-name-mapper' expect(resource[:json_type_label]).to be_nil end it 'has json_type_label default to String for oidc-usermodel-property-mapper' do config[:type] = 'oidc-usermodel-property-mapper' expect(resource[:json_type_label]).to eq('String') end it 'has friendly_name as nil' do expect(resource[:friendly_name]).to be_nil end it 'defaults friend_name for saml' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' expect(resource[:friendly_name]).to eq('foo') end it 'allows valid friendly_name' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' config[:friendly_name] = 'email' expect(resource[:friendly_name]).to eq('email') end it 'has attribute_name as nil' do expect(resource[:attribute_name]).to be_nil end it 'defaults attribute_name for saml' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' expect(resource[:attribute_name]).to eq('foo') end it 'allows valid attribute_name' do config[:protocol] = 'saml' config[:type] = 'saml-user-property-mapper' config[:attribute_name] = 'email' expect(resource[:attribute_name]).to eq('email') end it 'defaults for id_token_claim' do expect(resource[:id_token_claim]).to eq(:true) end it 'does not default id_token_claim for saml' do config[:protocol] = 'saml' expect(resource[:id_token_claim]).to be_nil end it 'accepts true for id_token_claim' do config[:id_token_claim] = true expect(resource[:id_token_claim]).to eq(:true) end it 'accepts true for id_token_claim as string' do config[:id_token_claim] = 'true' expect(resource[:id_token_claim]).to eq(:true) end it 'accepts false for id_token_claim' do config[:id_token_claim] = false expect(resource[:id_token_claim]).to eq(:false) end it 'accepts false for id_token_claim as string' do config[:id_token_claim] = 'false' expect(resource[:id_token_claim]).to eq(:false) end it 'does not accept strings for id_token_claim' do config[:id_token_claim] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'defaults for access_token_claim' do expect(resource[:access_token_claim]).to eq(:true) end it 'does not default access_token_claim for saml' do config[:protocol] = 'saml' expect(resource[:access_token_claim]).to be_nil end it 'accepts true for access_token_claim' do config[:access_token_claim] = true expect(resource[:access_token_claim]).to eq(:true) config[:access_token_claim] = 'true' expect(resource[:access_token_claim]).to eq(:true) end it 'accepts false for access_token_claim' do config[:access_token_claim] = false expect(resource[:access_token_claim]).to eq(:false) config[:access_token_claim] = 'false' expect(resource[:access_token_claim]).to eq(:false) end it 'does not accept strings for access_token_claim' do config[:access_token_claim] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'defaults for userinfo_token_claim' do expect(resource[:userinfo_token_claim]).to eq(:true) end it 'does not default userinfo_token_claim for saml' do config[:protocol] = 'saml' expect(resource[:userinfo_token_claim]).to be_nil end it 'accepts true for userinfo_token_claim' do config[:userinfo_token_claim] = true expect(resource[:userinfo_token_claim]).to eq(:true) end it 'accepts true for userinfo_token_claim as string' do config[:userinfo_token_claim] = 'true' expect(resource[:userinfo_token_claim]).to eq(:true) end it 'accepts false for userinfo_token_claim' do config[:userinfo_token_claim] = false expect(resource[:userinfo_token_claim]).to eq(:false) end it 'accepts false for userinfo_token_claim as string' do config[:userinfo_token_claim] = 'false' expect(resource[:userinfo_token_claim]).to eq(:false) end it 'does not accept strings for userinfo_token_claim' do config[:userinfo_token_claim] = 'foo' expect { resource }.to raise_error(%r{foo}) end defaults = {} describe 'basic properties' do # Test basic properties [ :claim_name, ].each do |p| it "should accept a #{p}" do config[p] = 'foo' expect(resource[p]).to eq('foo') end next unless defaults[p] it "should have default for #{p}" do expect(resource[p]).to eq(defaults[p]) end end end describe 'full_path' do it 'defaults to nil for non groups' do expect(resource[:full_path]).to be_nil end it 'defaults to false for groups' do config[:type] = 'oidc-group-membership-mapper' expect(resource[:full_path]).to eq(:false) end end it 'accepts Basic for attribute_nameformat' do config[:protocol] = 'saml' config[:attribute_nameformat] = 'Basic' expect(resource[:attribute_nameformat]).to eq(:basic) end it 'accepts uri for attribute_nameformat' do config[:protocol] = 'saml' config[:attribute_nameformat] = 'uri' expect(resource[:attribute_nameformat]).to eq(:uri) end it 'does not accept invalid value for attribute_nameformat' do config[:protocol] = 'saml' config[:attribute_nameformat] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'accepts value for single' do config[:protocol] = 'saml' config[:type] = 'saml-role-list-mapper' config[:single] = false expect(resource[:single]).to eq(:false) end it 'accepts value for single string' do config[:protocol] = 'saml' config[:type] = 'saml-role-list-mapper' config[:single] = 'false' expect(resource[:single]).to eq(:false) end it 'has default for single' do expect(resource[:single]).to be_nil end it 'has default for single and saml-role-list-mapper' do config[:protocol] = 'saml' config[:type] = 'saml-role-list-mapper' expect(resource[:single]).to eq(:false) end it 'does not accept invalid value for single' do config[:protocol] = 'saml' config[:type] = 'saml-role-list-mapper' config[:single] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'accepts value for script' do config[:protocol] = 'saml' config[:type] = 'saml-javascript-mapper' config[:script] = 'foobar' expect(resource[:script]).to eq('foobar') end it 'accepts value with newline for script' do config[:protocol] = 'saml' config[:type] = 'saml-javascript-mapper' config[:script] = 'foobar\nbaz' expect(resource[:script]).to eq('foobar\nbaz') end + it 'accepts value for included_client_audience' do + config[:type] = 'oidc-audience-mapper' + config[:included_client_audience] = 'foo' + expect(resource[:included_client_audience]).to eq('foo') + end + + it 'requires included_client_audience for oidc-audience-mapper' do + config[:type] = 'oidc-audience-mapper' + expect { resource }.to raise_error(%r{included_client_audience}) + end + it 'autorequires keycloak_conn_validator' do keycloak_conn_validator = Puppet::Type.type(:keycloak_conn_validator).new(name: 'keycloak') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_conn_validator rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_conn_validator.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires kcadm-wrapper.sh' do file = Puppet::Type.type(:file).new(name: 'kcadm-wrapper.sh', path: '/opt/keycloak/bin/kcadm-wrapper.sh') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource file rel = resource.autorequire[0] expect(rel.source.ref).to eq(file.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires keycloak_realm' do keycloak_realm = Puppet::Type.type(:keycloak_realm).new(name: 'test') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_realm rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_realm.ref) expect(rel.target.ref).to eq(resource.ref) end it 'autorequires keycloak_client_scope' do keycloak_client_scope = Puppet::Type.type(:keycloak_client_scope).new(name: 'oidc', realm: 'test') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_client_scope rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_client_scope.ref) expect(rel.target.ref).to eq(resource.ref) end end