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 openid_user_realm_role_protocol_mapper #159

Merged
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
90 changes: 90 additions & 0 deletions docs/resources/keycloak_openid_user_realm_role_protocol_mapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# keycloak_openid_user_realm_role_protocol_mapper

Allows for creating and managing user realm role protocol mappers within
Keycloak.

User realm role protocol mappers allow you to define a claim containing the list of the realm roles.
Protocol mappers can be defined for a single client, or they can
be defined for a client scope which can be shared between multiple different
clients.

### Example Usage (Client)

```hcl
resource "keycloak_realm" "realm" {
realm = "my-realm"
enabled = true
}

resource "keycloak_openid_client" "openid_client" {
realm_id = "${keycloak_realm.realm.id}"
client_id = "test-client"

name = "test client"
enabled = true

access_type = "CONFIDENTIAL"
valid_redirect_uris = [
"http://localhost:8080/openid-callback"
]
}

resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper" {
realm_id = "${keycloak_realm.realm.id}"
client_id = "${keycloak_openid_client.openid_client.id}"
name = "user-realm-role-mapper"

claim_name = "foo"
}
```

### Example Usage (Client Scope)

```hcl
resource "keycloak_realm" "realm" {
realm = "my-realm"
enabled = true
}

resource "keycloak_openid_client_scope" "client_scope" {
realm_id = "${keycloak_realm.realm.id}"
name = "test-client-scope"
}

resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_mapper" {
realm_id = "${keycloak_realm.realm.id}"
client_scope_id = "${keycloak_openid_client_scope.client_scope.id}"
name = "user-realm-role-mapper"

claim_name = "foo"
}
```

### Argument Reference

The following arguments are supported:

- `realm_id` - (Required) The realm this protocol mapper exists within.
- `client_id` - (Required if `client_scope_id` is not specified) The client this protocol mapper is attached to.
- `client_scope_id` - (Required if `client_id` is not specified) The client scope this protocol mapper is attached to.
- `name` - (Required) The display name of this protocol mapper in the GUI.
- `claim_name` - (Required) The name of the claim to insert into a token.
- `claim_value_type` - (Optional) The claim type used when serializing JSON tokens. Can be one of `String`, `long`, `int`, or `boolean`. Defaults to `String`.
- `multivalued` - (Optional) Indicates if attribute supports multiple values. If true, then the list of all values of this attribute will be set as claim. If false, then just first value will be set as claim. Defaults to `true`.
- `realm_role_prefix` - (Optional) A prefix for each Realm Role.
- `add_to_id_token` - (Optional) Indicates if the property should be added as a claim to the id token. Defaults to `true`.
- `add_to_access_token` - (Optional) Indicates if the property should be added as a claim to the access token. Defaults to `true`.
- `add_to_userinfo` - (Optional) Indicates if the property should be added as a claim to the UserInfo response body. Defaults to `true`.

### Import

Protocol mappers can be imported using one of the following formats:
- Client: `{{realm_id}}/client/{{client_keycloak_id}}/{{protocol_mapper_id}}`
- Client Scope: `{{realm_id}}/client-scope/{{client_scope_keycloak_id}}/{{protocol_mapper_id}}`

Example:

```bash
$ terraform import keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper my-realm/client/a7202154-8793-4656-b655-1dd18c181e14/71602afa-f7d1-4788-8c49-ef8fd00af0f4
$ terraform import keycloak_openid_user_realm_role_protocol_mapper.user_realm_role_mapper my-realm/client-scope/b799ea7e-73ee-4a73-990a-1eafebe8e20a/71602afa-f7d1-4788-8c49-ef8fd00af0f4
```
16 changes: 16 additions & 0 deletions example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,22 @@ resource "keycloak_openid_hardcoded_claim_protocol_mapper" "hardcoded_claim_clie
claim_value = "bar"
}

resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_client" {
name = "tf-test-open-id-user-realm-role-claim-protocol-mapper-client"
realm_id = "${keycloak_realm.test.id}"
client_id = "${keycloak_openid_client.test_client.id}"

claim_name = "foo"
}

resource "keycloak_openid_user_realm_role_protocol_mapper" "user_realm_role_client_scope" {
name = "tf-test-open-id-user-realm-role-protocol-mapper-client-scope"
realm_id = "${keycloak_realm.test.id}"
client_scope_id = "${keycloak_openid_client_scope.test_default_client_scope.id}"

claim_name = "foo"
}

