Skip to content

Commit

Permalink
add support for LDAP user attribute default value and binary attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
joeyberkovitz committed Oct 4, 2022
1 parent ef388b7 commit dec05a6
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 42 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ terraform-provider-keycloak

.idea/
.terraform/
terraform.d/
.terraform.lock.hcl
terraform.tfstate*

.gradle/
Expand Down
2 changes: 2 additions & 0 deletions docs/resources/ldap_user_attribute_mapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ resource "keycloak_ldap_user_attribute_mapper" "ldap_user_attribute_mapper" {
- `read_only` - (Optional) When `true`, this attribute is not saved back to LDAP when the user attribute is updated in Keycloak. Defaults to `false`.
- `always_read_value_from_ldap` - (Optional) When `true`, the value fetched from LDAP will override the value stored in Keycloak. Defaults to `false`.
- `is_mandatory_in_ldap` - (Optional) When `true`, this attribute must exist in LDAP. Defaults to `false`.
- `attribute_default_value` - (Optional) Default value to set in LDAP if is_mandatory_in_ldap and the value is empty.
- `is_binary_attribute` - (Optional) Should be true for binary LDAP attributes.

## Import

Expand Down
71 changes: 42 additions & 29 deletions example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ terraform {
}

provider "keycloak" {
client_id = "terraform"
client_secret = "884e0f95-0f42-4a63-9b1f-94274655669e"
url = "http://localhost:8080"
client_id = "terraform"
client_secret = "884e0f95-0f42-4a63-9b1f-94274655669e"
url = "http://localhost:8080"
additional_headers = {
foo = "bar"
}
Expand Down Expand Up @@ -85,7 +85,7 @@ resource "keycloak_realm" "test" {
web_authn_policy {
relying_party_entity_name = "Example"
relying_party_id = "keycloak.example.com"
signature_algorithms = [
signature_algorithms = [
"ES256",
"RS256"
]
Expand All @@ -94,7 +94,7 @@ resource "keycloak_realm" "test" {
web_authn_passwordless_policy {
relying_party_entity_name = "Example"
relying_party_id = "keycloak.example.com"
signature_algorithms = [
signature_algorithms = [
"ES256",
"RS256"
]
Expand Down Expand Up @@ -189,7 +189,7 @@ resource "keycloak_group" "baz" {
}

resource "keycloak_default_groups" "default" {
realm_id = keycloak_realm.test.id
realm_id = keycloak_realm.test.id
group_ids = [
keycloak_group.baz.id
]
Expand Down Expand Up @@ -310,7 +310,7 @@ resource "keycloak_ldap_role_mapper" "ldap_role_mapper" {

ldap_roles_dn = "dc=example,dc=org"
role_name_ldap_attribute = "cn"
role_object_classes = [
role_object_classes = [
"groupOfNames"
]
membership_attribute_type = "DN"
Expand All @@ -331,6 +331,19 @@ resource "keycloak_ldap_user_attribute_mapper" "description_attr_mapper" {
always_read_value_from_ldap = false
}

resource "keycloak_ldap_user_attribute_mapper" "default_attr_mapper" {
name = "defaultval-mapper"
realm_id = keycloak_ldap_user_federation.openldap.realm_id
ldap_user_federation_id = keycloak_ldap_user_federation.openldap.id

user_model_attribute = "defaultval"
ldap_attribute = "defaultval"

always_read_value_from_ldap = false
is_mandatory_in_ldap = true
attribute_default_value = "testing"
}

resource "keycloak_ldap_group_mapper" "group_mapper" {
name = "group mapper"
realm_id = keycloak_ldap_user_federation.openldap.realm_id
Expand Down Expand Up @@ -603,7 +616,7 @@ resource "keycloak_saml_user_property_protocol_mapper" "saml_user_property_mappe
saml_attribute_name_format = "Unspecified"
}

resource keycloak_oidc_identity_provider oidc {
resource "keycloak_oidc_identity_provider" "oidc" {
realm = keycloak_realm.test.id
alias = "oidc"
authorization_url = "https://example.com/auth"
Expand All @@ -615,7 +628,7 @@ resource keycloak_oidc_identity_provider oidc {
gui_order = 1
}

resource keycloak_oidc_google_identity_provider google {
resource "keycloak_oidc_google_identity_provider" "google" {
realm = keycloak_realm.test.id
client_id = "myclientid.apps.googleusercontent.com"
client_secret = "myclientsecret"
Expand Down Expand Up @@ -643,7 +656,7 @@ resource keycloak_oidc_google_identity_provider google {
// }
//}

resource keycloak_attribute_importer_identity_provider_mapper oidc {
resource "keycloak_attribute_importer_identity_provider_mapper" "oidc" {
realm = keycloak_realm.test.id
name = "attributeImporter"
claim_name = "upn"
Expand All @@ -656,7 +669,7 @@ resource keycloak_attribute_importer_identity_provider_mapper oidc {
}
}

resource keycloak_attribute_to_role_identity_provider_mapper oidc {
resource "keycloak_attribute_to_role_identity_provider_mapper" "oidc" {
realm = keycloak_realm.test.id
name = "attributeToRole"
claim_name = "upn"
Expand All @@ -670,7 +683,7 @@ resource keycloak_attribute_to_role_identity_provider_mapper oidc {
}
}

resource keycloak_user_template_importer_identity_provider_mapper oidc {
resource "keycloak_user_template_importer_identity_provider_mapper" "oidc" {
realm = keycloak_realm.test.id
name = "userTemplate"
identity_provider_alias = keycloak_oidc_identity_provider.oidc.alias
Expand All @@ -682,7 +695,7 @@ resource keycloak_user_template_importer_identity_provider_mapper oidc {
}
}

resource keycloak_hardcoded_role_identity_provider_mapper oidc {
resource "keycloak_hardcoded_role_identity_provider_mapper" "oidc" {
realm = keycloak_realm.test.id
name = "hardcodedRole"
identity_provider_alias = keycloak_oidc_identity_provider.oidc.alias
Expand All @@ -694,7 +707,7 @@ resource keycloak_hardcoded_role_identity_provider_mapper oidc {
}
}

resource keycloak_hardcoded_attribute_identity_provider_mapper oidc {
resource "keycloak_hardcoded_attribute_identity_provider_mapper" "oidc" {
realm = keycloak_realm.test.id
name = "hardcodedUserSessionAttribute"
identity_provider_alias = keycloak_oidc_identity_provider.oidc.alias
Expand All @@ -708,7 +721,7 @@ resource keycloak_hardcoded_attribute_identity_provider_mapper oidc {
}
}

resource keycloak_saml_identity_provider saml {
resource "keycloak_saml_identity_provider" "saml" {
realm = keycloak_realm.test.id
alias = "saml"
entity_id = "https://example.com/entity_id"
Expand All @@ -717,7 +730,7 @@ resource keycloak_saml_identity_provider saml {
gui_order = 3
}

resource keycloak_attribute_importer_identity_provider_mapper saml {
resource "keycloak_attribute_importer_identity_provider_mapper" "saml" {
realm = keycloak_realm.test.id
name = "Attribute: email"
attribute_name = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
Expand All @@ -730,7 +743,7 @@ resource keycloak_attribute_importer_identity_provider_mapper saml {
}
}

resource keycloak_attribute_to_role_identity_provider_mapper saml {
resource "keycloak_attribute_to_role_identity_provider_mapper" "saml" {
realm = keycloak_realm.test.id
name = "attributeToRole"
attribute_name = "upn"
Expand All @@ -744,7 +757,7 @@ resource keycloak_attribute_to_role_identity_provider_mapper saml {
}
}

resource keycloak_user_template_importer_identity_provider_mapper saml {
resource "keycloak_user_template_importer_identity_provider_mapper" "saml" {
realm = keycloak_realm.test.id
name = "userTemplate"
identity_provider_alias = keycloak_saml_identity_provider.saml.alias
Expand All @@ -756,7 +769,7 @@ resource keycloak_user_template_importer_identity_provider_mapper saml {
}
}

resource keycloak_hardcoded_role_identity_provider_mapper saml {
resource "keycloak_hardcoded_role_identity_provider_mapper" "saml" {
realm = keycloak_realm.test.id
name = "hardcodedRole"
identity_provider_alias = keycloak_saml_identity_provider.saml.alias
Expand All @@ -768,7 +781,7 @@ resource keycloak_hardcoded_role_identity_provider_mapper saml {
}
}

resource keycloak_hardcoded_attribute_identity_provider_mapper saml {
resource "keycloak_hardcoded_attribute_identity_provider_mapper" "saml" {
realm = keycloak_realm.test.id
name = "hardcodedAttribute"
identity_provider_alias = keycloak_saml_identity_provider.saml.alias
Expand All @@ -782,15 +795,15 @@ resource keycloak_hardcoded_attribute_identity_provider_mapper saml {
}
}

resource keycloak_saml_identity_provider saml_custom {
resource "keycloak_saml_identity_provider" "saml_custom" {
realm = keycloak_realm.test.id
alias = "custom_saml"
provider_id = "saml"
entity_id = "https://example.com/entity_id"
single_sign_on_service_url = "https://example.com/auth"
sync_mode = "FORCE"
gui_order = 4
extra_config = {
extra_config = {
mycustomAttribute = "aValue"
}
}
Expand Down Expand Up @@ -828,7 +841,7 @@ resource "keycloak_openid_client" "test_client_auth" {
client_secret = "secret"
}

resource keycloak_openid_client test_open_id_client_with_consent_text {
resource "keycloak_openid_client" "test_open_id_client_with_consent_text" {
client_id = "test_open_id_client_with_consent_text"
name = "test_open_id_client_with_consent_text"
realm_id = keycloak_realm.test.id
Expand Down Expand Up @@ -941,7 +954,7 @@ resource "keycloak_authentication_execution" "browser-copy-cookie" {
parent_flow_alias = keycloak_authentication_flow.browser-copy-flow.alias
authenticator = "auth-cookie"
requirement = "ALTERNATIVE"
depends_on = [
depends_on = [
keycloak_authentication_execution.browser-copy-kerberos
]
}
Expand All @@ -958,7 +971,7 @@ resource "keycloak_authentication_execution" "browser-copy-idp-redirect" {
parent_flow_alias = keycloak_authentication_flow.browser-copy-flow.alias
authenticator = "identity-provider-redirector"
requirement = "ALTERNATIVE"
depends_on = [
depends_on = [
keycloak_authentication_execution.browser-copy-cookie
]
}
Expand All @@ -968,7 +981,7 @@ resource "keycloak_authentication_subflow" "browser-copy-flow-forms" {
parent_flow_alias = keycloak_authentication_flow.browser-copy-flow.alias
alias = "browser-copy-flow-forms"
requirement = "ALTERNATIVE"
depends_on = [
depends_on = [
keycloak_authentication_execution.browser-copy-idp-redirect
]
}
Expand All @@ -985,7 +998,7 @@ resource "keycloak_authentication_execution" "browser-copy-otp" {
parent_flow_alias = keycloak_authentication_subflow.browser-copy-flow-forms.alias
authenticator = "auth-otp-form"
requirement = "REQUIRED"
depends_on = [
depends_on = [
keycloak_authentication_execution.browser-copy-auth-username-password-form
]
}
Expand All @@ -994,7 +1007,7 @@ resource "keycloak_authentication_execution_config" "config" {
realm_id = keycloak_realm.test.id
execution_id = keycloak_authentication_execution.browser-copy-idp-redirect.id
alias = "idp-XXX-config"
config = {
config = {
defaultProvider = "idp-XXX"
}
}
Expand Down Expand Up @@ -1036,7 +1049,7 @@ resource "keycloak_realm_user_profile" "userprofile" {
}

validator {
name = "pattern"
name = "pattern"
config = {
pattern = "^[a-z]+$"
error_message = "Nope"
Expand Down
2 changes: 1 addition & 1 deletion keycloak/authentication_subflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type AuthenticationSubFlow struct {
Requirement string `json:"-"`
}

//each subflow creates a flow and an execution under the covers
// each subflow creates a flow and an execution under the covers
type authenticationSubFlowCreate struct {
Alias string `json:"alias"`
Type string `json:"type"` //providerId of the flow
Expand Down
4 changes: 2 additions & 2 deletions keycloak/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ func (keycloakClient *KeycloakClient) GetGroupByName(ctx context.Context, realmI
}

/*
Find group by name in groups returned by /groups?search=${group_name}
If there are multiple groups match the name, it will return the first one it found, using DFS algorithm
Find group by name in groups returned by /groups?search=${group_name}
If there are multiple groups match the name, it will return the first one it found, using DFS algorithm
*/
func getGroupByDFS(groupName string, groups []*Group) *Group {
for _, group := range groups {
Expand Down
3 changes: 2 additions & 1 deletion keycloak/keycloak_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ func (keycloakClient *KeycloakClient) addRequestHeaders(request *http.Request) {
}
}

/**
/*
*
Sends an HTTP request and refreshes credentials on 403 or 401 errors
*/
func (keycloakClient *KeycloakClient) sendRequest(ctx context.Context, request *http.Request, body []byte) ([]byte, string, error) {
Expand Down
18 changes: 18 additions & 0 deletions keycloak/ldap_user_attribute_mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,16 @@ type LdapUserAttributeMapper struct {
ReadOnly bool
AlwaysReadValueFromLdap bool
UserModelAttribute string
AttributeDefaultValue string
IsBinaryAttribute bool
}

func convertFromLdapUserAttributeMapperToComponent(ldapUserAttributeMapper *LdapUserAttributeMapper) *component {
defaultValue := []string{}
if ldapUserAttributeMapper.AttributeDefaultValue != "" {
defaultValue = []string{ldapUserAttributeMapper.AttributeDefaultValue}
}

return &component{
Id: ldapUserAttributeMapper.Id,
Name: ldapUserAttributeMapper.Name,
Expand All @@ -42,6 +49,10 @@ func convertFromLdapUserAttributeMapperToComponent(ldapUserAttributeMapper *Ldap
"user.model.attribute": {
ldapUserAttributeMapper.UserModelAttribute,
},
"attribute.default.value": defaultValue,
"is.binary.attribute": {
strconv.FormatBool(ldapUserAttributeMapper.IsBinaryAttribute),
},
},
}
}
Expand All @@ -62,6 +73,11 @@ func convertFromComponentToLdapUserAttributeMapper(component *component, realmId
return nil, err
}

isBinaryAttribute, err := parseBoolAndTreatEmptyStringAsFalse(component.getConfig("is.binary.attribute"))
if err != nil {
return nil, err
}

return &LdapUserAttributeMapper{
Id: component.Id,
Name: component.Name,
Expand All @@ -73,6 +89,8 @@ func convertFromComponentToLdapUserAttributeMapper(component *component, realmId
ReadOnly: readOnly,
AlwaysReadValueFromLdap: alwaysReadValueFromLdap,
UserModelAttribute: component.getConfig("user.model.attribute"),
AttributeDefaultValue: component.getConfig("attribute.default.value"),
IsBinaryAttribute: isBinaryAttribute,
}, nil
}

Expand Down
14 changes: 7 additions & 7 deletions makefile
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor)

MAKEFLAGS += --silent
GOOS?=darwin
GOARCH?=amd64

build:
go build -o terraform-provider-keycloak

build-example: build
mkdir -p example/.terraform/plugins/terraform.local/mrparkers/keycloak/3.0.0/darwin_amd64
mkdir -p example/terraform.d/plugins/terraform.local/mrparkers/keycloak/3.0.0/darwin_amd64
cp terraform-provider-keycloak example/.terraform/plugins/terraform.local/mrparkers/keycloak/3.0.0/darwin_amd64/
cp terraform-provider-keycloak example/terraform.d/plugins/terraform.local/mrparkers/keycloak/3.0.0/darwin_amd64/
mkdir -p example/.terraform/plugins/terraform.local/mrparkers/keycloak/3.0.0/$(GOOS)_$(GOARCH)
mkdir -p example/terraform.d/plugins/terraform.local/mrparkers/keycloak/3.0.0/$(GOOS)_$(GOARCH)
cp terraform-provider-keycloak example/.terraform/plugins/terraform.local/mrparkers/keycloak/3.0.0/$(GOOS)_$(GOARCH)/
cp terraform-provider-keycloak example/terraform.d/plugins/terraform.local/mrparkers/keycloak/3.0.0/$(GOOS)_$(GOARCH)/

local: deps
docker-compose up --build -d
docker compose up --build -d
./scripts/wait-for-local-keycloak.sh
./scripts/create-terraform-client.sh

Expand Down
Loading

0 comments on commit dec05a6

Please sign in to comment.