pkg/client/keycloak/adapter/gocloak_adapter_realms.go (257 lines of code) (raw):
package adapter
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/Nerzal/gocloak/v12"
"github.com/pkg/errors"
ctrl "sigs.k8s.io/controller-runtime"
"github.com/epam/edp-keycloak-operator/api/common"
"github.com/epam/edp-keycloak-operator/pkg/client/keycloak/dto"
)
type RealmSettings struct {
Themes *RealmThemes
BrowserSecurityHeaders *map[string]string
PasswordPolicies []PasswordPolicy
DisplayHTMLName string
FrontendURL string
TokenSettings *TokenSettings
DisplayName string
}
type PasswordPolicy struct {
Type string
Value string
}
type RealmThemes struct {
LoginTheme *string
AccountTheme *string
AdminConsoleTheme *string
EmailTheme *string
InternationalizationEnabled *bool
}
type TokenSettings struct {
DefaultSignatureAlgorithm string
RevokeRefreshToken bool
RefreshTokenMaxReuse int
AccessTokenLifespan int
AccessTokenLifespanForImplicitFlow int
AccessCodeLifespan int
ActionTokenGeneratedByUserLifespan int
ActionTokenGeneratedByAdminLifespan int
}
func (a GoCloakAdapter) UpdateRealmSettings(realmName string, realmSettings *RealmSettings) error {
realm, err := a.client.GetRealm(context.Background(), a.token.AccessToken, realmName)
if err != nil {
return errors.Wrapf(err, "unable to realm: %s", realmName)
}
setRealmSettings(realm, realmSettings)
if err := a.client.UpdateRealm(context.Background(), a.token.AccessToken, *realm); err != nil {
return errors.Wrap(err, "unable to update realm")
}
return nil
}
func (a GoCloakAdapter) UpdateRealm(ctx context.Context, realm *gocloak.RealmRepresentation) error {
if err := a.client.UpdateRealm(ctx, a.token.AccessToken, *realm); err != nil {
return fmt.Errorf("unable to update realm: %w", err)
}
return nil
}
func setRealmSettings(realm *gocloak.RealmRepresentation, realmSettings *RealmSettings) {
if realmSettings.Themes != nil {
realm.LoginTheme = realmSettings.Themes.LoginTheme
realm.AccountTheme = realmSettings.Themes.AccountTheme
realm.AdminTheme = realmSettings.Themes.AdminConsoleTheme
realm.EmailTheme = realmSettings.Themes.EmailTheme
realm.InternationalizationEnabled = realmSettings.Themes.InternationalizationEnabled
}
if realmSettings.BrowserSecurityHeaders != nil {
if realm.BrowserSecurityHeaders == nil {
bsh := make(map[string]string)
realm.BrowserSecurityHeaders = &bsh
}
realmBrowserSecurityHeaders := *realm.BrowserSecurityHeaders
for k, v := range *realmSettings.BrowserSecurityHeaders {
realmBrowserSecurityHeaders[k] = v
}
realm.BrowserSecurityHeaders = &realmBrowserSecurityHeaders
}
if len(realmSettings.PasswordPolicies) > 0 {
policies := make([]string, len(realmSettings.PasswordPolicies))
for i, v := range realmSettings.PasswordPolicies {
policies[i] = fmt.Sprintf("%s(%s)", v.Type, v.Value)
}
realm.PasswordPolicy = gocloak.StringP(strings.Join(policies, " and "))
}
if realmSettings.DisplayHTMLName != "" {
if realm.Attributes == nil {
realm.Attributes = &map[string]string{}
}
(*realm.Attributes)["displayHTMLName"] = realmSettings.DisplayHTMLName
}
if realmSettings.FrontendURL != "" {
if realm.Attributes == nil {
realm.Attributes = &map[string]string{}
}
(*realm.Attributes)["frontendUrl"] = realmSettings.FrontendURL
}
realm.DisplayName = gocloak.StringP(realmSettings.DisplayName)
if realmSettings.TokenSettings != nil {
realm.DefaultSignatureAlgorithm = gocloak.StringP(realmSettings.TokenSettings.DefaultSignatureAlgorithm)
realm.RevokeRefreshToken = gocloak.BoolP(realmSettings.TokenSettings.RevokeRefreshToken)
realm.RefreshTokenMaxReuse = gocloak.IntP(realmSettings.TokenSettings.RefreshTokenMaxReuse)
realm.AccessTokenLifespan = gocloak.IntP(realmSettings.TokenSettings.AccessTokenLifespan)
realm.AccessTokenLifespanForImplicitFlow = gocloak.IntP(realmSettings.TokenSettings.AccessTokenLifespanForImplicitFlow)
realm.AccessCodeLifespan = gocloak.IntP(realmSettings.TokenSettings.AccessCodeLifespan)
realm.ActionTokenGeneratedByUserLifespan = gocloak.IntP(realmSettings.TokenSettings.ActionTokenGeneratedByUserLifespan)
realm.ActionTokenGeneratedByAdminLifespan = gocloak.IntP(realmSettings.TokenSettings.ActionTokenGeneratedByAdminLifespan)
}
}
func (a GoCloakAdapter) ExistRealm(realmName string) (bool, error) {
log := a.log.WithValues(logKeyRealm, realmName)
log.Info("Start check existing realm...")
_, err := a.client.GetRealm(context.Background(), a.token.AccessToken, realmName)
res, err := strip404(err)
if err != nil {
return false, err
}
log.Info("Check existing realm has been finished", "result", res)
return res, nil
}
// GetRealm returns realm by name.
func (a GoCloakAdapter) GetRealm(ctx context.Context, realmName string) (*gocloak.RealmRepresentation, error) {
log := ctrl.LoggerFrom(ctx).WithValues(logKeyRealm, realmName)
log.Info("Start getting realm")
r, err := a.client.GetRealm(ctx, a.token.AccessToken, realmName)
if err != nil {
return nil, fmt.Errorf("unable to get realm: %w", err)
}
return r, nil
}
func (a GoCloakAdapter) CreateRealmWithDefaultConfig(realm *dto.Realm) error {
log := a.log.WithValues(logKeyRealm, realm)
log.Info("Start creating realm with default config...")
_, err := a.client.CreateRealm(context.Background(), a.token.AccessToken, getDefaultRealm(realm))
if err != nil {
return errors.Wrap(err, "unable to create realm")
}
log.Info("End creating realm with default config")
return nil
}
func (a GoCloakAdapter) DeleteRealm(ctx context.Context, realmName string) error {
log := a.log.WithValues(logKeyRealm, realmName)
log.Info("Start deleting realm...")
if err := a.client.DeleteRealm(ctx, a.token.AccessToken, realmName); err != nil {
return errors.Wrap(err, "unable to delete realm")
}
log.Info("End deletion realm")
return nil
}
func (a GoCloakAdapter) SyncRealmIdentityProviderMappers(realmName string, mappers []dto.IdentityProviderMapper) error {
realm, err := a.client.GetRealm(context.Background(), a.token.AccessToken, realmName)
if err != nil {
return errors.Wrapf(err, "unable to get realm by name: %s", realmName)
}
currentMappers := make(map[string]*dto.IdentityProviderMapper)
if realm.IdentityProviderMappers != nil {
for _, idpm := range *realm.IdentityProviderMappers {
if idpmTyped, ok := decodeIdentityProviderMapper(idpm); ok {
currentMappers[idpmTyped.Name] = idpmTyped
}
}
}
for _, claimedMapper := range mappers {
if idpmTyped, ok := currentMappers[claimedMapper.Name]; ok {
claimedMapper.ID = idpmTyped.ID
if err := a.updateIdentityProviderMapper(realmName, claimedMapper); err != nil {
return errors.Wrapf(err, "unable to update idp mapper: %+v", claimedMapper)
}
continue
}
if err := a.createIdentityProviderMapper(realmName, claimedMapper); err != nil {
return errors.Wrapf(err, "unable to create idp mapper: %+v", claimedMapper)
}
}
return nil
}
func decodeIdentityProviderMapper(mp interface{}) (*dto.IdentityProviderMapper, bool) {
mapInterface, ok := mp.(map[string]interface{})
if !ok {
return nil, false
}
mapper := dto.IdentityProviderMapper{
Config: make(map[string]string),
}
if idRaw, ok := mapInterface["id"]; ok {
if id, ok := idRaw.(string); ok {
mapper.ID = id
}
}
if nameRaw, ok := mapInterface["name"]; ok {
if name, ok := nameRaw.(string); ok {
mapper.Name = name
}
}
if identityProviderAliasRaw, ok := mapInterface["identityProviderAlias"]; ok {
if identityProviderAlias, ok := identityProviderAliasRaw.(string); ok {
mapper.IdentityProviderAlias = identityProviderAlias
}
}
if identityProviderMapperRaw, ok := mapInterface["identityProviderMapper"]; ok {
if identityProviderMapper, ok := identityProviderMapperRaw.(string); ok {
mapper.IdentityProviderMapper = identityProviderMapper
}
}
if configRaw, ok := mapInterface["config"]; ok {
if configInterface, ok := configRaw.(map[string]interface{}); ok {
for k, v := range configInterface {
if value, ok := v.(string); ok {
mapper.Config[k] = value
}
}
}
}
return &mapper, true
}
func (a GoCloakAdapter) createIdentityProviderMapper(realmName string, mapper dto.IdentityProviderMapper) error {
resp, err := a.startRestyRequest().SetPathParams(map[string]string{
keycloakApiParamAlias: mapper.IdentityProviderAlias,
keycloakApiParamRealm: realmName,
}).SetBody(mapper).Post(a.buildPath(mapperToIdentityProvider))
if err != nil {
return fmt.Errorf("failed to create identity provider mapper - %+v: %w", mapper, err)
}
if resp.StatusCode() != http.StatusCreated {
return fmt.Errorf("failed to create identity provider mapper - %+v, response: %s", mapper,
resp.String())
}
return nil
}
func (a GoCloakAdapter) updateIdentityProviderMapper(realmName string, mapper dto.IdentityProviderMapper) error {
resp, err := a.startRestyRequest().
SetPathParams(map[string]string{
keycloakApiParamAlias: mapper.IdentityProviderAlias,
keycloakApiParamRealm: realmName,
keycloakApiParamId: mapper.ID,
}).
SetBody(mapper).
Put(a.buildPath(updateMapperToIdentityProvider))
if err = a.checkError(err, resp); err != nil {
return fmt.Errorf("failed to update identity provider mapper - %+v: %w", mapper, err)
}
return nil
}
func ToRealmTokenSettings(tokenSettings *common.TokenSettings) *TokenSettings {
if tokenSettings == nil {
return nil
}
return &TokenSettings{
DefaultSignatureAlgorithm: tokenSettings.DefaultSignatureAlgorithm,
RevokeRefreshToken: tokenSettings.RevokeRefreshToken,
RefreshTokenMaxReuse: tokenSettings.RefreshTokenMaxReuse,
AccessTokenLifespan: tokenSettings.AccessTokenLifespan,
AccessTokenLifespanForImplicitFlow: tokenSettings.AccessTokenLifespanForImplicitFlow,
AccessCodeLifespan: tokenSettings.AccessCodeLifespan,
ActionTokenGeneratedByUserLifespan: tokenSettings.ActionTokenGeneratedByUserLifespan,
ActionTokenGeneratedByAdminLifespan: tokenSettings.ActionTokenGeneratedByAdminLifespan,
}
}