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

feat: support role IDs in default role resource #764

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
18 changes: 16 additions & 2 deletions keycloak/default_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
134 changes: 59 additions & 75 deletions provider/resource_keycloak_default_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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)

Expand All @@ -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
}
Expand All @@ -96,65 +97,86 @@ 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)
if err != nil {
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)
Expand Down Expand Up @@ -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])
Expand All @@ -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
}
20 changes: 12 additions & 8 deletions provider/resource_keycloak_default_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
Loading