Skip to content

Commit

Permalink
Merge branch 'main' into mTLS-support
Browse files Browse the repository at this point in the history
  • Loading branch information
lucdew authored Jan 1, 2025
2 parents 9fe03e0 + 3ad4d82 commit 25e69af
Show file tree
Hide file tree
Showing 17 changed files with 947 additions and 95 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
1 change: 1 addition & 0 deletions docs/resources/role.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ resource "keycloak_role" "admin_role" {
- `description` - (Optional) The description of the role
- `composite_roles` - (Optional) When specified, this role will be a composite role, composed of all roles that have an ID present within this list.
- `attributes` - (Optional) A map representing attributes for the role. In order to add multivalue attributes, use `##` to seperate the values. Max length for each value is 255 chars
- `import` - (Optional) When `true`, the role with the specified `name` is assumed to already exist, and it will be imported into state instead of being created. This attribute is useful when dealing with roles that Keycloak creates automatically during realm creation, such as the client roles `create-client`, `view-realm`, ... for the client `realm-management` created per realm. Note, that the role will not be removed during destruction if `import` is `true`.


## Import
Expand Down
1 change: 1 addition & 0 deletions docs/resources/user.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ resource "keycloak_user" "user_with_initial_password" {
- `identity_provider` - (Required) The name of the identity provider
- `user_id` - (Required) The ID of the user defined in the identity provider
- `user_name` - (Required) The user name of the user defined in the identity provider
- `import` - (Optional) When `true`, the user with the specified `username` is assumed to already exist, and it will be imported into state instead of being created. This attribute is useful when dealing with users that Keycloak creates automatically during realm creation, such as `admin`. Note, that the user will not be removed during destruction if `import` is `true`.

## Import

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 25e69af

Please sign in to comment.