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

add support for LDAP user attribute default value and binary attributes #735

Merged
merged 5 commits into from
Oct 6, 2022
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
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` is true 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
15 changes: 15 additions & 0 deletions keycloak/ldap_user_attribute_mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type LdapUserAttributeMapper struct {
ReadOnly bool
AlwaysReadValueFromLdap bool
UserModelAttribute string
AttributeDefaultValue string
IsBinaryAttribute bool
}

func convertFromLdapUserAttributeMapperToComponent(ldapUserAttributeMapper *LdapUserAttributeMapper) *component {
Expand All @@ -42,6 +44,12 @@ func convertFromLdapUserAttributeMapperToComponent(ldapUserAttributeMapper *Ldap
"user.model.attribute": {
ldapUserAttributeMapper.UserModelAttribute,
},
"attribute.default.value": {
ldapUserAttributeMapper.AttributeDefaultValue,
},
"is.binary.attribute": {
strconv.FormatBool(ldapUserAttributeMapper.IsBinaryAttribute),
},
},
}
}
Expand All @@ -62,6 +70,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 +86,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