diff --git a/lib/puppet/provider/keycloak_realm/kcadm.rb b/lib/puppet/provider/keycloak_realm/kcadm.rb index d15f8fc..6a7a5c6 100644 --- a/lib/puppet/provider/keycloak_realm/kcadm.rb +++ b/lib/puppet/provider/keycloak_realm/kcadm.rb @@ -1,335 +1,364 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'keycloak_api')) Puppet::Type.type(:keycloak_realm).provide(:kcadm, parent: Puppet::Provider::KeycloakAPI) do desc '' mk_resource_methods def flow_properties [ :browser_flow, :registration_flow, :direct_grant_flow, :reset_credentials_flow, :client_authentication_flow, :docker_authentication_flow, ] end + def self.smtp_server_properties + [ + :smtp_server_user, + :smtp_server_password, + :smtp_server_host, + :smtp_server_port, + :smtp_server_auth, + :smtp_server_starttls, + :smtp_server_ssl, + :smtp_server_envelope_from, + :smtp_server_from, + :smtp_server_from_display_name, + :smtp_server_reply_to, + :smtp_server_reply_to_display_name, + ] + end + def self.browser_security_headers [ :content_security_policy, ] end def self.get_client_scopes(realm, type) output = kcadm('get', "realms/#{realm}/default-#{type}-client-scopes") Puppet.debug("Realms #{realm} #{type} client scopes: #{output}") data = JSON.parse(output) scopes = {} data.each do |d| scopes[d['name']] = d['id'] end Puppet.debug("Returned scopes: #{scopes}") scopes end def get_client_scopes(*args) self.class.get_client_scopes(*args) end def self.get_events_config(realm) output = kcadm('get', 'events/config', realm) Puppet.debug("#{realm} events/config: #{output}") begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get events/config') data = {} end data.delete('enabledEventTypes') data end def available_flows(realm) output = kcadm('get', 'authentication/flows', realm, nil, ['alias']) Puppet.debug("#{realm} authentication/flows: #{output}") begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get authentication/flows') return [] end data.map { |f| f['alias'] } end def self.instances realms = [] output = kcadm('get', 'realms') Puppet.debug("Realms: #{output}") begin data = JSON.parse(output) rescue JSON::ParserError Puppet.debug('Unable to parse output from kcadm get realms') data = [] end data.each do |d| realm = {} realm[:ensure] = :present realm[:id] = d['id'] realm[:name] = d['realm'] events_config = get_events_config(d['realm']) type_properties.each do |property| next if [:default_client_scopes, :optional_client_scopes].include?(property) value = if property.to_s =~ %r{events} events_config[camelize(property)] elsif browser_security_headers.include?(property) d['browserSecurityHeaders'][camelize(property)] + elsif smtp_server_properties.include?(property) + d['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] else d[camelize(property)] end if !!value == value # rubocop:disable Style/DoubleNegation value = value.to_s.to_sym end realm[property.to_sym] = value end default_scopes = get_client_scopes(realm[:name], 'default') realm[:default_client_scopes] = default_scopes.keys.map { |k| k.to_s } optional_scopes = get_client_scopes(realm[:name], 'optional') realm[:optional_client_scopes] = optional_scopes.keys.map { |k| k.to_s } realms << new(realm) end realms end def self.prefetch(resources) realms = instances resources.keys.each do |name| provider = realms.find { |realm| realm.name == name } if provider resources[name].provider = provider end end end def create data = {} events_config = {} data[:id] = resource[:id] data[:realm] = resource[:name] type_properties.each do |property| next if flow_properties.include?(property) next if [:default_client_scopes, :optional_client_scopes].include?(property) if self.class.browser_security_headers.include?(property) && !data.key?('browserSecurityHeaders') data['browserSecurityHeaders'] = {} end + if self.class.smtp_server_properties.include?(property) && !data.key?('smtpServer') + data['smtpServer'] = {} + end if property.to_s =~ %r{events} events_config[camelize(property)] = convert_property_value(resource[property.to_sym]) elsif resource[property.to_sym] if self.class.browser_security_headers.include?(property) data['browserSecurityHeaders'][camelize(property)] = convert_property_value(resource[property.to_sym]) + elsif self.class.smtp_server_properties.include?(property) && resource[property] + data['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] = resource[property] else data[camelize(property)] = convert_property_value(resource[property.to_sym]) end end end t = Tempfile.new('keycloak_realm') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin kcadm('create', 'realms', nil, t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm create realm failed\nError message: #{e.message}" end scope_id = nil if resource[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') remove_default_scopes = default_scopes.keys - resource[:default_client_scopes] begin remove_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if resource[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') remove_optional_scopes = optional_scopes.keys - resource[:optional_client_scopes] begin remove_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end if resource[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') add_default_scopes = resource[:default_client_scopes] - default_scopes.keys begin add_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if resource[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') add_optional_scopes = resource[:optional_client_scopes] - optional_scopes.keys begin add_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end unless events_config.empty? events_config_t = Tempfile.new('keycloak_events_config') events_config_t.write(JSON.pretty_generate(events_config)) events_config_t.close Puppet.debug(IO.read(events_config_t.path)) begin kcadm('update', 'events/config', resource[:name], events_config_t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update events config failed\nError message: #{e.message}" end end @property_hash[:ensure] = :present end def destroy begin kcadm('delete', "realms/#{resource[:name]}") 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? data = {} events_config = {} type_properties.each do |property| next if [:default_client_scopes, :optional_client_scopes].include?(property) if flow_properties.include?(property) && !available_flows(resource[:name]).include?(resource[property.to_sym]) Puppet.warning("Keycloak_realm[#{resource[:name]}]: #{property} '#{resource[property.to_sym]}' does not exist, skipping") next end if self.class.browser_security_headers.include?(property) && !data.key?('browserSecurityHeaders') data['browserSecurityHeaders'] = {} end - if @property_flush[property.to_sym] # || resource[property.to_sym] + if self.class.smtp_server_properties.include?(property) && !data.key?('smtpServer') + data['smtpServer'] = {} + end + if @property_flush[property.to_sym] || resource[property.to_sym] if self.class.browser_security_headers.include?(property) data['browserSecurityHeaders'][camelize(property)] = convert_property_value(resource[property.to_sym]) + elsif self.class.smtp_server_properties.include?(property) && resource[property] + data['smtpServer'][camelize(property.to_s.gsub(%r{smtp_server_}, ''))] = resource[property] else data[camelize(property)] = convert_property_value(resource[property.to_sym]) end end if property.to_s =~ %r{events} events_config[camelize(property)] = convert_property_value(resource[property.to_sym]) end end unless data.empty? t = Tempfile.new('keycloak_realm') t.write(JSON.pretty_generate(data)) t.close Puppet.debug(IO.read(t.path)) begin kcadm('update', "realms/#{resource[:name]}", nil, t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realm failed\nError message: #{e.message}" end end scope_id = nil if @property_flush[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') remove_default_scopes = default_scopes.keys - @property_flush[:default_client_scopes] begin remove_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') remove_optional_scopes = optional_scopes.keys - @property_flush[:optional_client_scopes] begin remove_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('delete', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm delete realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:default_client_scopes] default_scopes = default_scopes ||= get_client_scopes(resource[:name], 'default') add_default_scopes = @property_flush[:default_client_scopes] - default_scopes.keys begin add_default_scopes.each do |s| scope_id = default_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-default-client-scopes/#{scope_id}: #{e.message}" end end if @property_flush[:optional_client_scopes] optional_scopes = optional_scopes ||= get_client_scopes(resource[:name], 'optional') add_optional_scopes = @property_flush[:optional_client_scopes] - optional_scopes.keys begin add_optional_scopes.each do |s| scope_id = optional_scopes[s] kcadm('update', "realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}") end rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update realms/#{resource[:name]}/default-optional-client-scopes/#{scope_id}: #{e.message}" end end unless events_config.empty? events_config_t = Tempfile.new('keycloak_events_config') events_config_t.write(JSON.pretty_generate(events_config)) events_config_t.close Puppet.debug(IO.read(events_config_t.path)) begin kcadm('update', 'events/config', resource[:name], events_config_t.path) rescue Puppet::ExecutionFailure => e raise Puppet::Error, "kcadm update events config failed\nError message: #{e.message}" end 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_realm.rb b/lib/puppet/type/keycloak_realm.rb index 7739216..3c34583 100644 --- a/lib/puppet/type/keycloak_realm.rb +++ b/lib/puppet/type/keycloak_realm.rb @@ -1,189 +1,253 @@ require_relative '../../puppet_x/keycloak/type' require_relative '../../puppet_x/keycloak/array_property' require_relative '../../puppet_x/keycloak/integer_property' Puppet::Type.newtype(:keycloak_realm) do desc <<-DESC Manage Keycloak realms @example Add a realm with a custom theme keycloak_realm { 'test': ensure => 'present', remember_me => true, login_with_email_allowed => false, login_theme => 'my_theme', } DESC extend PuppetX::Keycloak::Type add_autorequires(false) ensurable newparam(:name, namevar: true) do desc 'The realm name' end newparam(:id) do desc 'Id. Default to `name`.' defaultto do @resource[:name] end end newproperty(:display_name) do desc 'displayName' end newproperty(:display_name_html) do desc 'displayNameHtml' end newproperty(:login_theme) do desc 'loginTheme' defaultto 'keycloak' end newproperty(:account_theme) do desc 'accountTheme' defaultto 'keycloak' end newproperty(:admin_theme) do desc 'adminTheme' defaultto 'keycloak' end newproperty(:email_theme) do desc 'emailTheme' defaultto 'keycloak' end newproperty(:internationalization_enabled, boolean: true) do desc 'internationalizationEnabled' newvalues(:true, :false) defaultto :false end newproperty(:sso_session_idle_timeout, parent: PuppetX::Keycloak::IntegerProperty) do desc 'ssoSessionIdleTimeout' end newproperty(:sso_session_max_lifespan, parent: PuppetX::Keycloak::IntegerProperty) do desc 'ssoSessionMaxLifespan' end newproperty(:access_code_lifespan, parent: PuppetX::Keycloak::IntegerProperty) do desc 'accessCodeLifespan' end newproperty(:access_code_lifespan_user_action, parent: PuppetX::Keycloak::IntegerProperty) do desc 'accessCodeLifespanUserAction' end newproperty(:access_token_lifespan, parent: PuppetX::Keycloak::IntegerProperty) do desc 'accessTokenLifespan' end newproperty(:access_token_lifespan_for_implicit_flow, parent: PuppetX::Keycloak::IntegerProperty) do desc 'accessTokenLifespanForImplicitFlow' end newproperty(:enabled, boolean: true) do desc 'enabled' newvalues(:true, :false) defaultto :true end newproperty(:remember_me, boolean: true) do desc 'rememberMe' newvalues(:true, :false) defaultto :false end newproperty(:login_with_email_allowed, boolean: true) do desc 'loginWithEmailAllowed' newvalues(:true, :false) defaultto :true end newproperty(:browser_flow) do desc 'browserFlow' defaultto('browser') munge { |v| v.to_s } end newproperty(:registration_flow) do desc 'registrationFlow' defaultto('registration') munge { |v| v.to_s } end newproperty(:direct_grant_flow) do desc 'directGrantFlow' defaultto('direct grant') munge { |v| v.to_s } end newproperty(:reset_credentials_flow) do desc 'resetCredentialsFlow' defaultto('reset credentials') munge { |v| v.to_s } end newproperty(:client_authentication_flow) do desc 'clientAuthenticationFlow' defaultto('clients') munge { |v| v.to_s } end newproperty(:docker_authentication_flow) do desc 'dockerAuthenticationFlow' defaultto('docker auth') munge { |v| v.to_s } end newproperty(:default_client_scopes, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'Default Client Scopes' end newproperty(:optional_client_scopes, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'Optional Client Scopes' end newproperty(:supported_locales, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'Supported Locales' end newproperty(:content_security_policy) do desc 'contentSecurityPolicy' defaultto("frame-src 'self'; frame-ancestors 'self'; object-src 'none';") munge { |v| v.to_s } end newproperty(:events_enabled, boolean: true) do desc 'eventsEnabled' newvalues(:true, :false) defaultto :false end newproperty(:events_expiration) do desc 'eventsExpiration' end newproperty(:events_listeners, array_matching: :all, parent: PuppetX::Keycloak::ArrayProperty) do desc 'eventsListeners' defaultto ['jboss-logging'] end newproperty(:admin_events_enabled, boolean: true) do desc 'adminEventsEnabled' newvalues(:true, :false) defaultto :false end newproperty(:admin_events_details_enabled, boolean: true) do desc 'adminEventsDetailsEnabled' newvalues(:true, :false) defaultto :false end + + newproperty(:smtp_server_user) do + desc 'smtpServer user' + end + + newproperty(:smtp_server_password) do + desc 'smtpServer password' + + def insync?(is) + if is =~ %r{^[\*]+$} + Puppet.warning("Property 'smtp_server_password' is set and Puppet has no way to check current value") + true + else + false + end + end + + def should_to_s(_newvalue) + '[new smtp_server_password redacted]' + end + end + + newproperty(:smtp_server_host) do + desc 'smtpServer host' + end + + newproperty(:smtp_server_port, parent: PuppetX::Keycloak::IntegerProperty) do + desc 'smtpServer port' + end + + newproperty(:smtp_server_auth, boolean: true) do + desc 'smtpServer auth' + newvalues(:true, :false) + end + + newproperty(:smtp_server_starttls, boolean: true) do + desc 'smtpServer starttls' + newvalues(:true, :false) + end + + newproperty(:smtp_server_ssl, boolean: true) do + desc 'smtpServer ssl' + newvalues(:true, :false) + end + + newproperty(:smtp_server_from) do + desc 'smtpServer from' + end + + newproperty(:smtp_server_envelope_from) do + desc 'smtpServer envelope_from' + end + + newproperty(:smtp_server_from_display_name) do + desc 'smtpServer fromDisplayName' + end + + newproperty(:smtp_server_reply_to) do + desc 'smtpServer replyto' + end + + newproperty(:smtp_server_reply_to_display_name) do + desc 'smtpServer replyToDisplayName' + end end diff --git a/spec/acceptance/2_realm_spec.rb b/spec/acceptance/2_realm_spec.rb index 8c2634d..9ff8470 100644 --- a/spec/acceptance/2_realm_spec.rb +++ b/spec/acceptance/2_realm_spec.rb @@ -1,142 +1,191 @@ require 'spec_helper_acceptance' describe 'keycloak_realm:', if: RSpec.configuration.keycloak_full do context 'creates realm' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test': - ensure => 'present', + ensure => 'present', + smtp_server_host => 'smtp.example.org', + smtp_server_port => 587, + smtp_server_starttls => false, + smtp_server_auth => false, + smtp_server_user => 'john', + smtp_server_password => 'secret', + smtp_server_envelope_from => 'keycloak@id.example.org', + smtp_server_from => 'keycloak@id.example.org', + smtp_server_from_display_name => 'Keycloak', + smtp_server_reply_to => 'webmaster@example.org', + smtp_server_reply_to_display_name => 'Webmaster', } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has created a realm' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test' do data = JSON.parse(stdout) expect(data['id']).to eq('test') end end it 'has left default-client-scopes' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test/default-default-client-scopes' do data = JSON.parse(stdout) names = data.map { |d| d['name'] }.sort expect(names).to include('email') expect(names).to include('profile') expect(names).to include('role_list') end end it 'has left optional-client-scopes' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test/default-optional-client-scopes' do data = JSON.parse(stdout) names = data.map { |d| d['name'] }.sort expect(names).to include('address') expect(names).to include('offline_access') expect(names).to include('phone') end end it 'has default events config' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get events/config -r test' do data = JSON.parse(stdout) expect(data['eventsEnabled']).to eq(false) expect(data['eventsExpiration']).to be_nil expect(data['eventsListeners']).to eq(['jboss-logging']) expect(data['adminEventsEnabled']).to eq(false) expect(data['adminEventsDetailsEnabled']).to eq(false) end end + + it 'has correct smtp settings' do + on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test' do + data = JSON.parse(stdout) + expect(data['smtpServer']['host']).to eq('smtp.example.org') + expect(data['smtpServer']['port']).to eq('587') + expect(data['smtpServer']['starttls']).to eq('false') + expect(data['smtpServer']['auth']).to eq('false') + expect(data['smtpServer']['user']).to eq('john') + expect(data['smtpServer']['envelopeFrom']).to eq('keycloak@id.example.org') + expect(data['smtpServer']['from']).to eq('keycloak@id.example.org') + expect(data['smtpServer']['fromDisplayName']).to eq('Keycloak') + expect(data['smtpServer']['replyTo']).to eq('webmaster@example.org') + expect(data['smtpServer']['replyToDisplayName']).to eq('Webmaster') + end + end end context 'updates realm' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test': ensure => 'present', remember_me => true, access_code_lifespan => 3600, access_token_lifespan => 3600, sso_session_idle_timeout => 3600, sso_session_max_lifespan => 72000, default_client_scopes => ['profile'], content_security_policy => "frame-src https://*.duosecurity.com/ 'self'; frame-src 'self'; frame-ancestors 'self'; object-src 'none';", events_enabled => true, events_expiration => 2678400, admin_events_enabled => true, admin_events_details_enabled => true, + smtp_server_host => 'smtp.example.org', + smtp_server_port => 587, + smtp_server_starttls => false, + smtp_server_auth => true, + smtp_server_user => 'jane', + smtp_server_password => 'secret', + smtp_server_envelope_from => 'keycloak@id.example.org', + smtp_server_from => 'keycloak@id.example.org', + smtp_server_from_display_name => 'Keycloak', + smtp_server_reply_to => 'webmaster@example.org', + smtp_server_reply_to_display_name => 'Hostmaster', + } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has updated the realm' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test' do data = JSON.parse(stdout) expect(data['rememberMe']).to eq(true) expect(data['accessCodeLifespan']).to eq(3600) expect(data['accessTokenLifespan']).to eq(3600) expect(data['ssoSessionIdleTimeout']).to eq(3600) expect(data['ssoSessionMaxLifespan']).to eq(72_000) expect(data['browserSecurityHeaders']['contentSecurityPolicy']).to eq("frame-src https://*.duosecurity.com/ 'self'; frame-src 'self'; frame-ancestors 'self'; object-src 'none';") + expect(data['smtpServer']['host']).to eq('smtp.example.org') + expect(data['smtpServer']['port']).to eq('587') + expect(data['smtpServer']['starttls']).to eq('false') + expect(data['smtpServer']['auth']).to eq('true') + expect(data['smtpServer']['user']).to eq('jane') + expect(data['smtpServer']['envelopeFrom']).to eq('keycloak@id.example.org') + expect(data['smtpServer']['from']).to eq('keycloak@id.example.org') + expect(data['smtpServer']['fromDisplayName']).to eq('Keycloak') + expect(data['smtpServer']['replyTo']).to eq('webmaster@example.org') + expect(data['smtpServer']['replyToDisplayName']).to eq('Hostmaster') end end it 'has updated the realm default-client-scopes' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test/default-default-client-scopes' do data = JSON.parse(stdout) names = data.map { |d| d['name'] } expect(names).to eq(['profile']) end end it 'has updated events config' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get events/config -r test' do data = JSON.parse(stdout) expect(data['eventsEnabled']).to eq(true) expect(data['eventsExpiration']).to eq(2_678_400) expect(data['eventsListeners']).to eq(['jboss-logging']) expect(data['adminEventsEnabled']).to eq(true) expect(data['adminEventsDetailsEnabled']).to eq(true) end end end context 'creates realm with invalid browser flow' do it 'runs successfully' do pp = <<-EOS include mysql::server class { 'keycloak': datasource_driver => 'mysql', } keycloak_realm { 'test2': ensure => 'present', browser_flow => 'Copy of browser', } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, expect_changes: true) end it 'has created a realm' do on hosts, '/opt/keycloak/bin/kcadm-wrapper.sh get realms/test2' do data = JSON.parse(stdout) expect(data['browserFlow']).to eq('browser') end end end end diff --git a/spec/unit/puppet/type/keycloak_realm_spec.rb b/spec/unit/puppet/type/keycloak_realm_spec.rb index cf65b2b..2d89aa6 100644 --- a/spec/unit/puppet/type/keycloak_realm_spec.rb +++ b/spec/unit/puppet/type/keycloak_realm_spec.rb @@ -1,181 +1,193 @@ require 'spec_helper' describe Puppet::Type.type(:keycloak_realm) do let(:default_config) do { name: '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('test') end it 'has id default to name' do expect(resource[:id]).to eq('test') end defaults = { login_theme: 'keycloak', account_theme: 'keycloak', admin_theme: 'keycloak', email_theme: 'keycloak', access_code_lifespan_user_action: nil, access_token_lifespan_for_implicit_flow: nil, enabled: :true, remember_me: :false, login_with_email_allowed: :true, browser_flow: 'browser', registration_flow: 'registration', direct_grant_flow: 'direct grant', reset_credentials_flow: 'reset credentials', client_authentication_flow: 'clients', docker_authentication_flow: 'docker auth', content_security_policy: "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", events_enabled: :false, events_listeners: ['jboss-logging'], admin_events_enabled: :false, admin_events_details_enabled: :false, } describe 'basic properties' do # Test basic properties [ :display_name, :display_name_html, :login_theme, :account_theme, :admin_theme, :email_theme, :events_expiration, :browser_flow, :registration_flow, :direct_grant_flow, :reset_credentials_flow, :client_authentication_flow, :docker_authentication_flow, :content_security_policy, + :smtp_server_user, + :smtp_server_password, + :smtp_server_host, + :smtp_server_envelope_from, + :smtp_server_from, + :smtp_server_from_display_name, + :smtp_server_reply_to, + :smtp_server_reply_to_display_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 'integer properties' do # Test integer properties [ :sso_session_idle_timeout, :sso_session_max_lifespan, :access_code_lifespan, :access_code_lifespan_user_action, :access_token_lifespan, :access_token_lifespan_for_implicit_flow, + :smtp_server_port, ].each do |p| it "should accept a #{p}" do config[p] = 100 expect(resource[p]).to eq(100) 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 [ :remember_me, :login_with_email_allowed, :internationalization_enabled, :events_enabled, :admin_events_enabled, :admin_events_details_enabled, + :smtp_server_auth, + :smtp_server_starttls, + :smtp_server_ssl, ].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, :events_listeners, :supported_locales, ].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 end