From b8e9e7c46a682ce7f8c1303865bceef5dbd72f3a Mon Sep 17 00:00:00 2001 From: Pierre Erraud Date: Mon, 30 Mar 2020 17:10:40 +0200 Subject: [PATCH] feat: add ldap hardcoded group mapper, add hardcoded-ldap-role-mapper and msad-lds-user-account-control-mapper mappers to GetLdapUserFederationMappers --- keycloak/ldap_hardcoded_group_mapper.go | 75 ++++++++ keycloak/ldap_user_federation.go | 13 +- provider/provider.go | 1 + ...ce_keycloak_ldap_hardcoded_group_mapper.go | 127 +++++++++++++ ...ycloak_ldap_hardcoded_group_mapper_test.go | 170 ++++++++++++++++++ 5 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 keycloak/ldap_hardcoded_group_mapper.go create mode 100644 provider/resource_keycloak_ldap_hardcoded_group_mapper.go create mode 100644 provider/resource_keycloak_ldap_hardcoded_group_mapper_test.go diff --git a/keycloak/ldap_hardcoded_group_mapper.go b/keycloak/ldap_hardcoded_group_mapper.go new file mode 100644 index 000000000..cda80039b --- /dev/null +++ b/keycloak/ldap_hardcoded_group_mapper.go @@ -0,0 +1,75 @@ +package keycloak + +import "fmt" + +type LdapHardcodedGroupMapper struct { + Id string + Name string + RealmId string + LdapUserFederationId string + Group string +} + +func convertFromLdapHardcodedGroupMapperToComponent(ldapMapper *LdapHardcodedGroupMapper) *component { + return &component{ + Id: ldapMapper.Id, + Name: ldapMapper.Name, + ProviderId: "hardcoded-ldap-group-mapper", + ProviderType: "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + ParentId: ldapMapper.LdapUserFederationId, + + Config: map[string][]string{ + "group": { + ldapMapper.Group, + }, + }, + } +} + +func convertFromComponentToLdapHardcodedGroupMapper(component *component, realmId string) *LdapHardcodedGroupMapper { + return &LdapHardcodedGroupMapper{ + Id: component.Id, + Name: component.Name, + RealmId: realmId, + LdapUserFederationId: component.ParentId, + + Group: component.getConfig("group"), + } +} + +func (keycloakClient *KeycloakClient) ValidateLdapHardcodedGroupMapper(ldapMapper *LdapHardcodedGroupMapper) error { + if len(ldapMapper.Group) == 0 { + return fmt.Errorf("validation error: hardcoded group name must not be empty") + } + return nil +} + +func (keycloakClient *KeycloakClient) NewLdapHardcodedGroupMapper(ldapMapper *LdapHardcodedGroupMapper) error { + _, location, err := keycloakClient.post(fmt.Sprintf("/realms/%s/components", ldapMapper.RealmId), convertFromLdapHardcodedGroupMapperToComponent(ldapMapper)) + if err != nil { + return err + } + + ldapMapper.Id = getIdFromLocationHeader(location) + + return nil +} + +func (keycloakClient *KeycloakClient) GetLdapHardcodedGroupMapper(realmId, id string) (*LdapHardcodedGroupMapper, error) { + var component *component + + err := keycloakClient.get(fmt.Sprintf("/realms/%s/components/%s", realmId, id), &component, nil) + if err != nil { + return nil, err + } + + return convertFromComponentToLdapHardcodedGroupMapper(component, realmId), nil +} + +func (keycloakClient *KeycloakClient) UpdateLdapHardcodedGroupMapper(ldapMapper *LdapHardcodedGroupMapper) error { + return keycloakClient.put(fmt.Sprintf("/realms/%s/components/%s", ldapMapper.RealmId, ldapMapper.Id), convertFromLdapHardcodedGroupMapperToComponent(ldapMapper)) +} + +func (keycloakClient *KeycloakClient) DeleteLdapHardcodedGroupMapper(realmId, id string) error { + return keycloakClient.delete(fmt.Sprintf("/realms/%s/components/%s", realmId, id), nil) +} diff --git a/keycloak/ldap_user_federation.go b/keycloak/ldap_user_federation.go index 15c775df2..15cf6c981 100644 --- a/keycloak/ldap_user_federation.go +++ b/keycloak/ldap_user_federation.go @@ -332,7 +332,6 @@ func (keycloakClient *KeycloakClient) GetLdapUserFederationMappers(realmId, id s if err != nil { return nil, err } - for _, component := range components { switch component.ProviderId { case "full-name-ldap-mapper": @@ -347,6 +346,18 @@ func (keycloakClient *KeycloakClient) GetLdapUserFederationMappers(realmId, id s return nil, err } ldapUserFederationMappers = append(ldapUserFederationMappers, mapper) + case "hardcoded-ldap-group-mapper": + mapper := convertFromComponentToLdapHardcodedGroupMapper(component, realmId) + ldapUserFederationMappers = append(ldapUserFederationMappers, mapper) + case "hardcoded-ldap-role-mapper": + mapper := convertFromComponentToLdapHardcodedRoleMapper(component, realmId) + ldapUserFederationMappers = append(ldapUserFederationMappers, mapper) + case "msad-lds-user-account-control-mapper": + mapper, err := convertFromComponentToLdapMsadLdsUserAccountControlMapper(component, realmId) + if err != nil { + return nil, err + } + ldapUserFederationMappers = append(ldapUserFederationMappers, mapper) case "msad-user-account-control-mapper": mapper, err := convertFromComponentToLdapMsadUserAccountControlMapper(component, realmId) if err != nil { diff --git a/provider/provider.go b/provider/provider.go index 14153444b..319cbac6f 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -31,6 +31,7 @@ func KeycloakProvider() *schema.Provider { "keycloak_ldap_user_attribute_mapper": resourceKeycloakLdapUserAttributeMapper(), "keycloak_ldap_group_mapper": resourceKeycloakLdapGroupMapper(), "keycloak_ldap_hardcoded_role_mapper": resourceKeycloakLdapHardcodedRoleMapper(), + "keycloak_ldap_hardcoded_group_mapper": resourceKeycloakLdapHardcodedGroupMapper(), "keycloak_ldap_msad_user_account_control_mapper": resourceKeycloakLdapMsadUserAccountControlMapper(), "keycloak_ldap_msad_lds_user_account_control_mapper": resourceKeycloakLdapMsadLdsUserAccountControlMapper(), "keycloak_ldap_full_name_mapper": resourceKeycloakLdapFullNameMapper(), diff --git a/provider/resource_keycloak_ldap_hardcoded_group_mapper.go b/provider/resource_keycloak_ldap_hardcoded_group_mapper.go new file mode 100644 index 000000000..62e7ea281 --- /dev/null +++ b/provider/resource_keycloak_ldap_hardcoded_group_mapper.go @@ -0,0 +1,127 @@ +package provider + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" +) + +func resourceKeycloakLdapHardcodedGroupMapper() *schema.Resource { + return &schema.Resource{ + Create: resourceKeycloakLdapHardcodedGroupMapperCreate, + Read: resourceKeycloakLdapHardcodedGroupMapperRead, + Update: resourceKeycloakLdapHardcodedGroupMapperUpdate, + Delete: resourceKeycloakLdapHardcodedGroupMapperDelete, + // This resource can be imported using {{realm}}/{{provider_id}}/{{mapper_id}}. The Provider and Mapper IDs are displayed in the GUI + Importer: &schema.ResourceImporter{ + State: 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.", + }, + "group": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Group to grant to user.", + }, + }, + } +} + +func getLdapHardcodedGroupMapperFromData(data *schema.ResourceData) *keycloak.LdapHardcodedGroupMapper { + return &keycloak.LdapHardcodedGroupMapper{ + Id: data.Id(), + Name: data.Get("name").(string), + RealmId: data.Get("realm_id").(string), + LdapUserFederationId: data.Get("ldap_user_federation_id").(string), + Group: data.Get("group").(string), + } +} + +func setLdapHardcodedGroupMapperData(data *schema.ResourceData, ldapMapper *keycloak.LdapHardcodedGroupMapper) { + 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("group", ldapMapper.Group) +} + +func resourceKeycloakLdapHardcodedGroupMapperCreate(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + + ldapMapper := getLdapHardcodedGroupMapperFromData(data) + + err := keycloakClient.ValidateLdapHardcodedGroupMapper(ldapMapper) + if err != nil { + return err + } + + err = keycloakClient.NewLdapHardcodedGroupMapper(ldapMapper) + if err != nil { + return err + } + + setLdapHardcodedGroupMapperData(data, ldapMapper) + + return resourceKeycloakLdapHardcodedGroupMapperRead(data, meta) +} + +func resourceKeycloakLdapHardcodedGroupMapperRead(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + + realmId := data.Get("realm_id").(string) + id := data.Id() + + ldapMapper, err := keycloakClient.GetLdapHardcodedGroupMapper(realmId, id) + if err != nil { + return handleNotFoundError(err, data) + } + + setLdapHardcodedGroupMapperData(data, ldapMapper) + + return nil +} + +func resourceKeycloakLdapHardcodedGroupMapperUpdate(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + + ldapMapper := getLdapHardcodedGroupMapperFromData(data) + + err := keycloakClient.ValidateLdapHardcodedGroupMapper(ldapMapper) + if err != nil { + return err + } + + err = keycloakClient.UpdateLdapHardcodedGroupMapper(ldapMapper) + if err != nil { + return err + } + + setLdapHardcodedGroupMapperData(data, ldapMapper) + + return nil +} + +func resourceKeycloakLdapHardcodedGroupMapperDelete(data *schema.ResourceData, meta interface{}) error { + keycloakClient := meta.(*keycloak.KeycloakClient) + + realmId := data.Get("realm_id").(string) + id := data.Id() + + return keycloakClient.DeleteLdapHardcodedGroupMapper(realmId, id) +} diff --git a/provider/resource_keycloak_ldap_hardcoded_group_mapper_test.go b/provider/resource_keycloak_ldap_hardcoded_group_mapper_test.go new file mode 100644 index 000000000..0e5eaeecf --- /dev/null +++ b/provider/resource_keycloak_ldap_hardcoded_group_mapper_test.go @@ -0,0 +1,170 @@ +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/mrparkers/terraform-provider-keycloak/keycloak" +) + +func TestAccKeycloakLdapHardcodedGroupMapper_basic(t *testing.T) { + realmName := "terraform-" + acctest.RandString(10) + groupMapperName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakLdapHardcodedGroupMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakLdapHardcodedGroupMapper(realmName, groupMapperName), + Check: testAccCheckKeycloakLdapHardcodedGroupMapperExists("keycloak_ldap_hardcoded_group_mapper.hardcoded_group_mapper"), + }, + { + ResourceName: "keycloak_ldap_hardcoded_group_mapper.hardcoded_group_mapper", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: getLdapGenericMapperImportId("keycloak_ldap_hardcoded_group_mapper.hardcoded_group_mapper"), + }, + }, + }) +} + +func TestAccKeycloakLdapHardcodedGroupMapper_createAfterManualDestroy(t *testing.T) { + var mapper = &keycloak.LdapHardcodedGroupMapper{} + + realmName := "terraform-" + acctest.RandString(10) + groupMapperName := "terraform-" + acctest.RandString(10) + + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckKeycloakLdapHardcodedGroupMapperDestroy(), + Steps: []resource.TestStep{ + { + Config: testKeycloakLdapHardcodedGroupMapper(realmName, groupMapperName), + Check: testAccCheckKeycloakLdapHardcodedGroupMapperFetch("keycloak_ldap_hardcoded_group_mapper.hardcoded_group_mapper", mapper), + }, + { + PreConfig: func() { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + err := keycloakClient.DeleteLdapHardcodedGroupMapper(mapper.RealmId, mapper.Id) + if err != nil { + t.Fatal(err) + } + }, + Config: testKeycloakLdapHardcodedGroupMapper(realmName, groupMapperName), + Check: testAccCheckKeycloakLdapHardcodedGroupMapperExists("keycloak_ldap_hardcoded_group_mapper.hardcoded_group_mapper"), + }, + }, + }) +} + +func testAccCheckKeycloakLdapHardcodedGroupMapperExists(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + _, err := getLdapHardcodedGroupMapperFromState(s, resourceName) + if err != nil { + return err + } + + return nil + } +} + +func testAccCheckKeycloakLdapHardcodedGroupMapperFetch(resourceName string, mapper *keycloak.LdapHardcodedGroupMapper) resource.TestCheckFunc { + return func(s *terraform.State) error { + fetchedMapper, err := getLdapHardcodedGroupMapperFromState(s, resourceName) + if err != nil { + return err + } + + mapper.Id = fetchedMapper.Id + mapper.RealmId = fetchedMapper.RealmId + + return nil + } +} + +func testAccCheckKeycloakLdapHardcodedGroupMapperDestroy() resource.TestCheckFunc { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "keycloak_ldap_hardcoded_group_mapper" { + continue + } + + id := rs.Primary.ID + realm := rs.Primary.Attributes["realm_id"] + + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + ldapMapper, _ := keycloakClient.GetLdapHardcodedGroupMapper(realm, id) + if ldapMapper != nil { + return fmt.Errorf("ldap hardcoded group mapper with id %s still exists", id) + } + } + + return nil + } +} + +func getLdapHardcodedGroupMapperFromState(s *terraform.State, resourceName string) (*keycloak.LdapHardcodedGroupMapper, error) { + keycloakClient := testAccProvider.Meta().(*keycloak.KeycloakClient) + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("resource not found: %s", resourceName) + } + + id := rs.Primary.ID + realm := rs.Primary.Attributes["realm_id"] + + ldapMapper, err := keycloakClient.GetLdapHardcodedGroupMapper(realm, id) + if err != nil { + return nil, fmt.Errorf("error getting ldap group mapper with id %s: %s", id, err) + } + + return ldapMapper, nil +} + +func testKeycloakLdapHardcodedGroupMapper(realm, groupMapperName string) string { + return fmt.Sprintf(` +resource "keycloak_realm" "realm" { + realm = "%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", + "organizationalGroup" + ] + connection_url = "ldap://openldap" + users_dn = "dc=example,dc=org" + bind_dn = "cn=admin,dc=example,dc=org" + bind_credential = "admin" +} + +resource "keycloak_group" "hardcoded_group_mapper_test" { + realm_id = keycloak_realm.realm.id + name = "hardcoded-group-test" +} + +resource "keycloak_ldap_hardcoded_group_mapper" "hardcoded_group_mapper" { + name = "%s" + realm_id = keycloak_realm.realm.id + ldap_user_federation_id = keycloak_ldap_user_federation.openldap.id + group = keycloak_group.hardcoded_group_mapper_test.name +} + `, realm, groupMapperName) +}