github.com/greenpau/go-authcrunch@v1.1.4/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 authcrunch
    16  
    17  import (
    18  	"fmt"
    19  	"github.com/greenpau/go-authcrunch/pkg/authn"
    20  	"github.com/greenpau/go-authcrunch/pkg/authz"
    21  	"github.com/greenpau/go-authcrunch/pkg/credentials"
    22  	"github.com/greenpau/go-authcrunch/pkg/errors"
    23  	"github.com/greenpau/go-authcrunch/pkg/idp"
    24  	"github.com/greenpau/go-authcrunch/pkg/ids"
    25  	"github.com/greenpau/go-authcrunch/pkg/messaging"
    26  	"github.com/greenpau/go-authcrunch/pkg/registry"
    27  	"github.com/greenpau/go-authcrunch/pkg/sso"
    28  )
    29  
    30  // Config is a configuration of Server.
    31  type Config struct {
    32  	Credentials               *credentials.Config               `json:"credentials,omitempty" xml:"credentials,omitempty" yaml:"credentials,omitempty"`
    33  	Messaging                 *messaging.Config                 `json:"messaging,omitempty" xml:"messaging,omitempty" yaml:"messaging,omitempty"`
    34  	AuthenticationPortals     []*authn.PortalConfig             `json:"authentication_portals,omitempty" xml:"authentication_portals,omitempty" yaml:"authentication_portals,omitempty"`
    35  	AuthorizationPolicies     []*authz.PolicyConfig             `json:"authorization_policies,omitempty" xml:"authorization_policies,omitempty" yaml:"authorization_policies,omitempty"`
    36  	IdentityStores            []*ids.IdentityStoreConfig        `json:"identity_stores,omitempty" xml:"identity_stores,omitempty" yaml:"identity_stores,omitempty"`
    37  	IdentityProviders         []*idp.IdentityProviderConfig     `json:"identity_providers,omitempty" xml:"identity_providers,omitempty" yaml:"identity_providers,omitempty"`
    38  	SingleSignOnProviders     []*sso.SingleSignOnProviderConfig `json:"sso_providers,omitempty" xml:"sso_providers,omitempty" yaml:"sso_providers,omitempty"`
    39  	disabledIdentityStores    map[string]interface{}
    40  	disabledIdentityProviders map[string]interface{}
    41  	UserRegistries            []*registry.UserRegistryConfig `json:"user_registries,omitempty" xml:"user_registries,omitempty" yaml:"user_registries,omitempty"`
    42  }
    43  
    44  // NewConfig returns an instance of Config.
    45  func NewConfig() *Config {
    46  	return &Config{}
    47  }
    48  
    49  // AddCredential adds a credential configuration.
    50  func (cfg *Config) AddCredential(c credentials.Credential) error {
    51  	if cfg.Credentials == nil {
    52  		cfg.Credentials = &credentials.Config{}
    53  	}
    54  	return cfg.Credentials.Add(c)
    55  }
    56  
    57  // AddMessagingProvider adds a messaging provider configuration.
    58  func (cfg *Config) AddMessagingProvider(p messaging.Provider) error {
    59  	if cfg.Messaging == nil {
    60  		cfg.Messaging = &messaging.Config{}
    61  	}
    62  	return cfg.Messaging.Add(p)
    63  }
    64  
    65  // AddIdentityStore adds an identity store configuration.
    66  func (cfg *Config) AddIdentityStore(name, kind string, data map[string]interface{}) error {
    67  	store, err := ids.NewIdentityStoreConfig(name, kind, data)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	cfg.IdentityStores = append(cfg.IdentityStores, store)
    72  	return nil
    73  }
    74  
    75  // AddIdentityProvider adds an identity provider configuration.
    76  func (cfg *Config) AddIdentityProvider(name, kind string, data map[string]interface{}) error {
    77  	provider, err := idp.NewIdentityProviderConfig(name, kind, data)
    78  	if err != nil {
    79  		return err
    80  	}
    81  	cfg.IdentityProviders = append(cfg.IdentityProviders, provider)
    82  	return nil
    83  }
    84  
    85  // AddSingleSignOnProvider adds a single sign-on provider configuration.
    86  func (cfg *Config) AddSingleSignOnProvider(data map[string]interface{}) error {
    87  	provider, err := sso.NewSingleSignOnProviderConfig(data)
    88  	if err != nil {
    89  		return err
    90  	}
    91  	cfg.SingleSignOnProviders = append(cfg.SingleSignOnProviders, provider)
    92  	return nil
    93  }
    94  
    95  // AddAuthenticationPortal adds an authentication portal configuration.
    96  func (cfg *Config) AddAuthenticationPortal(p *authn.PortalConfig) error {
    97  	if err := p.Validate(); err != nil {
    98  		return err
    99  	}
   100  	cfg.AuthenticationPortals = append(cfg.AuthenticationPortals, p)
   101  	return nil
   102  }
   103  
   104  // AddAuthorizationPolicy adds an authorization policy configuration.
   105  func (cfg *Config) AddAuthorizationPolicy(p *authz.PolicyConfig) error {
   106  	if err := p.Validate(); err != nil {
   107  		return err
   108  	}
   109  	cfg.AuthorizationPolicies = append(cfg.AuthorizationPolicies, p)
   110  	return nil
   111  }
   112  
   113  // Validate validates Config.
   114  func (cfg *Config) Validate() error {
   115  	if len(cfg.AuthenticationPortals) < 1 && len(cfg.AuthorizationPolicies) < 1 {
   116  		return fmt.Errorf("no portals and gatekeepers found")
   117  	}
   118  
   119  	identityStoreUserRegistry := make(map[string]string)
   120  	for _, userRegistry := range cfg.UserRegistries {
   121  		userRegistry.SetCredentials(cfg.Credentials)
   122  		userRegistry.SetMessaging(cfg.Messaging)
   123  		if err := userRegistry.ValidateMessaging(); err != nil {
   124  			return err
   125  		}
   126  		var identityStoreFound bool
   127  		for _, identityStore := range cfg.IdentityStores {
   128  			if identityStore.Name == userRegistry.IdentityStore {
   129  				identityStoreFound = true
   130  				identityStoreUserRegistry[identityStore.Name] = userRegistry.IdentityStore
   131  				break
   132  			}
   133  		}
   134  		if !identityStoreFound {
   135  			return fmt.Errorf(
   136  				"identity store %q referenced in %q user registry not found",
   137  				userRegistry.IdentityStore, userRegistry.Name,
   138  			)
   139  		}
   140  	}
   141  
   142  	// Validate auth portal configurations.
   143  	for _, portalCfg := range cfg.AuthenticationPortals {
   144  		// If there are no excplicitly specified identity stores and providers in a portal, add all of them.
   145  		if len(portalCfg.IdentityStores) == 0 && len(portalCfg.IdentityProviders) == 0 {
   146  			for _, entry := range cfg.IdentityStores {
   147  				portalCfg.IdentityStores = append(portalCfg.IdentityStores, entry.Name)
   148  			}
   149  			for _, entry := range cfg.IdentityProviders {
   150  				portalCfg.IdentityProviders = append(portalCfg.IdentityProviders, entry.Name)
   151  			}
   152  		}
   153  
   154  		if len(portalCfg.IdentityStores) == 0 && len(portalCfg.IdentityProviders) == 0 {
   155  			return errors.ErrPortalConfigBackendsNotFound
   156  		}
   157  
   158  		// Filter out disabled identity store names.
   159  		portalCfg.IdentityStores = cfg.filterDisabledIdentityStores(portalCfg.IdentityStores)
   160  
   161  		// Vealidate that there are no duplicate or overlapping identity store and providers.
   162  		authByName := make(map[string]string)
   163  		authByRealm := make(map[string]string)
   164  
   165  		for _, storeName := range portalCfg.IdentityStores {
   166  			if v, exists := authByName[storeName]; exists {
   167  				return fmt.Errorf(
   168  					"identity store %q has the same name as %s",
   169  					storeName, v,
   170  				)
   171  			}
   172  
   173  			authByName[storeName] = "another identity store"
   174  
   175  			var storeConfig *ids.IdentityStoreConfig
   176  			for _, entry := range cfg.IdentityStores {
   177  				if entry.Name == storeName {
   178  					storeConfig = entry
   179  					break
   180  				}
   181  			}
   182  			if storeConfig == nil {
   183  				continue
   184  			}
   185  			if storeConfig.Params == nil {
   186  				continue
   187  			}
   188  			if v, exists := storeConfig.Params["realm"]; exists {
   189  				realmName := v.(string)
   190  				if prevStoreName, exists := authByRealm[realmName]; exists {
   191  					return fmt.Errorf(
   192  						"identity provider %q has the same %q realm as %q",
   193  						storeName, realmName, prevStoreName,
   194  					)
   195  				}
   196  				authByRealm[realmName] = storeName
   197  				authByName[storeName] = "identity store in " + realmName + " realm"
   198  			}
   199  
   200  			// Add registry store if configured.
   201  			if v, exists := identityStoreUserRegistry[storeName]; exists {
   202  				storeConfig.Params["registration_enabled"] = true
   203  				portalCfg.UserRegistries = append(portalCfg.UserRegistries, v)
   204  			}
   205  		}
   206  
   207  		// Filter out disabled identity store names.
   208  		portalCfg.IdentityProviders = cfg.filterDisabledIdentityProviders(portalCfg.IdentityProviders)
   209  
   210  		for _, providerName := range portalCfg.IdentityProviders {
   211  			if v, exists := authByName[providerName]; exists {
   212  				return fmt.Errorf(
   213  					"identity provider %q has the same name as %s",
   214  					providerName, v,
   215  				)
   216  			}
   217  
   218  			authByName[providerName] = "another identity provider"
   219  
   220  			var providerConfig *idp.IdentityProviderConfig
   221  			for _, entry := range cfg.IdentityProviders {
   222  				providerConfig = entry
   223  				if entry.Name == providerName {
   224  					break
   225  				}
   226  			}
   227  			if providerConfig == nil {
   228  				continue
   229  			}
   230  			if providerConfig.Params == nil {
   231  				continue
   232  			}
   233  			if v, exists := providerConfig.Params["realm"]; exists {
   234  				realmName := v.(string)
   235  				if prevProviderName, exists := authByRealm[realmName]; exists {
   236  					return fmt.Errorf(
   237  						"identity provider %q has the same %q realm as %q",
   238  						providerName, realmName, prevProviderName,
   239  					)
   240  				}
   241  				authByRealm[realmName] = providerName
   242  				authByName[providerName] = "identity provider in " + realmName + " realm"
   243  			}
   244  		}
   245  
   246  		// Iterate over SSO providers.
   247  		for _, providerName := range portalCfg.SingleSignOnProviders {
   248  			var providerFound bool
   249  			for _, entry := range cfg.SingleSignOnProviders {
   250  				if providerName == entry.Name {
   251  					providerFound = true
   252  					break
   253  				}
   254  			}
   255  			if !providerFound {
   256  				return fmt.Errorf("sso provider %q configuration not found", providerName)
   257  			}
   258  		}
   259  	}
   260  
   261  	return nil
   262  }
   263  
   264  // AddDisabledIdentityStore adds the names of disabled identity stores.
   265  func (cfg *Config) AddDisabledIdentityStore(s string) {
   266  	if cfg.disabledIdentityStores == nil {
   267  		cfg.disabledIdentityStores = map[string]interface{}{
   268  			s: true,
   269  		}
   270  		return
   271  	}
   272  	cfg.disabledIdentityStores[s] = true
   273  }
   274  
   275  // AddDisabledIdentityProvider adds the names of disabled identity providers.
   276  func (cfg *Config) AddDisabledIdentityProvider(s string) {
   277  	if cfg.disabledIdentityProviders == nil {
   278  		cfg.disabledIdentityProviders = map[string]interface{}{
   279  			s: true,
   280  		}
   281  		return
   282  	}
   283  	cfg.disabledIdentityProviders[s] = true
   284  }
   285  
   286  func (cfg *Config) filterDisabledIdentityStores(arr []string) []string {
   287  	var output []string
   288  	if len(arr) == 0 || cfg.disabledIdentityStores == nil {
   289  		return arr
   290  	}
   291  	for _, s := range arr {
   292  		if _, exists := cfg.disabledIdentityStores[s]; exists {
   293  			continue
   294  		}
   295  		output = append(output, s)
   296  	}
   297  	return output
   298  }
   299  
   300  func (cfg *Config) filterDisabledIdentityProviders(arr []string) []string {
   301  	var output []string
   302  	if len(arr) == 0 || cfg.disabledIdentityProviders == nil {
   303  		return arr
   304  	}
   305  	for _, s := range arr {
   306  		if _, exists := cfg.disabledIdentityProviders[s]; exists {
   307  			continue
   308  		}
   309  		output = append(output, s)
   310  	}
   311  	return output
   312  }
   313  
   314  // AddUserRegistry adds a user registry configuration.
   315  func (cfg *Config) AddUserRegistry(r *registry.UserRegistryConfig) error {
   316  	if err := r.Validate(); err != nil {
   317  		return err
   318  	}
   319  	cfg.UserRegistries = append(cfg.UserRegistries, r)
   320  	return nil
   321  }