Skip to content

Commit

Permalink
new resource: keycloak_openid_script_protocol_mapper (keycloak#453)
Browse files Browse the repository at this point in the history
  • Loading branch information
Felix Ehrenpfort authored and PaulGgithub committed Feb 4, 2021
1 parent 30ec9b4 commit 35793cd
Show file tree
Hide file tree
Showing 5 changed files with 729 additions and 0 deletions.
135 changes: 135 additions & 0 deletions keycloak/openid_script_protocol_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package keycloak

import (
"fmt"
"strconv"
)

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

AddToIdToken bool
AddToAccessToken bool
AddToUserInfo bool

Script string
ClaimName string
ClaimValueType string

Multivalued bool // indicates whether is this an array of attributes or a single attribute
}

func (mapper *OpenIdScriptProtocolMapper) convertToGenericProtocolMapper() *protocolMapper {
return &protocolMapper{
Id: mapper.Id,
Name: mapper.Name,
Protocol: "openid-connect",
ProtocolMapper: "oidc-script-based-protocol-mapper",
Config: map[string]string{
addToIdTokenField: strconv.FormatBool(mapper.AddToIdToken),
addToAccessTokenField: strconv.FormatBool(mapper.AddToAccessToken),
addToUserInfoField: strconv.FormatBool(mapper.AddToUserInfo),
scriptField: mapper.Script,
claimNameField: mapper.ClaimName,
claimValueTypeField: mapper.ClaimValueType,
multivaluedField: strconv.FormatBool(mapper.Multivalued),
},
}
}

func (protocolMapper *protocolMapper) convertToOpenIdScriptProtocolMapper(realmId, clientId, clientScopeId string) (*OpenIdScriptProtocolMapper, error) {
addToIdToken, err := strconv.ParseBool(protocolMapper.Config[addToIdTokenField])
if err != nil {
return nil, err
}

addToAccessToken, err := strconv.ParseBool(protocolMapper.Config[addToAccessTokenField])
if err != nil {
return nil, err
}

addToUserInfo, err := strconv.ParseBool(protocolMapper.Config[addToUserInfoField])
if err != nil {
return nil, err
}

// multivalued's default is "", this is an issue when importing an existing mapper
multivalued, err := parseBoolAndTreatEmptyStringAsFalse(protocolMapper.Config[multivaluedField])
if err != nil {
return nil, err
}

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

AddToIdToken: addToIdToken,
AddToAccessToken: addToAccessToken,
AddToUserInfo: addToUserInfo,

Script: protocolMapper.Config[scriptField],
ClaimName: protocolMapper.Config[claimNameField],
ClaimValueType: protocolMapper.Config[claimValueTypeField],
Multivalued: multivalued,
}, nil
}

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

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

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

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

