controllers/keycloakrealm/chain/user_profile.go (193 lines of code) (raw):
package chain
import (
"context"
"fmt"
"slices"
keycloakgoclient "github.com/zmotso/keycloak-go-client"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"github.com/epam/edp-keycloak-operator/api/common"
keycloakApi "github.com/epam/edp-keycloak-operator/api/v1"
"github.com/epam/edp-keycloak-operator/controllers/keycloakrealm/chain/handler"
"github.com/epam/edp-keycloak-operator/pkg/client/keycloak"
)
type UserProfile struct {
next handler.RealmHandler
}
func (a UserProfile) ServeRequest(ctx context.Context, realm *keycloakApi.KeycloakRealm, kClient keycloak.Client) error {
l := ctrl.LoggerFrom(ctx)
if realm.Spec.UserProfileConfig == nil {
l.Info("User profile is empty, skipping configuration")
return nextServeOrNil(ctx, a.next, realm, kClient)
}
l.Info("Start configuring keycloak realm user profile")
err := ProcessUserProfile(ctx, realm.Spec.RealmName, realm.Spec.UserProfileConfig, kClient)
if err != nil {
return err
}
l.Info("User profile has been configured")
return nextServeOrNil(ctx, a.next, realm, kClient)
}
func ProcessUserProfile(ctx context.Context, realm string, userProfileSpec *common.UserProfileConfig, kClient keycloak.Client) error {
userProfile, err := kClient.GetUsersProfile(ctx, realm)
if err != nil {
return fmt.Errorf("unable to get current user profile: %w", err)
}
userProfileToUpdate := userProfileConfigSpecToModel(userProfileSpec)
attributesToUpdate := userProfileConfigAttributeToMap(&userProfileToUpdate)
if userProfile.Attributes == nil {
userProfile.Attributes = &[]keycloakgoclient.UserProfileAttribute{}
}
for i := 0; i < len(*userProfile.Attributes); i++ {
attribute := (*userProfile.Attributes)[i]
if v, ok := attributesToUpdate[*attribute.Name]; ok {
(*userProfile.Attributes)[i] = v
delete(attributesToUpdate, *attribute.Name)
}
}
for _, v := range attributesToUpdate {
*userProfile.Attributes = append(*userProfile.Attributes, v)
}
groupsToUpdate := userProfileConfigGroupToMap(&userProfileToUpdate)
if userProfile.Groups == nil {
userProfile.Groups = &[]keycloakgoclient.UserProfileGroup{}
}
for i := 0; i < len(*userProfile.Groups); i++ {
group := (*userProfile.Groups)[i]
if v, ok := groupsToUpdate[*group.Name]; ok {
(*userProfile.Groups)[i] = v
delete(groupsToUpdate, *group.Name)
}
}
for _, v := range groupsToUpdate {
*userProfile.Groups = append(*userProfile.Groups, v)
}
userProfile.UnmanagedAttributePolicy = userProfileToUpdate.UnmanagedAttributePolicy
if _, err = kClient.UpdateUsersProfile(
ctx,
realm,
*userProfile,
); err != nil {
return fmt.Errorf("unable to update user profile: %w", err)
}
return nil
}
func userProfileConfigAttributeToMap(profile *keycloakgoclient.UserProfileConfig) map[string]keycloakgoclient.UserProfileAttribute {
if profile.Attributes == nil {
return make(map[string]keycloakgoclient.UserProfileAttribute)
}
attributes := make(map[string]keycloakgoclient.UserProfileAttribute, len(*profile.Attributes))
for _, v := range *profile.Attributes {
attributes[*v.Name] = v
}
return attributes
}
func userProfileConfigGroupToMap(spec *keycloakgoclient.UserProfileConfig) map[string]keycloakgoclient.UserProfileGroup {
groups := make(map[string]keycloakgoclient.UserProfileGroup, len(*spec.Groups))
for _, v := range *spec.Groups {
groups[*v.Name] = v
}
return groups
}
func userProfileConfigSpecToModel(spec *common.UserProfileConfig) keycloakgoclient.UserProfileConfig {
userProfile := keycloakgoclient.UserProfileConfig{}
if spec.UnmanagedAttributePolicy != "" {
userProfile.UnmanagedAttributePolicy = ptr.To(keycloakgoclient.UnmanagedAttributePolicy(spec.UnmanagedAttributePolicy))
}
if spec.Attributes != nil {
attributes := make([]keycloakgoclient.UserProfileAttribute, 0, len(spec.Attributes))
for _, v := range spec.Attributes {
attr := userProfileConfigAttributeSpecToModel(&v)
attributes = append(attributes, attr)
}
userProfile.Attributes = &attributes
}
if spec.Groups != nil {
groups := make([]keycloakgoclient.UserProfileGroup, 0, len(spec.Groups))
for _, v := range spec.Groups {
group := userProfileConfigGroupSpecToModel(v)
groups = append(groups, group)
}
userProfile.Groups = &groups
}
return userProfile
}
func userProfileConfigGroupSpecToModel(v common.UserProfileGroup) keycloakgoclient.UserProfileGroup {
group := keycloakgoclient.UserProfileGroup{
DisplayDescription: &v.DisplayDescription,
DisplayHeader: &v.DisplayHeader,
Name: &v.Name,
}
annotations := make(map[string]interface{}, len(v.Annotations))
for ak, av := range v.Annotations {
annotations[ak] = av
}
group.Annotations = &annotations
return group
}
func userProfileConfigAttributeSpecToModel(v *common.UserProfileAttribute) keycloakgoclient.UserProfileAttribute {
if v == nil {
return keycloakgoclient.UserProfileAttribute{}
}
attr := keycloakgoclient.UserProfileAttribute{
DisplayName: &v.DisplayName,
Group: &v.Group,
Name: &v.Name,
Multivalued: &v.Multivalued,
}
annotations := make(map[string]interface{}, len(v.Annotations))
for ak, av := range v.Annotations {
annotations[ak] = av
}
attr.Annotations = &annotations
validations := userProfileConfigValidationSpecToModel(v.Validations)
attr.Validations = &validations
if v.Permissions != nil {
permissions := keycloakgoclient.UserProfileAttributePermissions{}
edit := slices.Clone(v.Permissions.Edit)
permissions.Edit = &edit
view := slices.Clone(v.Permissions.View)
permissions.View = &view
attr.Permissions = &permissions
}
if v.Required != nil {
required := keycloakgoclient.UserProfileAttributeRequired{}
roles := slices.Clone(v.Required.Roles)
required.Roles = &roles
scopes := slices.Clone(v.Required.Scopes)
required.Scopes = &scopes
attr.Required = &required
}
if v.Selector != nil {
selector := keycloakgoclient.UserProfileAttributeSelector{}
scopes := slices.Clone(v.Selector.Scopes)
selector.Scopes = &scopes
attr.Selector = &selector
}
return attr
}
func userProfileConfigValidationSpecToModel(validations map[string]map[string]common.UserProfileAttributeValidation) map[string]map[string]interface{} {
model := make(map[string]map[string]interface{}, len(validations))
for validatorName, validatorVal := range validations {
val := make(map[string]interface{}, len(validatorVal))
for k, v := range validatorVal {
if v.StringVal != "" {
val[k] = v.StringVal
continue
}
if v.MapVal != nil {
val[k] = v.MapVal
continue
}
if v.IntVal != 0 {
val[k] = v.IntVal
continue
}
if v.SliceVal != nil {
val[k] = v.SliceVal
}
}
model[validatorName] = val
}
return model
}