github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/portal.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package authn 16 17 import ( 18 "context" 19 "os" 20 "sort" 21 22 "github.com/greenpau/go-authcrunch/pkg/acl" 23 "github.com/greenpau/go-authcrunch/pkg/authn/cache" 24 "github.com/greenpau/go-authcrunch/pkg/authn/cookie" 25 "github.com/greenpau/go-authcrunch/pkg/authn/icons" 26 "github.com/greenpau/go-authcrunch/pkg/authn/transformer" 27 "github.com/greenpau/go-authcrunch/pkg/authn/ui" 28 "github.com/greenpau/go-authcrunch/pkg/authz/options" 29 "github.com/greenpau/go-authcrunch/pkg/authz/validator" 30 "github.com/greenpau/go-authcrunch/pkg/errors" 31 "github.com/greenpau/go-authcrunch/pkg/idp" 32 "github.com/greenpau/go-authcrunch/pkg/ids" 33 "github.com/greenpau/go-authcrunch/pkg/kms" 34 "github.com/greenpau/go-authcrunch/pkg/registry" 35 "github.com/greenpau/go-authcrunch/pkg/sso" 36 cfgutil "github.com/greenpau/go-authcrunch/pkg/util/cfg" 37 38 "fmt" 39 "path" 40 "strings" 41 "time" 42 43 "github.com/google/uuid" 44 "go.uber.org/zap" 45 ) 46 47 const ( 48 defaultPortalACLAction = "allow stop" 49 ) 50 51 // Portal is an authentication portal. 52 type Portal struct { 53 id string 54 config *PortalConfig 55 userRegistry registry.UserRegistry 56 validator *validator.TokenValidator 57 keystore *kms.CryptoKeyStore 58 identityStores []ids.IdentityStore 59 identityProviders []idp.IdentityProvider 60 ssoProviders []sso.SingleSignOnProvider 61 cookie *cookie.Factory 62 transformer *transformer.Factory 63 ui *ui.Factory 64 startedAt time.Time 65 sessions *cache.SessionCache 66 sandboxes *cache.SandboxCache 67 loginOptions map[string]interface{} 68 logger *zap.Logger 69 } 70 71 // PortalParameters are input parameters for NewPortal. 72 type PortalParameters struct { 73 Config *PortalConfig `json:"config,omitempty" xml:"config,omitempty" yaml:"config,omitempty"` 74 Logger *zap.Logger `json:"logger,omitempty" xml:"logger,omitempty" yaml:"logger,omitempty"` 75 IdentityStores []ids.IdentityStore `json:"identity_stores,omitempty" xml:"identity_stores,omitempty" yaml:"identity_stores,omitempty"` 76 IdentityProviders []idp.IdentityProvider `json:"identity_providers,omitempty" xml:"identity_providers,omitempty" yaml:"identity_providers,omitempty"` 77 SingleSignOnProviders []sso.SingleSignOnProvider `json:"sso_providers,omitempty" xml:"sso_providers,omitempty" yaml:"sso_providers,omitempty"` 78 } 79 80 // NewPortal returns an instance of Portal. 81 func NewPortal(params PortalParameters) (*Portal, error) { 82 if params.Logger == nil { 83 return nil, errors.ErrNewPortalLoggerNil 84 } 85 if params.Config == nil { 86 return nil, errors.ErrNewPortalConfigNil 87 } 88 89 if err := params.Config.Validate(); err != nil { 90 return nil, errors.ErrNewPortal.WithArgs(err) 91 } 92 p := &Portal{ 93 id: uuid.New().String(), 94 config: params.Config, 95 logger: params.Logger, 96 } 97 98 for _, storeName := range params.Config.IdentityStores { 99 var storeFound bool 100 for _, store := range params.IdentityStores { 101 if store.GetName() == storeName { 102 if !store.Configured() { 103 return nil, errors.ErrNewPortal.WithArgs( 104 fmt.Errorf("identity store %q not configured", storeName), 105 ) 106 } 107 p.identityStores = append(p.identityStores, store) 108 storeFound = true 109 break 110 } 111 } 112 if !storeFound { 113 return nil, errors.ErrNewPortal.WithArgs( 114 fmt.Errorf("identity store %q not found", storeName), 115 ) 116 } 117 } 118 119 for _, providerName := range params.Config.IdentityProviders { 120 var providerFound bool 121 for _, provider := range params.IdentityProviders { 122 if provider.GetName() == providerName { 123 if !provider.Configured() { 124 return nil, errors.ErrNewPortal.WithArgs( 125 fmt.Errorf("identity provider %q not configured", providerName), 126 ) 127 } 128 p.identityProviders = append(p.identityProviders, provider) 129 providerFound = true 130 break 131 } 132 } 133 if !providerFound { 134 return nil, errors.ErrNewPortal.WithArgs( 135 fmt.Errorf("identity provider %q not found", providerName), 136 ) 137 } 138 } 139 140 for _, providerName := range params.Config.SingleSignOnProviders { 141 var providerFound bool 142 for _, provider := range params.SingleSignOnProviders { 143 if provider.GetName() == providerName { 144 if !provider.Configured() { 145 return nil, errors.ErrNewPortal.WithArgs( 146 fmt.Errorf("sso provider %q not configured", providerName), 147 ) 148 } 149 p.ssoProviders = append(p.ssoProviders, provider) 150 providerFound = true 151 break 152 } 153 } 154 if !providerFound { 155 return nil, errors.ErrNewPortal.WithArgs( 156 fmt.Errorf("sso provider %q not found", providerName), 157 ) 158 } 159 } 160 161 if len(p.identityStores) < 1 && len(p.identityProviders) < 1 { 162 return nil, errors.ErrNewPortal.WithArgs(errors.ErrPortalConfigBackendsNotFound) 163 } 164 165 if err := p.configure(); err != nil { 166 return nil, err 167 } 168 return p, nil 169 } 170 171 // GetName returns the configuration name of the Portal. 172 func (p *Portal) GetName() string { 173 return p.config.Name 174 } 175 176 func (p *Portal) configure() error { 177 if err := p.configureEssentials(); err != nil { 178 return err 179 } 180 if err := p.configureCryptoKeyStore(); err != nil { 181 return err 182 } 183 if err := p.configureLoginOptions(); err != nil { 184 return err 185 } 186 if err := p.configureUserInterface(); err != nil { 187 return err 188 } 189 if err := p.configureUserTransformer(); err != nil { 190 return err 191 } 192 193 if len(p.config.TrustedLogoutRedirectURIConfigs) > 0 { 194 p.logger.Debug( 195 "Logout redirect URI configuration", 196 zap.Any("trusted_logout_redirect_uri_configs", p.config.TrustedLogoutRedirectURIConfigs), 197 ) 198 } else { 199 p.logger.Debug("Logout redirect URI configuration not present") 200 } 201 202 return nil 203 } 204 205 func (p *Portal) configureEssentials() error { 206 p.logger.Debug( 207 "Configuring caching", 208 zap.String("portal_name", p.config.Name), 209 zap.String("portal_id", p.id), 210 ) 211 212 p.sessions = cache.NewSessionCache() 213 p.sessions.Run() 214 p.sandboxes = cache.NewSandboxCache() 215 p.sandboxes.Run() 216 217 p.logger.Debug( 218 "Configuring cookie parameters", 219 zap.String("portal_name", p.config.Name), 220 zap.Any("cookie_config", p.config.CookieConfig), 221 ) 222 223 c, err := cookie.NewFactory(p.config.CookieConfig) 224 if err != nil { 225 return err 226 } 227 p.cookie = c 228 229 p.logger.Debug( 230 "Configuring default portal user roles", 231 zap.String("portal_name", p.config.Name), 232 zap.Any("portal_admin_roles", p.config.PortalAdminRoles), 233 zap.Any("portal_user_roles", p.config.PortalUserRoles), 234 zap.Any("portal_guest_roles", p.config.PortalGuestRoles), 235 zap.Any("portal_admin_role_patterns", p.config.PortalAdminRolePatterns), 236 zap.Any("portal_user_role_patterns", p.config.PortalUserRolePatterns), 237 zap.Any("portal_guest_role_patterns", p.config.PortalGuestRolePatterns), 238 ) 239 240 return nil 241 } 242 243 func (p *Portal) configureCryptoKeyStore() error { 244 if len(p.config.AccessListConfigs) == 0 { 245 defaultACLConfig := []*acl.RuleConfiguration{} 246 247 // Configure ACL by role names 248 for roleName := range p.config.PortalAdminRoles { 249 aclConfig := &acl.RuleConfiguration{ 250 Comment: "admin role name match", 251 Conditions: []string{"match role " + roleName}, 252 Action: defaultPortalACLAction, 253 } 254 defaultACLConfig = append(defaultACLConfig, aclConfig) 255 } 256 for roleName := range p.config.PortalUserRoles { 257 aclConfig := &acl.RuleConfiguration{ 258 Comment: "user role name match", 259 Conditions: []string{"match role " + roleName}, 260 Action: defaultPortalACLAction, 261 } 262 defaultACLConfig = append(defaultACLConfig, aclConfig) 263 } 264 for roleName := range p.config.PortalGuestRoles { 265 aclConfig := &acl.RuleConfiguration{ 266 Comment: "guest role name match", 267 Conditions: []string{"match role " + roleName}, 268 Action: defaultPortalACLAction, 269 } 270 defaultACLConfig = append(defaultACLConfig, aclConfig) 271 } 272 273 // Configure ACL by role patterns 274 for _, roleNameRegex := range p.config.adminRolePatterns { 275 aclConfig := &acl.RuleConfiguration{ 276 Comment: "admin role name pattern match", 277 Conditions: []string{"regex match role " + roleNameRegex.String()}, 278 Action: defaultPortalACLAction, 279 } 280 defaultACLConfig = append(defaultACLConfig, aclConfig) 281 } 282 for _, roleNameRegex := range p.config.userRolePatterns { 283 aclConfig := &acl.RuleConfiguration{ 284 Comment: "user role name pattern match", 285 Conditions: []string{"regex match role " + roleNameRegex.String()}, 286 Action: defaultPortalACLAction, 287 } 288 defaultACLConfig = append(defaultACLConfig, aclConfig) 289 } 290 for _, roleNameRegex := range p.config.guestRolePatterns { 291 aclConfig := &acl.RuleConfiguration{ 292 Comment: "guest role name pattern match", 293 Conditions: []string{"regex match role " + roleNameRegex.String()}, 294 Action: defaultPortalACLAction, 295 } 296 defaultACLConfig = append(defaultACLConfig, aclConfig) 297 } 298 299 p.config.AccessListConfigs = defaultACLConfig 300 } 301 302 p.logger.Debug( 303 "Configuring authentication ACL", 304 zap.String("portal_name", p.config.Name), 305 zap.String("portal_id", p.id), 306 zap.Any("access_list_configs", p.config.AccessListConfigs), 307 ) 308 309 if p.config.TokenValidatorOptions == nil { 310 p.config.TokenValidatorOptions = options.NewTokenValidatorOptions() 311 } 312 p.config.TokenValidatorOptions.ValidateBearerHeader = true 313 314 // The below line is disabled because path match is not part of the ACL. 315 // p.config.TokenValidatorOptions.ValidateMethodPath = true 316 317 accessList := acl.NewAccessList() 318 accessList.SetLogger(p.logger) 319 ctx := context.Background() 320 if err := accessList.AddRules(ctx, p.config.AccessListConfigs); err != nil { 321 return errors.ErrCryptoKeyStoreConfig.WithArgs(p.config.Name, err) 322 } 323 324 p.keystore = kms.NewCryptoKeyStore() 325 p.keystore.SetLogger(p.logger) 326 327 // Load token configuration into key managers, extract token verification 328 // keys and add them to token validator. 329 if p.config.CryptoKeyStoreConfig != nil { 330 // Add default token name, lifetime, etc. 331 if err := p.keystore.AddDefaults(p.config.CryptoKeyStoreConfig); err != nil { 332 return errors.ErrCryptoKeyStoreConfig.WithArgs(p.config.Name, err) 333 } 334 } 335 336 if len(p.config.CryptoKeyConfigs) == 0 { 337 if err := p.keystore.AutoGenerate("default", "ES512"); err != nil { 338 return errors.ErrCryptoKeyStoreConfig.WithArgs(p.config.Name, err) 339 } 340 } else { 341 if err := p.keystore.AddKeysWithConfigs(p.config.CryptoKeyConfigs); err != nil { 342 return errors.ErrCryptoKeyStoreConfig.WithArgs(p.config.Name, err) 343 } 344 } 345 346 if err := p.keystore.HasVerifyKeys(); err != nil { 347 return errors.ErrCryptoKeyStoreConfig.WithArgs(p.config.Name, err) 348 } 349 350 p.validator = validator.NewTokenValidator() 351 if err := p.validator.Configure(ctx, p.keystore.GetVerifyKeys(), accessList, p.config.TokenValidatorOptions); err != nil { 352 return errors.ErrCryptoKeyStoreConfig.WithArgs(p.config.Name, err) 353 } 354 355 p.logger.Debug( 356 "Configured validator ACL", 357 zap.String("portal_name", p.config.Name), 358 zap.String("portal_id", p.id), 359 zap.Any("token_validator_options", p.config.TokenValidatorOptions), 360 zap.Any("token_grantor_options", p.config.TokenGrantorOptions), 361 ) 362 return nil 363 } 364 365 func (p *Portal) configureLoginOptions() error { 366 p.loginOptions = make(map[string]interface{}) 367 p.loginOptions["form_required"] = "no" 368 p.loginOptions["realm_dropdown_required"] = "no" 369 p.loginOptions["authenticators_required"] = "no" 370 p.loginOptions["identity_required"] = "no" 371 372 if err := p.configureIdentityStoreLogin(); err != nil { 373 return err 374 } 375 376 if err := p.configureIdentityProviderLogin(); err != nil { 377 return err 378 } 379 380 if err := p.configureLoginIcons(); err != nil { 381 return err 382 } 383 384 p.logger.Debug( 385 "Provisioned login options", 386 zap.String("portal_name", p.config.Name), 387 zap.String("portal_id", p.id), 388 zap.Any("options", p.loginOptions), 389 zap.Int("identity_store_count", len(p.config.IdentityStores)), 390 zap.Int("identity_provider_count", len(p.config.IdentityProviders)), 391 ) 392 393 return nil 394 } 395 396 func (p *Portal) configureLoginIcons() error { 397 var entries []*icons.LoginIcon 398 399 for _, store := range p.identityStores { 400 icon := store.GetLoginIcon() 401 entries = append(entries, icon) 402 } 403 404 for _, provider := range p.identityProviders { 405 icon := provider.GetLoginIcon() 406 entries = append(entries, icon) 407 } 408 409 sort.Slice(entries[:], func(i, j int) bool { 410 return entries[i].Priority > entries[j].Priority 411 }) 412 413 var iconConfigs []map[string]string 414 415 for i, icon := range entries { 416 iconConfig := icon.GetConfig() 417 iconConfigs = append(iconConfigs, iconConfig) 418 if i == 0 { 419 p.loginOptions["default_realm"] = iconConfig["realm"] 420 } 421 } 422 423 p.loginOptions["authenticators"] = iconConfigs 424 425 if len(iconConfigs) == 1 { 426 p.loginOptions["hide_contact_support_link"] = "yes" 427 p.loginOptions["hide_forgot_username_link"] = "yes" 428 p.loginOptions["hide_register_link"] = "yes" 429 p.loginOptions["hide_links"] = "yes" 430 for _, iconConfig := range iconConfigs { 431 if v, exists := iconConfig["contact_support_enabled"]; exists && v == "yes" { 432 p.loginOptions["hide_contact_support_link"] = "no" 433 p.loginOptions["hide_links"] = "no" 434 } 435 if v, exists := iconConfig["registration_enabled"]; exists && v == "yes" { 436 p.loginOptions["hide_register_link"] = "no" 437 p.loginOptions["hide_links"] = "no" 438 } 439 if v, exists := iconConfig["username_recovery_enabled"]; exists && v == "yes" { 440 p.loginOptions["hide_forgot_username_link"] = "no" 441 p.loginOptions["hide_links"] = "no" 442 } 443 } 444 } 445 446 return nil 447 } 448 449 func (p *Portal) configureIdentityStoreLogin() error { 450 if len(p.config.IdentityStores) < 1 { 451 return nil 452 } 453 454 p.logger.Debug( 455 "Configuring identity store login options", 456 zap.String("portal_name", p.config.Name), 457 zap.String("portal_id", p.id), 458 zap.Int("identity_store_count", len(p.config.IdentityStores)), 459 ) 460 461 var stores []map[string]string 462 463 for _, store := range p.identityStores { 464 cfg := make(map[string]string) 465 cfg["realm"] = store.GetRealm() 466 cfg["default"] = "no" 467 switch store.GetKind() { 468 case "local": 469 cfg["label"] = strings.ToTitle(store.GetRealm()) 470 cfg["default"] = "yes" 471 case "ldap": 472 cfg["label"] = strings.ToUpper(store.GetRealm()) 473 default: 474 cfg["label"] = strings.ToTitle(store.GetRealm()) 475 } 476 stores = append(stores, cfg) 477 } 478 479 if len(stores) > 0 { 480 p.loginOptions["form_required"] = "yes" 481 p.loginOptions["identity_required"] = "yes" 482 p.loginOptions["realms"] = stores 483 } 484 485 if len(stores) > 1 { 486 p.loginOptions["realm_dropdown_required"] = "yes" 487 p.loginOptions["authenticators_required"] = "yes" 488 } 489 490 for _, store := range p.identityStores { 491 icon := store.GetLoginIcon() 492 icon.SetRealm(store.GetRealm()) 493 switch store.GetKind() { 494 case "local": 495 icon.RegistrationEnabled = false 496 icon.UsernameRecoveryEnabled = false 497 case "ldap": 498 icon.RegistrationEnabled = false 499 icon.UsernameRecoveryEnabled = false 500 } 501 } 502 503 return nil 504 } 505 506 func (p *Portal) configureIdentityProviderLogin() error { 507 if len(p.config.IdentityProviders) < 1 { 508 return nil 509 } 510 511 p.logger.Debug( 512 "Configuring identity provider login options", 513 zap.String("portal_name", p.config.Name), 514 zap.String("portal_id", p.id), 515 zap.Int("identity_provider_count", len(p.config.IdentityProviders)), 516 ) 517 518 for _, provider := range p.identityProviders { 519 icon := provider.GetLoginIcon() 520 icon.SetRealm(provider.GetRealm()) 521 switch provider.GetKind() { 522 case "oauth": 523 icon.SetEndpoint(path.Join(provider.GetKind()+"2", provider.GetRealm())) 524 default: 525 icon.SetEndpoint(path.Join(provider.GetKind(), provider.GetRealm())) 526 } 527 } 528 529 p.loginOptions["authenticators_required"] = "yes" 530 531 return nil 532 } 533 534 func (p *Portal) configureUserInterface() error { 535 p.logger.Debug( 536 "Configuring user interface", 537 zap.String("portal_name", p.config.Name), 538 zap.String("portal_id", p.id), 539 ) 540 541 p.ui = ui.NewFactory() 542 if p.config.UI.Title == "" { 543 p.ui.Title = "Sign In" 544 } else { 545 p.ui.Title = p.config.UI.Title 546 } 547 548 if p.config.UI.CustomCSSPath != "" { 549 p.ui.CustomCSSPath = p.config.UI.CustomCSSPath 550 if err := ui.StaticAssets.AddAsset("assets/css/custom.css", "text/css", p.config.UI.CustomCSSPath); err != nil { 551 return errors.ErrStaticAssetAddFailed.WithArgs("assets/css/custom.css", "text/css", p.config.UI.CustomCSSPath, p.config.Name, err) 552 } 553 } 554 555 if p.config.UI.CustomJsPath != "" { 556 p.ui.CustomJsPath = p.config.UI.CustomJsPath 557 if err := ui.StaticAssets.AddAsset("assets/js/custom.js", "application/javascript", p.config.UI.CustomJsPath); err != nil { 558 return errors.ErrStaticAssetAddFailed.WithArgs("assets/js/custom.js", "application/javascript", p.config.UI.CustomJsPath, p.config.Name, err) 559 } 560 } 561 562 if p.config.UI.CustomHTMLHeaderPath != "" { 563 b, err := os.ReadFile(p.config.UI.CustomHTMLHeaderPath) 564 if err != nil { 565 return errors.ErrCustomHTMLHeaderNotReadable.WithArgs(p.config.UI.CustomHTMLHeaderPath, p.config.Name, err) 566 } 567 for k, v := range ui.PageTemplates { 568 headIndex := strings.Index(v, "<meta name=\"description\"") 569 if headIndex < 1 { 570 continue 571 } 572 v = v[:headIndex] + string(b) + v[headIndex:] 573 ui.PageTemplates[k] = v 574 } 575 } 576 577 for _, staticAsset := range p.config.UI.StaticAssets { 578 if err := ui.StaticAssets.AddAsset(staticAsset.Path, staticAsset.ContentType, staticAsset.FsPath); err != nil { 579 return errors.ErrStaticAssetAddFailed.WithArgs(staticAsset.Path, staticAsset.ContentType, staticAsset.FsPath, p.config.Name, err) 580 } 581 } 582 583 if p.config.UI.LogoURL != "" { 584 p.ui.LogoURL = p.config.UI.LogoURL 585 p.ui.LogoDescription = p.config.UI.LogoDescription 586 } else { 587 p.ui.LogoURL = path.Join(p.ui.LogoURL) 588 } 589 590 if p.config.UI.MetaTitle != "" { 591 p.ui.MetaTitle = p.config.UI.MetaTitle 592 } else { 593 p.ui.MetaTitle = "Authentication Portal" 594 } 595 596 if p.config.UI.MetaAuthor != "" { 597 p.ui.MetaAuthor = p.config.UI.MetaAuthor 598 } else { 599 p.ui.MetaAuthor = "Paul Greenberg github.com/greenpau" 600 } 601 602 if p.config.UI.MetaDescription != "" { 603 p.ui.MetaDescription = p.config.UI.MetaDescription 604 } else { 605 p.ui.MetaDescription = "Performs user authentication." 606 } 607 608 if len(p.config.UI.PrivateLinks) > 0 { 609 p.ui.PrivateLinks = p.config.UI.PrivateLinks 610 } 611 612 if len(p.config.UI.Realms) > 0 { 613 p.ui.Realms = p.config.UI.Realms 614 } 615 616 if p.config.UI.Theme == "" { 617 p.config.UI.Theme = "basic" 618 } 619 if _, exists := ui.Themes[p.config.UI.Theme]; !exists { 620 return errors.ErrUserInterfaceThemeNotFound.WithArgs(p.config.Name, p.config.UI.Theme) 621 } 622 623 // User Interface Templates 624 for k := range ui.PageTemplates { 625 tmplNameParts := strings.SplitN(k, "/", 2) 626 tmplTheme := tmplNameParts[0] 627 tmplName := tmplNameParts[1] 628 if tmplTheme != p.config.UI.Theme { 629 continue 630 } 631 if _, exists := p.config.UI.Templates[tmplName]; !exists { 632 p.logger.Debug( 633 "Configuring default authentication user interface templates", 634 zap.String("portal_name", p.config.Name), 635 zap.String("template_theme", tmplTheme), 636 zap.String("template_name", tmplName), 637 ) 638 if err := p.ui.AddBuiltinTemplate(k); err != nil { 639 return errors.ErrUserInterfaceBuiltinTemplateAddFailed.WithArgs(p.config.Name, tmplName, tmplTheme, err) 640 } 641 p.ui.Templates[tmplName] = p.ui.Templates[k] 642 } 643 } 644 645 for tmplName, tmplPath := range p.config.UI.Templates { 646 p.logger.Debug( 647 "Configuring non-default authentication user interface templates", 648 zap.String("portal_name", p.config.Name), 649 zap.String("portal_id", p.id), 650 zap.String("template_name", tmplName), 651 zap.String("template_path", tmplPath), 652 ) 653 if err := p.ui.AddTemplate(tmplName, tmplPath); err != nil { 654 return errors.ErrUserInterfaceCustomTemplateAddFailed.WithArgs(p.config.Name, tmplName, tmplPath, err) 655 } 656 } 657 658 p.logger.Debug( 659 "Configured user interface", 660 zap.String("portal_name", p.config.Name), 661 zap.String("portal_id", p.id), 662 zap.String("title", p.ui.Title), 663 zap.String("logo_url", p.ui.LogoURL), 664 zap.String("logo_description", p.ui.LogoDescription), 665 zap.Any("action_endpoint", p.ui.ActionEndpoint), 666 zap.Any("private_links", p.ui.PrivateLinks), 667 zap.Any("realms", p.ui.Realms), 668 zap.String("theme", p.config.UI.Theme), 669 ) 670 671 return nil 672 } 673 674 func (p *Portal) configureUserTransformer() error { 675 if len(p.config.UserTransformerConfigs) == 0 { 676 return nil 677 } 678 679 p.logger.Debug( 680 "Configuring user transforms", 681 zap.String("portal_name", p.config.Name), 682 zap.String("portal_id", p.id), 683 ) 684 685 tr, err := transformer.NewFactory(p.config.UserTransformerConfigs) 686 if err != nil { 687 return err 688 } 689 p.transformer = tr 690 691 p.logger.Debug( 692 "Configured user transforms", 693 zap.String("portal_name", p.config.Name), 694 zap.String("portal_id", p.id), 695 zap.Any("transforms", p.config.UserTransformerConfigs), 696 ) 697 return nil 698 } 699 700 // AddUserRegistry adds registry.UserRegistry instance to Portal. 701 func (p *Portal) AddUserRegistry(userRegistry registry.UserRegistry) error { 702 p.config.UserRegistries = cfgutil.DedupStrArr(p.config.UserRegistries) 703 704 if len(p.config.UserRegistries) < 1 { 705 return fmt.Errorf("auth portal has no user registries configured") 706 } 707 if len(p.config.UserRegistries) > 1 { 708 return fmt.Errorf("auth portal does not support multiple user registries: %v", p.config.UserRegistries) 709 } 710 711 p.userRegistry = userRegistry 712 713 p.logger.Debug( 714 "Configured user registration", 715 zap.String("portal_name", p.config.Name), 716 zap.String("portal_id", p.id), 717 zap.Any("user_registry", p.userRegistry.GetConfig()), 718 ) 719 720 return nil 721 } 722 723 // GetIdentityStoreNames returns a list of existing identity stores. 724 func (p *Portal) GetIdentityStoreNames() map[string]string { 725 var m map[string]string 726 for _, store := range p.identityStores { 727 if m == nil { 728 m = make(map[string]string) 729 } 730 m[store.GetName()] = store.GetRealm() 731 } 732 return m 733 }