Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new resource: keycloak_openid_script_protocol_mapper #453

Merged
merged 3 commits into from
Jan 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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