func (keycloakClient *KeycloakClient) NewOpenIdScriptProtocolMapper(mapper *OpenIdScriptProtocolMapper) 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) UpdateOpenIdScriptProtocolMapper(mapper *OpenIdScriptProtocolMapper) error {
path := individualProtocolMapperPath(mapper.RealmId, mapper.ClientId, mapper.ClientScopeId, mapper.Id)

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

func (keycloakClient *KeycloakClient) ValidateOpenIdScriptProtocolMapper(mapper *OpenIdScriptProtocolMapper) 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 keycloak/protocol_mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var (
includedClientAudienceField = "included.client.audience"
includedCustomAudienceField = "included.custom.audience"
multivaluedField = "multivalued"
scriptField = "script"
userAttributeField = "user.attribute"
userPropertyField = "user.attribute"
userRealmRoleMappingRolePrefixField = "usermodel.realmRoleMapping.rolePrefix"
Expand Down
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
"keycloak_openid_user_realm_role_protocol_mapper": resourceKeycloakOpenIdUserRealmRoleProtocolMapper(),
"keycloak_openid_user_client_role_protocol_mapper": resourceKeycloakOpenIdUserClientRoleProtocolMapper(),
"keycloak_openid_user_session_note_protocol_mapper": resourceKeycloakOpenIdUserSessionNoteProtocolMapper(),
"keycloak_openid_script_protocol_mapper": resourceKeycloakOpenIdScriptProtocolMapper(),
"keycloak_openid_client_default_scopes": resourceKeycloakOpenidClientDefaultScopes(),
"keycloak_openid_client_optional_scopes": resourceKeycloakOpenidClientOptionalScopes(),
"keycloak_saml_client": resourceKeycloakSamlClient(),
Expand Down
193 changes: 193 additions & 0 deletions provider/resource_keycloak_openid_script_protocol_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
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 resourceKeycloakOpenIdScriptProtocolMapper() *schema.Resource {
return &schema.Resource{
Create: resourceKeycloakOpenIdScriptProtocolMapperCreate,
Read: resourceKeycloakOpenIdScriptProtocolMapperRead,
Update: resourceKeycloakOpenIdScriptProtocolMapperUpdate,
Delete: resourceKeycloakOpenIdScriptProtocolMapperDelete,
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,
Description: "A human-friendly name that will appear in the Keycloak console.",
},
"realm_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The realm id where the associated client or client scope exists.",
},
"client_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "The mapper's associated client. Cannot be used at the same time as client_scope_id.",
ConflictsWith: []string{"client_scope_id"},
},
"client_scope_id": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "The mapper's associated client scope. Cannot be used at the same time as client_id.",
ConflictsWith: []string{"client_id"},
},
"add_to_id_token": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Indicates if the attribute should be a claim in the id token.",
},
"add_to_access_token": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Indicates if the attribute should be a claim in the access token.",
},
"add_to_userinfo": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Indicates if the attribute should appear in the userinfo response body.",
},
"multivalued": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Indicates whether this attribute is a single value or an array of values.",
},
"script": {
Type: schema.TypeString,
Required: true,
Description: "JavaScript used to attach a value to a claim for OIDC tokens.",
},
"claim_name": {
Type: schema.TypeString,
Required: true,
},
"claim_value_type": {
Type: schema.TypeString,
Optional: true,
Description: "Claim type used when serializing tokens.",
Default: "String",
ValidateFunc: validation.StringInSlice([]string{"JSON", "String", "long", "int", "boolean"}, true),
},
},
}
}

func mapFromDataToOpenIdScriptProtocolMapper(data *schema.ResourceData) *keycloak.OpenIdScriptProtocolMapper {
return &keycloak.OpenIdScriptProtocolMapper{
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),
AddToIdToken: data.Get("add_to_id_token").(bool),
AddToAccessToken: data.Get("add_to_access_token").(bool),
AddToUserInfo: data.Get("add_to_userinfo").(bool),

Script: data.Get("script").(string),
ClaimName: data.Get("claim_name").(string),
ClaimValueType: data.Get("claim_value_type").(string),
Multivalued: data.Get("multivalued").(bool),
}
}

func mapFromOpenIdScriptMapperToData(mapper *keycloak.OpenIdScriptProtocolMapper, 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("add_to_id_token", mapper.AddToIdToken)
data.Set("add_to_access_token", mapper.AddToAccessToken)
data.Set("add_to_userinfo", mapper.AddToUserInfo)
data.Set("script", mapper.Script)
data.Set("claim_name", mapper.ClaimName)
data.Set("claim_value_type", mapper.ClaimValueType)
data.Set("multivalued", mapper.Multivalued)
}

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

openIdScriptMapper := mapFromDataToOpenIdScriptProtocolMapper(data)

err := keycloakClient.ValidateOpenIdScriptProtocolMapper(openIdScriptMapper)
if err != nil {
return err
}

err = keycloakClient.NewOpenIdScriptProtocolMapper(openIdScriptMapper)
if err != nil {
return err
}

mapFromOpenIdScriptMapperToData(openIdScriptMapper, data)

return resourceKeycloakOpenIdScriptProtocolMapperRead(data, meta)
}

func resourceKeycloakOpenIdScriptProtocolMapperRead(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)

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

mapFromOpenIdScriptMapperToData(openIdScriptMapper, data)

return nil
}

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

openIdScriptMapper := mapFromDataToOpenIdScriptProtocolMapper(data)

err := keycloakClient.ValidateOpenIdScriptProtocolMapper(openIdScriptMapper)
if err != nil {
return err
}

err = keycloakClient.UpdateOpenIdScriptProtocolMapper(openIdScriptMapper)
if err != nil {
return err
}

return resourceKeycloakOpenIdScriptProtocolMapperRead(data, meta)
}

func resourceKeycloakOpenIdScriptProtocolMapperDelete(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.DeleteOpenIdScriptProtocolMapper(realmId, clientId, clientScopeId, data.Id())
}
Loading

0 comments on commit 35793cd

Please sign in to comment.