Skip to content

Commit

Permalink
Feature/423 add hardcoded attribute mapper (#950)
Browse files Browse the repository at this point in the history
* initial feature

Signed-off-by: angeloxx <angeloxx@angeloxx.it>

* Tested with success

Signed-off-by: angeloxx <angeloxx@angeloxx.it>

* typo in test

Signed-off-by: angeloxx <angeloxx@angeloxx.it>

* fix record

Signed-off-by: angeloxx <angeloxx@angeloxx.it>

* move to the new org

Signed-off-by: angeloxx <angeloxx@angeloxx.it>

---------

Signed-off-by: angeloxx <angeloxx@angeloxx.it>
  • Loading branch information
angeloxx authored Jan 27, 2025
1 parent 83ddf6a commit 0448f48
Show file tree
Hide file tree
Showing 5 changed files with 438 additions and 0 deletions.
66 changes: 66 additions & 0 deletions docs/resources/hardcoded_attribute_mapper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
page_title: "keycloak_hardcoded_attribute_mapper Resource"
---

# keycloak_hardcoded_attribute_mapper Resource

Allows for creating and managing hardcoded attribute mappers for Keycloak users federated via LDAP.

The user model hardcoded attribute mapper will set the specified value to the attribute.


## Example Usage

```hcl
resource "keycloak_realm" "realm" {
realm = "my-realm"
enabled = true
}
resource "keycloak_ldap_user_federation" "ldap_user_federation" {
name = "openldap"
realm_id = keycloak_realm.realm.id
username_ldap_attribute = "cn"
rdn_ldap_attribute = "cn"
uuid_ldap_attribute = "entryDN"
user_object_classes = [
"simpleSecurityObject",
"organizationalRole"
]
connection_url = "ldap://openldap"
users_dn = "dc=example,dc=org"
bind_dn = "cn=admin,dc=example,dc=org"
bind_credential = "admin"
sync_registrations = true
}
resource "keycloak_hardcoded_attribute_mapper" "email_verified" {
realm_id = keycloak_realm.realm.id
ldap_user_federation_id = keycloak_ldap_user_federation.ldap_user_federation.id
name = "email_verified"
attribute_name = "email_verified"
attribute_value = "true"
}
```

## Argument Reference

- `realm_id` - (Required) The realm that this LDAP mapper will exist in.
- `ldap_user_federation_id` - (Required) The ID of the LDAP user federation provider to attach this mapper to.
- `name` - (Required) Display name of this mapper when displayed in the console.
- `attribute_name` - (Required) The name of the user model attribute to set.
- `attribute_value` - (Required) The value to set to model attribute. You can hardcode any value like 'foo'.

## Import

LDAP mappers can be imported using the format `{{realm_id}}/{{ldap_user_federation_id}}/{{attribute__mapper_id}}`.
The ID of the LDAP user federation provider and the mapper can be found within the Keycloak GUI, and they are typically GUIDs.

Example:

```bash
$ terraform import keycloak_hardcoded_attribute_mapper.email_verified my-realm/af2a6ca3-e4d7-49c3-b08b-1b3c70b4b860/3d923ece-1a91-4bf7-adaf-3b82f2a12b67
```
76 changes: 76 additions & 0 deletions keycloak/hardcoded_attribute_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package keycloak

import (
"context"
"fmt"
)

type HardcodedAttributeMapper struct {
Id string
Name string
RealmId string
LdapUserFederationId string
AttributeName string
AttributeValue string
}

func convertFromHardcodedAttributeMapperToComponent(hardcodedMapper *HardcodedAttributeMapper) *component {
return &component{
Id: hardcodedMapper.Id,
Name: hardcodedMapper.Name,
ProviderId: "hardcoded-attribute-mapper",
ProviderType: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper",
ParentId: hardcodedMapper.LdapUserFederationId,

Config: map[string][]string{
"user.model.attribute": {
hardcodedMapper.AttributeName,
},
"attribute.value": {
hardcodedMapper.AttributeValue,
},
},
}
}

func convertFromComponentToHardcodedAttributeMapper(component *component, realmId string) *HardcodedAttributeMapper {
return &HardcodedAttributeMapper{
Id: component.Id,
Name: component.Name,
RealmId: realmId,
LdapUserFederationId: component.ParentId,

AttributeName: component.getConfig("user.model.attribute"),
AttributeValue: component.getConfig("attribute.value"),
}
}

func (keycloakClient *KeycloakClient) NewHardcodedAttributeMapper(ctx context.Context, hardcodedMapper *HardcodedAttributeMapper) error {
_, location, err := keycloakClient.post(ctx, fmt.Sprintf("/realms/%s/components", hardcodedMapper.RealmId), convertFromHardcodedAttributeMapperToComponent(hardcodedMapper))
if err != nil {
return err
}

hardcodedMapper.Id = getIdFromLocationHeader(location)

return nil
}

func (keycloakClient *KeycloakClient) GetHardcodedAttributeMapper(ctx context.Context, realmId, id string) (*HardcodedAttributeMapper, error) {
var component *component

err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/components/%s", realmId, id), &component, nil)
if err != nil {
return nil, err
}

return convertFromComponentToHardcodedAttributeMapper(component, realmId), nil
}

func (keycloakClient *KeycloakClient) UpdateHardcodedAttributeMapper(ctx context.Context, hardcodedMapper *HardcodedAttributeMapper) error {
return keycloakClient.put(ctx, fmt.Sprintf("/realms/%s/components/%s", hardcodedMapper.RealmId, hardcodedMapper.Id), convertFromHardcodedAttributeMapperToComponent(hardcodedMapper))
}

func (keycloakClient *KeycloakClient) DeleteHardcodedAttributeMapper(ctx context.Context, realmId, id string) error {
return keycloakClient.delete(ctx, fmt.Sprintf("/realms/%s/components/%s", realmId, id), nil)
}
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ func KeycloakProvider(client *keycloak.KeycloakClient) *schema.Provider {
"keycloak_openid_client_scope": resourceKeycloakOpenidClientScope(),
"keycloak_ldap_user_federation": resourceKeycloakLdapUserFederation(),
"keycloak_ldap_user_attribute_mapper": resourceKeycloakLdapUserAttributeMapper(),
"keycloak_hardcoded_attribute_mapper": resourceKeycloakHardcodedAttributeMapper(),
"keycloak_ldap_group_mapper": resourceKeycloakLdapGroupMapper(),
"keycloak_ldap_role_mapper": resourceKeycloakLdapRoleMapper(),
"keycloak_ldap_hardcoded_role_mapper": resourceKeycloakLdapHardcodedRoleMapper(),
Expand Down
130 changes: 130 additions & 0 deletions provider/resource_keycloak_hardcoded_attribute_mapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package provider

import (
"context"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/keycloak/terraform-provider-keycloak/keycloak"
)

func resourceKeycloakHardcodedAttributeMapper() *schema.Resource {
return &schema.Resource{
CreateContext: resourceKeycloakHardcodedAttributeMapperCreate,
ReadContext: resourceKeycloakHardcodedAttributeMapperRead,
UpdateContext: resourceKeycloakHardcodedAttributeMapperUpdate,
DeleteContext: resourceKeycloakHardcodedAttributeMapperDelete,
// This resource can be imported using {{realm}}/{{provider_id}}/{{mapper_id}}. The Provider and Mapper IDs are displayed in the GUI
Importer: &schema.ResourceImporter{
StateContext: resourceKeycloakLdapGenericMapperImport,
},
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: "Display name of the mapper when displayed in the console.",
},
"realm_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The realm in which the ldap user federation provider exists.",
},
"ldap_user_federation_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The ldap user federation provider to attach this mapper to.",
},
"attribute_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Name of the user schema attribute",
},
"attribute_value": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Value of the attribute. You can hardcode any value like 'foo'",
},
},
}
}

