github.com/greenpau/go-authcrunch@v1.1.4/pkg/authn/config.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  	"regexp"
    19  	"slices"
    20  	"strings"
    21  
    22  	"github.com/greenpau/go-authcrunch/pkg/acl"
    23  	"github.com/greenpau/go-authcrunch/pkg/authn/cookie"
    24  	"github.com/greenpau/go-authcrunch/pkg/authn/transformer"
    25  	"github.com/greenpau/go-authcrunch/pkg/authn/ui"
    26  	"github.com/greenpau/go-authcrunch/pkg/authz/options"
    27  	"github.com/greenpau/go-authcrunch/pkg/errors"
    28  	"github.com/greenpau/go-authcrunch/pkg/kms"
    29  	"github.com/greenpau/go-authcrunch/pkg/redirects"
    30  	cfgutil "github.com/greenpau/go-authcrunch/pkg/util/cfg"
    31  )
    32  
    33  const (
    34  	defaultGuestRoleName = "authp/guest"
    35  	defaultUserRoleName  = "authp/user"
    36  	defaultAdminRoleName = "authp/admin"
    37  )
    38  
    39  // PortalConfig represents Portal configuration.
    40  type PortalConfig struct {
    41  	Name string `json:"name,omitempty" xml:"name,omitempty" yaml:"name,omitempty"`
    42  	// UI holds the configuration for the user interface.
    43  	UI *ui.Parameters `json:"ui,omitempty" xml:"ui,omitempty" yaml:"ui,omitempty"`
    44  	// UserTransformerConfig holds the configuration for the user transformer.
    45  	UserTransformerConfigs []*transformer.Config `json:"user_transformer_configs,omitempty" xml:"user_transformer_configs,omitempty" yaml:"user_transformer_configs,omitempty"`
    46  	// CookieConfig holds the configuration for the cookies issues by Authenticator.
    47  	CookieConfig *cookie.Config `json:"cookie_config,omitempty" xml:"cookie_config,omitempty" yaml:"cookie_config,omitempty"`
    48  	// The names of identity stores.
    49  	IdentityStores []string `json:"identity_stores,omitempty" xml:"identity_stores,omitempty" yaml:"identity_stores,omitempty"`
    50  	// The names of identity providers.
    51  	IdentityProviders []string `json:"identity_providers,omitempty" xml:"identity_providers,omitempty" yaml:"identity_providers,omitempty"`
    52  	// The names of SSO providers.
    53  	SingleSignOnProviders []string `json:"sso_providers,omitempty" xml:"sso_providers,omitempty" yaml:"sso_providers,omitempty"`
    54  	// The names of user registries.
    55  	UserRegistries []string `json:"user_registries,omitempty" xml:"user_registries,omitempty" yaml:"user_registries,omitempty"`
    56  	// AccessListConfigs hold the configurations for the ACL of the token validator.
    57  	AccessListConfigs []*acl.RuleConfiguration `json:"access_list_configs,omitempty" xml:"access_list_configs,omitempty" yaml:"access_list_configs,omitempty"`
    58  	// TokenValidatorOptions holds the configuration for the token validator.
    59  	TokenValidatorOptions *options.TokenValidatorOptions `json:"token_validator_options,omitempty" xml:"token_validator_options,omitempty" yaml:"token_validator_options,omitempty"`
    60  	// CryptoKeyConfigs hold the configurations for the keys used to issue and validate user tokens.
    61  	CryptoKeyConfigs []*kms.CryptoKeyConfig `json:"crypto_key_configs,omitempty" xml:"crypto_key_configs,omitempty" yaml:"crypto_key_configs,omitempty"`
    62  	// CryptoKeyStoreConfig hold the default configuration for the keys, e.g. token name and lifetime.
    63  	CryptoKeyStoreConfig map[string]interface{} `json:"crypto_key_store_config,omitempty" xml:"crypto_key_store_config,omitempty" yaml:"crypto_key_store_config,omitempty"`
    64  	// TokenGrantorOptions holds the configuration for the tokens issues by Authenticator.
    65  	TokenGrantorOptions *options.TokenGrantorOptions `json:"token_grantor_options,omitempty" xml:"token_grantor_options,omitempty" yaml:"token_grantor_options,omitempty"`
    66  	// TrustedLogoutRedirectURIConfigs holds the configuration of trusted logout redirect URIs.
    67  	TrustedLogoutRedirectURIConfigs []*redirects.RedirectURIMatchConfig `json:"trusted_logout_redirect_uri_configs,omitempty" xml:"trusted_logout_redirect_uri_configs,omitempty" yaml:"trusted_logout_redirect_uri_configs,omitempty"`
    68  
    69  	// PortalAdminRoles holds the list of role names granted to do administrative tasks in the portal.
    70  	PortalAdminRoles map[string]interface{} `json:"portal_admin_roles,omitempty" xml:"portal_admin_roles,omitempty" yaml:"portal_admin_roles,omitempty"`
    71  	// PortalUserRoles holds the list of role names granted to do perform profile tasks in the portal.
    72  	PortalUserRoles map[string]interface{} `json:"portal_user_roles,omitempty" xml:"portal_user_roles,omitempty" yaml:"portal_user_roles,omitempty"`
    73  	// PortalGuestRoles holds the list of role names without admin or user privileges in the portal.
    74  	PortalGuestRoles map[string]interface{} `json:"portal_guest_roles,omitempty" xml:"portal_guest_roles,omitempty" yaml:"portal_guest_roles,omitempty"`
    75  
    76  	// PortalAdminRolePatterns holds the list of regular expressions for the role names granted to do administrative tasks in the portal.
    77  	PortalAdminRolePatterns []string `json:"portal_admin_role_patterns,omitempty" xml:"portal_admin_role_patterns,omitempty" yaml:"portal_admin_role_patterns,omitempty"`
    78  	adminRolePatterns       []*regexp.Regexp
    79  	// PortalUserRolePatterns holds the list of regular expressions for the role names granted to do perform profile tasks in the portal.
    80  	PortalUserRolePatterns []string `json:"portal_user_role_patterns,omitempty" xml:"portal_user_role_patterns,omitempty" yaml:"portal_user_role_patterns,omitempty"`
    81  	userRolePatterns       []*regexp.Regexp
    82  	// PortalGuestRolePatterns holds the list of regular expressions for the role names without admin or user privileges in the portal.
    83  	PortalGuestRolePatterns []string `json:"portal_guest_role_patterns,omitempty" xml:"portal_guest_role_patterns,omitempty" yaml:"portal_guest_role_patterns,omitempty"`
    84  	guestRolePatterns       []*regexp.Regexp
    85  	reservedPortalRoles     map[string]interface{}
    86  	guestPortalRoles        []string
    87  	// API holds the configuration for API endpoints.
    88  	API *APIConfig `json:"api,omitempty" xml:"api,omitempty" yaml:"api,omitempty"`
    89  
    90  	// Holds raw crypto configuration.
    91  	cryptoRawConfigs []string
    92  
    93  	// Indicated that the config was successfully validated.
    94  	validated bool
    95  }
    96  
    97  // AddRawCryptoConfigs adds raw crypto configs.
    98  func (cfg *PortalConfig) AddRawCryptoConfigs(s string) {
    99  	cfg.cryptoRawConfigs = append(cfg.cryptoRawConfigs, s)
   100  }
   101  
   102  // parseRawCryptoConfigs parses raw crypto configs into CryptoKeyConfigs
   103  // and CryptoKeyStoreConfig.
   104  func (cfg *PortalConfig) parseRawCryptoConfigs() error {
   105  	var cryptoKeyConfig, cryptoKeyStoreConfig []string
   106  	var cryptoKeyConfigFound, cryptoKeyStoreConfigFound bool
   107  	for _, encodedArgs := range cfg.cryptoRawConfigs {
   108  		args, err := cfgutil.DecodeArgs(encodedArgs)
   109  		if err != nil {
   110  			return errors.ErrConfigDirectiveFail.WithArgs("crypto", encodedArgs, err)
   111  		}
   112  		if len(args) < 3 {
   113  			return errors.ErrConfigDirectiveShort.WithArgs("crypto", args)
   114  		}
   115  		cryptoKeyConfig = append(cryptoKeyConfig, encodedArgs)
   116  		switch args[0] {
   117  		case "key":
   118  			cryptoKeyConfigFound = true
   119  		case "default":
   120  			cryptoKeyStoreConfig = append(cryptoKeyStoreConfig, encodedArgs)
   121  			cryptoKeyStoreConfigFound = true
   122  		default:
   123  			return errors.ErrConfigDirectiveValueUnsupported.WithArgs("crypto", args)
   124  		}
   125  	}
   126  
   127  	if cryptoKeyConfigFound {
   128  		configs, err := kms.ParseCryptoKeyConfigs(strings.Join(cryptoKeyConfig, "\n"))
   129  		if err != nil {
   130  			return errors.ErrConfigDirectiveFail.WithArgs("crypto.key", cryptoKeyConfig, err)
   131  		}
   132  		cfg.CryptoKeyConfigs = configs
   133  	}
   134  
   135  	if cryptoKeyStoreConfigFound {
   136  		configs, err := kms.ParseCryptoKeyStoreConfig(strings.Join(cryptoKeyStoreConfig, "\n"))
   137  		if err != nil {
   138  			return errors.ErrConfigDirectiveFail.WithArgs("crypto.keystore", cryptoKeyStoreConfig, err)
   139  		}
   140  		cfg.CryptoKeyStoreConfig = configs
   141  	}
   142  	return nil
   143  }
   144  
   145  // GetReservedPortalRoles returns the names of reserved portal roles.
   146  func (cfg *PortalConfig) GetReservedPortalRoles() map[string]interface{} {
   147  	if cfg.reservedPortalRoles == nil {
   148  		cfg.parsePortalRoles()
   149  	}
   150  	return cfg.reservedPortalRoles
   151  }
   152  
   153  // GetGuestPortalRoles returns the names of guest portal roles.
   154  func (cfg *PortalConfig) GetGuestPortalRoles() []string {
   155  	return cfg.guestPortalRoles
   156  }
   157  
   158  // parsePortalRoles validates the configuration of portal roles.
   159  func (cfg *PortalConfig) parsePortalRoles() error {
   160  	if cfg.reservedPortalRoles == nil {
   161  		cfg.reservedPortalRoles = make(map[string]interface{})
   162  	}
   163  
   164  	if cfg.PortalAdminRoles == nil {
   165  		cfg.PortalAdminRoles = make(map[string]interface{})
   166  	}
   167  	if len(cfg.PortalAdminRoles) < 1 {
   168  		cfg.PortalAdminRoles[defaultAdminRoleName] = true
   169  		cfg.reservedPortalRoles[defaultAdminRoleName] = true
   170  	}
   171  
   172  	if cfg.PortalUserRoles == nil {
   173  		cfg.PortalUserRoles = make(map[string]interface{})
   174  	}
   175  	if len(cfg.PortalUserRoles) < 1 {
   176  		cfg.PortalUserRoles[defaultUserRoleName] = true
   177  		cfg.reservedPortalRoles[defaultUserRoleName] = true
   178  	}
   179  
   180  	if cfg.PortalGuestRoles == nil {
   181  		cfg.PortalGuestRoles = make(map[string]interface{})
   182  		cfg.reservedPortalRoles[defaultGuestRoleName] = true
   183  	}
   184  	if len(cfg.PortalGuestRoles) < 1 {
   185  		cfg.PortalGuestRoles[defaultGuestRoleName] = true
   186  	}
   187  
   188  	if slices.Contains(cfg.guestPortalRoles, defaultGuestRoleName) {
   189  		cfg.guestPortalRoles = append(cfg.guestPortalRoles, defaultGuestRoleName)
   190  	}
   191  
   192  	for _, ptrn := range cfg.PortalAdminRolePatterns {
   193  		if ptrn == "" {
   194  			return errors.ErrInvalidConfiguration.WithArgs("portal", "admin role pattern is empty")
   195  		}
   196  		r, err := regexp.Compile(ptrn)
   197  		if err != nil {
   198  			return errors.ErrInvalidConfiguration.WithArgs("portal admin role pattern", err)
   199  		}
   200  		cfg.adminRolePatterns = append(cfg.adminRolePatterns, r)
   201  	}
   202  
   203  	for _, ptrn := range cfg.PortalUserRolePatterns {
   204  		if ptrn == "" {
   205  			return errors.ErrInvalidConfiguration.WithArgs("portal", "user role pattern is empty")
   206  		}
   207  		r, err := regexp.Compile(ptrn)
   208  		if err != nil {
   209  			return errors.ErrInvalidConfiguration.WithArgs("portal user role pattern", err)
   210  		}
   211  		cfg.userRolePatterns = append(cfg.userRolePatterns, r)
   212  	}
   213  
   214  	for _, ptrn := range cfg.PortalGuestRolePatterns {
   215  		if ptrn == "" {
   216  			return errors.ErrInvalidConfiguration.WithArgs("portal", "guest role pattern is empty")
   217  		}
   218  		r, err := regexp.Compile(ptrn)
   219  		if err != nil {
   220  			return errors.ErrInvalidConfiguration.WithArgs("portal guest role pattern", err)
   221  		}
   222  		cfg.guestRolePatterns = append(cfg.guestRolePatterns, r)
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  // Validate validates PortalConfig.
   229  func (cfg *PortalConfig) Validate() error {
   230  	if cfg.validated {
   231  		return nil
   232  	}
   233  	if cfg.Name == "" {
   234  		return errors.ErrPortalConfigNameNotFound
   235  	}
   236  
   237  	// if len(cfg.IdentityStores) == 0 && len(cfg.IdentityProviders) == 0 {
   238  	//	  return errors.ErrPortalConfigBackendsNotFound
   239  	// }
   240  
   241  	if err := cfg.parsePortalRoles(); err != nil {
   242  		return err
   243  	}
   244  
   245  	if err := cfg.parseRawCryptoConfigs(); err != nil {
   246  		return err
   247  	}
   248  
   249  	for _, redirURIConfig := range cfg.TrustedLogoutRedirectURIConfigs {
   250  		if err := redirURIConfig.Validate(); err != nil {
   251  			return err
   252  		}
   253  	}
   254  
   255  	// Inialize user interface settings
   256  	if cfg.UI == nil {
   257  		cfg.UI = &ui.Parameters{}
   258  	}
   259  
   260  	if cfg.UI.Templates == nil {
   261  		cfg.UI.Templates = make(map[string]string)
   262  	}
   263  
   264  	cfg.validated = true
   265  	return nil
   266  }