diff --git a/docs-old/resources/keycloak_ldap_group_mapper.md b/docs-old/resources/keycloak_ldap_group_mapper.md index 1b3efb4fa..7e225cdb2 100644 --- a/docs-old/resources/keycloak_ldap_group_mapper.md +++ b/docs-old/resources/keycloak_ldap_group_mapper.md @@ -70,6 +70,7 @@ The following arguments are supported: - `memberof_ldap_attribute` - (Optional) Specifies the name of the LDAP attribute on the LDAP user that contains the groups the user is a member of. Defaults to `memberOf`. - `mapped_group_attributes` - (Optional) Array of strings representing attributes on the LDAP group which will be mapped to attributes on the Keycloak group. - `drop_non_existing_groups_during_sync` - (Optional) When `true`, groups that no longer exist within LDAP will be dropped in Keycloak during sync. Defaults to `false`. +- `groups_path` - (Optional) Keycloak group path the LDAP groups are added to. For example if value '/Applications/App1' is used, then LDAP groups will be available in Keycloak under group 'App1', which is child of top level group 'Applications'. The configured group path must already exists in the Keycloak when creating this mapper. The default value is '/' so LDAP groups will be mapped to the Keycloak groups at the top level. ### Import diff --git a/docs/resources/ldap_group_mapper.md b/docs/resources/ldap_group_mapper.md index 4393d122f..a4aad0698 100644 --- a/docs/resources/ldap_group_mapper.md +++ b/docs/resources/ldap_group_mapper.md @@ -71,6 +71,7 @@ resource "keycloak_ldap_group_mapper" "ldap_group_mapper" { - `memberof_ldap_attribute` - (Optional) Specifies the name of the LDAP attribute on the LDAP user that contains the groups the user is a member of. Defaults to `memberOf`. - `mapped_group_attributes` - (Optional) Array of strings representing attributes on the LDAP group which will be mapped to attributes on the Keycloak group. - `drop_non_existing_groups_during_sync` - (Optional) When `true`, groups that no longer exist within LDAP will be dropped in Keycloak during sync. Defaults to `false`. +- `groups_path` - (Optional) Keycloak group path the LDAP groups are added to. For example if value `/Applications/App1` is used, then LDAP groups will be available in Keycloak under group `App1`, which is the child of top level group `Applications`. The configured group path must already exist in Keycloak when creating this mapper. ## Import diff --git a/keycloak/ldap_group_mapper.go b/keycloak/ldap_group_mapper.go index cc7d460d6..1495085ae 100644 --- a/keycloak/ldap_group_mapper.go +++ b/keycloak/ldap_group_mapper.go @@ -27,6 +27,8 @@ type LdapGroupMapper struct { MappedGroupAttributes []string DropNonExistingGroupsDuringSync bool + + GroupsPath string } func convertFromLdapGroupMapperToComponent(ldapGroupMapper *LdapGroupMapper) *component { @@ -67,6 +69,9 @@ func convertFromLdapGroupMapperToComponent(ldapGroupMapper *LdapGroupMapper) *co "drop.non.existing.groups.during.sync": { strconv.FormatBool(ldapGroupMapper.DropNonExistingGroupsDuringSync), }, + "groups.path": { + ldapGroupMapper.GroupsPath, + }, } if ldapGroupMapper.GroupsLdapFilter != "" { @@ -126,6 +131,7 @@ func convertFromComponentToLdapGroupMapper(component *component, realmId string) UserRolesRetrieveStrategy: component.getConfig("user.roles.retrieve.strategy"), MemberofLdapAttribute: component.getConfig("memberof.ldap.attribute"), DropNonExistingGroupsDuringSync: dropNonExistingGroupsDuringSync, + GroupsPath: component.getConfig("groups.path"), } if groupsLdapFilter := component.getConfig("groups.ldap.filter"); groupsLdapFilter != "" { diff --git a/provider/resource_keycloak_ldap_group_mapper.go b/provider/resource_keycloak_ldap_group_mapper.go index 6376f04e2..773ec0a11 100644 --- a/provider/resource_keycloak_ldap_group_mapper.go +++ b/provider/resource_keycloak_ldap_group_mapper.go @@ -111,11 +111,16 @@ func resourceKeycloakLdapGroupMapper() *schema.Resource { Optional: true, Default: false, }, + "groups_path": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, }, } } -func getLdapGroupMapperFromData(data *schema.ResourceData) *keycloak.LdapGroupMapper { +func getLdapGroupMapperFromData(keycloakClient *keycloak.KeycloakClient, data *schema.ResourceData) *keycloak.LdapGroupMapper { var groupObjectClasses []string for _, groupObjectClass := range data.Get("group_object_classes").([]interface{}) { @@ -128,7 +133,7 @@ func getLdapGroupMapperFromData(data *schema.ResourceData) *keycloak.LdapGroupMa mappedGroupAttributes = append(mappedGroupAttributes, mappedGroupAttribute.(string)) } - return &keycloak.LdapGroupMapper{ + mapper := &keycloak.LdapGroupMapper{ Id: data.Id(), Name: data.Get("name").(string), RealmId: data.Get("realm_id").(string), @@ -149,9 +154,15 @@ func getLdapGroupMapperFromData(data *schema.ResourceData) *keycloak.LdapGroupMa MappedGroupAttributes: mappedGroupAttributes, DropNonExistingGroupsDuringSync: data.Get("drop_non_existing_groups_during_sync").(bool), } + + if groupsPath, ok := data.GetOk("groups_path"); ok && keycloakClient.VersionIsGreaterThanOrEqualTo(keycloak.Version_11) { + mapper.GroupsPath = groupsPath.(string) + } + + return mapper } -func setLdapGroupMapperData(data *schema.ResourceData, ldapGroupMapper *keycloak.LdapGroupMapper) { +func setLdapGroupMapperData(keycloakClient *keycloak.KeycloakClient, data *schema.ResourceData, ldapGroupMapper *keycloak.LdapGroupMapper) { data.SetId(ldapGroupMapper.Id) data.Set("name", ldapGroupMapper.Name) @@ -172,12 +183,16 @@ func setLdapGroupMapperData(data *schema.ResourceData, ldapGroupMapper *keycloak data.Set("memberof_ldap_attribute", ldapGroupMapper.MemberofLdapAttribute) data.Set("mapped_group_attributes", ldapGroupMapper.MappedGroupAttributes) data.Set("drop_non_existing_groups_during_sync", ldapGroupMapper.DropNonExistingGroupsDuringSync) + + if ldapGroupMapper.GroupsPath != "" && keycloakClient.VersionIsGreaterThanOrEqualTo(keycloak.Version_11) { + data.Set("groups_path", ldapGroupMapper.GroupsPath) + } } func resourceKeycloakLdapGroupMapperCreate(data *schema.ResourceData, meta interface{}) error { keycloakClient := meta.(*keycloak.KeycloakClient) - ldapGroupMapper := getLdapGroupMapperFromData(data) + ldapGroupMapper := getLdapGroupMapperFromData(keycloakClient, data) err := keycloakClient.ValidateLdapGroupMapper(ldapGroupMapper) if err != nil { @@ -189,7 +204,7 @@ func resourceKeycloakLdapGroupMapperCreate(data *schema.ResourceData, meta inter return err } - setLdapGroupMapperData(data, ldapGroupMapper) + setLdapGroupMapperData(keycloakClient, data, ldapGroupMapper) return resourceKeycloakLdapGroupMapperRead(data, meta) } @@ -205,7 +220,7 @@ func resourceKeycloakLdapGroupMapperRead(data *schema.ResourceData, meta interfa return handleNotFoundError(err, data) } - setLdapGroupMapperData(data, ldapGroupMapper) + setLdapGroupMapperData(keycloakClient, data, ldapGroupMapper) return nil } @@ -213,7 +228,7 @@ func resourceKeycloakLdapGroupMapperRead(data *schema.ResourceData, meta interfa func resourceKeycloakLdapGroupMapperUpdate(data *schema.ResourceData, meta interface{}) error { keycloakClient := meta.(*keycloak.KeycloakClient) - ldapGroupMapper := getLdapGroupMapperFromData(data) + ldapGroupMapper := getLdapGroupMapperFromData(keycloakClient, data) err := keycloakClient.ValidateLdapGroupMapper(ldapGroupMapper) if err != nil { @@ -225,7 +240,7 @@ func resourceKeycloakLdapGroupMapperUpdate(data *schema.ResourceData, meta inter return err } - setLdapGroupMapperData(data, ldapGroupMapper) + setLdapGroupMapperData(keycloakClient, data, ldapGroupMapper) return nil } diff --git a/provider/resource_keycloak_ldap_group_mapper_test.go b/provider/resource_keycloak_ldap_group_mapper_test.go index 1375cde99..f198854bf 100644 --- a/provider/resource_keycloak_ldap_group_mapper_test.go +++ b/provider/resource_keycloak_ldap_group_mapper_test.go @@ -252,6 +252,30 @@ func TestAccKeycloakLdapGroupMapper_updateLdapUserFederationInPlace(t *testing.T }) } +func TestAccKeycloakLdapGroupMapper_groupsPath(t *testing.T) { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + if !keycloakClient.VersionIsGreaterThanOrEqualTo(keycloak.Version_11) { + t.Skip() + } + + realmName := "terraform-" + acctest.RandString(10) + groupName := "terraform-" + acctest.RandString(10) + groupMapperName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + ProviderFactories: testAccProviderFactories, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakLdapGroupMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakLdapGroupMapper_groupsPath(realmName, groupName, groupMapperName), + Check: testAccCheckKeycloakLdapGroupMapperExists("keycloak_ldap_group_mapper.group_mapper"), + }, + }, + }) +} + func testAccCheckKeycloakLdapGroupMapperExists(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { _, err := getLdapGroupMapperFromState(s, resourceName) @@ -401,6 +425,7 @@ resource "keycloak_ldap_group_mapper" "group_mapper" { membership_ldap_attribute = "member" membership_user_ldap_attribute = "cn" memberof_ldap_attribute = "memberOf" + groups_path = "/" } `, realm, groupMapperName, attr, val) } @@ -446,6 +471,7 @@ resource "keycloak_ldap_group_mapper" "group_mapper" { membership_ldap_attribute = "member" membership_user_ldap_attribute = "cn" memberof_ldap_attribute = "memberOf" + groups_path = "/" } `, realm, groupMapperName) } @@ -560,6 +586,7 @@ resource "keycloak_ldap_group_mapper" "group_mapper" { membership_ldap_attribute = "member" membership_user_ldap_attribute = "cn" memberof_ldap_attribute = "memberOf" + groups_path = "/" } `, realmOne, realmTwo, groupMapperName) } @@ -629,3 +656,53 @@ resource "keycloak_ldap_group_mapper" "group_mapper" { } `, realmOne, realmTwo, groupMapperName) } + +func testKeycloakLdapGroupMapper_groupsPath(realm, groupName, groupMapperName string) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%s" +} + +resource "keycloak_group" "group" { + realm_id = keycloak_realm.realm.id + name = "%s" +} + +resource "keycloak_ldap_user_federation" "openldap" { + name = "openldap" + realm_id = keycloak_realm.realm.id + + enabled = true + + 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" +} + +resource "keycloak_ldap_group_mapper" "group_mapper" { + name = "%s" + realm_id = keycloak_realm.realm.id + ldap_user_federation_id = keycloak_ldap_user_federation.openldap.id + + ldap_groups_dn = "dc=example,dc=org" + group_name_ldap_attribute = "cn" + group_object_classes = [ + "groupOfNames" + ] + membership_attribute_type = "DN" + membership_ldap_attribute = "member" + membership_user_ldap_attribute = "cn" + memberof_ldap_attribute = "memberOf" + + groups_path = keycloak_group.group.path +} + `, realm, groupName, groupMapperName) +}