Skip to content

Commit

Permalink
new resource: keycloak_saml_script_protocol_mapper (#473)
Browse files Browse the repository at this point in the history
  • Loading branch information
dullest authored Feb 12, 2021
1 parent 490a46b commit 0dda29b
Show file tree
Hide file tree
Showing 5 changed files with 646 additions and 0 deletions.
2 changes: 2 additions & 0 deletions keycloak/protocol_mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ var (
includedClientAudienceField = "included.client.audience"
includedCustomAudienceField = "included.custom.audience"
multivaluedField = "multivalued"
samlScriptField = "Script" // needs to start with uppercase S for SAML script mapper
scriptField = "script"
singleValueAttributeField = "single"
userAttributeField = "user.attribute"
userPropertyField = "user.attribute"
userRealmRoleMappingRolePrefixField = "usermodel.realmRoleMapping.rolePrefix"
Expand Down
112 changes: 112 additions & 0 deletions keycloak/saml_script_protocol_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package keycloak

import (
"fmt"
"strconv"
)

type SamlScriptProtocolMapper struct {
Id string
Name string
RealmId string
ClientId string
ClientScopeId string

SingleValueAttribute bool

SamlScript string
FriendlyName string
SamlAttributeName string
SamlAttributeNameFormat string
}

func (mapper *SamlScriptProtocolMapper) convertToGenericProtocolMapper() *protocolMapper {
return &protocolMapper{
Id: mapper.Id,
Name: mapper.Name,
Protocol: "saml",
ProtocolMapper: "saml-javascript-mapper",
Config: map[string]string{
attributeNameField: mapper.SamlAttributeName,
attributeNameFormatField: mapper.SamlAttributeNameFormat,
friendlyNameField: mapper.FriendlyName,
samlScriptField: mapper.SamlScript,
singleValueAttributeField: strconv.FormatBool(mapper.SingleValueAttribute),
},
}
}

func (protocolMapper *protocolMapper) convertToSamlScriptProtocolMapper(realmId, clientId, clientScopeId string) (*SamlScriptProtocolMapper, error) {
singleValueAttribute, err := strconv.ParseBool(protocolMapper.Config[singleValueAttributeField])
if err != nil {
return nil, err
}

return &SamlScriptProtocolMapper{
Id: protocolMapper.Id,
Name: protocolMapper.Name,
RealmId: realmId,
ClientId: clientId,
ClientScopeId: clientScopeId,

SingleValueAttribute: singleValueAttribute,

SamlScript: protocolMapper.Config[samlScriptField],
FriendlyName: protocolMapper.Config[friendlyNameField],
SamlAttributeName: protocolMapper.Config[attributeNameField],
SamlAttributeNameFormat: protocolMapper.Config[attributeNameFormatField],
}, nil
}

func (keycloakClient *KeycloakClient) GetSamlScriptProtocolMapper(realmId, clientId, clientScopeId, mapperId string) (*SamlScriptProtocolMapper, error) {
var protocolMapper *protocolMapper

err := keycloakClient.get(individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId), &protocolMapper, nil)
if err != nil {
return nil, err
}

return protocolMapper.convertToSamlScriptProtocolMapper(realmId, clientId, clientScopeId)
}

func (keycloakClient *KeycloakClient) DeleteSamlScriptProtocolMapper(realmId, clientId, clientScopeId, mapperId string) error {
return keycloakClient.delete(individualProtocolMapperPath(realmId, clientId, clientScopeId, mapperId), nil)
}

func (keycloakClient *KeycloakClient) NewSamlScriptProtocolMapper(mapper *SamlScriptProtocolMapper) error {
path := protocolMapperPath(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId)

_, location, err := keycloakClient.post(path, mapper.convertToGenericProtocolMapper())
if err != nil {
return err
}

mapper.Id = getIdFromLocationHeader(location)

return nil
}

func (keycloakClient *KeycloakClient) UpdateSamlScriptProtocolMapper(mapper *SamlScriptProtocolMapper) error {
path := individualProtocolMapperPath(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId, mapper.Id)

return keycloakClient.put(path, mapper.convertToGenericProtocolMapper())
}

func (keycloakClient *KeycloakClient) ValidateSamlScriptProtocolMapper(mapper *SamlScriptProtocolMapper) error {
if mapper.ClientId == "" && mapper.ClientScopeId == "" {
return fmt.Errorf("validation error: one of ClientId or ClientScopeId must be set")
}

protocolMappers, err := keycloakClient.listGenericProtocolMappers(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId)
if err != nil {
return err
}

for _, protocolMapper := range protocolMappers {
if protocolMapper.Name == mapper.Name && protocolMapper.Id != mapper.Id {
return fmt.Errorf("validation error: a protocol mapper with name %s already exists for this client", mapper.Name)
}
}

return nil
}
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
"keycloak_generic_client_role_mapper": resourceKeycloakGenericClientRoleMapper(),
"keycloak_saml_user_attribute_protocol_mapper": resourceKeycloakSamlUserAttributeProtocolMapper(),
"keycloak_saml_user_property_protocol_mapper": resourceKeycloakSamlUserPropertyProtocolMapper(),
"keycloak_saml_script_protocol_mapper": resourceKeycloakSamlScriptProtocolMapper(),
"keycloak_hardcoded_attribute_identity_provider_mapper": resourceKeycloakHardcodedAttributeIdentityProviderMapper(),
"keycloak_hardcoded_role_identity_provider_mapper": resourceKeycloakHardcodedRoleIdentityProviderMapper(),
"keycloak_attribute_importer_identity_provider_mapper": resourceKeycloakAttributeImporterIdentityProviderMapper(),
Expand Down
168 changes: 168 additions & 0 deletions provider/resource_keycloak_saml_script_protocol_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package provider

import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/mrparkers/terraform-provider-keycloak/keycloak"
)