func getHardcodedAttributeMapperFromData(data *schema.ResourceData) *keycloak.HardcodedAttributeMapper {
return &keycloak.HardcodedAttributeMapper{
Id: data.Id(),
Name: data.Get("name").(string),
RealmId: data.Get("realm_id").(string),
LdapUserFederationId: data.Get("ldap_user_federation_id").(string),
AttributeName: data.Get("attribute_name").(string),
AttributeValue: data.Get("attribute_value").(string),
}
}

func setHardcodedAttributeMapperData(data *schema.ResourceData, ldapMapper *keycloak.HardcodedAttributeMapper) {
data.SetId(ldapMapper.Id)
data.Set("name", ldapMapper.Name)
data.Set("realm_id", ldapMapper.RealmId)
data.Set("ldap_user_federation_id", ldapMapper.LdapUserFederationId)
data.Set("attribute_name", ldapMapper.AttributeName)
data.Set("attribute_value", ldapMapper.AttributeValue)
}

func resourceKeycloakHardcodedAttributeMapperCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)

ldapMapper := getHardcodedAttributeMapperFromData(data)

err := keycloakClient.NewHardcodedAttributeMapper(ctx, ldapMapper)
if err != nil {
return diag.FromErr(err)
}

setHardcodedAttributeMapperData(data, ldapMapper)

return resourceKeycloakHardcodedAttributeMapperRead(ctx, data, meta)
}

func resourceKeycloakHardcodedAttributeMapperRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
id := data.Id()

ldapMapper, err := keycloakClient.GetHardcodedAttributeMapper(ctx, realmId, id)
if err != nil {
return handleNotFoundError(ctx, err, data)
}

setHardcodedAttributeMapperData(data, ldapMapper)

return diag.FromErr(nil)
}

func resourceKeycloakHardcodedAttributeMapperUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)

ldapMapper := getHardcodedAttributeMapperFromData(data)

err := keycloakClient.UpdateHardcodedAttributeMapper(ctx, ldapMapper)
if err != nil {
return diag.FromErr(err)
}

setHardcodedAttributeMapperData(data, ldapMapper)

return diag.FromErr(nil)
}

func resourceKeycloakHardcodedAttributeMapperDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
id := data.Id()

err := keycloakClient.DeleteHardcodedAttributeMapper(ctx, realmId, id)

return diag.FromErr(err)
}
Loading

0 comments on commit 0448f48

Please sign in to comment.