diff --git a/lib/puppet/type/keycloak_client.rb b/lib/puppet/type/keycloak_client.rb index 1156e2f..bfc56fa 100644 --- a/lib/puppet/type/keycloak_client.rb +++ b/lib/puppet/type/keycloak_client.rb @@ -1,196 +1,204 @@ require_relative '../../puppet_x/keycloak/type' require_relative '../../puppet_x/keycloak/array_property' Puppet::Type.newtype(:keycloak_client) do desc <<-DESC Manage Keycloak clients @example Add a OpenID Connect client keycloak_client { 'www.example.com': ensure => 'present', realm => 'test', redirect_uris => [ "https://www.example.com/oidc", "https://www.example.com", ], default_client_scopes => ['profile','email'], secret => 'supersecret', } DESC extend PuppetX::Keycloak::Type add_autorequires ensurable newparam(:name, namevar: true) do desc 'The client name' end newparam(:client_id, namevar: true) do desc 'clientId. Defaults to `name`.' defaultto do @resource[:name] end end newparam(:id) do desc 'Id. Defaults to `client_id`' defaultto do @resource[:client_id] end end newparam(:realm, namevar: true) do desc 'realm' end newparam(:secret) do desc 'secret' def change_to_s(currentvalue, _newvalue) if currentvalue == :absent 'created secret' else 'changed secret' end end def is_to_s(_currentvalue) # rubocop:disable Style/PredicateName '[old secret redacted]' end def should_to_s(_newvalue) '[new secret redacted]' end end newproperty(:protocol) do desc 'protocol' defaultto('openid-connect') newvalues('openid-connect', 'saml') munge { |v| v } end newproperty(:client_authenticator_type) do desc 'clientAuthenticatorType' defaultto 'client-secret' end newproperty(:default_client_scopes, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'defaultClientScopes' defaultto [] end newproperty(:optional_client_scopes, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'optionalClientScopes' defaultto [] end newproperty(:full_scope_allowed, boolean: true) do desc 'fullScopeAllowed' newvalues(:true, :false) defaultto(:true) end newproperty(:enabled, boolean: true) do desc 'enabled' newvalues(:true, :false) defaultto :true end newproperty(:standard_flow_enabled, boolean: true) do desc 'standardFlowEnabled' newvalues(:true, :false) defaultto :true end newproperty(:implicit_flow_enabled, boolean: true) do desc 'implicitFlowEnabled' newvalues(:true, :false) defaultto :false end newproperty(:direct_access_grants_enabled, boolean: true) do desc 'enabled' newvalues(:true, :false) defaultto :true end newproperty(:service_accounts_enabled, boolean: true) do desc 'serviceAccountsEnabled' newvalues(:true, :false) defaultto :false end newproperty(:public_client, boolean: true) do desc 'enabled' newvalues(:true, :false) defaultto :false end + newproperty(:root_url) do + desc 'rootUrl' + end + newproperty(:redirect_uris, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'redirectUris' defaultto [] end + newproperty(:base_url) do + desc 'baseUrl' + end + newproperty(:web_origins, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'webOrigins' defaultto [] end newproperty(:login_theme) do desc 'login_theme' defaultto 'absent' end newproperty(:access_token_lifespan) do desc 'access.token.lifespan' end autorequire(:keycloak_client_scope) do requires = [] catalog.resources.each do |resource| next unless resource.class.to_s == 'Puppet::Type::Keycloak_client_scope' if self[:default_client_scopes].include?(resource[:resource_name]) requires << resource.name end if self[:optional_client_scopes].include?(resource[:resource_name]) requires << resource.name end end requires end autorequire(:keycloak_protocol_mapper) do requires = [] catalog.resources.each do |resource| next unless resource.class.to_s == 'Puppet::Type::Keycloak_protocol_mapper' if self[:default_client_scopes].include?(resource[:client_scope]) requires << resource.name end if self[:optional_client_scopes].include?(resource[:client_scope]) requires << resource.name end end requires end def self.title_patterns [ [ %r{^((\S+) on (\S+))$}, [ [:name], [:client_id], [:realm], ], ], [ %r{(.*)}, [ [:name], ], ], ] end end diff --git a/spec/acceptance/5_client_spec.rb b/spec/acceptance/5_client_spec.rb index 6fa3c79..a40ed73 100644 --- a/spec/acceptance/5_client_spec.rb +++ b/spec/acceptance/5_client_spec.rb @@ -1,82 +1,86 @@ require 'spec_helper_acceptance' describe 'keycloak_client define:', if: RSpec.configuration.keycloak_full do context 'creates client' 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', + root_url => 'https://test.foo.bar', redirect_uris => ['https://test.foo.bar/test1'], default_client_scopes => ['address'], secret => 'foobar', login_theme => 'keycloak', } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has created a client' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar -r test' do data = JSON.parse(stdout) expect(data['id']).to eq('test.foo.bar') expect(data['clientId']).to eq('test.foo.bar') expect(data['defaultClientScopes']).to eq(['address']) + expect(data['rootUrl']).to eq('https://test.foo.bar') expect(data['redirectUris']).to eq(['https://test.foo.bar/test1']) expect(data['attributes']['login_theme']).to eq('keycloak') end end it 'has set the client secret' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar/client-secret -r test' do data = JSON.parse(stdout) expect(data['value']).to eq('foobar') end end end context 'updates client' 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', + root_url => 'https://test.foo.bar/test', redirect_uris => ['https://test.foo.bar/test2'], default_client_scopes => ['profile', 'email'], secret => 'foobar', } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has updated a client' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar -r test' do data = JSON.parse(stdout) expect(data['id']).to eq('test.foo.bar') expect(data['clientId']).to eq('test.foo.bar') expect(data['defaultClientScopes']).to eq(['profile', 'email']) + expect(data['rootUrl']).to eq('https://test.foo.bar/test') expect(data['redirectUris']).to eq(['https://test.foo.bar/test2']) expect(data['attributes']['login_theme']).to be_nil end end it 'has set the same client secret' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get clients/test.foo.bar/client-secret -r test' do data = JSON.parse(stdout) expect(data['value']).to eq('foobar') end end end end diff --git a/spec/unit/puppet/type/keycloak_client_spec.rb b/spec/unit/puppet/type/keycloak_client_spec.rb index 4335445..d72cfff 100644 --- a/spec/unit/puppet/type/keycloak_client_spec.rb +++ b/spec/unit/puppet/type/keycloak_client_spec.rb @@ -1,204 +1,206 @@ require 'spec_helper' describe Puppet::Type.type(:keycloak_client) do let(:default_config) do { name: 'foo', realm: 'test', } 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 client_id default to name' do expect(resource[:client_id]).to eq('foo') end it 'has id default to name' do expect(resource[:id]).to eq('foo') 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[:client_id]).to eq('foo') expect(component[:realm]).to eq('test') end it 'defaults to client_authenticator_type=client-secret' do expect(resource[:client_authenticator_type]).to eq('client-secret') 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 defaults = { enabled: :true, standard_flow_enabled: :true, implicit_flow_enabled: :false, direct_access_grants_enabled: :true, service_accounts_enabled: :false, public_client: :false, full_scope_allowed: :true, default_client_scopes: [], optional_client_scopes: [], redirect_uris: [], web_origins: [], login_theme: 'absent', } describe 'basic properties' do # Test basic properties [ :secret, :login_theme, + :root_url, + :base_url, ].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, :standard_flow_enabled, :implicit_flow_enabled, :direct_access_grants_enabled, :service_accounts_enabled, :public_client, :full_scope_allowed, ].each do |p| it "should accept true for #{p}" do config[p] = true expect(resource[p]).to eq(:true) end it "should accept true for #{p} string" do 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) end it "should accept false for #{p} string" do 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 [ :default_client_scopes, :optional_client_scopes, :redirect_uris, :web_origins, ].each do |p| it "should accept array for #{p}" 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 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 config[:default_client_scopes] = ['foo'] keycloak_client_scope = Puppet::Type.type(:keycloak_client_scope).new(name: 'foo', 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 it 'autorequires client_scope protocol mappers' do config[:default_client_scopes] = ['foo'] keycloak_protocol_mapper = Puppet::Type.type(:keycloak_protocol_mapper).new(name: 'bar', realm: 'test', client_scope: 'foo') catalog = Puppet::Resource::Catalog.new catalog.add_resource resource catalog.add_resource keycloak_protocol_mapper rel = resource.autorequire[0] expect(rel.source.ref).to eq(keycloak_protocol_mapper.ref) expect(rel.target.ref).to eq(resource.ref) end end