func resourceKeycloakSamlScriptProtocolMapper() *schema.Resource {
return &schema.Resource{
Create: resourceKeycloakSamlScriptProtocolMapperCreate,
Read: resourceKeycloakSamlScriptProtocolMapperRead,
Update: resourceKeycloakSamlScriptProtocolMapperUpdate,
Delete: resourceKeycloakSamlScriptProtocolMapperDelete,
Importer: &schema.ResourceImporter{
// import a mapper tied to a client:
// {{realmId}}/client/{{clientId}}/{{protocolMapperId}}
// or a client scope:
// {{realmId}}/client-scope/{{clientScopeId}}/{{protocolMapperId}}
State: genericProtocolMapperImport,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"realm_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"client_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"client_scope_id"},
},
"client_scope_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ConflictsWith: []string{"client_id"},
},
"single_value_attribute": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"script": {
Type: schema.TypeString,
Required: true,
},
"friendly_name": {
Type: schema.TypeString,
Optional: true,
},
"saml_attribute_name": {
Type: schema.TypeString,
Required: true,
},
"saml_attribute_name_format": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice(keycloakSamlUserAttributeProtocolMapperNameFormats, false),
},
},
}
}

func mapFromDataToSamlScriptProtocolMapper(data *schema.ResourceData) *keycloak.SamlScriptProtocolMapper {
return &keycloak.SamlScriptProtocolMapper{
Id: data.Id(),
Name: data.Get("name").(string),
RealmId: data.Get("realm_id").(string),
ClientId: data.Get("client_id").(string),
ClientScopeId: data.Get("client_scope_id").(string),

SingleValueAttribute: data.Get("single_value_attribute").(bool),

SamlScript: data.Get("script").(string),
FriendlyName: data.Get("friendly_name").(string),
SamlAttributeName: data.Get("saml_attribute_name").(string),
SamlAttributeNameFormat: data.Get("saml_attribute_name_format").(string),
}
}

func mapFromSamlScriptMapperToData(mapper *keycloak.SamlScriptProtocolMapper, data *schema.ResourceData) {
data.SetId(mapper.Id)
data.Set("name", mapper.Name)
data.Set("realm_id", mapper.RealmId)

if mapper.ClientId != "" {
data.Set("client_id", mapper.ClientId)
} else {
data.Set("client_scope_id", mapper.ClientScopeId)
}

data.Set("single_value_attribute", mapper.SingleValueAttribute)
data.Set("script", mapper.SamlScript)
data.Set("friendly_name", mapper.FriendlyName)
data.Set("saml_attribute_name", mapper.SamlAttributeName)
data.Set("saml_attribute_name_format", mapper.SamlAttributeNameFormat)
}

func resourceKeycloakSamlScriptProtocolMapperCreate(data *schema.ResourceData, meta interface{}) error {
keycloakClient := meta.(*keycloak.KeycloakClient)

samlScriptMapper := mapFromDataToSamlScriptProtocolMapper(data)

err := keycloakClient.ValidateSamlScriptProtocolMapper(samlScriptMapper)
if err != nil {
return err
}

err = keycloakClient.NewSamlScriptProtocolMapper(samlScriptMapper)
if err != nil {
return err
}

mapFromSamlScriptMapperToData(samlScriptMapper, data)

return resourceKeycloakSamlScriptProtocolMapperRead(data, meta)
}

func resourceKeycloakSamlScriptProtocolMapperRead(data *schema.ResourceData, meta interface{}) error {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
clientId := data.Get("client_id").(string)
clientScopeId := data.Get("client_scope_id").(string)

samlScriptMapper, err := keycloakClient.GetSamlScriptProtocolMapper(realmId, clientId, clientScopeId, data.Id())
if err != nil {
return handleNotFoundError(err, data)
}

mapFromSamlScriptMapperToData(samlScriptMapper, data)

return nil
}

func resourceKeycloakSamlScriptProtocolMapperUpdate(data *schema.ResourceData, meta interface{}) error {
keycloakClient := meta.(*keycloak.KeycloakClient)

samlScriptMapper := mapFromDataToSamlScriptProtocolMapper(data)

err := keycloakClient.ValidateSamlScriptProtocolMapper(samlScriptMapper)
if err != nil {
return err
}

err = keycloakClient.UpdateSamlScriptProtocolMapper(samlScriptMapper)
if err != nil {
return err
}

return resourceKeycloakSamlScriptProtocolMapperRead(data, meta)
}

func resourceKeycloakSamlScriptProtocolMapperDelete(data *schema.ResourceData, meta interface{}) error {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
clientId := data.Get("client_id").(string)
clientScopeId := data.Get("client_scope_id").(string)

return keycloakClient.DeleteSamlScriptProtocolMapper(realmId, clientId, clientScopeId, data.Id())
}
Loading

0 comments on commit 0dda29b

Please sign in to comment.