resource "keycloak_openid_client" "bearer_only_client" {
client_id = "test-bearer-only-client"
name = "test-bearer-only-client"
Expand Down
133 changes: 133 additions & 0 deletions keycloak/openid_user_realm_role_protocol_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package keycloak

import (
"fmt"
"strconv"
)

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

AddToIdToken bool
AddToAccessToken bool
AddToUserInfo bool

RealmRolePrefix string
Multivalued bool
ClaimName string
ClaimValueType string
}

func (mapper *OpenIdUserRealmRoleProtocolMapper) convertToGenericProtocolMapper() *protocolMapper {
return &protocolMapper{
Id: mapper.Id,
Name: mapper.Name,
Protocol: "openid-connect",
ProtocolMapper: "oidc-usermodel-realm-role-mapper",
Config: map[string]string{
addToIdTokenField: strconv.FormatBool(mapper.AddToIdToken),
addToAccessTokenField: strconv.FormatBool(mapper.AddToAccessToken),
addToUserInfoField: strconv.FormatBool(mapper.AddToUserInfo),
claimNameField: mapper.ClaimName,
claimValueTypeField: mapper.ClaimValueType,
multivaluedField: strconv.FormatBool(mapper.Multivalued),
userRealmRoleMappingRolePrefixField: mapper.RealmRolePrefix,
},
}
}

func (protocolMapper *protocolMapper) convertToOpenIdUserRealmRoleProtocolMapper(realmId, clientId, clientScopeId string) (*OpenIdUserRealmRoleProtocolMapper, 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, err := strconv.ParseBool(protocolMapper.Config[multivaluedField])
if err != nil {
return nil, err
}

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

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

ClaimName: protocolMapper.Config[claimNameField],
ClaimValueType: protocolMapper.Config[claimValueTypeField],
Multivalued: multivalued,
RealmRolePrefix: protocolMapper.Config[userRealmRoleMappingRolePrefixField],
}, nil
}

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

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

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

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

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

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

func (keycloakClient *KeycloakClient) ValidateOpenIdUserRealmRoleProtocolMapper(mapper *OpenIdUserRealmRoleProtocolMapper) 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
}
31 changes: 16 additions & 15 deletions keycloak/protocol_mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,22 @@ type protocolMapper struct {
}

var (
addToAccessTokenField = "access.token.claim"
addToIdTokenField = "id.token.claim"
addToUserInfoField = "userinfo.token.claim"
attributeNameField = "attribute.name"
attributeNameFormatField = "attribute.nameformat"
claimNameField = "claim.name"
claimValueField = "claim.value"
claimValueTypeField = "jsonType.label"
friendlyNameField = "friendly.name"
fullPathField = "full.path"
includedClientAudienceField = "included.client.audience"
includedCustomAudienceField = "included.custom.audience"
multivaluedField = "multivalued"
userAttributeField = "user.attribute"
userPropertyField = "user.attribute"
addToAccessTokenField = "access.token.claim"
addToIdTokenField = "id.token.claim"
addToUserInfoField = "userinfo.token.claim"
attributeNameField = "attribute.name"
attributeNameFormatField = "attribute.nameformat"
claimNameField = "claim.name"
claimValueField = "claim.value"
claimValueTypeField = "jsonType.label"
friendlyNameField = "friendly.name"
fullPathField = "full.path"
includedClientAudienceField = "included.client.audience"
includedCustomAudienceField = "included.custom.audience"
multivaluedField = "multivalued"
userAttributeField = "user.attribute"
userPropertyField = "user.attribute"
userRealmRoleMappingRolePrefixField = "usermodel.realmRoleMapping.rolePrefix"
)

func protocolMapperPath(realmId, clientId, clientScopeId string) string {
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ nav:
- keycloak_openid_full_name_protocol_mapper: resources/keycloak_openid_full_name_protocol_mapper.md
- keycloak_openid_audience_protocol_mapper: resources/keycloak_openid_audience_protocol_mapper.md
- keycloak_openid_hardcoded_role_protocol_mapper: resources/keycloak_openid_hardcoded_role_protocol_mapper.md
- keycloak_openid_user_realm_role_protocol_mapper: resources/keycloak_openid_user_realm_role_protocol_mapper.md
- keycloak_saml_client: resources/keycloak_saml_client.md
- keycloak_saml_user_attribute_protocol_mapper: resources/keycloak_saml_user_attribute_protocol_mapper.md
- keycloak_saml_user_property_protocol_mapper: resources/keycloak_saml_user_property_protocol_mapper.md
Expand Down
Loading