Changeset View
Changeset View
Standalone View
Standalone View
lib/puppet/provider/keycloak_client/kcadm.rb
Show All 12 Lines | Puppet::Type.type(:keycloak_client).provide(:kcadm, parent: Puppet::Provider::KeycloakAPI) do | ||||
end | end | ||||
def dot_attributes_properties | def dot_attributes_properties | ||||
[ | [ | ||||
:access_token_lifespan, | :access_token_lifespan, | ||||
] | ] | ||||
end | end | ||||
def self.auth_flow_properties | |||||
{ | |||||
browser_flow: 'browser', | |||||
direct_grant_flow: 'direct_grant', | |||||
} | |||||
end | |||||
def auth_flow_properties | |||||
self.class.auth_flow_properties | |||||
end | |||||
def attribute_key(property) | def attribute_key(property) | ||||
if dot_attributes_properties.include?(property) | if dot_attributes_properties.include?(property) | ||||
property.to_s.tr('_', '.') | property.to_s.tr('_', '.') | ||||
else | else | ||||
property | property | ||||
end | end | ||||
end | end | ||||
def self.get_client_roles(realm, client) | |||||
output = kcadm('get', "clients/#{client}/roles", realm) | |||||
Puppet.debug("Client #{client} in realm #{realm} roles: #{output}") | |||||
data = JSON.parse(output) | |||||
roles = [] | |||||
data.each do |d| | |||||
# filter out 'uma_protection' client role created when | |||||
# authorization_services_enabled property is set to true | |||||
if !d['composite'] && d['name'] != 'uma_protection' | |||||
roles.push(d['name']) | |||||
end | |||||
end | |||||
Puppet.debug("Returned client roles: #{roles}") | |||||
roles | |||||
end | |||||
def get_client_roles(*args) | |||||
self.class.get_client_roles(*args) | |||||
end | |||||
def self.instances | def self.instances | ||||
clients = [] | clients = [] | ||||
realms.each do |realm| | realms.each do |realm| | ||||
output = kcadm('get', 'clients', realm) | output = kcadm('get', 'clients', realm) | ||||
Puppet.debug("#{realm} clients: #{output}") | Puppet.debug("#{realm} clients: #{output}") | ||||
begin | begin | ||||
data = JSON.parse(output) | data = JSON.parse(output) | ||||
rescue JSON::ParserError | rescue JSON::ParserError | ||||
Show All 19 Lines | realms.each do |realm| | ||||
end | end | ||||
client = {} | client = {} | ||||
client[:ensure] = :present | client[:ensure] = :present | ||||
client[:id] = d['id'] | client[:id] = d['id'] | ||||
client[:client_id] = d['clientId'] | client[:client_id] = d['clientId'] | ||||
client[:realm] = realm | client[:realm] = realm | ||||
client[:name] = "#{client[:client_id]} on #{client[:realm]}" | client[:name] = "#{client[:client_id]} on #{client[:realm]}" | ||||
type_properties.each do |property| | type_properties.each do |property| | ||||
next if [:roles].include?(property) | |||||
camel_key = camelize(property) | camel_key = camelize(property) | ||||
dot_key = property.to_s.tr('_', '.') | dot_key = property.to_s.tr('_', '.') | ||||
key = property.to_s | key = property.to_s | ||||
attributes = d['attributes'] || {} | attributes = d['attributes'] || {} | ||||
auth_flows = d['authenticationFlowBindingOverrides'] || {} | |||||
if property == :secret | if property == :secret | ||||
value = secret | value = secret | ||||
elsif d.key?(camel_key) | elsif d.key?(camel_key) | ||||
value = d[camel_key] | value = d[camel_key] | ||||
elsif attributes.key?(dot_key) | elsif attributes.key?(dot_key) | ||||
value = attributes[dot_key] | value = attributes[dot_key] | ||||
elsif attributes.key?(key) | elsif attributes.key?(key) | ||||
value = attributes[key] | value = attributes[key] | ||||
elsif auth_flows.key?(auth_flow_properties[property]) | |||||
flow_alias = flow_ids(realm)[auth_flows[auth_flow_properties[property]]] | |||||
value = flow_alias | |||||
end | end | ||||
if !!value == value # rubocop:disable Style/DoubleNegation | if !!value == value # rubocop:disable Style/DoubleNegation | ||||
value = value.to_s.to_sym | value = value.to_s.to_sym | ||||
end | end | ||||
client[property.to_sym] = value | client[property.to_sym] = value | ||||
end | end | ||||
# The absence of a value should be 'absent' | # The absence of a value should be 'absent' | ||||
client[:login_theme] = 'absent' if client[:login_theme].nil? | client[:login_theme] = 'absent' if client[:login_theme].nil? | ||||
client[:roles] = get_client_roles(realm, client[:id]) | |||||
clients << new(client) | clients << new(client) | ||||
end | end | ||||
end | end | ||||
clients | clients | ||||
end | end | ||||
def self.prefetch(resources) | def self.prefetch(resources) | ||||
clients = instances | clients = instances | ||||
Show All 16 Lines | def scope_map | ||||
end | end | ||||
@scope_map = {} | @scope_map = {} | ||||
data.each do |d| | data.each do |d| | ||||
@scope_map[d['name']] = d['id'] | @scope_map[d['name']] = d['id'] | ||||
end | end | ||||
@scope_map | @scope_map | ||||
end | end | ||||
def self.flow_ids(realm) | |||||
@flow_ids = {} unless @flow_ids | |||||
return @flow_ids[realm] if @flow_ids[realm] | |||||
output = kcadm('get', 'authentication/flows', realm, nil, ['id', 'alias']) | |||||
begin | |||||
data = JSON.parse(output) | |||||
rescue JSON::ParserError | |||||
Puppet.debug('Unable to parse output from kcadm get authentication/flows') | |||||
return {} | |||||
end | |||||
@flow_ids[realm] = {} | |||||
data.each do |d| | |||||
@flow_ids[realm][d['alias']] = d['id'] | |||||
@flow_ids[realm][d['id']] = d['alias'] | |||||
end | |||||
@flow_ids[realm] | |||||
end | |||||
def flow_ids | |||||
@flow_ids = {} unless @flow_ids | |||||
return @flow_ids unless @flow_ids.empty? | |||||
self.class.instance_variable_set(:@flow_ids, nil) | |||||
@flow_ids = self.class.flow_ids(resource[:realm]) | |||||
@flow_ids | |||||
end | |||||
def create | def create | ||||
raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? | raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? | ||||
data = {} | data = {} | ||||
data[:id] = resource[:id] | data[:id] = resource[:id] | ||||
data[:clientId] = resource[:client_id] | data[:clientId] = resource[:client_id] | ||||
data[:secret] = resource[:secret] if resource[:secret] | data[:secret] = resource[:secret] if resource[:secret] | ||||
type_properties.each do |property| | type_properties.each do |property| | ||||
next if [:default_client_scopes, :optional_client_scopes].include?(property) | next if [:default_client_scopes, :optional_client_scopes, :roles].include?(property) | ||||
next unless resource[property.to_sym] | next unless resource[property.to_sym] | ||||
value = convert_property_value(resource[property.to_sym]) | value = convert_property_value(resource[property.to_sym]) | ||||
next if value == 'absent' || value == :absent || value.nil? | next if value == 'absent' || value == :absent || value.nil? | ||||
if attributes_properties.include?(property) | if attributes_properties.include?(property) | ||||
unless data.key?(:attributes) | unless data.key?(:attributes) | ||||
data[:attributes] = {} | data[:attributes] = {} | ||||
end | end | ||||
data[:attributes][attribute_key(property)] = value | data[:attributes][attribute_key(property)] = value | ||||
elsif auth_flow_properties.include?(property) | |||||
unless data.key?(:authenticationFlowBindingOverrides) | |||||
data[:authenticationFlowBindingOverrides] = {} | |||||
end | |||||
flow_id = flow_ids[value] | |||||
data[:authenticationFlowBindingOverrides][auth_flow_properties[property]] = flow_id | |||||
else | else | ||||
data[camelize(property)] = value | data[camelize(property)] = value | ||||
end | end | ||||
end | end | ||||
t = Tempfile.new('keycloak_client') | t = Tempfile.new('keycloak_client') | ||||
t.write(JSON.pretty_generate(data)) | t.write(JSON.pretty_generate(data)) | ||||
t.close | t.close | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | if resource[:optional_client_scopes] | ||||
add_optional_scopes.each do |s| | add_optional_scopes.each do |s| | ||||
scope_id = scope_map[s] | scope_id = scope_map[s] | ||||
kcadm('update', "clients/#{resource[:id]}/optional-client-scopes/#{scope_id}", resource[:realm]) | kcadm('update', "clients/#{resource[:id]}/optional-client-scopes/#{scope_id}", resource[:realm]) | ||||
end | end | ||||
rescue Puppet::ExecutionFailure => e | rescue Puppet::ExecutionFailure => e | ||||
raise Puppet::Error, "kcadm update clients/#{resource[:id]}/optional-client-scopes/#{scope_id}: #{e.message}" | raise Puppet::Error, "kcadm update clients/#{resource[:id]}/optional-client-scopes/#{scope_id}: #{e.message}" | ||||
end | end | ||||
end | end | ||||
role = nil | |||||
if resource[:roles] | |||||
roles = get_client_roles(resource[:realm], resource[:id]) | |||||
remove_roles = roles - resource[:roles] | |||||
begin | |||||
remove_roles.each do |s| | |||||
role = s | |||||
kcadm('delete', "clients/#{resource[:id]}/roles/#{role}", resource[:realm]) | |||||
end | |||||
rescue Puppet::ExecutionFailure => e | |||||
raise Puppet::Error, "kcadm delete realms/#{resource[:realm]}/clients/#{resource[:id]}/roles/#{role}: #{e.message}" | |||||
end | |||||
add_roles = resource[:roles] - roles | |||||
begin | |||||
add_roles.each do |s| | |||||
role = s | |||||
role_data = { 'description' => "${role_#{role}}", 'name' => role } | |||||
role_data_t = Tempfile.new('keycloak_client_role') | |||||
role_data_t.write(JSON.pretty_generate(role_data)) | |||||
role_data_t.close | |||||
Puppet.debug(IO.read(role_data_t.path)) | |||||
kcadm('create', "clients/#{resource[:id]}/roles", resource[:realm], role_data_t.path) | |||||
end | |||||
rescue Puppet::ExecutionFailure => e | |||||
raise Puppet::Error, "kcadm create realms/#{resource[:realm]}/clients/#{resource[:id]}/roles/#{role}: #{e.message}" | |||||
end | |||||
end | |||||
@property_hash[:ensure] = :present | @property_hash[:ensure] = :present | ||||
end | end | ||||
def destroy | def destroy | ||||
raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? | raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? | ||||
begin | begin | ||||
kcadm('delete', "clients/#{id}", resource[:realm]) | kcadm('delete', "clients/#{id}", resource[:realm]) | ||||
rescue Puppet::ExecutionFailure => e | rescue Puppet::ExecutionFailure => e | ||||
Show All 19 Lines | Puppet::Type.type(:keycloak_client).provide(:kcadm, parent: Puppet::Provider::KeycloakAPI) do | ||||
end | end | ||||
def flush | def flush | ||||
unless @property_flush.empty? | unless @property_flush.empty? | ||||
raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? | raise(Puppet::Error, "Realm is mandatory for #{resource.type} #{resource.name}") if resource[:realm].nil? | ||||
data = {} | data = {} | ||||
data[:clientId] = resource[:client_id] | data[:clientId] = resource[:client_id] | ||||
data[:authenticationFlowBindingOverrides] = {} | |||||
type_properties.each do |property| | type_properties.each do |property| | ||||
next if [:default_client_scopes, :optional_client_scopes].include?(property) | next if [:default_client_scopes, :optional_client_scopes, :roles].include?(property) | ||||
next unless @property_flush[property.to_sym] | next unless @property_flush[property.to_sym] | ||||
value = convert_property_value(@property_flush[property.to_sym]) | value = convert_property_value(@property_flush[property.to_sym]) | ||||
value = nil if value.to_s == 'absent' | value = nil if value.to_s == 'absent' | ||||
if attributes_properties.include?(property) | if attributes_properties.include?(property) | ||||
unless data.key?(:attributes) | unless data.key?(:attributes) | ||||
data[:attributes] = {} | data[:attributes] = {} | ||||
end | end | ||||
data[:attributes][attribute_key(property)] = value | data[:attributes][attribute_key(property)] = value | ||||
elsif auth_flow_properties.include?(property) | |||||
flow_id = value.nil? ? nil : flow_ids[value] | |||||
data[:authenticationFlowBindingOverrides][auth_flow_properties[property]] = flow_id | |||||
else | else | ||||
data[camelize(property)] = value | data[camelize(property)] = value | ||||
end | end | ||||
end | end | ||||
# Keycload API requires "serviceAccountsEnabled": true to be present in | # Keycload API requires "serviceAccountsEnabled": true to be present in | ||||
# the JSON when "authorizationServicesEnabled": true | # the JSON when "authorizationServicesEnabled": true | ||||
if data['authorizationServicesEnabled'] && data['serviceAccountsEnabled'].nil? | if data['authorizationServicesEnabled'] && data['serviceAccountsEnabled'].nil? | ||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | unless @property_flush.empty? | ||||
add_optional_scopes.each do |s| | add_optional_scopes.each do |s| | ||||
scope_id = scope_map[s] | scope_id = scope_map[s] | ||||
kcadm('update', "clients/#{id}/optional-client-scopes/#{scope_id}", resource[:realm]) | kcadm('update', "clients/#{id}/optional-client-scopes/#{scope_id}", resource[:realm]) | ||||
end | end | ||||
rescue Puppet::ExecutionFailure => e | rescue Puppet::ExecutionFailure => e | ||||
raise Puppet::Error, "kcadm update clients/#{id}/optional-client-scopes/#{scope_id}: #{e.message}" | raise Puppet::Error, "kcadm update clients/#{id}/optional-client-scopes/#{scope_id}: #{e.message}" | ||||
end | end | ||||
end | end | ||||
role = nil | |||||
if @property_flush[:roles] | |||||
remove_roles = @property_hash[:roles] - @property_flush[:roles] | |||||
begin | |||||
remove_roles.each do |s| | |||||
role = s | |||||
kcadm('delete', "clients/#{resource[:id]}/roles/#{role}", resource[:realm]) | |||||
end | |||||
rescue Puppet::ExecutionFailure => e | |||||
raise Puppet::Error, "kcadm delete realms/#{resource[:realm]}/clients/#{resource[:id]}/roles/#{role}: #{e.message}" | |||||
end | |||||
add_roles = @property_flush[:roles] - @property_hash[:roles] | |||||
begin | |||||
add_roles.each do |s| | |||||
role = s | |||||
role_data = { 'description' => "${role_#{role}}", 'name' => role } | |||||
role_data_t = Tempfile.new('keycloak_client_role') | |||||
role_data_t.write(JSON.pretty_generate(role_data)) | |||||
role_data_t.close | |||||
Puppet.debug(IO.read(role_data_t.path)) | |||||
kcadm('create', "clients/#{resource[:id]}/roles", resource[:realm], role_data_t.path) | |||||
end | |||||
rescue Puppet::ExecutionFailure => e | |||||
raise Puppet::Error, "kcadm create realms/#{resource[:realm]}/clients/#{resource[:id]}/roles/#{role}: #{e.message}" | |||||
end | |||||
end | |||||
end | end | ||||
# Collect the resources again once they've been changed (that way `puppet | # Collect the resources again once they've been changed (that way `puppet | ||||
# resource` will show the correct values after changes have been made). | # resource` will show the correct values after changes have been made). | ||||
@property_hash = resource.to_hash | @property_hash = resource.to_hash | ||||
end | end | ||||
end | end |