From 256101cf986ad945ab9b5dc89af0845e31ec86fd Mon Sep 17 00:00:00 2001 From: Zorian Motso Date: Wed, 13 Nov 2024 16:19:22 +0200 Subject: [PATCH] feat: Add the ability to manage Realm Attributes (#85) --- .golangci.yaml | 50 +- Makefile | 4 +- api/common/realm.go | 106 + api/common/ref.go | 2 + api/common/zz_generated.deepcopy.go | 265 ++ api/v1/keycloakrealm_types.go | 7 + api/v1/zz_generated.deepcopy.go | 5 + api/v1alpha1/clusterkeycloakrealm_types.go | 5 + api/v1alpha1/zz_generated.deepcopy.go | 5 + ...v1.edp.epam.com_clusterkeycloakrealms.yaml | 137 + .../bases/v1.edp.epam.com_keycloakrealms.yaml | 139 + config/samples/v1_v1_keycloakrealm.yaml | 44 + .../chain/auth_flow_test.go | 2 - .../clusterkeycloakrealm/chain/factory.go | 1 + .../chain/user_profile.go | 40 + .../chain/user_profile_test.go | 97 + .../helper/controller_helper_auth_test.go | 3 - .../chain/process_permissions_test.go | 1 - .../chain/process_policy_test.go | 2 - .../chain/process_resources_test.go | 1 - .../chain/process_scope_test.go | 1 - .../chain/put_client_scope_test.go | 1 - .../keycloakclient/chain/put_client_test.go | 1 - .../keycloakclient_controller.go | 1 + controllers/keycloakrealm/chain/factory.go | 4 +- .../keycloakrealm/chain/user_profile.go | 261 ++ .../keycloakrealm/chain/user_profile_test.go | 217 ++ ...ycloakrealm_controller_integration_test.go | 57 +- .../keycloakrealmgroup_controller.go | 1 + ...eycloakrealmidentityprovider_controller.go | 1 + .../keycloakrealmrolebatch_controller.go | 3 + .../_crd_examples/keycloakrealm.yaml | 44 + ...v1.edp.epam.com_clusterkeycloakrealms.yaml | 137 + .../crds/v1.edp.epam.com_keycloakrealms.yaml | 139 + docs/api.md | 2390 +++++++++++------ go.mod | 7 +- go.sum | 19 +- pkg/client/keycloak/adapter/errors_test.go | 2 - .../keycloak/adapter/gocloak_adapter.go | 2 +- .../gocloak_adapter_client_scope_test.go | 2 - .../adapter/gocloak_adapter_client_test.go | 3 +- .../adapter/gocloak_adapter_groups.go | 2 - .../adapter/gocloak_adapter_groups_test.go | 2 - .../adapter/gocloak_adapter_realms_test.go | 3 - .../adapter/gocloak_adapter_roles_test.go | 2 - .../keycloak/adapter/gocloak_adapter_test.go | 9 - .../keycloak/adapter/gocloak_adapter_user.go | 58 +- .../adapter/gocloak_adapter_user_test.go | 3 +- .../keycloak/adapter/mocks/gocloak_mock.go | 2 +- pkg/client/keycloak/dto/keycloak_dto.go | 15 - pkg/client/keycloak/keycloak_client.go | 3 + pkg/client/keycloak/mocks/client_mock.go | 123 +- pkg/fakehttp/server_test.go | 11 - pkg/objectmeta/deletion_test.go | 1 - pkg/secretref/secretref_test.go | 2 - 55 files changed, 3457 insertions(+), 988 deletions(-) create mode 100644 controllers/clusterkeycloakrealm/chain/user_profile.go create mode 100644 controllers/clusterkeycloakrealm/chain/user_profile_test.go create mode 100644 controllers/keycloakrealm/chain/user_profile.go create mode 100644 controllers/keycloakrealm/chain/user_profile_test.go diff --git a/.golangci.yaml b/.golangci.yaml index b10cd343..1ff1a9a2 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -4,30 +4,9 @@ run: issues-exit-code: 1 build-tags: - mytag - skip-dirs: - - "mocks" - skip-dirs-use-default: true - skip-files: - - "mock_.*\\.go" modules-download-mode: mod - allow-parallel-runners: false - -output: - format: colored-line-number - print-issued-lines: true - - # print linter name in the end of issue text, default is true - print-linter-name: true - - # make issues output unique by line, default is true - uniq-by-line: true - - # add a prefix to the output file references; default is no prefix - path-prefix: "" - - # sorts results by: filepath, line and column - sort-results: false + allow-parallel-runners: true # all available settings of specific linters @@ -66,11 +45,6 @@ linters-settings: # default is false: such cases aren't reported by default. check-blank: false - # [deprecated] comma-separated list of pairs of the form pkg:regex - # the regex is used to ignore names within pkg. (default "fmt:.*"). - # see https://github.com/kisielk/errcheck#the-deprecated-method for details - ignore: fmt:.*,io/ioutil:^Read.* - # # [deprecated] use exclude-functions instead. # # path to a file containing a list of functions to exclude from checking # # see https://github.com/kisielk/errcheck#excluding-functions for details @@ -193,9 +167,9 @@ linters-settings: # By default list of stable checks is used. enabled-checks: - nestingReduce - - unnamedresult - ruleguard - truncateCmp + - unnamedResult # Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty disabled-checks: @@ -302,9 +276,6 @@ linters-settings: simplify: true gofumpt: - # Select the Go version to target. The default is `1.15`. - lang-version: "1.15" - # Choose whether or not to use the extra rules that are disabled # by default extra-rules: false @@ -420,9 +391,6 @@ linters-settings: checks: [ "all" ] govet: - # report about shadowed variables - check-shadowing: true - # settings per analyzer settings: printf: # analyzer name, run `go tool vet help` to see all analyzers @@ -458,6 +426,7 @@ linters-settings: - nilness - printf - reflectvaluecompare + - shadow - shift - sigchanyzer - sortslice @@ -771,9 +740,8 @@ linters: - errchkjson - errname - errorlint - - execinquery - exhaustive - - exportloopref + - copyloopvar - forbidigo - gci - goconst @@ -785,7 +753,6 @@ linters: - ineffassign - typecheck - revive - - megacheck - decorder - forcetypeassert - funlen @@ -793,11 +760,6 @@ linters: - wrapcheck - wsl - unused - - disabled: - - exhaustruct - - contextcheck #Disabled due to issue https://github.com/golangci/golangci-lint/issues/2649. - - bodyclose #Disabled due to issue https://github.com/timakin/bodyclose/issues/30 fast: false issues: @@ -836,6 +798,10 @@ issues: - cyclop - funlen text: 'Reconcile' + exclude-files: + - "mock_.*\\.go" + exclude-dirs: + - "mocks" # Independently of option `exclude` we use default exclude patterns, # it can be disabled by this option. To list all diff --git a/Makefile b/Makefile index 9e5f820f..f1438b6f 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,7 @@ helm-docs: helmdocs ## generate helm docs GOLANGCILINT = ${CURRENT_DIR}/bin/golangci-lint .PHONY: golangci-lint golangci-lint: ## Download golangci-lint locally if necessary. - $(call go-get-tool,$(GOLANGCILINT),github.com/golangci/golangci-lint/cmd/golangci-lint,v1.55.2) + $(call go-get-tool,$(GOLANGCILINT),github.com/golangci/golangci-lint/cmd/golangci-lint,v1.62.0) .PHONY: install install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. @@ -218,4 +218,4 @@ mocks: mockery MOCKERY = $(LOCALBIN)/mockery .PHONY: mockery mockery: ## Download mockery locally if necessary. - $(call go-get-tool,$(MOCKERY),github.com/vektra/mockery/v2,v2.43.0) + $(call go-get-tool,$(MOCKERY),github.com/vektra/mockery/v2,v2.46.3) diff --git a/api/common/realm.go b/api/common/realm.go index e0d1882c..1e6c84d4 100644 --- a/api/common/realm.go +++ b/api/common/realm.go @@ -1,3 +1,4 @@ +// +kubebuilder:object:generate=true package common // TokenSettings is the configuration for tokens in the realm. @@ -53,3 +54,108 @@ type TokenSettings struct { // +kubebuilder:default=43200 ActionTokenGeneratedByAdminLifespan int `json:"actionTokenGeneratedByAdminLifespan,omitempty"` } + +// UserProfileConfig defines the configuration for user profile in the realm. +type UserProfileConfig struct { + // UnmanagedAttributePolicy are user attributes not explicitly defined in the user profile configuration. + // Empty value means that unmanaged attributes are disabled. + // Possible values: + // ENABLED - unmanaged attributes are allowed. + // ADMIN_VIEW - unmanaged attributes are read-only and only available through the administration console and API. + // ADMIN_EDIT - unmanaged attributes can be managed only through the administration console and API. + // +optional + UnmanagedAttributePolicy string `json:"unmanagedAttributePolicy,omitempty"` + + // Attributes specifies the list of user profile attributes. + Attributes []UserProfileAttribute `json:"attributes,omitempty"` + + // Groups specifies the list of user profile groups. + Groups []UserProfileGroup `json:"groups,omitempty"` +} + +type UserProfileAttribute struct { + // Name of the user attribute, used to uniquely identify an attribute. + // +required + Name string `json:"name"` + + // Display name for the attribute. + DisplayName string `json:"displayName,omitempty"` + + // Group to which the attribute belongs. + Group string `json:"group,omitempty"` + + // Multivalued specifies if this attribute supports multiple values. + // This setting is an indicator and does not enable any validation + Multivalued bool `json:"multivalued,omitempty"` + + // Permissions specifies the permissions for the attribute. + Permissions *UserProfileAttributePermissions `json:"permissions,omitempty"` + + // Required indicates that the attribute must be set by users and administrators. + Required *UserProfileAttributeRequired `json:"required,omitempty"` + + // Selector specifies the scopes for which the attribute is available. + Selector *UserProfileAttributeSelector `json:"selector,omitempty"` + + // Annotations specifies the annotations for the attribute. + Annotations map[string]string `json:"annotations,omitempty"` + + // Validations specifies the validations for the attribute. + Validations map[string]map[string]UserProfileAttributeValidation `json:"validations,omitempty"` +} + +type UserProfileAttributeValidation struct { + // +optional + StringVal string `json:"stringVal,omitempty"` + + // +optional + // +nullable + MapVal map[string]string `json:"mapVal,omitempty"` + + // +optional + IntVal int `json:"intVal,omitempty"` + + // +optional + // +nullable + SliceVal []string `json:"sliceVal,omitempty"` +} + +type UserProfileAttributePermissions struct { + // Edit specifies who can edit the attribute. + Edit []string `json:"edit,omitempty"` + + // View specifies who can view the attribute. + View []string `json:"view,omitempty"` +} + +// UserProfileAttributeRequired defines model for UserProfileAttributeRequired. +type UserProfileAttributeRequired struct { + // Roles specifies the roles for whom the attribute is required. + Roles []string `json:"roles,omitempty"` + + // Scopes specifies the scopes when the attribute is required. + Scopes []string `json:"scopes,omitempty"` +} + +// UserProfileAttributeSelector defines model for UserProfileAttributeSelector. +type UserProfileAttributeSelector struct { + // Scopes specifies the scopes for which the attribute is available. + Scopes []string `json:"scopes,omitempty"` +} + +type UserProfileGroup struct { + // Name is unique name of the group. + // +required + Name string `json:"name"` + + // Annotations specifies the annotations for the group. + // +optional + // nullable + Annotations map[string]string `json:"annotations,omitempty"` + + // DisplayDescription specifies a user-friendly name for the group that should be used when rendering a group of attributes in user-facing forms. + DisplayDescription string `json:"displayDescription,omitempty"` + + // DisplayHeader specifies a text that should be used as a header when rendering user-facing forms. + DisplayHeader string `json:"displayHeader,omitempty"` +} diff --git a/api/common/ref.go b/api/common/ref.go index 9654a049..f99cf98a 100644 --- a/api/common/ref.go +++ b/api/common/ref.go @@ -18,10 +18,12 @@ type RealmRef struct { Name string `json:"name,omitempty"` } +// +kubebuilder:object:generate=false type HasRealmRef interface { GetRealmRef() RealmRef } +// +kubebuilder:object:generate=false type HasKeycloakRef interface { GetKeycloakRef() KeycloakRef } diff --git a/api/common/zz_generated.deepcopy.go b/api/common/zz_generated.deepcopy.go index a6d835f4..292e5268 100644 --- a/api/common/zz_generated.deepcopy.go +++ b/api/common/zz_generated.deepcopy.go @@ -6,6 +6,68 @@ package common import () +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConfigMapKeySelector) DeepCopyInto(out *ConfigMapKeySelector) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapKeySelector. +func (in *ConfigMapKeySelector) DeepCopy() *ConfigMapKeySelector { + if in == nil { + return nil + } + out := new(ConfigMapKeySelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *KeycloakRef) DeepCopyInto(out *KeycloakRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeycloakRef. +func (in *KeycloakRef) DeepCopy() *KeycloakRef { + if in == nil { + return nil + } + out := new(KeycloakRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RealmRef) DeepCopyInto(out *RealmRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RealmRef. +func (in *RealmRef) DeepCopy() *RealmRef { + if in == nil { + return nil + } + out := new(RealmRef) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { + *out = *in + out.LocalObjectReference = in.LocalObjectReference +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector. +func (in *SecretKeySelector) DeepCopy() *SecretKeySelector { + if in == nil { + return nil + } + out := new(SecretKeySelector) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SourceRef) DeepCopyInto(out *SourceRef) { *out = *in @@ -45,3 +107,206 @@ func (in *TokenSettings) DeepCopy() *TokenSettings { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserProfileAttribute) DeepCopyInto(out *UserProfileAttribute) { + *out = *in + if in.Permissions != nil { + in, out := &in.Permissions, &out.Permissions + *out = new(UserProfileAttributePermissions) + (*in).DeepCopyInto(*out) + } + if in.Required != nil { + in, out := &in.Required, &out.Required + *out = new(UserProfileAttributeRequired) + (*in).DeepCopyInto(*out) + } + if in.Selector != nil { + in, out := &in.Selector, &out.Selector + *out = new(UserProfileAttributeSelector) + (*in).DeepCopyInto(*out) + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Validations != nil { + in, out := &in.Validations, &out.Validations + *out = make(map[string]map[string]UserProfileAttributeValidation, len(*in)) + for key, val := range *in { + var outVal map[string]UserProfileAttributeValidation + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make(map[string]UserProfileAttributeValidation, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } + (*out)[key] = outVal + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserProfileAttribute. +func (in *UserProfileAttribute) DeepCopy() *UserProfileAttribute { + if in == nil { + return nil + } + out := new(UserProfileAttribute) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserProfileAttributePermissions) DeepCopyInto(out *UserProfileAttributePermissions) { + *out = *in + if in.Edit != nil { + in, out := &in.Edit, &out.Edit + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.View != nil { + in, out := &in.View, &out.View + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserProfileAttributePermissions. +func (in *UserProfileAttributePermissions) DeepCopy() *UserProfileAttributePermissions { + if in == nil { + return nil + } + out := new(UserProfileAttributePermissions) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserProfileAttributeRequired) DeepCopyInto(out *UserProfileAttributeRequired) { + *out = *in + if in.Roles != nil { + in, out := &in.Roles, &out.Roles + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Scopes != nil { + in, out := &in.Scopes, &out.Scopes + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserProfileAttributeRequired. +func (in *UserProfileAttributeRequired) DeepCopy() *UserProfileAttributeRequired { + if in == nil { + return nil + } + out := new(UserProfileAttributeRequired) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserProfileAttributeSelector) DeepCopyInto(out *UserProfileAttributeSelector) { + *out = *in + if in.Scopes != nil { + in, out := &in.Scopes, &out.Scopes + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserProfileAttributeSelector. +func (in *UserProfileAttributeSelector) DeepCopy() *UserProfileAttributeSelector { + if in == nil { + return nil + } + out := new(UserProfileAttributeSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserProfileAttributeValidation) DeepCopyInto(out *UserProfileAttributeValidation) { + *out = *in + if in.MapVal != nil { + in, out := &in.MapVal, &out.MapVal + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.SliceVal != nil { + in, out := &in.SliceVal, &out.SliceVal + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserProfileAttributeValidation. +func (in *UserProfileAttributeValidation) DeepCopy() *UserProfileAttributeValidation { + if in == nil { + return nil + } + out := new(UserProfileAttributeValidation) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserProfileConfig) DeepCopyInto(out *UserProfileConfig) { + *out = *in + if in.Attributes != nil { + in, out := &in.Attributes, &out.Attributes + *out = make([]UserProfileAttribute, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]UserProfileGroup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserProfileConfig. +func (in *UserProfileConfig) DeepCopy() *UserProfileConfig { + if in == nil { + return nil + } + out := new(UserProfileConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserProfileGroup) DeepCopyInto(out *UserProfileGroup) { + *out = *in + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserProfileGroup. +func (in *UserProfileGroup) DeepCopy() *UserProfileGroup { + if in == nil { + return nil + } + out := new(UserProfileGroup) + in.DeepCopyInto(out) + return out +} diff --git a/api/v1/keycloakrealm_types.go b/api/v1/keycloakrealm_types.go index 4978e8a0..4f12b44c 100644 --- a/api/v1/keycloakrealm_types.go +++ b/api/v1/keycloakrealm_types.go @@ -72,6 +72,13 @@ type KeycloakRealmSpec struct { // DisplayName is the display name of the realm. // +optional DisplayName string `json:"displayName,omitempty"` + + // UserProfileConfig is the configuration for user profiles in the realm. + // Attributes and groups will be added to the current realm configuration. + // Deletion of attributes and groups is not supported. + // +nullable + // +optional + UserProfileConfig *common.UserProfileConfig `json:"userProfileConfig,omitempty"` } type User struct { diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 5473e8cc..c1a78296 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -1357,6 +1357,11 @@ func (in *KeycloakRealmSpec) DeepCopyInto(out *KeycloakRealmSpec) { *out = new(common.TokenSettings) **out = **in } + if in.UserProfileConfig != nil { + in, out := &in.UserProfileConfig, &out.UserProfileConfig + *out = new(common.UserProfileConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KeycloakRealmSpec. diff --git a/api/v1alpha1/clusterkeycloakrealm_types.go b/api/v1alpha1/clusterkeycloakrealm_types.go index 2e67db5c..6d8df434 100644 --- a/api/v1alpha1/clusterkeycloakrealm_types.go +++ b/api/v1alpha1/clusterkeycloakrealm_types.go @@ -62,6 +62,11 @@ type ClusterKeycloakRealmSpec struct { // DisplayName is the display name of the realm. // +optional DisplayName string `json:"displayName,omitempty"` + + // UserProfileConfig is the configuration for user profiles in the realm. + // +nullable + // +optional + UserProfileConfig *common.UserProfileConfig `json:"userProfileConfig,omitempty"` } type AuthenticationFlow struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index ba1bbe3e..b07ff051 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -186,6 +186,11 @@ func (in *ClusterKeycloakRealmSpec) DeepCopyInto(out *ClusterKeycloakRealmSpec) *out = new(AuthenticationFlow) **out = **in } + if in.UserProfileConfig != nil { + in, out := &in.UserProfileConfig, &out.UserProfileConfig + *out = new(common.UserProfileConfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterKeycloakRealmSpec. diff --git a/config/crd/bases/v1.edp.epam.com_clusterkeycloakrealms.yaml b/config/crd/bases/v1.edp.epam.com_clusterkeycloakrealms.yaml index 009297f2..7996b0d8 100644 --- a/config/crd/bases/v1.edp.epam.com_clusterkeycloakrealms.yaml +++ b/config/crd/bases/v1.edp.epam.com_clusterkeycloakrealms.yaml @@ -243,6 +243,143 @@ spec: Otherwise, refresh tokens are not revoked when used and can be used multiple times. type: boolean type: object + userProfileConfig: + description: UserProfileConfig is the configuration for user profiles + in the realm. + nullable: true + properties: + attributes: + description: Attributes specifies the list of user profile attributes. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations specifies the annotations for the + attribute. + type: object + displayName: + description: Display name for the attribute. + type: string + group: + description: Group to which the attribute belongs. + type: string + multivalued: + description: |- + Multivalued specifies if this attribute supports multiple values. + This setting is an indicator and does not enable any validation + type: boolean + name: + description: Name of the user attribute, used to uniquely + identify an attribute. + type: string + permissions: + description: Permissions specifies the permissions for the + attribute. + properties: + edit: + description: Edit specifies who can edit the attribute. + items: + type: string + type: array + view: + description: View specifies who can view the attribute. + items: + type: string + type: array + type: object + required: + description: Required indicates that the attribute must + be set by users and administrators. + properties: + roles: + description: Roles specifies the roles for whom the + attribute is required. + items: + type: string + type: array + scopes: + description: Scopes specifies the scopes when the attribute + is required. + items: + type: string + type: array + type: object + selector: + description: Selector specifies the scopes for which the + attribute is available. + properties: + scopes: + description: Scopes specifies the scopes for which the + attribute is available. + items: + type: string + type: array + type: object + validations: + additionalProperties: + additionalProperties: + properties: + intVal: + type: integer + mapVal: + additionalProperties: + type: string + nullable: true + type: object + sliceVal: + items: + type: string + nullable: true + type: array + stringVal: + type: string + type: object + type: object + description: Validations specifies the validations for the + attribute. + type: object + required: + - name + type: object + type: array + groups: + description: Groups specifies the list of user profile groups. + items: + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations specifies the annotations for the group. + nullable + type: object + displayDescription: + description: DisplayDescription specifies a user-friendly + name for the group that should be used when rendering + a group of attributes in user-facing forms. + type: string + displayHeader: + description: DisplayHeader specifies a text that should + be used as a header when rendering user-facing forms. + type: string + name: + description: Name is unique name of the group. + type: string + required: + - name + type: object + type: array + unmanagedAttributePolicy: + description: |- + UnmanagedAttributePolicy are user attributes not explicitly defined in the user profile configuration. + Empty value means that unmanaged attributes are disabled. + Possible values: + ENABLED - unmanaged attributes are allowed. + ADMIN_VIEW - unmanaged attributes are read-only and only available through the administration console and API. + ADMIN_EDIT - unmanaged attributes can be managed only through the administration console and API. + type: string + type: object required: - clusterKeycloakRef - realmName diff --git a/config/crd/bases/v1.edp.epam.com_keycloakrealms.yaml b/config/crd/bases/v1.edp.epam.com_keycloakrealms.yaml index 0041df8b..f448519f 100644 --- a/config/crd/bases/v1.edp.epam.com_keycloakrealms.yaml +++ b/config/crd/bases/v1.edp.epam.com_keycloakrealms.yaml @@ -253,6 +253,145 @@ spec: Otherwise, refresh tokens are not revoked when used and can be used multiple times. type: boolean type: object + userProfileConfig: + description: |- + UserProfileConfig is the configuration for user profiles in the realm. + Attributes and groups will be added to the current realm configuration. + Deletion of attributes and groups is not supported. + nullable: true + properties: + attributes: + description: Attributes specifies the list of user profile attributes. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations specifies the annotations for the + attribute. + type: object + displayName: + description: Display name for the attribute. + type: string + group: + description: Group to which the attribute belongs. + type: string + multivalued: + description: |- + Multivalued specifies if this attribute supports multiple values. + This setting is an indicator and does not enable any validation + type: boolean + name: + description: Name of the user attribute, used to uniquely + identify an attribute. + type: string + permissions: + description: Permissions specifies the permissions for the + attribute. + properties: + edit: + description: Edit specifies who can edit the attribute. + items: + type: string + type: array + view: + description: View specifies who can view the attribute. + items: + type: string + type: array + type: object + required: + description: Required indicates that the attribute must + be set by users and administrators. + properties: + roles: + description: Roles specifies the roles for whom the + attribute is required. + items: + type: string + type: array + scopes: + description: Scopes specifies the scopes when the attribute + is required. + items: + type: string + type: array + type: object + selector: + description: Selector specifies the scopes for which the + attribute is available. + properties: + scopes: + description: Scopes specifies the scopes for which the + attribute is available. + items: + type: string + type: array + type: object + validations: + additionalProperties: + additionalProperties: + properties: + intVal: + type: integer + mapVal: + additionalProperties: + type: string + nullable: true + type: object + sliceVal: + items: + type: string + nullable: true + type: array + stringVal: + type: string + type: object + type: object + description: Validations specifies the validations for the + attribute. + type: object + required: + - name + type: object + type: array + groups: + description: Groups specifies the list of user profile groups. + items: + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations specifies the annotations for the group. + nullable + type: object + displayDescription: + description: DisplayDescription specifies a user-friendly + name for the group that should be used when rendering + a group of attributes in user-facing forms. + type: string + displayHeader: + description: DisplayHeader specifies a text that should + be used as a header when rendering user-facing forms. + type: string + name: + description: Name is unique name of the group. + type: string + required: + - name + type: object + type: array + unmanagedAttributePolicy: + description: |- + UnmanagedAttributePolicy are user attributes not explicitly defined in the user profile configuration. + Empty value means that unmanaged attributes are disabled. + Possible values: + ENABLED - unmanaged attributes are allowed. + ADMIN_VIEW - unmanaged attributes are read-only and only available through the administration console and API. + ADMIN_EDIT - unmanaged attributes can be managed only through the administration console and API. + type: string + type: object users: description: Users is a list of users to create in the realm. items: diff --git a/config/samples/v1_v1_keycloakrealm.yaml b/config/samples/v1_v1_keycloakrealm.yaml index e41b6f08..9c9e1aff 100644 --- a/config/samples/v1_v1_keycloakrealm.yaml +++ b/config/samples/v1_v1_keycloakrealm.yaml @@ -23,3 +23,47 @@ spec: eventsExpiration: 15000 eventsListeners: - jboss-logging + userProfileConfig: + unmanagedAttributePolicy: "ENABLED" + attributes: + - name: "test-attribute" + displayName: "Test Attribute" + required: + roles: + - "admin" + scopes: + - "profile" + multivalued: true + group: "test-group" + permissions: + edit: + - "admin" + view: + - "admin" + - "user" + selector: + scopes: + - "profile" + annotations: + inputType: "text" + validations: + email: + max-local-length: + intVal: 64 + local-date: { } + options: + options: + sliceVal: + - "option1" + - "option2" + multivalued: + min: + stringVal: "1" + max: + stringVal: "10" + groups: + - name: "test-group" + displayDescription: "Test Group" + displayHeader: "Test Group" + annotations: + groupAnnotation: "groupAnnotation" diff --git a/controllers/clusterkeycloakrealm/chain/auth_flow_test.go b/controllers/clusterkeycloakrealm/chain/auth_flow_test.go index a4f4e106..deaba332 100644 --- a/controllers/clusterkeycloakrealm/chain/auth_flow_test.go +++ b/controllers/clusterkeycloakrealm/chain/auth_flow_test.go @@ -76,8 +76,6 @@ func TestAuthFlow_ServeRequest(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/controllers/clusterkeycloakrealm/chain/factory.go b/controllers/clusterkeycloakrealm/chain/factory.go index 73ff7059..11876db1 100644 --- a/controllers/clusterkeycloakrealm/chain/factory.go +++ b/controllers/clusterkeycloakrealm/chain/factory.go @@ -9,6 +9,7 @@ func MakeChain(c client.Client) RealmHandler { ch.Use( NewPutRealm(c), NewPutRealmSettings(), + NewUserProfile(), ) return ch diff --git a/controllers/clusterkeycloakrealm/chain/user_profile.go b/controllers/clusterkeycloakrealm/chain/user_profile.go new file mode 100644 index 00000000..c40b36ca --- /dev/null +++ b/controllers/clusterkeycloakrealm/chain/user_profile.go @@ -0,0 +1,40 @@ +package chain + +import ( + "context" + "fmt" + + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/epam/edp-keycloak-operator/api/v1alpha1" + keycloakrealmchain "github.com/epam/edp-keycloak-operator/controllers/keycloakrealm/chain" + "github.com/epam/edp-keycloak-operator/pkg/client/keycloak" +) + +type UserProfile struct { +} + +func NewUserProfile() *UserProfile { + return &UserProfile{} +} + +func (h UserProfile) ServeRequest(ctx context.Context, realm *v1alpha1.ClusterKeycloakRealm, kClient keycloak.Client) error { + l := ctrl.LoggerFrom(ctx) + + if realm.Spec.UserProfileConfig == nil { + l.Info("User profile is empty, skipping configuration") + + return nil + } + + l.Info("Start configuring keycloak realm user profile") + + err := keycloakrealmchain.ProcessUserProfile(ctx, realm.Spec.RealmName, realm.Spec.UserProfileConfig, kClient) + if err != nil { + return fmt.Errorf("unable to process user profile: %w", err) + } + + l.Info("User profile has been configured") + + return nil +} diff --git a/controllers/clusterkeycloakrealm/chain/user_profile_test.go b/controllers/clusterkeycloakrealm/chain/user_profile_test.go new file mode 100644 index 00000000..4e077591 --- /dev/null +++ b/controllers/clusterkeycloakrealm/chain/user_profile_test.go @@ -0,0 +1,97 @@ +package chain + +import ( + "context" + "testing" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + 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/v1alpha1" + "github.com/epam/edp-keycloak-operator/pkg/client/keycloak" + "github.com/epam/edp-keycloak-operator/pkg/client/keycloak/mocks" +) + +func TestUserProfile_ServeRequest(t *testing.T) { + tests := []struct { + name string + realm *keycloakApi.ClusterKeycloakRealm + kClient func(t *testing.T) keycloak.Client + wantErr require.ErrorAssertionFunc + }{ + { + name: "should update user profile successfully", + realm: &keycloakApi.ClusterKeycloakRealm{ + Spec: keycloakApi.ClusterKeycloakRealmSpec{ + RealmName: "realm", + UserProfileConfig: &common.UserProfileConfig{ + UnmanagedAttributePolicy: "ENABLED", + Attributes: []common.UserProfileAttribute{ + { + DisplayName: "Attribute 2", + Group: "test-group", + Name: "attr2", + }, + }, + Groups: []common.UserProfileGroup{ + { + Name: "test-group2", + }, + }, + }, + }, + }, + kClient: func(t *testing.T) keycloak.Client { + m := mocks.NewMockClient(t) + + m.On("GetUsersProfile", mock.Anything, "realm"). + Return(&keycloakgoclient.UserProfileConfig{ + Attributes: &[]keycloakgoclient.UserProfileAttribute{ + { + DisplayName: ptr.To("Attribute 1"), + Group: ptr.To("test-group"), + Name: ptr.To("attr1"), + }, + }, + Groups: &[]keycloakgoclient.UserProfileGroup{ + { + Name: ptr.To("test-group"), + DisplayDescription: ptr.To("Group description"), + DisplayHeader: ptr.To("Group header"), + }, + }, + }, nil) + + m.On("UpdateUsersProfile", mock.Anything, "realm", mock.Anything). + Return(&keycloakgoclient.UserProfileConfig{}, nil) + + return m + }, + wantErr: require.NoError, + }, + { + name: "empty user profile config", + realm: &keycloakApi.ClusterKeycloakRealm{}, + kClient: func(t *testing.T) keycloak.Client { + return mocks.NewMockClient(t) + }, + wantErr: require.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := NewUserProfile() + tt.wantErr(t, h.ServeRequest( + ctrl.LoggerInto(context.Background(), logr.Discard()), + tt.realm, + tt.kClient(t), + )) + }) + } +} diff --git a/controllers/helper/controller_helper_auth_test.go b/controllers/helper/controller_helper_auth_test.go index 88d414df..dc751ab3 100644 --- a/controllers/helper/controller_helper_auth_test.go +++ b/controllers/helper/controller_helper_auth_test.go @@ -439,7 +439,6 @@ func TestMakeKeycloakAuthDataFromKeycloak(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -509,8 +508,6 @@ func TestMakeKeycloakAuthDataFromClusterKeycloak(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/controllers/keycloakclient/chain/process_permissions_test.go b/controllers/keycloakclient/chain/process_permissions_test.go index d5c08120..03f3f9c8 100644 --- a/controllers/keycloakclient/chain/process_permissions_test.go +++ b/controllers/keycloakclient/chain/process_permissions_test.go @@ -447,7 +447,6 @@ func TestProcessPermissions_Serve(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/controllers/keycloakclient/chain/process_policy_test.go b/controllers/keycloakclient/chain/process_policy_test.go index 979815be..61fb92eb 100644 --- a/controllers/keycloakclient/chain/process_policy_test.go +++ b/controllers/keycloakclient/chain/process_policy_test.go @@ -402,8 +402,6 @@ func TestProcessPolicy_Serve(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { h := NewProcessPolicy(tt.keycloakApiClient(t)) diff --git a/controllers/keycloakclient/chain/process_resources_test.go b/controllers/keycloakclient/chain/process_resources_test.go index 21b4f996..b8be9051 100644 --- a/controllers/keycloakclient/chain/process_resources_test.go +++ b/controllers/keycloakclient/chain/process_resources_test.go @@ -337,7 +337,6 @@ func TestProcessResources_Serve(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/controllers/keycloakclient/chain/process_scope_test.go b/controllers/keycloakclient/chain/process_scope_test.go index ca872709..1bff3f27 100644 --- a/controllers/keycloakclient/chain/process_scope_test.go +++ b/controllers/keycloakclient/chain/process_scope_test.go @@ -127,7 +127,6 @@ func TestProcessScope_Serve(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() h := NewProcessScope(tt.keycloakApiClient(t)) diff --git a/controllers/keycloakclient/chain/put_client_scope_test.go b/controllers/keycloakclient/chain/put_client_scope_test.go index 6b634c00..624bcca9 100644 --- a/controllers/keycloakclient/chain/put_client_scope_test.go +++ b/controllers/keycloakclient/chain/put_client_scope_test.go @@ -97,7 +97,6 @@ func TestPutClientScope_Serve(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/controllers/keycloakclient/chain/put_client_test.go b/controllers/keycloakclient/chain/put_client_test.go index 475fde2b..cb153250 100644 --- a/controllers/keycloakclient/chain/put_client_test.go +++ b/controllers/keycloakclient/chain/put_client_test.go @@ -313,7 +313,6 @@ func TestPutClient_Serve(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/controllers/keycloakclient/keycloakclient_controller.go b/controllers/keycloakclient/keycloakclient_controller.go index 1a288344..ae18614a 100644 --- a/controllers/keycloakclient/keycloakclient_controller.go +++ b/controllers/keycloakclient/keycloakclient_controller.go @@ -107,6 +107,7 @@ func (r *ReconcileKeycloakClient) Reconcile(ctx context.Context, request reconci log.Error(err, "an error has occurred while handling keycloak client", "name", request.Name) } else { helper.SetSuccessStatus(&instance) + result.RequeueAfter = r.successReconcileTimeout } diff --git a/controllers/keycloakrealm/chain/factory.go b/controllers/keycloakrealm/chain/factory.go index 61ef07d0..b590955a 100644 --- a/controllers/keycloakrealm/chain/factory.go +++ b/controllers/keycloakrealm/chain/factory.go @@ -25,7 +25,9 @@ func CreateDefChain(client client.Client, scheme *runtime.Scheme, hlp Helper) ha next: PutUsers{ next: PutUsersRoles{ next: RealmSettings{ - next: AuthFlow{}, + next: AuthFlow{ + next: UserProfile{}, + }, }, }, }, diff --git a/controllers/keycloakrealm/chain/user_profile.go b/controllers/keycloakrealm/chain/user_profile.go new file mode 100644 index 00000000..3243a258 --- /dev/null +++ b/controllers/keycloakrealm/chain/user_profile.go @@ -0,0 +1,261 @@ +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 +} diff --git a/controllers/keycloakrealm/chain/user_profile_test.go b/controllers/keycloakrealm/chain/user_profile_test.go new file mode 100644 index 00000000..51612fd9 --- /dev/null +++ b/controllers/keycloakrealm/chain/user_profile_test.go @@ -0,0 +1,217 @@ +package chain + +import ( + "context" + "testing" + + "github.com/go-logr/logr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + keycloak_go_client "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/pkg/client/keycloak" + "github.com/epam/edp-keycloak-operator/pkg/client/keycloak/mocks" +) + +func TestUserProfileConfigSpecToModel(t *testing.T) { + tests := []struct { + name string + spec *common.UserProfileConfig + want keycloak_go_client.UserProfileConfig + }{ + { + name: "should convert spec to model", + spec: &common.UserProfileConfig{ + UnmanagedAttributePolicy: "ENABLED", + Attributes: []common.UserProfileAttribute{ + { + DisplayName: "Attribute 1", + Group: "test-group", + Name: "attr1", + Multivalued: true, + Permissions: &common.UserProfileAttributePermissions{ + Edit: []string{"edit"}, + View: []string{"view"}, + }, + Required: &common.UserProfileAttributeRequired{ + Roles: []string{"role"}, + Scopes: []string{"scope"}, + }, + Selector: &common.UserProfileAttributeSelector{ + Scopes: []string{"scope"}, + }, + Annotations: map[string]string{ + "inputType": "text", + }, + Validations: map[string]map[string]common.UserProfileAttributeValidation{ + "email": { + "max-local-length": { + IntVal: 64, + }, + }, + "local-date": {}, + "multivalued": { + "min": { + StringVal: "1", + }, + "max": { + StringVal: "10", + }, + }, + "options": { + "options": { + SliceVal: []string{"option1", "option2"}, + }, + }, + }, + }, + }, + Groups: []common.UserProfileGroup{ + { + Annotations: map[string]string{"group": "test"}, + DisplayDescription: "Group description", + DisplayHeader: "Group header", + Name: "Group", + }, + }, + }, + want: keycloak_go_client.UserProfileConfig{ + UnmanagedAttributePolicy: ptr.To(keycloak_go_client.UnmanagedAttributePolicy("ENABLED")), + Attributes: &[]keycloak_go_client.UserProfileAttribute{ + { + DisplayName: ptr.To("Attribute 1"), + Group: ptr.To("test-group"), + Name: ptr.To("attr1"), + Multivalued: ptr.To(true), + Permissions: &keycloak_go_client.UserProfileAttributePermissions{ + Edit: &[]string{"edit"}, + View: &[]string{"view"}, + }, + Required: &keycloak_go_client.UserProfileAttributeRequired{ + Roles: &[]string{"role"}, + Scopes: &[]string{"scope"}, + }, + Selector: &keycloak_go_client.UserProfileAttributeSelector{ + Scopes: &[]string{"scope"}, + }, + Annotations: &map[string]interface{}{ + "inputType": "text", + }, + Validations: &map[string]map[string]interface{}{ + "email": { + "max-local-length": 64, + }, + "local-date": {}, + "multivalued": { + "min": "1", + "max": "10", + }, + "options": { + "options": []string{"option1", "option2"}, + }, + }, + }, + }, + Groups: &[]keycloak_go_client.UserProfileGroup{ + { + Annotations: &map[string]interface{}{"group": "test"}, + DisplayDescription: ptr.To("Group description"), + DisplayHeader: ptr.To("Group header"), + Name: ptr.To("Group"), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := userProfileConfigSpecToModel(tt.spec) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestUserProfile_ServeRequest(t *testing.T) { + tests := []struct { + name string + realm *keycloakApi.KeycloakRealm + kClient func(t *testing.T) keycloak.Client + wantErr require.ErrorAssertionFunc + }{ + { + name: "should update user profile successfully", + realm: &keycloakApi.KeycloakRealm{ + Spec: keycloakApi.KeycloakRealmSpec{ + RealmName: "realm", + UserProfileConfig: &common.UserProfileConfig{ + UnmanagedAttributePolicy: "ENABLED", + Attributes: []common.UserProfileAttribute{ + { + DisplayName: "Attribute 2", + Group: "test-group", + Name: "attr2", + }, + }, + Groups: []common.UserProfileGroup{ + { + Name: "test-group2", + }, + }, + }, + }, + }, + kClient: func(t *testing.T) keycloak.Client { + m := mocks.NewMockClient(t) + + m.On("GetUsersProfile", mock.Anything, "realm"). + Return(&keycloak_go_client.UserProfileConfig{ + Attributes: &[]keycloak_go_client.UserProfileAttribute{ + { + DisplayName: ptr.To("Attribute 1"), + Group: ptr.To("test-group"), + Name: ptr.To("attr1"), + }, + }, + Groups: &[]keycloak_go_client.UserProfileGroup{ + { + Name: ptr.To("test-group"), + DisplayDescription: ptr.To("Group description"), + DisplayHeader: ptr.To("Group header"), + }, + }, + }, nil) + + m.On("UpdateUsersProfile", mock.Anything, "realm", mock.Anything). + Return(&keycloak_go_client.UserProfileConfig{}, nil) + + return m + }, + wantErr: require.NoError, + }, + { + name: "empty user profile config", + realm: &keycloakApi.KeycloakRealm{}, + kClient: func(t *testing.T) keycloak.Client { + return mocks.NewMockClient(t) + }, + wantErr: require.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &UserProfile{} + tt.wantErr(t, a.ServeRequest( + ctrl.LoggerInto(context.Background(), logr.Discard()), + tt.realm, + tt.kClient(t), + )) + }) + } +} diff --git a/controllers/keycloakrealm/keycloakrealm_controller_integration_test.go b/controllers/keycloakrealm/keycloakrealm_controller_integration_test.go index d06a3f9f..e18633b8 100644 --- a/controllers/keycloakrealm/keycloakrealm_controller_integration_test.go +++ b/controllers/keycloakrealm/keycloakrealm_controller_integration_test.go @@ -9,7 +9,7 @@ import ( k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" "github.com/epam/edp-keycloak-operator/api/common" keycloakApi "github.com/epam/edp-keycloak-operator/api/v1" @@ -32,7 +32,7 @@ var _ = Describe("KeycloakRealm controller", Ordered, func() { Name: keycloakCR, Kind: keycloakApi.KeycloakKind, }, - BrowserFlow: pointer.String("browser"), + BrowserFlow: ptr.To("browser"), RealmEventConfig: &keycloakApi.RealmEventConfig{ AdminEventsDetailsEnabled: false, AdminEventsEnabled: true, @@ -65,6 +65,59 @@ var _ = Describe("KeycloakRealm controller", Ordered, func() { }, DisplayName: "Test Realm", DisplayHTMLName: "Test Realm", + UserProfileConfig: &common.UserProfileConfig{ + Attributes: []common.UserProfileAttribute{ + { + DisplayName: "Attribute 1", + Group: "test-group", + Name: "attr1", + Multivalued: true, + Permissions: &common.UserProfileAttributePermissions{ + Edit: []string{"admin"}, + View: []string{"admin"}, + }, + Required: &common.UserProfileAttributeRequired{ + Roles: []string{"admin", "user"}, + Scopes: []string{"email"}, + }, + Selector: &common.UserProfileAttributeSelector{ + Scopes: []string{"roles"}, + }, + Annotations: map[string]string{ + "inputType": "text", + }, + Validations: map[string]map[string]common.UserProfileAttributeValidation{ + "email": { + "max-local-length": { + IntVal: 64, + }, + }, + "local-date": {}, + "multivalued": { + "min": { + StringVal: "1", + }, + "max": { + StringVal: "10", + }, + }, + "options": { + "options": { + SliceVal: []string{"option1", "option2"}, + }, + }, + }, + }, + }, + Groups: []common.UserProfileGroup{ + { + Annotations: map[string]string{"group": "test"}, + DisplayDescription: "Group description", + DisplayHeader: "Group header", + Name: "test-group", + }, + }, + }, }, } Expect(k8sClient.Create(ctx, keycloakRealm)).Should(Succeed()) diff --git a/controllers/keycloakrealmgroup/keycloakrealmgroup_controller.go b/controllers/keycloakrealmgroup/keycloakrealmgroup_controller.go index 354b8373..19d0e666 100644 --- a/controllers/keycloakrealmgroup/keycloakrealmgroup_controller.go +++ b/controllers/keycloakrealmgroup/keycloakrealmgroup_controller.go @@ -102,6 +102,7 @@ func (r *ReconcileKeycloakRealmGroup) Reconcile(ctx context.Context, request rec log.Error(err, "an error has occurred while handling keycloak realm group", "name", request.Name) } else { helper.SetSuccessStatus(&instance) + result.RequeueAfter = r.successReconcileTimeout } diff --git a/controllers/keycloakrealmidentityprovider/keycloakrealmidentityprovider_controller.go b/controllers/keycloakrealmidentityprovider/keycloakrealmidentityprovider_controller.go index 87c541da..817755b8 100644 --- a/controllers/keycloakrealmidentityprovider/keycloakrealmidentityprovider_controller.go +++ b/controllers/keycloakrealmidentityprovider/keycloakrealmidentityprovider_controller.go @@ -128,6 +128,7 @@ func (r *Reconcile) Reconcile(ctx context.Context, request reconcile.Request) (r log.Error(err, "an error has occurred while handling keycloak realm idp", "name", request.Name) } else { helper.SetSuccessStatus(&instance) + result.RequeueAfter = r.successReconcileTimeout } diff --git a/controllers/keycloakrealmrolebatch/keycloakrealmrolebatch_controller.go b/controllers/keycloakrealmrolebatch/keycloakrealmrolebatch_controller.go index 582eb4b0..f5af6ecf 100644 --- a/controllers/keycloakrealmrolebatch/keycloakrealmrolebatch_controller.go +++ b/controllers/keycloakrealmrolebatch/keycloakrealmrolebatch_controller.go @@ -95,6 +95,7 @@ func (r *ReconcileKeycloakRealmRoleBatch) Reconcile(ctx context.Context, request log.Error(err, "an error has occurred while handling keycloak realm role batch") } else { helper.SetSuccessStatus(&instance) + result.RequeueAfter = r.successReconcileTimeout } @@ -165,7 +166,9 @@ func (r *ReconcileKeycloakRealmRoleBatch) putRoles( } else if err == nil { if r.isOwner(batch, &crRole) { log.Info("Role already created") + roles = append(roles, crRole) + continue } diff --git a/deploy-templates/_crd_examples/keycloakrealm.yaml b/deploy-templates/_crd_examples/keycloakrealm.yaml index bc390acb..03826681 100644 --- a/deploy-templates/_crd_examples/keycloakrealm.yaml +++ b/deploy-templates/_crd_examples/keycloakrealm.yaml @@ -32,3 +32,47 @@ spec: refreshTokenMaxReuse: 300 revokeRefreshToken: true defaultSignatureAlgorithm: RS256 + userProfileConfig: + unmanagedAttributePolicy: "ENABLED" + attributes: + - name: "test-attribute" + displayName: "Test Attribute" + required: + roles: + - "admin" + scopes: + - "profile" + multivalued: true + group: "test-group" + permissions: + edit: + - "admin" + view: + - "admin" + - "user" + selector: + scopes: + - "profile" + annotations: + inputType: "text" + validations: + email: + max-local-length: + intVal: 64 + local-date: {} + options: + options: + sliceVal: + - "option1" + - "option2" + multivalued: + min: + stringVal: "1" + max: + stringVal: "10" + groups: + - name: "test-group" + displayDescription: "Test Group" + displayHeader: "Test Group" + annotations: + groupAnnotation: "groupAnnotation" diff --git a/deploy-templates/crds/v1.edp.epam.com_clusterkeycloakrealms.yaml b/deploy-templates/crds/v1.edp.epam.com_clusterkeycloakrealms.yaml index 009297f2..7996b0d8 100644 --- a/deploy-templates/crds/v1.edp.epam.com_clusterkeycloakrealms.yaml +++ b/deploy-templates/crds/v1.edp.epam.com_clusterkeycloakrealms.yaml @@ -243,6 +243,143 @@ spec: Otherwise, refresh tokens are not revoked when used and can be used multiple times. type: boolean type: object + userProfileConfig: + description: UserProfileConfig is the configuration for user profiles + in the realm. + nullable: true + properties: + attributes: + description: Attributes specifies the list of user profile attributes. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations specifies the annotations for the + attribute. + type: object + displayName: + description: Display name for the attribute. + type: string + group: + description: Group to which the attribute belongs. + type: string + multivalued: + description: |- + Multivalued specifies if this attribute supports multiple values. + This setting is an indicator and does not enable any validation + type: boolean + name: + description: Name of the user attribute, used to uniquely + identify an attribute. + type: string + permissions: + description: Permissions specifies the permissions for the + attribute. + properties: + edit: + description: Edit specifies who can edit the attribute. + items: + type: string + type: array + view: + description: View specifies who can view the attribute. + items: + type: string + type: array + type: object + required: + description: Required indicates that the attribute must + be set by users and administrators. + properties: + roles: + description: Roles specifies the roles for whom the + attribute is required. + items: + type: string + type: array + scopes: + description: Scopes specifies the scopes when the attribute + is required. + items: + type: string + type: array + type: object + selector: + description: Selector specifies the scopes for which the + attribute is available. + properties: + scopes: + description: Scopes specifies the scopes for which the + attribute is available. + items: + type: string + type: array + type: object + validations: + additionalProperties: + additionalProperties: + properties: + intVal: + type: integer + mapVal: + additionalProperties: + type: string + nullable: true + type: object + sliceVal: + items: + type: string + nullable: true + type: array + stringVal: + type: string + type: object + type: object + description: Validations specifies the validations for the + attribute. + type: object + required: + - name + type: object + type: array + groups: + description: Groups specifies the list of user profile groups. + items: + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations specifies the annotations for the group. + nullable + type: object + displayDescription: + description: DisplayDescription specifies a user-friendly + name for the group that should be used when rendering + a group of attributes in user-facing forms. + type: string + displayHeader: + description: DisplayHeader specifies a text that should + be used as a header when rendering user-facing forms. + type: string + name: + description: Name is unique name of the group. + type: string + required: + - name + type: object + type: array + unmanagedAttributePolicy: + description: |- + UnmanagedAttributePolicy are user attributes not explicitly defined in the user profile configuration. + Empty value means that unmanaged attributes are disabled. + Possible values: + ENABLED - unmanaged attributes are allowed. + ADMIN_VIEW - unmanaged attributes are read-only and only available through the administration console and API. + ADMIN_EDIT - unmanaged attributes can be managed only through the administration console and API. + type: string + type: object required: - clusterKeycloakRef - realmName diff --git a/deploy-templates/crds/v1.edp.epam.com_keycloakrealms.yaml b/deploy-templates/crds/v1.edp.epam.com_keycloakrealms.yaml index 0041df8b..f448519f 100644 --- a/deploy-templates/crds/v1.edp.epam.com_keycloakrealms.yaml +++ b/deploy-templates/crds/v1.edp.epam.com_keycloakrealms.yaml @@ -253,6 +253,145 @@ spec: Otherwise, refresh tokens are not revoked when used and can be used multiple times. type: boolean type: object + userProfileConfig: + description: |- + UserProfileConfig is the configuration for user profiles in the realm. + Attributes and groups will be added to the current realm configuration. + Deletion of attributes and groups is not supported. + nullable: true + properties: + attributes: + description: Attributes specifies the list of user profile attributes. + items: + properties: + annotations: + additionalProperties: + type: string + description: Annotations specifies the annotations for the + attribute. + type: object + displayName: + description: Display name for the attribute. + type: string + group: + description: Group to which the attribute belongs. + type: string + multivalued: + description: |- + Multivalued specifies if this attribute supports multiple values. + This setting is an indicator and does not enable any validation + type: boolean + name: + description: Name of the user attribute, used to uniquely + identify an attribute. + type: string + permissions: + description: Permissions specifies the permissions for the + attribute. + properties: + edit: + description: Edit specifies who can edit the attribute. + items: + type: string + type: array + view: + description: View specifies who can view the attribute. + items: + type: string + type: array + type: object + required: + description: Required indicates that the attribute must + be set by users and administrators. + properties: + roles: + description: Roles specifies the roles for whom the + attribute is required. + items: + type: string + type: array + scopes: + description: Scopes specifies the scopes when the attribute + is required. + items: + type: string + type: array + type: object + selector: + description: Selector specifies the scopes for which the + attribute is available. + properties: + scopes: + description: Scopes specifies the scopes for which the + attribute is available. + items: + type: string + type: array + type: object + validations: + additionalProperties: + additionalProperties: + properties: + intVal: + type: integer + mapVal: + additionalProperties: + type: string + nullable: true + type: object + sliceVal: + items: + type: string + nullable: true + type: array + stringVal: + type: string + type: object + type: object + description: Validations specifies the validations for the + attribute. + type: object + required: + - name + type: object + type: array + groups: + description: Groups specifies the list of user profile groups. + items: + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations specifies the annotations for the group. + nullable + type: object + displayDescription: + description: DisplayDescription specifies a user-friendly + name for the group that should be used when rendering + a group of attributes in user-facing forms. + type: string + displayHeader: + description: DisplayHeader specifies a text that should + be used as a header when rendering user-facing forms. + type: string + name: + description: Name is unique name of the group. + type: string + required: + - name + type: object + type: array + unmanagedAttributePolicy: + description: |- + UnmanagedAttributePolicy are user attributes not explicitly defined in the user profile configuration. + Empty value means that unmanaged attributes are disabled. + Possible values: + ENABLED - unmanaged attributes are allowed. + ADMIN_VIEW - unmanaged attributes are read-only and only available through the administration console and API. + ADMIN_EDIT - unmanaged attributes can be managed only through the administration console and API. + type: string + type: object users: description: Users is a list of users to create in the realm. items: diff --git a/docs/api.md b/docs/api.md index bb08f356..dacf7d3d 100644 --- a/docs/api.md +++ b/docs/api.md @@ -171,6 +171,13 @@ Use in combination with the default hostname provider to override the base URL f TokenSettings is the configuration for tokens in the realm.
false + + userProfileConfig + object + + UserProfileConfig is the configuration for user profiles in the realm.
+ + false @@ -474,12 +481,12 @@ Otherwise, refresh tokens are not revoked when used and can be used multiple tim -### ClusterKeycloakRealm.status -[↩ Parent](#clusterkeycloakrealm) +### ClusterKeycloakRealm.spec.userProfileConfig +[↩ Parent](#clusterkeycloakrealmspec) -ClusterKeycloakRealmStatus defines the observed state of ClusterKeycloakRealm. +UserProfileConfig is the configuration for user profiles in the realm. @@ -491,40 +498,41 @@ ClusterKeycloakRealmStatus defines the observed state of ClusterKeycloakRealm. - - + + - - + + - +
availablebooleanattributes[]object -
+ Attributes specifies the list of user profile attributes.
false
failureCountintegergroups[]object -
-
- Format: int64
+ Groups specifies the list of user profile groups.
false
valueunmanagedAttributePolicy string -
+ UnmanagedAttributePolicy are user attributes not explicitly defined in the user profile configuration. +Empty value means that unmanaged attributes are disabled. +Possible values: +ENABLED - unmanaged attributes are allowed. +ADMIN_VIEW - unmanaged attributes are read-only and only available through the administration console and API. +ADMIN_EDIT - unmanaged attributes can be managed only through the administration console and API.
false
-## ClusterKeycloak -[↩ Parent](#v1edpepamcomv1alpha1 ) - +### ClusterKeycloakRealm.spec.userProfileConfig.attributes[index] +[↩ Parent](#clusterkeycloakrealmspecuserprofileconfig) -ClusterKeycloak is the Schema for the clusterkeycloaks API. @@ -536,148 +544,79 @@ ClusterKeycloak is the Schema for the clusterkeycloaks API. - - - - - - - - - - - - - - - - - - - + + - + - - + + - -
apiVersionstringv1.edp.epam.com/v1alpha1true
kindstringClusterKeycloaktrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobjectnamestring - ClusterKeycloakSpec defines the desired state of ClusterKeycloak.
+ Name of the user attribute, used to uniquely identify an attribute.
falsetrue
statusobjectannotationsmap[string]string - ClusterKeycloakStatus defines the observed state of ClusterKeycloak.
-
- Default: map[connected:false]
+ Annotations specifies the annotations for the attribute.
false
- - -### ClusterKeycloak.spec -[↩ Parent](#clusterkeycloak) - - - -ClusterKeycloakSpec defines the desired state of ClusterKeycloak. - - - - - - - - - - - - + + - + - + - + - - + + - + - - + + - -
NameTypeDescriptionRequired
secret
displayName string - Secret is a secret name which contains admin credentials.
+ Display name for the attribute.
truefalse
urlgroup string - URL of keycloak service.
+ Group to which the attribute belongs.
truefalse
adminTypeenummultivaluedboolean - AdminType can be user or serviceAccount, if serviceAccount was specified, -then client_credentials grant type should be used for getting admin realm token.
-
- Enum: serviceAccount, user
- Default: user
+ Multivalued specifies if this attribute supports multiple values. +This setting is an indicator and does not enable any validation
false
caCertpermissions object - CACert defines the root certificate authority -that api clients use when verifying server certificates. -Resources should be in the namespace defined in operator OPERATOR_NAMESPACE env.
+ Permissions specifies the permissions for the attribute.
false
insecureSkipVerifybooleanrequiredobject - InsecureSkipVerify controls whether api client verifies the server's -certificate chain and host name. If InsecureSkipVerify is true, api client -accepts any certificate presented by the server and any host name in that -certificate.
+ Required indicates that the attribute must be set by users and administrators.
false
- - -### ClusterKeycloak.spec.caCert -[↩ Parent](#clusterkeycloakspec) - - - -CACert defines the root certificate authority -that api clients use when verifying server certificates. -Resources should be in the namespace defined in operator OPERATOR_NAMESPACE env. - - - - - - - - - - - - + + - - + +
NameTypeDescriptionRequired
configMapKeyRef
selector object - Selects a key of a ConfigMap.
+ Selector specifies the scopes for which the attribute is available.
false
secretKeyRefobjectvalidationsmap[string]map[string]object - Selects a key of a secret.
+ Validations specifies the validations for the attribute.
false
-### ClusterKeycloak.spec.caCert.configMapKeyRef -[↩ Parent](#clusterkeycloakspeccacert) +### ClusterKeycloakRealm.spec.userProfileConfig.attributes[index].permissions +[↩ Parent](#clusterkeycloakrealmspecuserprofileconfigattributesindex) -Selects a key of a ConfigMap. +Permissions specifies the permissions for the attribute. @@ -689,31 +628,29 @@ Selects a key of a ConfigMap. - - + + - + - - + +
keystringedit[]string - The key to select.
+ Edit specifies who can edit the attribute.
truefalse
namestringview[]string - Name of the referent. -More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -TODO: Add other useful fields. apiVersion, kind, uid?
+ View specifies who can view the attribute.
false
-### ClusterKeycloak.spec.caCert.secretKeyRef -[↩ Parent](#clusterkeycloakspeccacert) +### ClusterKeycloakRealm.spec.userProfileConfig.attributes[index].required +[↩ Parent](#clusterkeycloakrealmspecuserprofileconfigattributesindex) -Selects a key of a secret. +Required indicates that the attribute must be set by users and administrators. @@ -725,31 +662,29 @@ Selects a key of a secret. - - + + - + - - + +
keystringroles[]string - The key of the secret to select from.
+ Roles specifies the roles for whom the attribute is required.
truefalse
namestringscopes[]string - Name of the referent. -More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names -TODO: Add other useful fields. apiVersion, kind, uid?
+ Scopes specifies the scopes when the attribute is required.
false
-### ClusterKeycloak.status -[↩ Parent](#clusterkeycloak) +### ClusterKeycloakRealm.spec.userProfileConfig.attributes[index].selector +[↩ Parent](#clusterkeycloakrealmspecuserprofileconfigattributesindex) -ClusterKeycloakStatus defines the observed state of ClusterKeycloak. +Selector specifies the scopes for which the attribute is available. @@ -761,53 +696,22 @@ ClusterKeycloakStatus defines the observed state of ClusterKeycloak. - - + + - +
connectedbooleanscopes[]string - Connected shows if keycloak service is up and running.
+ Scopes specifies the scopes for which the attribute is available.
truefalse
-# v1.edp.epam.com/v1 - -Resource Types: - -- [KeycloakAuthFlow](#keycloakauthflow) - -- [KeycloakClient](#keycloakclient) - -- [KeycloakClientScope](#keycloakclientscope) - -- [KeycloakRealmComponent](#keycloakrealmcomponent) - -- [KeycloakRealmGroup](#keycloakrealmgroup) - -- [KeycloakRealmIdentityProvider](#keycloakrealmidentityprovider) - -- [KeycloakRealmRoleBatch](#keycloakrealmrolebatch) - -- [KeycloakRealmRole](#keycloakrealmrole) - -- [KeycloakRealm](#keycloakrealm) - -- [KeycloakRealmUser](#keycloakrealmuser) - -- [Keycloak](#keycloak) - - - - -## KeycloakAuthFlow -[↩ Parent](#v1edpepamcomv1 ) - +### ClusterKeycloakRealm.spec.userProfileConfig.attributes[index].validations[key][key] +[↩ Parent](#clusterkeycloakrealmspecuserprofileconfigattributesindex) -KeycloakAuthFlow is the Schema for the keycloak authentication flow API. @@ -819,46 +723,43 @@ KeycloakAuthFlow is the Schema for the keycloak authentication flow API. - - - - - - - - - - - - - - - - + + + + - - + + - - + + + + + + +
apiVersionstringv1.edp.epam.com/v1true
kindstringKeycloakAuthFlowtrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.trueintValinteger +
+
false
specobjectmapValmap[string]string - KeycloakAuthFlowSpec defines the desired state of KeycloakAuthFlow.
+
false
statusobjectsliceVal[]string - KeycloakAuthFlowStatus defines the observed state of KeycloakAuthFlow.
+
+
false
stringValstring +
false
-### KeycloakAuthFlow.spec -[↩ Parent](#keycloakauthflow) +### ClusterKeycloakRealm.spec.userProfileConfig.groups[index] +[↩ Parent](#clusterkeycloakrealmspecuserprofileconfig) + -KeycloakAuthFlowSpec defines the desired state of KeycloakAuthFlow. @@ -870,93 +771,44 @@ KeycloakAuthFlowSpec defines the desired state of KeycloakAuthFlow. - + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + - - - - -
aliasname string - Alias is display name for authentication flow.
+ Name is unique name of the group.
true
builtInbooleanannotationsmap[string]string - BuiltIn is true if this is built-in auth flow.
-
true
providerIdstring - ProviderID for root auth flow and provider for child auth flows.
-
true
topLevelboolean - TopLevel is true if this is root auth flow.
-
true
authenticationExecutions[]object - AuthenticationExecutions is list of authentication executions for this auth flow.
-
false
childRequirementstring - ChildRequirement is requirement for child execution. Available options: REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL.
-
false
childTypestring - ChildType is type for auth flow if it has a parent, available options: basic-flow, form-flow
-
false
descriptionstring - Description is description for authentication flow.
+ Annotations specifies the annotations for the group. +nullable
false
parentNamedisplayDescription string - ParentName is name of parent auth flow.
+ DisplayDescription specifies a user-friendly name for the group that should be used when rendering a group of attributes in user-facing forms.
false
realmdisplayHeader string - Deprecated: use RealmRef instead. -Realm is name of KeycloakRealm custom resource.
-
false
realmRefobject - RealmRef is reference to Realm custom resource.
+ DisplayHeader specifies a text that should be used as a header when rendering user-facing forms.
false
-### KeycloakAuthFlow.spec.authenticationExecutions[index] -[↩ Parent](#keycloakauthflowspec) +### ClusterKeycloakRealm.status +[↩ Parent](#clusterkeycloakrealm) -AuthenticationExecution defines keycloak authentication execution. +ClusterKeycloakRealmStatus defines the observed state of ClusterKeycloakRealm. @@ -968,57 +820,40 @@ AuthenticationExecution defines keycloak authentication execution. - - - - - - - - - - - - - - - - + - + - +
aliasstring - Alias is display name for this execution.
-
false
authenticatorstring - Authenticator is name of authenticator.
-
false
authenticatorConfigobject - AuthenticatorConfig is configuration for authenticator.
-
false
authenticatorFlowavailable boolean - AuthenticatorFlow is true if this is auth flow.
+
false
priorityfailureCount integer - Priority is priority for this execution. Lower values have higher priority.
+
+
+ Format: int64
false
requirementvalue string - Requirement is requirement for this execution. Available options: REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL.
+
false
+## ClusterKeycloak +[↩ Parent](#v1edpepamcomv1alpha1 ) -### KeycloakAuthFlow.spec.authenticationExecutions[index].authenticatorConfig -[↩ Parent](#keycloakauthflowspecauthenticationexecutionsindex) -AuthenticatorConfig is configuration for authenticator. + + +ClusterKeycloak is the Schema for the clusterkeycloaks API. @@ -1030,29 +865,48 @@ AuthenticatorConfig is configuration for authenticator. - - + + + + + + + + + + + + + + + + + + + - - + +
aliasstringapiVersionstringv1.edp.epam.com/v1alpha1true
kindstringClusterKeycloaktrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject - Alias is display name for authenticator config.
+ ClusterKeycloakSpec defines the desired state of ClusterKeycloak.
false
configmap[string]stringstatusobject - Config is configuration for authenticator.
+ ClusterKeycloakStatus defines the observed state of ClusterKeycloak.
+
+ Default: map[connected:false]
false
-### KeycloakAuthFlow.spec.realmRef -[↩ Parent](#keycloakauthflowspec) +### ClusterKeycloak.spec +[↩ Parent](#clusterkeycloak) -RealmRef is reference to Realm custom resource. +ClusterKeycloakSpec defines the desired state of ClusterKeycloak. @@ -1064,31 +918,61 @@ RealmRef is reference to Realm custom resource. - + + + + + + + + + + + - - + + + + + + +
kindsecretstring + Secret is a secret name which contains admin credentials.
+
true
urlstring + URL of keycloak service.
+
true
adminType enum - Kind specifies the kind of the Keycloak resource.
+ AdminType can be user or serviceAccount, if serviceAccount was specified, +then client_credentials grant type should be used for getting admin realm token.

- Enum: KeycloakRealm, ClusterKeycloakRealm
+ Enum: serviceAccount, user
+ Default: user
false
namestringcaCertobject - Name specifies the name of the Keycloak resource.
+ CACert defines the root certificate authority +that api clients use when verifying server certificates. +Resources should be in the namespace defined in operator OPERATOR_NAMESPACE env.
+
false
insecureSkipVerifyboolean + InsecureSkipVerify controls whether api client verifies the server's +certificate chain and host name. If InsecureSkipVerify is true, api client +accepts any certificate presented by the server and any host name in that +certificate.
false
-### KeycloakAuthFlow.status -[↩ Parent](#keycloakauthflow) +### ClusterKeycloak.spec.caCert +[↩ Parent](#clusterkeycloakspec) -KeycloakAuthFlowStatus defines the observed state of KeycloakAuthFlow. +CACert defines the root certificate authority +that api clients use when verifying server certificates. +Resources should be in the namespace defined in operator OPERATOR_NAMESPACE env. @@ -1100,33 +984,29 @@ KeycloakAuthFlowStatus defines the observed state of KeycloakAuthFlow. - - + + - - + +
failureCountintegerconfigMapKeyRefobject -
-
- Format: int64
+ Selects a key of a ConfigMap.
false
valuestringsecretKeyRefobject -
+ Selects a key of a secret.
false
-## KeycloakClient -[↩ Parent](#v1edpepamcomv1 ) - - +### ClusterKeycloak.spec.caCert.configMapKeyRef +[↩ Parent](#clusterkeycloakspeccacert) -KeycloakClient is the Schema for the keycloak clients API. +Selects a key of a ConfigMap. @@ -1138,46 +1018,31 @@ KeycloakClient is the Schema for the keycloak clients API. - - - - - - - - - - - - - - - - - - - + + - + - - + +
apiVersionstringv1.edp.epam.com/v1true
kindstringKeycloakClienttrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobjectkeystring - KeycloakClientSpec defines the desired state of KeycloakClient.
+ The key to select.
falsetrue
statusobjectnamestring - KeycloakClientStatus defines the observed state of KeycloakClient.
+ Name of the referent. +More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +TODO: Add other useful fields. apiVersion, kind, uid?
false
-### KeycloakClient.spec -[↩ Parent](#keycloakclient) +### ClusterKeycloak.spec.caCert.secretKeyRef +[↩ Parent](#clusterkeycloakspeccacert) -KeycloakClientSpec defines the desired state of KeycloakClient. +Selects a key of a secret. @@ -1189,15 +1054,479 @@ KeycloakClientSpec defines the desired state of KeycloakClient. - + - - + + + + + +
clientIdkey string - ClientId is a unique keycloak client ID referenced in URI and tokens.
+ The key of the secret to select from.
true
advancedProtocolMappersbooleannamestring + Name of the referent. +More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +TODO: Add other useful fields. apiVersion, kind, uid?
+
false
+ + +### ClusterKeycloak.status +[↩ Parent](#clusterkeycloak) + + + +ClusterKeycloakStatus defines the observed state of ClusterKeycloak. + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
connectedboolean + Connected shows if keycloak service is up and running.
+
true
+ +# v1.edp.epam.com/v1 + +Resource Types: + +- [KeycloakAuthFlow](#keycloakauthflow) + +- [KeycloakClient](#keycloakclient) + +- [KeycloakClientScope](#keycloakclientscope) + +- [KeycloakRealmComponent](#keycloakrealmcomponent) + +- [KeycloakRealmGroup](#keycloakrealmgroup) + +- [KeycloakRealmIdentityProvider](#keycloakrealmidentityprovider) + +- [KeycloakRealmRoleBatch](#keycloakrealmrolebatch) + +- [KeycloakRealmRole](#keycloakrealmrole) + +- [KeycloakRealm](#keycloakrealm) + +- [KeycloakRealmUser](#keycloakrealmuser) + +- [Keycloak](#keycloak) + + + + +## KeycloakAuthFlow +[↩ Parent](#v1edpepamcomv1 ) + + + + + + +KeycloakAuthFlow is the Schema for the keycloak authentication flow API. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringv1.edp.epam.com/v1true
kindstringKeycloakAuthFlowtrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + KeycloakAuthFlowSpec defines the desired state of KeycloakAuthFlow.
+
false
statusobject + KeycloakAuthFlowStatus defines the observed state of KeycloakAuthFlow.
+
false
+ + +### KeycloakAuthFlow.spec +[↩ Parent](#keycloakauthflow) + + + +KeycloakAuthFlowSpec defines the desired state of KeycloakAuthFlow. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
aliasstring + Alias is display name for authentication flow.
+
true
builtInboolean + BuiltIn is true if this is built-in auth flow.
+
true
providerIdstring + ProviderID for root auth flow and provider for child auth flows.
+
true
topLevelboolean + TopLevel is true if this is root auth flow.
+
true
authenticationExecutions[]object + AuthenticationExecutions is list of authentication executions for this auth flow.
+
false
childRequirementstring + ChildRequirement is requirement for child execution. Available options: REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL.
+
false
childTypestring + ChildType is type for auth flow if it has a parent, available options: basic-flow, form-flow
+
false
descriptionstring + Description is description for authentication flow.
+
false
parentNamestring + ParentName is name of parent auth flow.
+
false
realmstring + Deprecated: use RealmRef instead. +Realm is name of KeycloakRealm custom resource.
+
false
realmRefobject + RealmRef is reference to Realm custom resource.
+
false
+ + +### KeycloakAuthFlow.spec.authenticationExecutions[index] +[↩ Parent](#keycloakauthflowspec) + + + +AuthenticationExecution defines keycloak authentication execution. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
aliasstring + Alias is display name for this execution.
+
false
authenticatorstring + Authenticator is name of authenticator.
+
false
authenticatorConfigobject + AuthenticatorConfig is configuration for authenticator.
+
false
authenticatorFlowboolean + AuthenticatorFlow is true if this is auth flow.
+
false
priorityinteger + Priority is priority for this execution. Lower values have higher priority.
+
false
requirementstring + Requirement is requirement for this execution. Available options: REQUIRED, ALTERNATIVE, DISABLED, CONDITIONAL.
+
false
+ + +### KeycloakAuthFlow.spec.authenticationExecutions[index].authenticatorConfig +[↩ Parent](#keycloakauthflowspecauthenticationexecutionsindex) + + + +AuthenticatorConfig is configuration for authenticator. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
aliasstring + Alias is display name for authenticator config.
+
false
configmap[string]string + Config is configuration for authenticator.
+
false
+ + +### KeycloakAuthFlow.spec.realmRef +[↩ Parent](#keycloakauthflowspec) + + + +RealmRef is reference to Realm custom resource. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindenum + Kind specifies the kind of the Keycloak resource.
+
+ Enum: KeycloakRealm, ClusterKeycloakRealm
+
false
namestring + Name specifies the name of the Keycloak resource.
+
false
+ + +### KeycloakAuthFlow.status +[↩ Parent](#keycloakauthflow) + + + +KeycloakAuthFlowStatus defines the observed state of KeycloakAuthFlow. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
failureCountinteger +
+
+ Format: int64
+
false
valuestring +
+
false
+ +## KeycloakClient +[↩ Parent](#v1edpepamcomv1 ) + + + + + + +KeycloakClient is the Schema for the keycloak clients API. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
apiVersionstringv1.edp.epam.com/v1true
kindstringKeycloakClienttrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + KeycloakClientSpec defines the desired state of KeycloakClient.
+
false
statusobject + KeycloakClientStatus defines the observed state of KeycloakClient.
+
false
+ + +### KeycloakClient.spec +[↩ Parent](#keycloakclient) + + + +KeycloakClientSpec defines the desired state of KeycloakClient. + + + + + + + + + + + + + + + + + + @@ -2835,7 +3164,257 @@ KeycloakRealmGroup is the Schema for the keycloak group API. - + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
clientIdstring + ClientId is a unique keycloak client ID referenced in URI and tokens.
+
true
advancedProtocolMappersboolean AdvancedProtocolMappers is a flag to enable advanced protocol mappers.
kind stringKeycloakRealmGroupKeycloakRealmGrouptrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.true
specobject + KeycloakRealmGroupSpec defines the desired state of KeycloakRealmGroup.
+
false
statusobject + KeycloakRealmGroupStatus defines the observed state of KeycloakRealmGroup.
+
false
+ + +### KeycloakRealmGroup.spec +[↩ Parent](#keycloakrealmgroup) + + + +KeycloakRealmGroupSpec defines the desired state of KeycloakRealmGroup. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
namestring + Name of keycloak group.
+
true
accessmap[string]boolean + Access is a map of group access.
+
false
attributesmap[string][]string + Attributes is a map of group attributes.
+
false
clientRoles[]object + ClientRoles is a list of client roles assigned to group.
+
false
pathstring + Path is a group path.
+
false
realmstring + Deprecated: use RealmRef instead. +Realm is name of KeycloakRealm custom resource.
+
false
realmRefobject + RealmRef is reference to Realm custom resource.
+
false
realmRoles[]string + RealmRoles is a list of realm roles assigned to group.
+
false
subGroups[]string + SubGroups is a list of subgroups assigned to group.
+
false
+ + +### KeycloakRealmGroup.spec.clientRoles[index] +[↩ Parent](#keycloakrealmgroupspec) + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
clientIdstring + ClientID is a client ID.
+
true
roles[]string + Roles is a list of client roles names assigned to service account.
+
false
+ + +### KeycloakRealmGroup.spec.realmRef +[↩ Parent](#keycloakrealmgroupspec) + + + +RealmRef is reference to Realm custom resource. + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
kindenum + Kind specifies the kind of the Keycloak resource.
+
+ Enum: KeycloakRealm, ClusterKeycloakRealm
+
false
namestring + Name specifies the name of the Keycloak resource.
+
false
+ + +### KeycloakRealmGroup.status +[↩ Parent](#keycloakrealmgroup) + + + +KeycloakRealmGroupStatus defines the observed state of KeycloakRealmGroup. + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescriptionRequired
failureCountinteger +
+
+ Format: int64
+
false
idstring + ID is a group ID.
+
false
valuestring +
+
false
+ +## KeycloakRealmIdentityProvider +[↩ Parent](#v1edpepamcomv1 ) + + + + + + +KeycloakRealmIdentityProvider is the Schema for the keycloak realm identity provider API. + + + + + + + + + + + + + + + + + + + + @@ -2844,29 +3423,29 @@ KeycloakRealmGroup is the Schema for the keycloak group API. - + - +
NameTypeDescriptionRequired
apiVersionstringv1.edp.epam.com/v1true
kindstringKeycloakRealmIdentityProvider true
Refer to the Kubernetes API documentation for the fields of the `metadata` field. true
specspec object - KeycloakRealmGroupSpec defines the desired state of KeycloakRealmGroup.
+ KeycloakRealmIdentityProviderSpec defines the desired state of KeycloakRealmIdentityProvider.
false
statusstatus object - KeycloakRealmGroupStatus defines the observed state of KeycloakRealmGroup.
+ KeycloakRealmIdentityProviderStatus defines the observed state of KeycloakRealmIdentityProvider.
false
-### KeycloakRealmGroup.spec -[↩ Parent](#keycloakrealmgroup) +### KeycloakRealmIdentityProvider.spec +[↩ Parent](#keycloakrealmidentityprovider) -KeycloakRealmGroupSpec defines the desired state of KeycloakRealmGroup. +KeycloakRealmIdentityProviderSpec defines the desired state of KeycloakRealmIdentityProvider. @@ -2878,38 +3457,75 @@ KeycloakRealmGroupSpec defines the desired state of KeycloakRealmGroup. - + - - + + + + + + + + + + + + + + + + + - - + + - - + + - + + + + + + + + + + + @@ -2921,32 +3537,32 @@ Realm is name of KeycloakRealm custom resource.
- + - - + + - - + +
namealias string - Name of keycloak group.
+ Alias is a alias of identity provider.
true
accessmap[string]booleanconfigmap[string]string - Access is a map of group access.
+ Config is a map of identity provider configuration. +Map key is a name of configuration property, map value is a value of configuration property. +Any value can be a reference to k8s secret, in this case value should be in format $secretName:secretKey.
+
true
enabledboolean + Enabled is a flag to enable/disable identity provider.
+
true
providerIdstring + ProviderID is a provider ID of identity provider.
+
true
addReadTokenRoleOnCreateboolean + AddReadTokenRoleOnCreate is a flag to add read token role on create.
false
attributesmap[string][]stringauthenticateByDefaultboolean - Attributes is a map of group attributes.
+ AuthenticateByDefault is a flag to authenticate by default.
false
clientRoles[]objectdisplayNamestring - ClientRoles is a list of client roles assigned to group.
+ DisplayName is a display name of identity provider.
false
pathfirstBrokerLoginFlowAlias string - Path is a group path.
+ FirstBrokerLoginFlowAlias is a first broker login flow alias.
+
false
linkOnlyboolean + LinkOnly is a flag to link only.
+
false
mappers[]object + Mappers is a list of identity provider mappers.
false
false
realmRefrealmRef object RealmRef is reference to Realm custom resource.
false
realmRoles[]stringstoreTokenboolean - RealmRoles is a list of realm roles assigned to group.
+ StoreToken is a flag to store token.
false
subGroups[]stringtrustEmailboolean - SubGroups is a list of subgroups assigned to group.
+ TrustEmail is a flag to trust email.
false
-### KeycloakRealmGroup.spec.clientRoles[index] -[↩ Parent](#keycloakrealmgroupspec) +### KeycloakRealmIdentityProvider.spec.mappers[index] +[↩ Parent](#keycloakrealmidentityproviderspec) @@ -2962,25 +3578,39 @@ Realm is name of KeycloakRealm custom resource.
- clientId + config + map[string]string + + Config is a map of identity provider mapper configuration.
+ + false + + identityProviderAlias string - ClientID is a client ID.
+ IdentityProviderAlias is a identity provider alias.
- true + false - roles - []string + identityProviderMapper + string - Roles is a list of client roles names assigned to service account.
+ IdentityProviderMapper is a identity provider mapper.
+ + false + + name + string + + Name is a name of identity provider mapper.
false -### KeycloakRealmGroup.spec.realmRef -[↩ Parent](#keycloakrealmgroupspec) +### KeycloakRealmIdentityProvider.spec.realmRef +[↩ Parent](#keycloakrealmidentityproviderspec) @@ -3015,12 +3645,12 @@ RealmRef is reference to Realm custom resource. -### KeycloakRealmGroup.status -[↩ Parent](#keycloakrealmgroup) +### KeycloakRealmIdentityProvider.status +[↩ Parent](#keycloakrealmidentityprovider) -KeycloakRealmGroupStatus defines the observed state of KeycloakRealmGroup. +KeycloakRealmIdentityProviderStatus defines the observed state of KeycloakRealmIdentityProvider. @@ -3040,13 +3670,6 @@ KeycloakRealmGroupStatus defines the observed state of KeycloakRealmGroup. Format: int64
- - - - - @@ -3057,7 +3680,7 @@ KeycloakRealmGroupStatus defines the observed state of KeycloakRealmGroup.
false
idstring - ID is a group ID.
-
false
value string
-## KeycloakRealmIdentityProvider +## KeycloakRealmRoleBatch [↩ Parent](#v1edpepamcomv1 ) @@ -3065,7 +3688,7 @@ KeycloakRealmGroupStatus defines the observed state of KeycloakRealmGroup. -KeycloakRealmIdentityProvider is the Schema for the keycloak realm identity provider API. +KeycloakRealmRoleBatch is the Schema for the keycloak roles API. @@ -3085,7 +3708,7 @@ KeycloakRealmIdentityProvider is the Schema for the keycloak realm identity prov - + @@ -3094,29 +3717,29 @@ KeycloakRealmIdentityProvider is the Schema for the keycloak realm identity prov - + - +
kind stringKeycloakRealmIdentityProviderKeycloakRealmRoleBatch true
Refer to the Kubernetes API documentation for the fields of the `metadata` field. true
specspec object - KeycloakRealmIdentityProviderSpec defines the desired state of KeycloakRealmIdentityProvider.
+ KeycloakRealmRoleBatchSpec defines the desired state of KeycloakRealmRoleBatch.
false
statusstatus object - KeycloakRealmIdentityProviderStatus defines the observed state of KeycloakRealmIdentityProvider.
+ KeycloakRealmRoleBatchStatus defines the observed state of KeycloakRealmRoleBatch.
false
-### KeycloakRealmIdentityProvider.spec -[↩ Parent](#keycloakrealmidentityprovider) +### KeycloakRealmRoleBatch.spec +[↩ Parent](#keycloakrealmrolebatch) -KeycloakRealmIdentityProviderSpec defines the desired state of KeycloakRealmIdentityProvider. +KeycloakRealmRoleBatchSpec defines the desired state of KeycloakRealmRoleBatch. @@ -3128,112 +3751,95 @@ KeycloakRealmIdentityProviderSpec defines the desired state of KeycloakRealmIden - - - - - - - - - - - - + + - + - - - - - - - + + - - + +
aliasstring - Alias is a alias of identity provider.
-
true
configmap[string]string - Config is a map of identity provider configuration. -Map key is a name of configuration property, map value is a value of configuration property. -Any value can be a reference to k8s secret, in this case value should be in format $secretName:secretKey.
-
true
enabledbooleanroles[]object - Enabled is a flag to enable/disable identity provider.
+ Roles is a list of roles to be created.
true
providerIdrealm string - ProviderID is a provider ID of identity provider.
-
true
addReadTokenRoleOnCreateboolean - AddReadTokenRoleOnCreate is a flag to add read token role on create.
+ Deprecated: use RealmRef instead. +Realm is name of KeycloakRealm custom resource.
false
authenticateByDefaultbooleanrealmRefobject - AuthenticateByDefault is a flag to authenticate by default.
+ RealmRef is reference to Realm custom resource.
false
displayName
+ + +### KeycloakRealmRoleBatch.spec.roles[index] +[↩ Parent](#keycloakrealmrolebatchspec) + + + + + + + + + + + + + + + + - + - - + + - + - - - - - - - - - - - - + + - - + + - +
NameTypeDescriptionRequired
name string - DisplayName is a display name of identity provider.
+ Name of keycloak role.
falsetrue
firstBrokerLoginFlowAliasstringattributesmap[string][]string - FirstBrokerLoginFlowAlias is a first broker login flow alias.
+ Attributes is a map of role attributes.
false
linkOnlycomposite boolean - LinkOnly is a flag to link only.
-
false
mappers[]object - Mappers is a list of identity provider mappers.
-
false
realmstring - Deprecated: use RealmRef instead. -Realm is name of KeycloakRealm custom resource.
+ Composite is a flag if role is composite.
false
realmRefobjectcomposites[]object - RealmRef is reference to Realm custom resource.
+ Composites is a list of composites roles assigned to role.
false
storeTokenbooleandescriptionstring - StoreToken is a flag to store token.
+ Description is a role description.
false
trustEmailisDefault boolean - TrustEmail is a flag to trust email.
+ IsDefault is a flag if role is default.
false
-### KeycloakRealmIdentityProvider.spec.mappers[index] -[↩ Parent](#keycloakrealmidentityproviderspec) +### KeycloakRealmRoleBatch.spec.roles[index].composites[index] +[↩ Parent](#keycloakrealmrolebatchspecrolesindex) @@ -3249,39 +3855,18 @@ Realm is name of KeycloakRealm custom resource.
- config - map[string]string - - Config is a map of identity provider mapper configuration.
- - false - - identityProviderAlias - string - - IdentityProviderAlias is a identity provider alias.
- - false - - identityProviderMapper - string - - IdentityProviderMapper is a identity provider mapper.
- - false - name string - Name is a name of identity provider mapper.
+ Name is a name of composite role.
- false + true -### KeycloakRealmIdentityProvider.spec.realmRef -[↩ Parent](#keycloakrealmidentityproviderspec) +### KeycloakRealmRoleBatch.spec.realmRef +[↩ Parent](#keycloakrealmrolebatchspec) @@ -3316,12 +3901,12 @@ RealmRef is reference to Realm custom resource. -### KeycloakRealmIdentityProvider.status -[↩ Parent](#keycloakrealmidentityprovider) +### KeycloakRealmRoleBatch.status +[↩ Parent](#keycloakrealmrolebatch) -KeycloakRealmIdentityProviderStatus defines the observed state of KeycloakRealmIdentityProvider. +KeycloakRealmRoleBatchStatus defines the observed state of KeycloakRealmRoleBatch. @@ -3351,7 +3936,7 @@ KeycloakRealmIdentityProviderStatus defines the observed state of KeycloakRealmI
-## KeycloakRealmRoleBatch +## KeycloakRealmRole [↩ Parent](#v1edpepamcomv1 ) @@ -3359,7 +3944,7 @@ KeycloakRealmIdentityProviderStatus defines the observed state of KeycloakRealmI -KeycloakRealmRoleBatch is the Schema for the keycloak roles API. +KeycloakRealmRole is the Schema for the keycloak group API. @@ -3379,7 +3964,7 @@ KeycloakRealmRoleBatch is the Schema for the keycloak roles API. - + @@ -3388,29 +3973,29 @@ KeycloakRealmRoleBatch is the Schema for the keycloak roles API. - + - +
kind stringKeycloakRealmRoleBatchKeycloakRealmRole true
Refer to the Kubernetes API documentation for the fields of the `metadata` field. true
specspec object - KeycloakRealmRoleBatchSpec defines the desired state of KeycloakRealmRoleBatch.
+ KeycloakRealmRoleSpec defines the desired state of KeycloakRealmRole.
false
statusstatus object - KeycloakRealmRoleBatchStatus defines the observed state of KeycloakRealmRoleBatch.
+ KeycloakRealmRoleStatus defines the observed state of KeycloakRealmRole.
false
-### KeycloakRealmRoleBatch.spec -[↩ Parent](#keycloakrealmrolebatch) +### KeycloakRealmRole.spec +[↩ Parent](#keycloakrealmrole) -KeycloakRealmRoleBatchSpec defines the desired state of KeycloakRealmRoleBatch. +KeycloakRealmRoleSpec defines the desired state of KeycloakRealmRole. @@ -3422,12 +4007,54 @@ KeycloakRealmRoleBatchSpec defines the desired state of KeycloakRealmRoleBatch. - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -3437,7 +4064,7 @@ Realm is name of KeycloakRealm custom resource.
- +
roles[]objectnamestring - Roles is a list of roles to be created.
+ Name of keycloak role.
true
attributesmap[string][]string + Attributes is a map of role attributes.
+
false
compositeboolean + Composite is a flag if role is composite.
+
false
composites[]object + Composites is a list of composites roles assigned to role.
+
false
compositesClientRolesmap[string][]object + CompositesClientRoles is a map of composites client roles assigned to role.
+
false
descriptionstring + Description is a role description.
+
false
isDefaultboolean + IsDefault is a flag if role is default.
+
false
realm string false
realmRefrealmRef object RealmRef is reference to Realm custom resource.
@@ -3447,8 +4074,8 @@ Realm is name of KeycloakRealm custom resource.
-### KeycloakRealmRoleBatch.spec.roles[index] -[↩ Parent](#keycloakrealmrolebatchspec) +### KeycloakRealmRole.spec.composites[index] +[↩ Parent](#keycloakrealmrolespec) @@ -3467,50 +4094,15 @@ Realm is name of KeycloakRealm custom resource.
name string - Name of keycloak role.
+ Name is a name of composite role.
true - - attributes - map[string][]string - - Attributes is a map of role attributes.
- - false - - composite - boolean - - Composite is a flag if role is composite.
- - false - - composites - []object - - Composites is a list of composites roles assigned to role.
- - false - - description - string - - Description is a role description.
- - false - - isDefault - boolean - - IsDefault is a flag if role is default.
- - false -### KeycloakRealmRoleBatch.spec.roles[index].composites[index] -[↩ Parent](#keycloakrealmrolebatchspecrolesindex) +### KeycloakRealmRole.spec.compositesClientRoles[key][index] +[↩ Parent](#keycloakrealmrolespec) @@ -3536,8 +4128,8 @@ Realm is name of KeycloakRealm custom resource.
-### KeycloakRealmRoleBatch.spec.realmRef -[↩ Parent](#keycloakrealmrolebatchspec) +### KeycloakRealmRole.spec.realmRef +[↩ Parent](#keycloakrealmrolespec) @@ -3572,12 +4164,12 @@ RealmRef is reference to Realm custom resource. -### KeycloakRealmRoleBatch.status -[↩ Parent](#keycloakrealmrolebatch) +### KeycloakRealmRole.status +[↩ Parent](#keycloakrealmrole) -KeycloakRealmRoleBatchStatus defines the observed state of KeycloakRealmRoleBatch. +KeycloakRealmRoleStatus defines the observed state of KeycloakRealmRole. @@ -3597,6 +4189,13 @@ KeycloakRealmRoleBatchStatus defines the observed state of KeycloakRealmRoleBatc Format: int64
+ + + + + @@ -3607,7 +4206,7 @@ KeycloakRealmRoleBatchStatus defines the observed state of KeycloakRealmRoleBatc
false
idstring + ID is a role ID.
+
false
value string
-## KeycloakRealmRole +## KeycloakRealm [↩ Parent](#v1edpepamcomv1 ) @@ -3615,7 +4214,7 @@ KeycloakRealmRoleBatchStatus defines the observed state of KeycloakRealmRoleBatc -KeycloakRealmRole is the Schema for the keycloak group API. +KeycloakRealm is the Schema for the keycloak realms API. @@ -3635,7 +4234,7 @@ KeycloakRealmRole is the Schema for the keycloak group API. - + @@ -3644,29 +4243,29 @@ KeycloakRealmRole is the Schema for the keycloak group API. - + - +
kind stringKeycloakRealmRoleKeycloakRealm true
Refer to the Kubernetes API documentation for the fields of the `metadata` field. true
specspec object - KeycloakRealmRoleSpec defines the desired state of KeycloakRealmRole.
+ KeycloakRealmSpec defines the desired state of KeycloakRealm.
false
statusstatus object - KeycloakRealmRoleStatus defines the observed state of KeycloakRealmRole.
+ KeycloakRealmStatus defines the observed state of KeycloakRealm.
false
-### KeycloakRealmRole.spec -[↩ Parent](#keycloakrealmrole) +### KeycloakRealm.spec +[↩ Parent](#keycloakrealm) -KeycloakRealmRoleSpec defines the desired state of KeycloakRealmRole. +KeycloakRealmSpec defines the desired state of KeycloakRealm. @@ -3678,79 +4277,123 @@ KeycloakRealmRoleSpec defines the desired state of KeycloakRealmRole. - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - - + + - - + + - - + + - - + + - - + +
namerealmName string - Name of keycloak role.
+ RealmName specifies the name of the realm.
true
attributesmap[string][]stringbrowserFlowstring - Attributes is a map of role attributes.
+ BrowserFlow specifies the authentication flow to use for the realm's browser clients.
+
false
browserSecurityHeadersmap[string]string + BrowserSecurityHeaders is a map of security headers to apply to HTTP responses from the realm's browser clients.
+
false
displayHtmlNamestring + DisplayHTMLName name to render in the UI
+
false
displayNamestring + DisplayName is the display name of the realm.
+
false
frontendUrlstring + FrontendURL Set the frontend URL for the realm. Use in combination with the default hostname provider to override the base URL for frontend requests for a specific realm.
+
false
idstring + ID is the ID of the realm.
+
false
keycloakOwnerstring + Deprecated: use KeycloakRef instead. +KeycloakOwner specifies the name of the Keycloak instance that owns the realm.
false
compositebooleankeycloakRefobject - Composite is a flag if role is composite.
+ KeycloakRef is reference to Keycloak custom resource.
false
compositespasswordPolicy []object - Composites is a list of composites roles assigned to role.
+ PasswordPolicies is a list of password policies to apply to the realm.
false
compositesClientRolesmap[string][]objectrealmEventConfigobject - CompositesClientRoles is a map of composites client roles assigned to role.
+ RealmEventConfig is the configuration for events in the realm.
false
descriptionstringthemesobject - Description is a role description.
+ Themes is a map of themes to apply to the realm.
false
isDefaultbooleantokenSettingsobject - IsDefault is a flag if role is default.
+ TokenSettings is the configuration for tokens in the realm.
false
realmstringuserProfileConfigobject - Deprecated: use RealmRef instead. -Realm is name of KeycloakRealm custom resource.
+ UserProfileConfig is the configuration for user profiles in the realm. +Attributes and groups will be added to the current realm configuration. +Deletion of attributes and groups is not supported.
false
realmRefobjectusers[]object - RealmRef is reference to Realm custom resource.
+ Users is a list of users to create in the realm.
false
-### KeycloakRealmRole.spec.composites[index] -[↩ Parent](#keycloakrealmrolespec) - +### KeycloakRealm.spec.keycloakRef +[↩ Parent](#keycloakrealmspec) +KeycloakRef is reference to Keycloak custom resource. @@ -3762,18 +4405,27 @@ Realm is name of KeycloakRealm custom resource.
+ + + + + - +
kindenum + Kind specifies the kind of the Keycloak resource.
+
+ Enum: Keycloak, ClusterKeycloak
+
false
name string - Name is a name of composite role.
+ Name specifies the name of the Keycloak resource.
truefalse
-### KeycloakRealmRole.spec.compositesClientRoles[key][index] -[↩ Parent](#keycloakrealmrolespec) +### KeycloakRealm.spec.passwordPolicy[index] +[↩ Parent](#keycloakrealmspec) @@ -3789,22 +4441,29 @@ Realm is name of KeycloakRealm custom resource.
- name + type string - Name is a name of composite role.
+ Type of password policy.
+ + true + + value + string + + Value of password policy.
true -### KeycloakRealmRole.spec.realmRef -[↩ Parent](#keycloakrealmrolespec) +### KeycloakRealm.spec.realmEventConfig +[↩ Parent](#keycloakrealmspec) -RealmRef is reference to Realm custom resource. +RealmEventConfig is the configuration for events in the realm. @@ -3816,31 +4475,57 @@ RealmRef is reference to Realm custom resource. - - + + - - + + + + + + + + + + + + + + + + + + + + + +
kindenumadminEventsDetailsEnabledboolean - Kind specifies the kind of the Keycloak resource.
-
- Enum: KeycloakRealm, ClusterKeycloakRealm
+ AdminEventsDetailsEnabled indicates whether to enable detailed admin events.
false
namestringadminEventsEnabledboolean - Name specifies the name of the Keycloak resource.
+ AdminEventsEnabled indicates whether to enable admin events.
+
false
enabledEventTypes[]string + EnabledEventTypes is a list of event types to enable.
+
false
eventsEnabledboolean + EventsEnabled indicates whether to enable events.
+
false
eventsExpirationinteger + EventsExpiration is the number of seconds after which events expire.
+
false
eventsListeners[]string + EventsListeners is a list of event listeners to enable.
false
-### KeycloakRealmRole.status -[↩ Parent](#keycloakrealmrole) +### KeycloakRealm.spec.themes +[↩ Parent](#keycloakrealmspec) -KeycloakRealmRoleStatus defines the observed state of KeycloakRealmRole. +Themes is a map of themes to apply to the realm. @@ -3852,40 +4537,50 @@ KeycloakRealmRoleStatus defines the observed state of KeycloakRealmRole. - - + + - + - + + + + + + + + + + +
failureCountintegeraccountThemestring -
-
- Format: int64
+ AccountTheme specifies the account theme to use for the realm.
false
idadminConsoleTheme string - ID is a role ID.
+ AdminConsoleTheme specifies the admin console theme to use for the realm.
false
valueemailTheme string -
+ EmailTheme specifies the email theme to use for the realm.
+
false
internationalizationEnabledboolean + InternationalizationEnabled indicates whether to enable internationalization.
+
false
loginThemestring + LoginTheme specifies the login theme to use for the realm.
false
-## KeycloakRealm -[↩ Parent](#v1edpepamcomv1 ) - - +### KeycloakRealm.spec.tokenSettings +[↩ Parent](#keycloakrealmspec) -KeycloakRealm is the Schema for the keycloak realms API. +TokenSettings is the configuration for tokens in the realm. @@ -3897,46 +4592,98 @@ KeycloakRealm is the Schema for the keycloak realms API. - - - - - - - - - - - - - - - - + + + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + +
apiVersionstringv1.edp.epam.com/v1true
kindstringKeycloakRealmtrue
metadataobjectRefer to the Kubernetes API documentation for the fields of the `metadata` field.trueaccessCodeLifespaninteger + AccessCodeLifespan specifies max time(in seconds)a client has to finish the access token protocol. +This should normally be 1 minute.
+
+ Default: 60
+
false
specobjectaccessTokeninteger - KeycloakRealmSpec defines the desired state of KeycloakRealm.
+ AccessTokenLifespanForImplicitFlow specifies max time(in seconds) before an access token is expired for implicit flow.
+
+ Default: 900
false
statusobjectaccessTokenLifespaninteger - KeycloakRealmStatus defines the observed state of KeycloakRealm.
+ AccessTokenLifespan specifies max time(in seconds) before an access token is expired. +This value is recommended to be short relative to the SSO timeout.
+
+ Default: 300
+
false
actionTokenGeneratedByAdminLifespaninteger + ActionTokenGeneratedByAdminLifespan specifies max time(in seconds) before an action permit sent to a user by administrator is expired. +This value is recommended to be long to allow administrators to send e-mails for users that are currently offline. +The default timeout can be overridden immediately before issuing the token.
+
+ Default: 43200
+
false
actionTokenGeneratedByUserLifespaninteger + AccessCodeLifespanUserAction specifies max time(in seconds) before an action permit sent by a user (such as a forgot password e-mail) is expired. +This value is recommended to be short because it's expected that the user would react to self-created action quickly.
+
+ Default: 300
+
false
defaultSignatureAlgorithmenum + DefaultSignatureAlgorithm specifies the default algorithm used to sign tokens for the realm
+
+ Enum: ES256, ES384, ES512, EdDSA, HS256, HS384, HS512, PS256, PS384, PS512, RS256, RS384, RS512
+ Default: RS256
+
false
refreshTokenMaxReuseinteger + RefreshTokenMaxReuse specifies maximum number of times a refresh token can be reused. +When a different token is used, revocation is immediate.
+
+ Default: 0
+
false
revokeRefreshTokenboolean + RevokeRefreshToken if enabled a refresh token can only be used up to 'refreshTokenMaxReuse' and +is revoked when a different token is used. +Otherwise, refresh tokens are not revoked when used and can be used multiple times.
+
+ Default: false
false
-### KeycloakRealm.spec -[↩ Parent](#keycloakrealm) +### KeycloakRealm.spec.userProfileConfig +[↩ Parent](#keycloakrealmspec) -KeycloakRealmSpec defines the desired state of KeycloakRealm. +UserProfileConfig is the configuration for user profiles in the realm. +Attributes and groups will be added to the current realm configuration. +Deletion of attributes and groups is not supported. @@ -3948,114 +4695,125 @@ KeycloakRealmSpec defines the desired state of KeycloakRealm. - - - - - - - + + - - + + - + - - + +
realmNamestring - RealmName specifies the name of the realm.
-
true
browserFlowstringattributes[]object - BrowserFlow specifies the authentication flow to use for the realm's browser clients.
+ Attributes specifies the list of user profile attributes.
false
browserSecurityHeadersmap[string]stringgroups[]object - BrowserSecurityHeaders is a map of security headers to apply to HTTP responses from the realm's browser clients.
+ Groups specifies the list of user profile groups.
false
displayHtmlNameunmanagedAttributePolicy string - DisplayHTMLName name to render in the UI
+ UnmanagedAttributePolicy are user attributes not explicitly defined in the user profile configuration. +Empty value means that unmanaged attributes are disabled. +Possible values: +ENABLED - unmanaged attributes are allowed. +ADMIN_VIEW - unmanaged attributes are read-only and only available through the administration console and API. +ADMIN_EDIT - unmanaged attributes can be managed only through the administration console and API.
false
displayName
+ + +### KeycloakRealm.spec.userProfileConfig.attributes[index] +[↩ Parent](#keycloakrealmspecuserprofileconfig) + + + + + + + + + + + + + + + + - + - - + + - + - + - - - - - - - + + - + - + - + - - + +
NameTypeDescriptionRequired
name string - DisplayName is the display name of the realm.
+ Name of the user attribute, used to uniquely identify an attribute.
falsetrue
frontendUrlstringannotationsmap[string]string - FrontendURL Set the frontend URL for the realm. Use in combination with the default hostname provider to override the base URL for frontend requests for a specific realm.
+ Annotations specifies the annotations for the attribute.
false
iddisplayName string - ID is the ID of the realm.
+ Display name for the attribute.
false
keycloakOwnergroup string - Deprecated: use KeycloakRef instead. -KeycloakOwner specifies the name of the Keycloak instance that owns the realm.
-
false
keycloakRefobject - KeycloakRef is reference to Keycloak custom resource.
+ Group to which the attribute belongs.
false
passwordPolicy[]objectmultivaluedboolean - PasswordPolicies is a list of password policies to apply to the realm.
+ Multivalued specifies if this attribute supports multiple values. +This setting is an indicator and does not enable any validation
false
realmEventConfigpermissions object - RealmEventConfig is the configuration for events in the realm.
+ Permissions specifies the permissions for the attribute.
false
themesrequired object - Themes is a map of themes to apply to the realm.
+ Required indicates that the attribute must be set by users and administrators.
false
tokenSettingsselector object - TokenSettings is the configuration for tokens in the realm.
+ Selector specifies the scopes for which the attribute is available.
false
users[]objectvalidationsmap[string]map[string]object - Users is a list of users to create in the realm.
+ Validations specifies the validations for the attribute.
false
-### KeycloakRealm.spec.keycloakRef -[↩ Parent](#keycloakrealmspec) +### KeycloakRealm.spec.userProfileConfig.attributes[index].permissions +[↩ Parent](#keycloakrealmspecuserprofileconfigattributesindex) -KeycloakRef is reference to Keycloak custom resource. +Permissions specifies the permissions for the attribute. @@ -4067,31 +4825,29 @@ KeycloakRef is reference to Keycloak custom resource. - - + + - - + +
kindenumedit[]string - Kind specifies the kind of the Keycloak resource.
-
- Enum: Keycloak, ClusterKeycloak
+ Edit specifies who can edit the attribute.
false
namestringview[]string - Name specifies the name of the Keycloak resource.
+ View specifies who can view the attribute.
false
-### KeycloakRealm.spec.passwordPolicy[index] -[↩ Parent](#keycloakrealmspec) - +### KeycloakRealm.spec.userProfileConfig.attributes[index].required +[↩ Parent](#keycloakrealmspecuserprofileconfigattributesindex) +Required indicates that the attribute must be set by users and administrators. @@ -4103,29 +4859,29 @@ KeycloakRef is reference to Keycloak custom resource. - - + + - + - - + + - +
typestringroles[]string - Type of password policy.
+ Roles specifies the roles for whom the attribute is required.
truefalse
valuestringscopes[]string - Value of password policy.
+ Scopes specifies the scopes when the attribute is required.
truefalse
-### KeycloakRealm.spec.realmEventConfig -[↩ Parent](#keycloakrealmspec) +### KeycloakRealm.spec.userProfileConfig.attributes[index].selector +[↩ Parent](#keycloakrealmspecuserprofileconfigattributesindex) -RealmEventConfig is the configuration for events in the realm. +Selector specifies the scopes for which the attribute is available. @@ -4137,57 +4893,22 @@ RealmEventConfig is the configuration for events in the realm. - - - - - - - - - - - - - - - - - - - - - - - - - - +
adminEventsDetailsEnabledboolean - AdminEventsDetailsEnabled indicates whether to enable detailed admin events.
-
false
adminEventsEnabledboolean - AdminEventsEnabled indicates whether to enable admin events.
-
false
enabledEventTypes[]string - EnabledEventTypes is a list of event types to enable.
-
false
eventsEnabledboolean - EventsEnabled indicates whether to enable events.
-
false
eventsExpirationinteger - EventsExpiration is the number of seconds after which events expire.
-
false
eventsListenersscopes []string - EventsListeners is a list of event listeners to enable.
+ Scopes specifies the scopes for which the attribute is available.
false
-### KeycloakRealm.spec.themes -[↩ Parent](#keycloakrealmspec) +### KeycloakRealm.spec.userProfileConfig.attributes[index].validations[key][key] +[↩ Parent](#keycloakrealmspecuserprofileconfigattributesindex) + -Themes is a map of themes to apply to the realm. @@ -4199,50 +4920,43 @@ Themes is a map of themes to apply to the realm. - - - - - - - + + - - + + - - + + - +
accountThemestring - AccountTheme specifies the account theme to use for the realm.
-
false
adminConsoleThemestringintValinteger - AdminConsoleTheme specifies the admin console theme to use for the realm.
+
false
emailThemestringmapValmap[string]string - EmailTheme specifies the email theme to use for the realm.
+
false
internationalizationEnabledbooleansliceVal[]string - InternationalizationEnabled indicates whether to enable internationalization.
+
false
loginThemestringVal string - LoginTheme specifies the login theme to use for the realm.
+
false
-### KeycloakRealm.spec.tokenSettings -[↩ Parent](#keycloakrealmspec) +### KeycloakRealm.spec.userProfileConfig.groups[index] +[↩ Parent](#keycloakrealmspecuserprofileconfig) + -TokenSettings is the configuration for tokens in the realm. @@ -4254,84 +4968,32 @@ TokenSettings is the configuration for tokens in the realm. - - - - - - - - - - - - - - - - - - - - - - + + - + - - + + - - + + - - + + diff --git a/go.mod b/go.mod index dbbbdef3..04b52696 100644 --- a/go.mod +++ b/go.mod @@ -13,16 +13,18 @@ require ( github.com/pkg/errors v0.9.1 github.com/sethvargo/go-password v0.2.0 github.com/stretchr/testify v1.8.4 + github.com/zmotso/keycloak-go-client v0.0.0-20241113095118-1d5b34e077a1 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a golang.org/x/sync v0.1.0 k8s.io/api v0.26.10 k8s.io/apimachinery v0.26.10 k8s.io/client-go v0.26.10 - k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 + k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 sigs.k8s.io/controller-runtime v0.14.7 ) require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -50,6 +52,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oapi-codegen/runtime v1.1.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.14.0 // indirect @@ -67,7 +70,7 @@ require ( golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/time v0.3.0 // indirect + golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/go.sum b/go.sum index 0d1f48cb..f5c75d1b 100644 --- a/go.sum +++ b/go.sum @@ -4,11 +4,15 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/Nerzal/gocloak/v12 v12.0.0 h1:oOddyLpf+CxdGHFx5bABn4yCAtIGDwJkvJP4hFSospY= github.com/Nerzal/gocloak/v12 v12.0.0/go.mod h1:EAIc7luf3+dwMMHNWC9/X9vAA+KZJl5qfSWDIu7IlSs= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -106,6 +110,7 @@ github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8Hm github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -129,6 +134,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= github.com/onsi/ginkgo/v2 v2.6.0/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= @@ -157,6 +164,7 @@ github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetS github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -177,6 +185,10 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zmotso/keycloak-go-client v0.0.0-20241112133027-0e61242b76ac h1:zmUvtBuIjVsE3spaweKaWAePUXt86GSPKqaHjCyzb4M= +github.com/zmotso/keycloak-go-client v0.0.0-20241112133027-0e61242b76ac/go.mod h1:PAvCkvb+irLo/9DRh+wpMR8+duFJcxthaixEveen2wU= +github.com/zmotso/keycloak-go-client v0.0.0-20241113095118-1d5b34e077a1 h1:DuYSQTTDngQtUlcqESuNZ+IkQTdjrF9xVEoAKyYsycE= +github.com/zmotso/keycloak-go-client v0.0.0-20241113095118-1d5b34e077a1/go.mod h1:PAvCkvb+irLo/9DRh+wpMR8+duFJcxthaixEveen2wU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -275,8 +287,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -358,8 +371,8 @@ k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715 h1:tBEbstoM+K0FiBV5KGAKQ0kuvf54v/hwpldiJt69w1s= k8s.io/kube-openapi v0.0.0-20221207184640-f3cff1453715/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 h1:KTgPnR10d5zhztWptI952TNtt/4u5h3IzDXkdIMuo2Y= -k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078 h1:jGnCPejIetjiy2gqaJ5V0NLwTpF4wbQ6cZIItJCSHno= +k8s.io/utils v0.0.0-20241104163129-6fe5fd82f078/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.14.7 h1:Vrnm2vk9ZFlRkXATHz0W0wXcqNl7kPat8q2JyxVy0Q8= sigs.k8s.io/controller-runtime v0.14.7/go.mod h1:ErTs3SJCOujNUnTz4AS+uh8hp6DHMo1gj6fFndJT1X8= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/pkg/client/keycloak/adapter/errors_test.go b/pkg/client/keycloak/adapter/errors_test.go index 2e73499c..595bb11d 100644 --- a/pkg/client/keycloak/adapter/errors_test.go +++ b/pkg/client/keycloak/adapter/errors_test.go @@ -33,8 +33,6 @@ func TestSkipAlreadyExistsErr(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/client/keycloak/adapter/gocloak_adapter.go b/pkg/client/keycloak/adapter/gocloak_adapter.go index eaa2d494..05bfeb20 100644 --- a/pkg/client/keycloak/adapter/gocloak_adapter.go +++ b/pkg/client/keycloak/adapter/gocloak_adapter.go @@ -535,7 +535,7 @@ func getGclCln(client *dto.Client) gocloak.Client { WebOrigins: &client.WebOrigins, } - if client.RedirectUris != nil && len(client.RedirectUris) > 0 { + if len(client.RedirectUris) > 0 { cl.RedirectURIs = &client.RedirectUris } diff --git a/pkg/client/keycloak/adapter/gocloak_adapter_client_scope_test.go b/pkg/client/keycloak/adapter/gocloak_adapter_client_scope_test.go index 28af2a74..f96123ef 100644 --- a/pkg/client/keycloak/adapter/gocloak_adapter_client_scope_test.go +++ b/pkg/client/keycloak/adapter/gocloak_adapter_client_scope_test.go @@ -407,8 +407,6 @@ func TestGoCloakAdapter_GetClientScopesByNames(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/client/keycloak/adapter/gocloak_adapter_client_test.go b/pkg/client/keycloak/adapter/gocloak_adapter_client_test.go index 396e90cb..adaeb9c2 100644 --- a/pkg/client/keycloak/adapter/gocloak_adapter_client_test.go +++ b/pkg/client/keycloak/adapter/gocloak_adapter_client_test.go @@ -120,8 +120,6 @@ func TestGoCloakAdapter_AddDefaultScopeToClient(t *testing.T) { } for name, tc := range tests { - tc := tc - t.Run(name, func(t *testing.T) { t.Parallel() @@ -135,6 +133,7 @@ func TestGoCloakAdapter_AddDefaultScopeToClient(t *testing.T) { assert.Error(t, err) return } + assert.NoError(t, err) }) } diff --git a/pkg/client/keycloak/adapter/gocloak_adapter_groups.go b/pkg/client/keycloak/adapter/gocloak_adapter_groups.go index 2281a1e6..ef9d50d3 100644 --- a/pkg/client/keycloak/adapter/gocloak_adapter_groups.go +++ b/pkg/client/keycloak/adapter/gocloak_adapter_groups.go @@ -95,8 +95,6 @@ func (a GoCloakAdapter) getGroupsByNames(ctx context.Context, realm string, grou m := sync.Mutex{} for _, groupName := range groupNames { - groupName := groupName - eg.Go(func() error { group, err := a.getGroup(ctx, realm, groupName) if err != nil { diff --git a/pkg/client/keycloak/adapter/gocloak_adapter_groups_test.go b/pkg/client/keycloak/adapter/gocloak_adapter_groups_test.go index aa118902..8ba9a3fc 100644 --- a/pkg/client/keycloak/adapter/gocloak_adapter_groups_test.go +++ b/pkg/client/keycloak/adapter/gocloak_adapter_groups_test.go @@ -60,7 +60,6 @@ func TestIsErrNotFound(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -237,7 +236,6 @@ func TestGoCloakAdapter_SyncRealmGroup(t *testing.T) { tt.wantErr(t, err) assert.Equal(t, tt.want, got) - }) } } diff --git a/pkg/client/keycloak/adapter/gocloak_adapter_realms_test.go b/pkg/client/keycloak/adapter/gocloak_adapter_realms_test.go index 740ec419..f242b0d5 100644 --- a/pkg/client/keycloak/adapter/gocloak_adapter_realms_test.go +++ b/pkg/client/keycloak/adapter/gocloak_adapter_realms_test.go @@ -210,7 +210,6 @@ func TestGoCloakAdapter_GetRealm(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -265,8 +264,6 @@ func TestToRealmTokenSettings(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() assert.Equal(t, tt.want, ToRealmTokenSettings(tt.tokenSettings)) diff --git a/pkg/client/keycloak/adapter/gocloak_adapter_roles_test.go b/pkg/client/keycloak/adapter/gocloak_adapter_roles_test.go index 7a3492b8..37604817 100644 --- a/pkg/client/keycloak/adapter/gocloak_adapter_roles_test.go +++ b/pkg/client/keycloak/adapter/gocloak_adapter_roles_test.go @@ -556,8 +556,6 @@ func TestGoCloakAdapter_SyncRealmRole(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/client/keycloak/adapter/gocloak_adapter_test.go b/pkg/client/keycloak/adapter/gocloak_adapter_test.go index 1ce8836f..fac80c13 100644 --- a/pkg/client/keycloak/adapter/gocloak_adapter_test.go +++ b/pkg/client/keycloak/adapter/gocloak_adapter_test.go @@ -96,8 +96,6 @@ func (e *AdapterTestSuite) TestMakeFromServiceAccount() { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -167,8 +165,6 @@ func (e *AdapterTestSuite) TestMake() { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -742,8 +738,6 @@ func TestMakeFromToken(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -901,7 +895,6 @@ func TestGoCloakAdapter_GetUsersByNames(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -996,8 +989,6 @@ func TestGoCloakAdapter_CreatePrimaryRealmRole(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/client/keycloak/adapter/gocloak_adapter_user.go b/pkg/client/keycloak/adapter/gocloak_adapter_user.go index f195d245..2cac2a99 100644 --- a/pkg/client/keycloak/adapter/gocloak_adapter_user.go +++ b/pkg/client/keycloak/adapter/gocloak_adapter_user.go @@ -7,6 +7,7 @@ import ( "github.com/Nerzal/gocloak/v12" "github.com/pkg/errors" + keycloak_go_client "github.com/zmotso/keycloak-go-client" ) type KeycloakUser struct { @@ -73,7 +74,7 @@ func (a GoCloakAdapter) createOrUpdateUser(ctx context.Context, realmName string Email: &userDto.Email, } - if userDto.Attributes != nil && len(userDto.Attributes) > 0 { + if len(userDto.Attributes) > 0 { kcUser.Attributes = a.makeUserAttributes(&kcUser, userDto, addOnly) } @@ -95,7 +96,7 @@ func (a GoCloakAdapter) createOrUpdateUser(ctx context.Context, realmName string user.RequiredActions = &userDto.RequiredUserActions user.Email = &userDto.Email - if userDto.Attributes != nil && len(userDto.Attributes) > 0 { + if len(userDto.Attributes) > 0 { user.Attributes = a.makeUserAttributes(user, userDto, addOnly) } @@ -289,6 +290,59 @@ func (a GoCloakAdapter) AddUserToGroup(ctx context.Context, realmName, userID, g return nil } +func (a GoCloakAdapter) UpdateUsersProfile( + ctx context.Context, + realm string, + userProfile keycloak_go_client.UserProfileConfig, +) (*keycloak_go_client.UserProfileConfig, error) { + cl, err := keycloak_go_client.NewClient(a.basePath, keycloak_go_client.WithToken(a.token.AccessToken)) + if err != nil { + return nil, fmt.Errorf("failed to create keycloak_go_client client: %w", err) + } + + profile, res, err := cl.Users.UpdateUsersProfile(ctx, realm, userProfile) + if err = checkHttpResp(res, err); err != nil { + return nil, err + } + + return profile, nil +} + +func (a GoCloakAdapter) GetUsersProfile( + ctx context.Context, + realm string, +) (*keycloak_go_client.UserProfileConfig, error) { + cl, err := keycloak_go_client.NewClient(a.basePath, keycloak_go_client.WithToken(a.token.AccessToken)) + if err != nil { + return nil, fmt.Errorf("failed to create keycloak_go_client client: %w", err) + } + + profile, res, err := cl.Users.GetUsersProfile(ctx, realm) + if err = checkHttpResp(res, err); err != nil { + return nil, err + } + + return profile, nil +} + +func checkHttpResp(res *keycloak_go_client.Response, err error) error { + if err != nil { + return fmt.Errorf("failed to send request: %w", err) + } + + if res == nil || res.HTTPResponse == nil { + return errors.New("empty response") + } + + const maxStatusCodesSuccess = 399 + + if res.HTTPResponse.StatusCode > maxStatusCodesSuccess { + return errors.Errorf("status: %s, body: %s", res.HTTPResponse.Status, res.Body) + } + + return nil +} + func (a GoCloakAdapter) clearUserRealmRoles(ctx context.Context, realmName string, userID string) error { roles, err := a.GetUserRealmRoleMappings(ctx, realmName, userID) if err != nil { diff --git a/pkg/client/keycloak/adapter/gocloak_adapter_user_test.go b/pkg/client/keycloak/adapter/gocloak_adapter_user_test.go index 246938a0..de95ff32 100644 --- a/pkg/client/keycloak/adapter/gocloak_adapter_user_test.go +++ b/pkg/client/keycloak/adapter/gocloak_adapter_user_test.go @@ -31,6 +31,7 @@ func TestGoCloakAdapter_SyncRealmUser(t *testing.T) { return } + w.WriteHeader(http.StatusOK) })) @@ -368,8 +369,6 @@ func TestGoCloakAdapter_SyncRealmUser(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/client/keycloak/adapter/mocks/gocloak_mock.go b/pkg/client/keycloak/adapter/mocks/gocloak_mock.go index c988d538..24545f25 100644 --- a/pkg/client/keycloak/adapter/mocks/gocloak_mock.go +++ b/pkg/client/keycloak/adapter/mocks/gocloak_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.43.0. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks diff --git a/pkg/client/keycloak/dto/keycloak_dto.go b/pkg/client/keycloak/dto/keycloak_dto.go index 18922d07..f63a48c9 100644 --- a/pkg/client/keycloak/dto/keycloak_dto.go +++ b/pkg/client/keycloak/dto/keycloak_dto.go @@ -158,18 +158,3 @@ type IdentityProviderMapper struct { Config map[string]string `json:"config"` ID string `json:"id"` } - -func ConvertSSOMappersToIdentityProviderMappers(idpAlias string, - ssoMappers []keycloakApi.SSORealmMapper) []IdentityProviderMapper { - idpMappers := make([]IdentityProviderMapper, 0, len(ssoMappers)) - for _, sm := range ssoMappers { - idpMappers = append(idpMappers, IdentityProviderMapper{ - IdentityProviderAlias: idpAlias, - IdentityProviderMapper: sm.IdentityProviderMapper, - Config: sm.Config, - Name: sm.Name, - }) - } - - return idpMappers -} diff --git a/pkg/client/keycloak/keycloak_client.go b/pkg/client/keycloak/keycloak_client.go index af2f67d9..a37b0cec 100644 --- a/pkg/client/keycloak/keycloak_client.go +++ b/pkg/client/keycloak/keycloak_client.go @@ -4,6 +4,7 @@ import ( "context" "github.com/Nerzal/gocloak/v12" + keycloak_go_client "github.com/zmotso/keycloak-go-client" keycloakApi "github.com/epam/edp-keycloak-operator/api/v1" "github.com/epam/edp-keycloak-operator/pkg/client/keycloak/adapter" @@ -60,6 +61,8 @@ type KCloakUsers interface { SyncRealmUser(ctx context.Context, realmName string, user *adapter.KeycloakUser, addOnly bool) error DeleteRealmUser(ctx context.Context, realmName, username string) error GetUsersByNames(ctx context.Context, realm string, names []string) (map[string]gocloak.User, error) + UpdateUsersProfile(ctx context.Context, realm string, userProfile keycloak_go_client.UserProfileConfig) (*keycloak_go_client.UserProfileConfig, error) + GetUsersProfile(ctx context.Context, realm string) (*keycloak_go_client.UserProfileConfig, error) } type KCloakRealms interface { diff --git a/pkg/client/keycloak/mocks/client_mock.go b/pkg/client/keycloak/mocks/client_mock.go index f429bc61..2ee10ef1 100644 --- a/pkg/client/keycloak/mocks/client_mock.go +++ b/pkg/client/keycloak/mocks/client_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.43.0. DO NOT EDIT. +// Code generated by mockery v2.46.3. DO NOT EDIT. package mocks @@ -9,6 +9,8 @@ import ( dto "github.com/epam/edp-keycloak-operator/pkg/client/keycloak/dto" + generated "github.com/zmotso/keycloak-go-client/generated" + gocloak "github.com/Nerzal/gocloak/v12" mock "github.com/stretchr/testify/mock" @@ -3176,6 +3178,65 @@ func (_c *MockClient_GetUsersByNames_Call) RunAndReturn(run func(context.Context return _c } +// GetUsersProfile provides a mock function with given fields: ctx, realm +func (_m *MockClient) GetUsersProfile(ctx context.Context, realm string) (*generated.UPConfig, error) { + ret := _m.Called(ctx, realm) + + if len(ret) == 0 { + panic("no return value specified for GetUsersProfile") + } + + var r0 *generated.UPConfig + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*generated.UPConfig, error)); ok { + return rf(ctx, realm) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *generated.UPConfig); ok { + r0 = rf(ctx, realm) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*generated.UPConfig) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, realm) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClient_GetUsersProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUsersProfile' +type MockClient_GetUsersProfile_Call struct { + *mock.Call +} + +// GetUsersProfile is a helper method to define mock.On call +// - ctx context.Context +// - realm string +func (_e *MockClient_Expecter) GetUsersProfile(ctx interface{}, realm interface{}) *MockClient_GetUsersProfile_Call { + return &MockClient_GetUsersProfile_Call{Call: _e.mock.On("GetUsersProfile", ctx, realm)} +} + +func (_c *MockClient_GetUsersProfile_Call) Run(run func(ctx context.Context, realm string)) *MockClient_GetUsersProfile_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *MockClient_GetUsersProfile_Call) Return(_a0 *generated.UPConfig, _a1 error) *MockClient_GetUsersProfile_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClient_GetUsersProfile_Call) RunAndReturn(run func(context.Context, string) (*generated.UPConfig, error)) *MockClient_GetUsersProfile_Call { + _c.Call.Return(run) + return _c +} + // HasUserClientRole provides a mock function with given fields: realmName, clientId, user, role func (_m *MockClient) HasUserClientRole(realmName string, clientId string, user *dto.User, role string) (bool, error) { ret := _m.Called(realmName, clientId, user, role) @@ -4325,6 +4386,66 @@ func (_c *MockClient_UpdateResource_Call) RunAndReturn(run func(context.Context, return _c } +// UpdateUsersProfile provides a mock function with given fields: ctx, realm, userProfile +func (_m *MockClient) UpdateUsersProfile(ctx context.Context, realm string, userProfile generated.UPConfig) (*generated.UPConfig, error) { + ret := _m.Called(ctx, realm, userProfile) + + if len(ret) == 0 { + panic("no return value specified for UpdateUsersProfile") + } + + var r0 *generated.UPConfig + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, generated.UPConfig) (*generated.UPConfig, error)); ok { + return rf(ctx, realm, userProfile) + } + if rf, ok := ret.Get(0).(func(context.Context, string, generated.UPConfig) *generated.UPConfig); ok { + r0 = rf(ctx, realm, userProfile) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*generated.UPConfig) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, generated.UPConfig) error); ok { + r1 = rf(ctx, realm, userProfile) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClient_UpdateUsersProfile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateUsersProfile' +type MockClient_UpdateUsersProfile_Call struct { + *mock.Call +} + +// UpdateUsersProfile is a helper method to define mock.On call +// - ctx context.Context +// - realm string +// - userProfile generated.UPConfig +func (_e *MockClient_Expecter) UpdateUsersProfile(ctx interface{}, realm interface{}, userProfile interface{}) *MockClient_UpdateUsersProfile_Call { + return &MockClient_UpdateUsersProfile_Call{Call: _e.mock.On("UpdateUsersProfile", ctx, realm, userProfile)} +} + +func (_c *MockClient_UpdateUsersProfile_Call) Run(run func(ctx context.Context, realm string, userProfile generated.UPConfig)) *MockClient_UpdateUsersProfile_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(generated.UPConfig)) + }) + return _c +} + +func (_c *MockClient_UpdateUsersProfile_Call) Return(_a0 *generated.UPConfig, _a1 error) *MockClient_UpdateUsersProfile_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClient_UpdateUsersProfile_Call) RunAndReturn(run func(context.Context, string, generated.UPConfig) (*generated.UPConfig, error)) *MockClient_UpdateUsersProfile_Call { + _c.Call.Return(run) + return _c +} + // NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockClient(t interface { diff --git a/pkg/fakehttp/server_test.go b/pkg/fakehttp/server_test.go index ba5468fa..f954f801 100644 --- a/pkg/fakehttp/server_test.go +++ b/pkg/fakehttp/server_test.go @@ -32,8 +32,6 @@ func TestMockServerBuilder_AddStringResponder(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -82,8 +80,6 @@ func TestMockServerBuilder_AddStringResponderWithCode(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -132,8 +128,6 @@ func TestMockServerBuilder_AddJsonResponderWithCode(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -157,7 +151,6 @@ func TestMockServerBuilder_AddJsonResponderWithCode(t *testing.T) { require.NoError(t, err) require.JSONEq(t, string(jsonResp), string(body)) - }) } } @@ -188,8 +181,6 @@ func TestDefaultFakeServer_Close(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -226,8 +217,6 @@ func TestDefaultFakeServer_GetURL(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/objectmeta/deletion_test.go b/pkg/objectmeta/deletion_test.go index b2378314..455d2d9c 100644 --- a/pkg/objectmeta/deletion_test.go +++ b/pkg/objectmeta/deletion_test.go @@ -52,7 +52,6 @@ func TestPreserveResourcesOnDeletion(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() diff --git a/pkg/secretref/secretref_test.go b/pkg/secretref/secretref_test.go index 78ae250b..c484bf37 100644 --- a/pkg/secretref/secretref_test.go +++ b/pkg/secretref/secretref_test.go @@ -315,8 +315,6 @@ func TestGenerateSecretRef(t *testing.T) { } for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { t.Parallel()
accessCodeLifespaninteger - AccessCodeLifespan specifies max time(in seconds)a client has to finish the access token protocol. -This should normally be 1 minute.
-
- Default: 60
-
false
accessTokeninteger - AccessTokenLifespanForImplicitFlow specifies max time(in seconds) before an access token is expired for implicit flow.
-
- Default: 900
-
false
accessTokenLifespaninteger - AccessTokenLifespan specifies max time(in seconds) before an access token is expired. -This value is recommended to be short relative to the SSO timeout.
-
- Default: 300
-
false
actionTokenGeneratedByAdminLifespaninteger - ActionTokenGeneratedByAdminLifespan specifies max time(in seconds) before an action permit sent to a user by administrator is expired. -This value is recommended to be long to allow administrators to send e-mails for users that are currently offline. -The default timeout can be overridden immediately before issuing the token.
-
- Default: 43200
-
false
actionTokenGeneratedByUserLifespanintegernamestring - AccessCodeLifespanUserAction specifies max time(in seconds) before an action permit sent by a user (such as a forgot password e-mail) is expired. -This value is recommended to be short because it's expected that the user would react to self-created action quickly.
-
- Default: 300
+ Name is unique name of the group.
falsetrue
defaultSignatureAlgorithmenumannotationsmap[string]string - DefaultSignatureAlgorithm specifies the default algorithm used to sign tokens for the realm
-
- Enum: ES256, ES384, ES512, EdDSA, HS256, HS384, HS512, PS256, PS384, PS512, RS256, RS384, RS512
- Default: RS256
+ Annotations specifies the annotations for the group. +nullable
false
refreshTokenMaxReuseintegerdisplayDescriptionstring - RefreshTokenMaxReuse specifies maximum number of times a refresh token can be reused. -When a different token is used, revocation is immediate.
-
- Default: 0
+ DisplayDescription specifies a user-friendly name for the group that should be used when rendering a group of attributes in user-facing forms.
false
revokeRefreshTokenbooleandisplayHeaderstring - RevokeRefreshToken if enabled a refresh token can only be used up to 'refreshTokenMaxReuse' and -is revoked when a different token is used. -Otherwise, refresh tokens are not revoked when used and can be used multiple times.
-
- Default: false
+ DisplayHeader specifies a text that should be used as a header when rendering user-facing forms.
false