diff --git a/keycloak/default_roles.go b/keycloak/default_roles.go index f07843d47..941661694 100644 --- a/keycloak/default_roles.go +++ b/keycloak/default_roles.go @@ -13,10 +13,24 @@ type DefaultRoles struct { func (keycloakClient *KeycloakClient) GetDefaultRoles(ctx context.Context, realmId, id string) ([]*Role, error) { var composites []*Role - err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/roles-by-id/%s/composites/realm", realmId, id), &composites, nil) + err := keycloakClient.get(ctx, fmt.Sprintf("/realms/%s/roles-by-id/%s/composites", realmId, id), &composites, nil) if err != nil { return nil, err } - return composites, nil } + +func (keycloakClient *KeycloakClient) GetQualifiedRoleName(ctx context.Context, realmId string, role *Role) (string, error) { + if !role.ClientRole { + return role.Name, nil + } + if role.ClientId != "" { + return fmt.Sprintf("%s/%s", role.ClientId, role.Name), nil + } + genericClient, err := keycloakClient.GetGenericClient(ctx, realmId, role.ContainerId) + if err != nil { + return "", err + } + role.ClientId = genericClient.ClientId + return fmt.Sprintf("%s/%s", role.ClientId, role.Name), nil +} diff --git a/provider/resource_keycloak_default_roles.go b/provider/resource_keycloak_default_roles.go index 30f8e0fa6..52eefdd87 100644 --- a/provider/resource_keycloak_default_roles.go +++ b/provider/resource_keycloak_default_roles.go @@ -4,9 +4,10 @@ import ( "context" "errors" "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "reflect" "strings" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/mrparkers/terraform-provider-keycloak/keycloak" ) @@ -29,37 +30,28 @@ func resourceKeycloakDefaultRoles() *schema.Resource { "default_roles": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, - Description: "Realm level roles assigned to new users.", + Description: "Realm level roles (name) assigned to new users.", Required: true, }, }, } } -func mapFromDataToDefaultRoles(data *schema.ResourceData) *keycloak.DefaultRoles { - defaultRolesList := make([]string, 0) +func getDefaultRolesFromData(data *schema.ResourceData) *keycloak.DefaultRoles { + var defaultRolesList []string if v, ok := data.GetOk("default_roles"); ok { for _, defaultRole := range v.(*schema.Set).List() { defaultRolesList = append(defaultRolesList, defaultRole.(string)) } } - defaultRoles := &keycloak.DefaultRoles{ Id: data.Id(), RealmId: data.Get("realm_id").(string), DefaultRoles: defaultRolesList, } - return defaultRoles } -func mapFromDefaultRolesToData(data *schema.ResourceData, defaultRoles *keycloak.DefaultRoles) { - data.SetId(defaultRoles.Id) - - data.Set("realm_id", defaultRoles.RealmId) - data.Set("default_roles", defaultRoles.DefaultRoles) -} - func resourceKeycloakDefaultRolesRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { keycloakClient := meta.(*keycloak.KeycloakClient) @@ -71,15 +63,24 @@ func resourceKeycloakDefaultRolesRead(ctx context.Context, data *schema.Resource return handleNotFoundError(ctx, err, data) } - defaultRoleNamesList := getDefaultRoleNames(composites) + var defaultRoleNames []string + for _, composite := range composites { + name, err := keycloakClient.GetQualifiedRoleName(ctx, realmId, composite) + if err != nil { + return handleNotFoundError(ctx, err, data) + } + defaultRoleNames = append(defaultRoleNames, name) + } defaultRoles := &keycloak.DefaultRoles{ Id: id, RealmId: realmId, - DefaultRoles: defaultRoleNamesList, + DefaultRoles: defaultRoleNames, } - mapFromDefaultRolesToData(data, defaultRoles) + data.SetId(defaultRoles.Id) + data.Set("realm_id", defaultRoles.RealmId) + data.Set("default_roles", defaultRoles.DefaultRoles) return nil } @@ -96,55 +97,75 @@ func resourceKeycloakDefaultRolesReconcile(ctx context.Context, data *schema.Res return diag.FromErr(err) } - defaultRoles := mapFromDataToDefaultRoles(data) + local := getDefaultRolesFromData(data) + localDefaultRoles := make(map[string]struct{}, len(local.DefaultRoles)) + for _, defaultRole := range local.DefaultRoles { + localDefaultRoles[defaultRole] = struct{}{} + } - realm, err := keycloakClient.GetRealm(ctx, defaultRoles.RealmId) + realm, err := keycloakClient.GetRealm(ctx, local.RealmId) if err != nil { return diag.FromErr(err) } data.SetId(realm.DefaultRole.Id) - composites, err := keycloakClient.GetDefaultRoles(ctx, defaultRoles.RealmId, realm.DefaultRole.Id) + composites, err := keycloakClient.GetDefaultRoles(ctx, local.RealmId, realm.DefaultRole.Id) if err != nil { return diag.FromErr(err) } - defaultRoleNamesList := getDefaultRoleNames(composites) - rolesList, err := keycloakClient.GetRealmRoles(ctx, defaultRoles.RealmId) - if err != nil { - return diag.FromErr(err) + currentDefaultRoles := make([]string, len(composites)) + defaultRolesMap := make(map[string]*keycloak.Role, len(composites)) + + for i, composite := range composites { + name, err := keycloakClient.GetQualifiedRoleName(ctx, local.RealmId, composite) + if err != nil { + return diag.FromErr(err) + } + currentDefaultRoles[i] = name + defaultRolesMap[name] = composite } // skip if actual default roles in keycloak same as we want - if roleListsEqual(defaultRoleNamesList, defaultRoles.DefaultRoles) { + if reflect.DeepEqual(currentDefaultRoles, local.DefaultRoles) { return nil } - var putList, deleteList []*keycloak.Role - for _, roleName := range defaultRoles.DefaultRoles { - if !roleListContains(defaultRoleNamesList, roleName) { - defaultRoles, err := getRoleByNameFromList(rolesList, roleName) + getRole := func(roleName string) (*keycloak.Role, error) { + var clientId string + if parts := strings.Split(roleName, "/"); len(parts) == 2 { + client, err := keycloakClient.GetGenericClientByClientId(ctx, local.RealmId, parts[0]) if err != nil { - return diag.FromErr(err) + return nil, err } - putList = append(putList, defaultRoles) + clientId, roleName = client.Id, parts[1] } + return keycloakClient.GetRoleByName(ctx, local.RealmId, clientId, roleName) } - for _, roleName := range defaultRoleNamesList { - if !roleListContains(defaultRoles.DefaultRoles, roleName) { - defaultRoles, err := getRoleByNameFromList(rolesList, roleName) + + var putList, deleteList []*keycloak.Role + + for _, roleName := range local.DefaultRoles { + if _, ok := defaultRolesMap[roleName]; !ok { + role, err := getRole(roleName) if err != nil { return diag.FromErr(err) } - deleteList = append(deleteList, defaultRoles) + putList = append(putList, role) + } + } + + for _, roleName := range currentDefaultRoles { + if _, ok := localDefaultRoles[roleName]; !ok { + deleteList = append(deleteList, defaultRolesMap[roleName]) } } // apply if not empty if len(putList) > 0 { role := &keycloak.Role{ - RealmId: defaultRoles.RealmId, + RealmId: local.RealmId, Id: realm.DefaultRole.Id, } err := keycloakClient.AddCompositesToRole(ctx, role, putList) @@ -152,9 +173,10 @@ func resourceKeycloakDefaultRolesReconcile(ctx context.Context, data *schema.Res return diag.FromErr(err) } } + if len(deleteList) > 0 { role := &keycloak.Role{ - RealmId: defaultRoles.RealmId, + RealmId: local.RealmId, Id: realm.DefaultRole.Id, } err := keycloakClient.RemoveCompositesFromRole(ctx, role, deleteList) @@ -200,7 +222,7 @@ func resourceKeycloakDefaultRolesImport(ctx context.Context, d *schema.ResourceD parts := strings.Split(d.Id(), "/") if len(parts) != 2 { - return nil, fmt.Errorf("Invalid import. Supported import format: {{realm}}/{{defaultRoleId}}.") + return nil, fmt.Errorf("invalid import, supported import format: {{realm}}/{{defaultRoleId}}") } _, err := keycloakClient.GetDefaultRoles(ctx, parts[0], parts[1]) @@ -218,41 +240,3 @@ func resourceKeycloakDefaultRolesImport(ctx context.Context, d *schema.ResourceD return []*schema.ResourceData{d}, nil } - -func getDefaultRoleNames(roles []*keycloak.Role) []string { - var defaultRolesNames []string - for _, defaultRoles := range roles { - defaultRolesNames = append(defaultRolesNames, defaultRoles.Name) - } - return defaultRolesNames -} - -func getRoleByNameFromList(defaultRoles []*keycloak.Role, name string) (*keycloak.Role, error) { - for _, element := range defaultRoles { - if element.Name == name { - return element, nil - } - } - return nil, fmt.Errorf("defaultRoles not found by name") -} - -func roleListContains(s []string, e string) bool { - for _, a := range s { - if a == e { - return true - } - } - return false -} - -func roleListsEqual(a, b []string) bool { - if len(a) != len(b) { - return false - } - for i, v := range a { - if v != b[i] { - return false - } - } - return true -} diff --git a/provider/resource_keycloak_default_roles_test.go b/provider/resource_keycloak_default_roles_test.go index 56ad76aef..5af3d0ad7 100644 --- a/provider/resource_keycloak_default_roles_test.go +++ b/provider/resource_keycloak_default_roles_test.go @@ -2,11 +2,12 @@ package provider import ( "fmt" + "testing" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/mrparkers/terraform-provider-keycloak/keycloak" - "testing" ) func TestAccKeycloakDefaultRoles_basic(t *testing.T) { @@ -84,12 +85,8 @@ func testAccCheckKeycloakDefaultRolesDestroy(realmId string) resource.TestCheckF return fmt.Errorf("error getting defaultRoles with id %s: %s", realm.DefaultRole.Id, err) } - defaultRoles := getDefaultRoleNames(composites) - if err != nil { - return err - } - if len(defaultRoles) != 0 { - return fmt.Errorf("realm %s still has %d default roles, expected zero", realmId, len(defaultRoles)) + if len(composites) != 0 { + return fmt.Errorf("realm %s still has %d default roles, expected zero", realmId, len(composites)) } return nil @@ -110,7 +107,14 @@ func getKeycloakDefaultRolesFromState(s *terraform.State, resourceName string) ( return nil, fmt.Errorf("error getting defaultRoles with id %s: %s", id, err) } - defaultRoleNamesList := getDefaultRoleNames(composites) + var defaultRoleNamesList []string + for _, composite := range composites { + name, err := keycloakClient.GetQualifiedRoleName(testCtx, realm, composite) + if err != nil { + return nil, fmt.Errorf("error getting qualified name for role id %s: %s", composite.Id, err) + } + defaultRoleNamesList = append(defaultRoleNamesList, name) + } defaultRoles := &keycloak.DefaultRoles{ Id: id,