diff --git a/lib/puppet/type/keycloak_identity_provider.rb b/lib/puppet/type/keycloak_identity_provider.rb index 3a5879a..42a82f1 100644 --- a/lib/puppet/type/keycloak_identity_provider.rb +++ b/lib/puppet/type/keycloak_identity_provider.rb @@ -1,269 +1,276 @@ require_relative '../../puppet_x/keycloak/type' require_relative '../../puppet_x/keycloak/array_property' Puppet::Type.newtype(:keycloak_identity_provider) do desc <<-DESC Manage Keycloak identity providers @example Add CILogon identity provider to test realm keycloak_identity_provider { 'cilogon on test': ensure => 'present', display_name => 'CILogon', provider_id => 'oidc', first_broker_login_flow_alias => 'browser', client_id => 'cilogon:/client_id/foobar', client_secret => 'supersecret', user_info_url => 'https://cilogon.org/oauth2/userinfo', token_url => 'https://cilogon.org/oauth2/token', authorization_url => 'https://cilogon.org/authorize', } DESC extend PuppetX::Keycloak::Type add_autorequires ensurable newparam(:name, namevar: true) do desc 'The identity provider name' end newparam(:alias, namevar: true) do desc 'The identity provider name. Defaults to `name`.' defaultto do @resource[:name] end end newparam(:internal_id) do desc 'internalId. Defaults to "`alias`-`realm`"' defaultto do "#{@resource[:alias]}-#{@resource[:realm]}" end end newparam(:realm, namevar: true) do desc 'realm' end newproperty(:display_name) do desc 'displayName' end newparam(:provider_id) do desc 'providerId' newvalues('oidc') defaultto 'oidc' munge { |v| v } end newproperty(:enabled, boolean: true) do desc 'enabled' newvalues(:true, :false) defaultto :true end newproperty(:update_profile_first_login_mode) do desc 'updateProfileFirstLoginMode' defaultto 'on' newvalues('on', 'off') munge { |v| v } end newproperty(:trust_email, boolean: true) do desc 'trustEmail' newvalues(:true, :false) defaultto :false end newproperty(:store_token, boolean: true) do desc 'storeToken' newvalues(:true, :false) defaultto :false end newproperty(:add_read_token_role_on_create, boolean: true) do desc 'addReadTokenRoleOnCreate' newvalues(:true, :false) defaultto :false end newproperty(:authenticate_by_default, boolean: true) do desc 'authenticateByDefault' newvalues(:true, :false) defaultto :false end newproperty(:link_only, boolean: true) do desc 'linkOnly' newvalues(:true, :false) defaultto :false end newproperty(:first_broker_login_flow_alias) do desc 'firstBrokerLoginFlowAlias' defaultto 'first broker login' munge { |v| v } end newproperty(:post_broker_login_flow_alias) do desc 'postBrokerLoginFlowAlias' munge { |v| v } end # BEGIN: oidc newproperty(:hide_on_login_page, boolean: true) do desc 'hideOnLoginPage' newvalues(:true, :false) defaultto :false end newproperty(:user_info_url) do desc 'userInfoUrl' munge { |v| v } end newproperty(:validate_signature, boolean: true) do desc 'validateSignature' newvalues(:true, :false) defaultto :false end newproperty(:client_id) do desc 'clientId' end newproperty(:client_secret) do desc 'clientSecret' def insync?(is) if is =~ %r{^[\*]+$} Puppet.warning("Parameter 'client_secret' is set and Puppet has no way to check current value") true else false end end def change_to_s(currentvalue, _newvalue) if currentvalue == :absent 'created client_secret' else 'changed client_secret' end end def is_to_s(_currentvalue) # rubocop:disable Style/PredicateName '[old client_secret redacted]' end def should_to_s(_newvalue) '[new client_secret redacted]' end end + newproperty(:client_auth_method) do + desc 'clientAuthMethod' + newvalues('client_secret_post', 'client_secret_basic', 'client_secret_jwt', 'private_key_jwt') + defaultto('client_secret_post') + munge { |v| v.to_s } + end + newproperty(:token_url) do desc 'tokenUrl' end newproperty(:ui_locales, boolean: true) do desc 'uiLocales' newvalues(:true, :false) defaultto :false end newproperty(:backchannel_supported, boolean: true) do desc 'backchannelSupported' newvalues(:true, :false) defaultto :false end newproperty(:use_jwks_url, boolean: true) do desc 'useJwksUrl' newvalues(:true, :false) defaultto :true end newproperty(:login_hint, boolean: true) do desc 'loginHint' newvalues(:true, :false) defaultto :false end newproperty(:authorization_url) do desc 'authorizationUrl' end newproperty(:disable_user_info, boolean: true) do desc 'disableUserInfo' newvalues(:true, :false) defaultto :false end newproperty(:logout_url) do desc 'logoutUrl' end newproperty(:issuer) do desc 'issuer' end newproperty(:default_scope) do desc 'default_scope' end newproperty(:prompt) do desc 'prompt' newvalues('none', 'consent', 'login', 'select_account') munge { |v| v } end newproperty(:allowed_clock_skew) do desc 'allowedClockSkew' end newproperty(:forward_parameters) do desc 'forwardParameters' end # END: oidc def self.title_patterns [ [ %r{^((\S+) on (\S+))$}, [ [:name], [:alias], [:realm], ], ], [ %r{(.*)}, [ [:name], ], ], ] end validate do if self[:realm].nil? raise Puppet::Error, 'realm is required' end if self[:ensure].to_s == 'present' && self[:provider_id] == 'oidc' if self[:authorization_url].nil? raise Puppet::Error, 'authorization_url is required' end if self[:token_url].nil? raise Puppet::Error, 'token_url is required' end if self[:client_id].nil? raise Puppet::Error, 'client_id is required' end if self[:client_secret].nil? raise Puppet::Error, 'client_secret is required' end end end end diff --git a/spec/unit/puppet/type/keycloak_identity_provider_spec.rb b/spec/unit/puppet/type/keycloak_identity_provider_spec.rb index 33a38d9..afcef15 100644 --- a/spec/unit/puppet/type/keycloak_identity_provider_spec.rb +++ b/spec/unit/puppet/type/keycloak_identity_provider_spec.rb @@ -1,249 +1,265 @@ require 'spec_helper' describe Puppet::Type.type(:keycloak_identity_provider) do let(:default_config) do { name: 'foo', realm: 'test', authorization_url: 'http://authorization', token_url: 'http://token', client_id: 'foobar', client_secret: 'secret', } 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 alias default to name' do expect(resource[:alias]).to eq('foo') end it 'has internal_id default to resource_name-realm' do expect(resource[:internal_id]).to eq('foo-test') end it 'has realm' do expect(resource[:realm]).to eq('test') end it 'handles componsite name' do component = described_class.new(name: 'foo on test') expect(component[:name]).to eq('foo on test') expect(component[:alias]).to eq('foo') expect(component[:realm]).to eq('test') end it 'defaults to provider_id=oidc' do expect(resource[:provider_id]).to eq('oidc') end it 'does not allow invalid provider_id' do config[:provider_id] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'defaults to update_profile_first_login_mode=on' do expect(resource[:update_profile_first_login_mode]).to eq('on') end it 'does not allow invalid update_profile_first_login_mode' do config[:update_profile_first_login_mode] = 'foo' expect { resource }.to raise_error(%r{foo}) end it 'defaults to prompt undefined' do expect(resource[:prompt]).to be_nil end it 'does not allow invalid prompt' do config[:prompt] = 'foo' expect { resource }.to raise_error(%r{foo}) end defaults = { enabled: :true, trust_email: :false, store_token: :false, add_read_token_role_on_create: :false, authenticate_by_default: :false, link_only: :false, first_broker_login_flow_alias: 'first broker login', hide_on_login_page: :false, validate_signature: :false, ui_locales: :false, backchannel_supported: :false, use_jwks_url: :true, login_hint: :false, disable_user_info: :false, } describe 'basic properties' do # Test basic properties [ :display_name, :first_broker_login_flow_alias, :post_broker_login_flow_alias, :user_info_url, :client_id, :token_url, :authorization_url, :logout_url, :issuer, :default_scope, :allowed_clock_skew, :forward_parameters, ].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 'boolean properties' do # Test boolean properties [ :enabled, :trust_email, :store_token, :add_read_token_role_on_create, :authenticate_by_default, :link_only, :hide_on_login_page, :validate_signature, :ui_locales, :backchannel_supported, :use_jwks_url, :login_hint, :disable_user_info, ].each do |p| it "should accept true for #{p}" do config[p] = true expect(resource[p]).to eq(:true) config[p] = 'true' expect(resource[p]).to eq(:true) end it "should accept false for #{p}" do config[p] = false expect(resource[p]).to eq(:false) config[p] = 'false' expect(resource[p]).to eq(:false) end it "should not accept strings for #{p}" do config[p] = 'foo' expect { resource }.to raise_error(%r{foo}) end next unless defaults[p] it "should have default for #{p}" do expect(resource[p]).to eq(defaults[p]) end end end describe 'array properties' do # Array properties [ ].each do |p| it 'accepts array' do config[p] = ['foo', 'bar'] expect(resource[p]).to eq(['foo', 'bar']) end next unless defaults[p] it "should have default for #{p}" do expect(resource[p]).to eq(defaults[p]) end end end + describe 'client_auth_method' do + [ + 'client_secret_post', 'client_secret_basic', 'client_secret_jwt', 'private_key_jwt' + ].each do |v| + it "accepts #{v}" do + config[:client_auth_method] = v + expect(resource[:client_auth_method]).to eq(v) + end + end + + it 'does not accept invalid values' do + config[:client_auth_method] = 'foo' + expect { resource }.to raise_error(%r{Invalid}) + end + 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 'requires realm' do config[:ensure] = :present config[:provider_id] = 'oidc' config.delete(:realm) expect { resource }.to raise_error(Puppet::Error, %r{realm is required}) end it 'requires authorization_url' do config[:ensure] = :present config[:provider_id] = 'oidc' config.delete(:authorization_url) expect { resource }.to raise_error(Puppet::Error, %r{authorization_url is required}) end it 'requires token_url' do config[:ensure] = :present config[:provider_id] = 'oidc' config.delete(:token_url) expect { resource }.to raise_error(Puppet::Error, %r{token_url is required}) end it 'requires client_id' do config[:ensure] = :present config[:provider_id] = 'oidc' config.delete(:client_id) expect { resource }.to raise_error(Puppet::Error, %r{client_id is required}) end it 'requires client_secret' do config[:ensure] = :present config[:provider_id] = 'oidc' config.delete(:client_secret) expect { resource }.to raise_error(Puppet::Error, %r{client_secret is required}) end end