Skip to content

Commit

Permalink
Fix user & userprofile tests (#1042)
Browse files Browse the repository at this point in the history
* feat(user-profile): Implemented UserProfile.UnmanagedAttributePolicy

Signed-off-by: Dennis Kniep <[email protected]>

* test(user): fix skipped user tests

Signed-off-by: Dennis Kniep <[email protected]>

* test(user-profile): Fixed checkUserProfileEnabled

Signed-off-by: Dennis Kniep <[email protected]>

---------

Signed-off-by: Dennis Kniep <[email protected]>
  • Loading branch information
denniskniep authored Dec 31, 2024
1 parent b35eb9f commit a68ebf1
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 61 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ KEYCLOAK_CLIENT_ID=terraform \
KEYCLOAK_CLIENT_SECRET=884e0f95-0f42-4a63-9b1f-94274655669e \
KEYCLOAK_CLIENT_TIMEOUT=5 \
KEYCLOAK_REALM=master \
KEYCLOAK_TEST_PASSWORD_GRANT=true \
KEYCLOAK_URL="http://localhost:8080" \
make testacc
```
Expand Down
11 changes: 3 additions & 8 deletions docs/resources/realm_user_profile.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ page_title: "keycloak_realm_user_profile Resource"
Allows for managing Realm User Profiles within Keycloak.

A user profile defines a schema for representing user attributes and how they are managed within a realm.
This is a preview feature, hence not fully supported and disabled by default.
To enable it, start the server with one of the following flags:
- WildFly distribution: `-Dkeycloak.profile.feature.declarative_user_profile=enabled`
- Quarkus distribution: `--features=preview` or `--features=declarative-user-profile`

Information for Keycloak versions < 24:
The realm linked to the `keycloak_realm_user_profile` resource must have the user profile feature enabled.
It can be done via the administration UI, or by setting the `userProfileEnabled` realm attribute to `true`.

Expand All @@ -20,14 +17,11 @@ It can be done via the administration UI, or by setting the `userProfileEnabled`
```hcl
resource "keycloak_realm" "realm" {
realm = "my-realm"
attributes = {
userProfileEnabled = true
}
}
resource "keycloak_realm_user_profile" "userprofile" {
realm_id = keycloak_realm.my_realm.id
unmanaged_attribute_policy = "ENABLED"
attribute {
name = "field1"
Expand Down Expand Up @@ -98,6 +92,7 @@ resource "keycloak_realm_user_profile" "userprofile" {
- `realm_id` - (Required) The ID of the realm the user profile applies to.
- `attribute` - (Optional) An ordered list of [attributes](#attribute-arguments).
- `group` - (Optional) A list of [groups](#group-arguments).
- `unmanaged_attribute_policy` - (Optional) Unmanaged attributes are user attributes not explicitly defined in the user profile configuration. By default, unmanaged attributes are not enabled. Value could be one of `DISABLED`, `ENABLED`, `ADMIN_EDIT` or `ADMIN_VIEW`. If value is not specified it means `DISABLED`

### Attribute Arguments

Expand Down
5 changes: 3 additions & 2 deletions keycloak/realm_user_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ type RealmUserProfileGroup struct {
}

type RealmUserProfile struct {
Attributes []*RealmUserProfileAttribute `json:"attributes"`
Groups []*RealmUserProfileGroup `json:"groups,omitempty"`
Attributes []*RealmUserProfileAttribute `json:"attributes"`
Groups []*RealmUserProfileGroup `json:"groups,omitempty"`
UnmanagedAttributePolicy *string `json:"unmanagedAttributePolicy,omitempty"`
}

func (keycloakClient *KeycloakClient) UpdateRealmUserProfile(ctx context.Context, realmId string, realmUserProfile *RealmUserProfile) error {
Expand Down
16 changes: 16 additions & 0 deletions keycloak/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,19 @@ func (keycloakClient *KeycloakClient) VersionIsLessThanOrEqualTo(ctx context.Con

return keycloakClient.version.LessThanOrEqual(v), nil
}

func (keycloakClient *KeycloakClient) VersionIsLessThan(ctx context.Context, versionString Version) (bool, error) {
if keycloakClient.version == nil {
err := keycloakClient.login(ctx)
if err != nil {
return false, err
}
}

v, err := version.NewVersion(string(versionString))
if err != nil {
return false, nil
}

return keycloakClient.version.LessThan(v), nil
}
1 change: 0 additions & 1 deletion provider/data_source_keycloak_user_realm_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
)

func TestAccKeycloakDataSourceUserRoles(t *testing.T) {
t.Parallel()
username := acctest.RandomWithPrefix("tf-acc")
email := acctest.RandomWithPrefix("tf-acc") + "@fakedomain.com"
realmRoleName := acctest.RandomWithPrefix("tf-acc")
Expand Down
2 changes: 0 additions & 2 deletions provider/data_source_keycloak_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import (
)

func TestAccKeycloakDataSourceUser(t *testing.T) {
t.Parallel()

username := acctest.RandomWithPrefix("tf-acc")
email := acctest.RandomWithPrefix("tf-acc") + "@fakedomain.com"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
)

func TestAccKeycloakOpenidClientAuthorizationUserPolicy(t *testing.T) {
t.Parallel()
clientId := acctest.RandomWithPrefix("tf-acc")
username := acctest.RandomWithPrefix("tf-acc")
email := acctest.RandomWithPrefix("tf-acc") + "@fakedomain.com"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
)

func TestAccKeycloakOpenidClientPermission_basic(t *testing.T) {
t.Parallel()
clientId := acctest.RandomWithPrefix("tf-acc")
username := acctest.RandomWithPrefix("tf-acc")
email := acctest.RandomWithPrefix("tf-acc") + "@fakedomain.com"
Expand Down
122 changes: 110 additions & 12 deletions provider/resource_keycloak_realm_user_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,24 @@ package provider
import (
"context"
"encoding/json"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/keycloak/terraform-provider-keycloak/keycloak"
)

const (
DISABLED string = "DISABLED"
ENABLED = "ENABLED"
ADMIN_VIEW = "ADMIN_VIEW"
ADMIN_EDIT = "ADMIN_EDIT"
)

const USER_PROFILE_ENABLED string = "userProfileEnabled"

func resourceKeycloakRealmUserProfile() *schema.Resource {
return &schema.Resource{
CreateContext: resourceKeycloakRealmUserProfileCreate,
Expand Down Expand Up @@ -125,6 +136,12 @@ func resourceKeycloakRealmUserProfile() *schema.Resource {
},
},
},
"unmanaged_attribute_policy": {
Type: schema.TypeString,
Default: DISABLED,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{DISABLED, ENABLED, ADMIN_VIEW, ADMIN_EDIT}, false),
},
},
}
}
Expand Down Expand Up @@ -287,13 +304,23 @@ func getRealmUserProfileGroupsFromData(lst []interface{}) []*keycloak.RealmUserP
return groups
}

func getRealmUserProfileFromData(data *schema.ResourceData) *keycloak.RealmUserProfile {
func getRealmUserProfileFromData(ctx context.Context, keycloakClient *keycloak.KeycloakClient, data *schema.ResourceData) (*keycloak.RealmUserProfile, error) {
realmUserProfile := &keycloak.RealmUserProfile{}

realmUserProfile.Attributes = getRealmUserProfileAttributesFromData(data.Get("attribute").([]interface{}))
realmUserProfile.Groups = getRealmUserProfileGroupsFromData(data.Get("group").(*schema.Set).List())

return realmUserProfile
versionOk, err := keycloakClient.VersionIsGreaterThanOrEqualTo(ctx, keycloak.Version_24)
if err != nil {
return nil, err
}

unmanagedAttr, unmanagedAttrOk := data.Get("unmanaged_attribute_policy").(string)
if versionOk && unmanagedAttrOk && unmanagedAttr != DISABLED {
realmUserProfile.UnmanagedAttributePolicy = &unmanagedAttr
}

return realmUserProfile, nil
}

func getRealmUserProfileAttributeData(attr *keycloak.RealmUserProfileAttribute) map[string]interface{} {
Expand Down Expand Up @@ -388,7 +415,7 @@ func getRealmUserProfileGroupData(group *keycloak.RealmUserProfileGroup) map[str
return groupData
}

func setRealmUserProfileData(data *schema.ResourceData, realmUserProfile *keycloak.RealmUserProfile) {
func setRealmUserProfileData(ctx context.Context, keycloakClient *keycloak.KeycloakClient, data *schema.ResourceData, realmUserProfile *keycloak.RealmUserProfile) error {
attributes := make([]interface{}, 0)
for _, attr := range realmUserProfile.Attributes {
attributes = append(attributes, getRealmUserProfileAttributeData(attr))
Expand All @@ -400,16 +427,39 @@ func setRealmUserProfileData(data *schema.ResourceData, realmUserProfile *keyclo
groups = append(groups, getRealmUserProfileGroupData(group))
}
data.Set("group", groups)

versionOk, err := keycloakClient.VersionIsGreaterThanOrEqualTo(ctx, keycloak.Version_24)
if err != nil {
return err
}

if versionOk {
if realmUserProfile.UnmanagedAttributePolicy != nil {
data.Set("unmanaged_attribute_policy", *realmUserProfile.UnmanagedAttributePolicy)
} else {
data.Set("unmanaged_attribute_policy", DISABLED)
}
}
return nil
}

func resourceKeycloakRealmUserProfileCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics {
keycloakClient := meta.(*keycloak.KeycloakClient)
realmId := data.Get("realm_id").(string)

err := checkUserProfileEnabled(ctx, keycloakClient, realmId)
if err != nil {
return diag.FromErr(err)
}

data.SetId(realmId)

realmUserProfile := getRealmUserProfileFromData(data)
realmUserProfile, err := getRealmUserProfileFromData(ctx, keycloakClient, data)
if err != nil {
return diag.FromErr(err)
}

err := keycloakClient.UpdateRealmUserProfile(ctx, realmId, realmUserProfile)
err = keycloakClient.UpdateRealmUserProfile(ctx, realmId, realmUserProfile)
if err != nil {
return diag.FromErr(err)
}
Expand All @@ -427,7 +477,10 @@ func resourceKeycloakRealmUserProfileRead(ctx context.Context, data *schema.Reso
return handleNotFoundError(ctx, err, data)
}

setRealmUserProfileData(data, realmUserProfile)
err = setRealmUserProfileData(ctx, keycloakClient, data, realmUserProfile)
if err != nil {
return diag.FromErr(err)
}

return nil
}
Expand All @@ -436,10 +489,16 @@ func resourceKeycloakRealmUserProfileDelete(ctx context.Context, data *schema.Re
keycloakClient := meta.(*keycloak.KeycloakClient)
realmId := data.Get("realm_id").(string)

err := checkUserProfileEnabled(ctx, keycloakClient, realmId)
if err != nil {
return diag.FromErr(err)
}

// The realm user profile cannot be deleted, so instead we set it back to its "zero" values.
realmUserProfile := &keycloak.RealmUserProfile{
Attributes: []*keycloak.RealmUserProfileAttribute{},
Groups: []*keycloak.RealmUserProfileGroup{},
Attributes: []*keycloak.RealmUserProfileAttribute{},
Groups: []*keycloak.RealmUserProfileGroup{},
UnmanagedAttributePolicy: nil,
}

if ok, _ := keycloakClient.VersionIsGreaterThanOrEqualTo(ctx, keycloak.Version_23); ok {
Expand All @@ -450,7 +509,7 @@ func resourceKeycloakRealmUserProfileDelete(ctx context.Context, data *schema.Re
}
}

err := keycloakClient.UpdateRealmUserProfile(ctx, realmId, realmUserProfile)
err = keycloakClient.UpdateRealmUserProfile(ctx, realmId, realmUserProfile)
if err != nil {
return diag.FromErr(err)
}
Expand All @@ -462,14 +521,53 @@ func resourceKeycloakRealmUserProfileUpdate(ctx context.Context, data *schema.Re
keycloakClient := meta.(*keycloak.KeycloakClient)

realmId := data.Get("realm_id").(string)
realmUserProfile := getRealmUserProfileFromData(data)
err := checkUserProfileEnabled(ctx, keycloakClient, realmId)
if err != nil {
return diag.FromErr(err)
}

err := keycloakClient.UpdateRealmUserProfile(ctx, realmId, realmUserProfile)
realmUserProfile, err := getRealmUserProfileFromData(ctx, keycloakClient, data)
if err != nil {
return diag.FromErr(err)
}

setRealmUserProfileData(data, realmUserProfile)
err = keycloakClient.UpdateRealmUserProfile(ctx, realmId, realmUserProfile)
if err != nil {
return diag.FromErr(err)
}

err = setRealmUserProfileData(ctx, keycloakClient, data, realmUserProfile)
if err != nil {
return diag.FromErr(err)
}

return nil
}

func checkUserProfileEnabled(ctx context.Context, keycloakClient *keycloak.KeycloakClient, realmId string) error {
versionOk, err := keycloakClient.VersionIsGreaterThanOrEqualTo(ctx, keycloak.Version_24)
if err != nil {
return err
}

if versionOk {
return nil
}

realm, err := keycloakClient.GetRealm(ctx, realmId)
if err != nil {
return err
}

userProfileEnabled := realm.Attributes[USER_PROFILE_ENABLED]
if userProfileEnabled != nil {
if value, ok := userProfileEnabled.(bool); ok && value {
return nil
}

if value, ok := userProfileEnabled.(string); ok && strings.ToLower(value) == "true" {
return nil
}
}
return fmt.Errorf("User Profile is disabled for the %s realm", realmId)
}
Loading

0 comments on commit a68ebf1

Please sign in to comment.