mirror of
				https://github.com/h44z/wg-portal.git
				synced 2025-11-03 23:56:18 +00:00 
			
		
		
		
	Improve admin privilege handling for OAuth. Update documentation.
This commit is contained in:
		@@ -2,6 +2,7 @@ package auth
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
@@ -9,6 +10,7 @@ import (
 | 
			
		||||
	"github.com/h44z/wg-portal/internal"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/config"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/domain"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type LdapAuthenticator struct {
 | 
			
		||||
@@ -78,7 +80,10 @@ func (l LdapAuthenticator) PlaintextAuthentication(userId domain.UserIdentifier,
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l LdapAuthenticator) GetUserInfo(_ context.Context, userId domain.UserIdentifier) (map[string]interface{}, error) {
 | 
			
		||||
func (l LdapAuthenticator) GetUserInfo(_ context.Context, userId domain.UserIdentifier) (
 | 
			
		||||
	map[string]interface{},
 | 
			
		||||
	error,
 | 
			
		||||
) {
 | 
			
		||||
	conn, err := internal.LdapConnect(l.cfg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to setup connection: %w", err)
 | 
			
		||||
@@ -109,6 +114,11 @@ func (l LdapAuthenticator) GetUserInfo(_ context.Context, userId domain.UserIden
 | 
			
		||||
 | 
			
		||||
	users := internal.LdapConvertEntries(sr, &l.cfg.FieldMap)
 | 
			
		||||
 | 
			
		||||
	if l.cfg.LogUserInfo {
 | 
			
		||||
		contents, _ := json.Marshal(users[0])
 | 
			
		||||
		logrus.Tracef("User info from LDAP source %s for %s: %v", l.GetName(), userId, string(contents))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return users[0], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,12 +6,11 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/h44z/wg-portal/internal"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/config"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/domain"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"golang.org/x/oauth2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -21,10 +20,16 @@ type PlainOauthAuthenticator struct {
 | 
			
		||||
	userInfoEndpoint    string
 | 
			
		||||
	client              *http.Client
 | 
			
		||||
	userInfoMapping     config.OauthFields
 | 
			
		||||
	userAdminMapping    *config.OauthAdminMapping
 | 
			
		||||
	registrationEnabled bool
 | 
			
		||||
	userInfoLogging     bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newPlainOauthAuthenticator(_ context.Context, callbackUrl string, cfg *config.OAuthProvider) (*PlainOauthAuthenticator, error) {
 | 
			
		||||
func newPlainOauthAuthenticator(
 | 
			
		||||
	_ context.Context,
 | 
			
		||||
	callbackUrl string,
 | 
			
		||||
	cfg *config.OAuthProvider,
 | 
			
		||||
) (*PlainOauthAuthenticator, error) {
 | 
			
		||||
	var provider = &PlainOauthAuthenticator{}
 | 
			
		||||
 | 
			
		||||
	provider.name = cfg.ProviderName
 | 
			
		||||
@@ -44,7 +49,9 @@ func newPlainOauthAuthenticator(_ context.Context, callbackUrl string, cfg *conf
 | 
			
		||||
	}
 | 
			
		||||
	provider.userInfoEndpoint = cfg.UserInfoURL
 | 
			
		||||
	provider.userInfoMapping = getOauthFieldMapping(cfg.FieldMap)
 | 
			
		||||
	provider.userAdminMapping = &cfg.AdminMapping
 | 
			
		||||
	provider.registrationEnabled = cfg.RegistrationEnabled
 | 
			
		||||
	provider.userInfoLogging = cfg.LogUserInfo
 | 
			
		||||
 | 
			
		||||
	return provider, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -65,11 +72,19 @@ func (p PlainOauthAuthenticator) AuthCodeURL(state string, opts ...oauth2.AuthCo
 | 
			
		||||
	return p.cfg.AuthCodeURL(state, opts...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p PlainOauthAuthenticator) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
 | 
			
		||||
func (p PlainOauthAuthenticator) Exchange(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	code string,
 | 
			
		||||
	opts ...oauth2.AuthCodeOption,
 | 
			
		||||
) (*oauth2.Token, error) {
 | 
			
		||||
	return p.cfg.Exchange(ctx, code, opts...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p PlainOauthAuthenticator) GetUserInfo(ctx context.Context, token *oauth2.Token, _ string) (map[string]interface{}, error) {
 | 
			
		||||
func (p PlainOauthAuthenticator) GetUserInfo(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	token *oauth2.Token,
 | 
			
		||||
	_ string,
 | 
			
		||||
) (map[string]interface{}, error) {
 | 
			
		||||
	req, err := http.NewRequest("GET", p.userInfoEndpoint, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to create user info get request: %w", err)
 | 
			
		||||
@@ -93,57 +108,13 @@ func (p PlainOauthAuthenticator) GetUserInfo(ctx context.Context, token *oauth2.
 | 
			
		||||
		return nil, fmt.Errorf("failed to parse user info: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.userInfoLogging {
 | 
			
		||||
		logrus.Tracef("User info from OAuth source %s: %v", p.name, string(contents))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return userFields, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p PlainOauthAuthenticator) ParseUserInfo(raw map[string]interface{}) (*domain.AuthenticatorUserInfo, error) {
 | 
			
		||||
	isAdmin, _ := strconv.ParseBool(internal.MapDefaultString(raw, p.userInfoMapping.IsAdmin, ""))
 | 
			
		||||
	userInfo := &domain.AuthenticatorUserInfo{
 | 
			
		||||
		Identifier: domain.UserIdentifier(internal.MapDefaultString(raw, p.userInfoMapping.UserIdentifier, "")),
 | 
			
		||||
		Email:      internal.MapDefaultString(raw, p.userInfoMapping.Email, ""),
 | 
			
		||||
		Firstname:  internal.MapDefaultString(raw, p.userInfoMapping.Firstname, ""),
 | 
			
		||||
		Lastname:   internal.MapDefaultString(raw, p.userInfoMapping.Lastname, ""),
 | 
			
		||||
		Phone:      internal.MapDefaultString(raw, p.userInfoMapping.Phone, ""),
 | 
			
		||||
		Department: internal.MapDefaultString(raw, p.userInfoMapping.Department, ""),
 | 
			
		||||
		IsAdmin:    isAdmin,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return userInfo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getOauthFieldMapping(f config.OauthFields) config.OauthFields {
 | 
			
		||||
	defaultMap := config.OauthFields{
 | 
			
		||||
		BaseFields: config.BaseFields{
 | 
			
		||||
			UserIdentifier: "sub",
 | 
			
		||||
			Email:          "email",
 | 
			
		||||
			Firstname:      "given_name",
 | 
			
		||||
			Lastname:       "family_name",
 | 
			
		||||
			Phone:          "phone",
 | 
			
		||||
			Department:     "department",
 | 
			
		||||
		},
 | 
			
		||||
		IsAdmin: "admin_flag",
 | 
			
		||||
	}
 | 
			
		||||
	if f.UserIdentifier != "" {
 | 
			
		||||
		defaultMap.UserIdentifier = f.UserIdentifier
 | 
			
		||||
	}
 | 
			
		||||
	if f.Email != "" {
 | 
			
		||||
		defaultMap.Email = f.Email
 | 
			
		||||
	}
 | 
			
		||||
	if f.Firstname != "" {
 | 
			
		||||
		defaultMap.Firstname = f.Firstname
 | 
			
		||||
	}
 | 
			
		||||
	if f.Lastname != "" {
 | 
			
		||||
		defaultMap.Lastname = f.Lastname
 | 
			
		||||
	}
 | 
			
		||||
	if f.Phone != "" {
 | 
			
		||||
		defaultMap.Phone = f.Phone
 | 
			
		||||
	}
 | 
			
		||||
	if f.Department != "" {
 | 
			
		||||
		defaultMap.Department = f.Department
 | 
			
		||||
	}
 | 
			
		||||
	if f.IsAdmin != "" {
 | 
			
		||||
		defaultMap.IsAdmin = f.IsAdmin
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return defaultMap
 | 
			
		||||
	return parseOauthUserInfo(p.userInfoMapping, p.userAdminMapping, raw)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,14 @@ package auth
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/coreos/go-oidc/v3/oidc"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/config"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/domain"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"golang.org/x/oauth2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -19,15 +19,22 @@ type OidcAuthenticator struct {
 | 
			
		||||
	verifier            *oidc.IDTokenVerifier
 | 
			
		||||
	cfg                 *oauth2.Config
 | 
			
		||||
	userInfoMapping     config.OauthFields
 | 
			
		||||
	userAdminMapping    *config.OauthAdminMapping
 | 
			
		||||
	registrationEnabled bool
 | 
			
		||||
	userInfoLogging     bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newOidcAuthenticator(ctx context.Context, callbackUrl string, cfg *config.OpenIDConnectProvider) (*OidcAuthenticator, error) {
 | 
			
		||||
func newOidcAuthenticator(
 | 
			
		||||
	_ context.Context,
 | 
			
		||||
	callbackUrl string,
 | 
			
		||||
	cfg *config.OpenIDConnectProvider,
 | 
			
		||||
) (*OidcAuthenticator, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	var provider = &OidcAuthenticator{}
 | 
			
		||||
 | 
			
		||||
	provider.name = cfg.ProviderName
 | 
			
		||||
	provider.provider, err = oidc.NewProvider(context.Background(), cfg.BaseUrl) // use new context here, see https://github.com/coreos/go-oidc/issues/339
 | 
			
		||||
	provider.provider, err = oidc.NewProvider(context.Background(),
 | 
			
		||||
		cfg.BaseUrl) // use new context here, see https://github.com/coreos/go-oidc/issues/339
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to create new oidc provider: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -45,7 +52,9 @@ func newOidcAuthenticator(ctx context.Context, callbackUrl string, cfg *config.O
 | 
			
		||||
		Scopes:       scopes,
 | 
			
		||||
	}
 | 
			
		||||
	provider.userInfoMapping = getOauthFieldMapping(cfg.FieldMap)
 | 
			
		||||
	provider.userAdminMapping = &cfg.AdminMapping
 | 
			
		||||
	provider.registrationEnabled = cfg.RegistrationEnabled
 | 
			
		||||
	provider.userInfoLogging = cfg.LogUserInfo
 | 
			
		||||
 | 
			
		||||
	return provider, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -66,11 +75,17 @@ func (o OidcAuthenticator) AuthCodeURL(state string, opts ...oauth2.AuthCodeOpti
 | 
			
		||||
	return o.cfg.AuthCodeURL(state, opts...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o OidcAuthenticator) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
 | 
			
		||||
func (o OidcAuthenticator) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (
 | 
			
		||||
	*oauth2.Token,
 | 
			
		||||
	error,
 | 
			
		||||
) {
 | 
			
		||||
	return o.cfg.Exchange(ctx, code, opts...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o OidcAuthenticator) GetUserInfo(ctx context.Context, token *oauth2.Token, nonce string) (map[string]interface{}, error) {
 | 
			
		||||
func (o OidcAuthenticator) GetUserInfo(ctx context.Context, token *oauth2.Token, nonce string) (
 | 
			
		||||
	map[string]interface{},
 | 
			
		||||
	error,
 | 
			
		||||
) {
 | 
			
		||||
	rawIDToken, ok := token.Extra("id_token").(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, errors.New("token does not contain id_token")
 | 
			
		||||
@@ -88,20 +103,14 @@ func (o OidcAuthenticator) GetUserInfo(ctx context.Context, token *oauth2.Token,
 | 
			
		||||
		return nil, fmt.Errorf("failed to parse extra claims: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.userInfoLogging {
 | 
			
		||||
		contents, _ := json.Marshal(tokenFields)
 | 
			
		||||
		logrus.Tracef("User info from OIDC source %s: %v", o.name, string(contents))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return tokenFields, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o OidcAuthenticator) ParseUserInfo(raw map[string]interface{}) (*domain.AuthenticatorUserInfo, error) {
 | 
			
		||||
	isAdmin, _ := strconv.ParseBool(internal.MapDefaultString(raw, o.userInfoMapping.IsAdmin, ""))
 | 
			
		||||
	userInfo := &domain.AuthenticatorUserInfo{
 | 
			
		||||
		Identifier: domain.UserIdentifier(internal.MapDefaultString(raw, o.userInfoMapping.UserIdentifier, "")),
 | 
			
		||||
		Email:      internal.MapDefaultString(raw, o.userInfoMapping.Email, ""),
 | 
			
		||||
		Firstname:  internal.MapDefaultString(raw, o.userInfoMapping.Firstname, ""),
 | 
			
		||||
		Lastname:   internal.MapDefaultString(raw, o.userInfoMapping.Lastname, ""),
 | 
			
		||||
		Phone:      internal.MapDefaultString(raw, o.userInfoMapping.Phone, ""),
 | 
			
		||||
		Department: internal.MapDefaultString(raw, o.userInfoMapping.Department, ""),
 | 
			
		||||
		IsAdmin:    isAdmin,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return userInfo, nil
 | 
			
		||||
	return parseOauthUserInfo(o.userInfoMapping, o.userAdminMapping, raw)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										88
									
								
								internal/app/auth/oauth_common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								internal/app/auth/oauth_common.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
			
		||||
package auth
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/h44z/wg-portal/internal"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/config"
 | 
			
		||||
	"github.com/h44z/wg-portal/internal/domain"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// parseOauthUserInfo parses the raw user info from the oauth provider and maps it to the internal user info struct
 | 
			
		||||
func parseOauthUserInfo(
 | 
			
		||||
	mapping config.OauthFields,
 | 
			
		||||
	adminMapping *config.OauthAdminMapping,
 | 
			
		||||
	raw map[string]interface{},
 | 
			
		||||
) (*domain.AuthenticatorUserInfo, error) {
 | 
			
		||||
	var isAdmin bool
 | 
			
		||||
 | 
			
		||||
	// first try to match the is_admin field against the given regex
 | 
			
		||||
	if mapping.IsAdmin != "" {
 | 
			
		||||
		re := adminMapping.GetAdminValueRegex()
 | 
			
		||||
		if re.MatchString(strings.TrimSpace(internal.MapDefaultString(raw, mapping.IsAdmin, ""))) {
 | 
			
		||||
			isAdmin = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// next try to parse the user's groups
 | 
			
		||||
	if !isAdmin && mapping.UserGroups != "" && adminMapping.AdminGroupRegex != "" {
 | 
			
		||||
		userGroups := internal.MapDefaultStringSlice(raw, mapping.UserGroups, nil)
 | 
			
		||||
		re := adminMapping.GetAdminGroupRegex()
 | 
			
		||||
		for _, group := range userGroups {
 | 
			
		||||
			if re.MatchString(strings.TrimSpace(group)) {
 | 
			
		||||
				isAdmin = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	userInfo := &domain.AuthenticatorUserInfo{
 | 
			
		||||
		Identifier: domain.UserIdentifier(internal.MapDefaultString(raw, mapping.UserIdentifier, "")),
 | 
			
		||||
		Email:      internal.MapDefaultString(raw, mapping.Email, ""),
 | 
			
		||||
		Firstname:  internal.MapDefaultString(raw, mapping.Firstname, ""),
 | 
			
		||||
		Lastname:   internal.MapDefaultString(raw, mapping.Lastname, ""),
 | 
			
		||||
		Phone:      internal.MapDefaultString(raw, mapping.Phone, ""),
 | 
			
		||||
		Department: internal.MapDefaultString(raw, mapping.Department, ""),
 | 
			
		||||
		IsAdmin:    isAdmin,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return userInfo, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getOauthFieldMapping returns the default field mapping for the oauth provider
 | 
			
		||||
func getOauthFieldMapping(f config.OauthFields) config.OauthFields {
 | 
			
		||||
	defaultMap := config.OauthFields{
 | 
			
		||||
		BaseFields: config.BaseFields{
 | 
			
		||||
			UserIdentifier: "sub",
 | 
			
		||||
			Email:          "email",
 | 
			
		||||
			Firstname:      "given_name",
 | 
			
		||||
			Lastname:       "family_name",
 | 
			
		||||
			Phone:          "phone",
 | 
			
		||||
			Department:     "department",
 | 
			
		||||
		},
 | 
			
		||||
		IsAdmin: "admin_flag",
 | 
			
		||||
	}
 | 
			
		||||
	if f.UserIdentifier != "" {
 | 
			
		||||
		defaultMap.UserIdentifier = f.UserIdentifier
 | 
			
		||||
	}
 | 
			
		||||
	if f.Email != "" {
 | 
			
		||||
		defaultMap.Email = f.Email
 | 
			
		||||
	}
 | 
			
		||||
	if f.Firstname != "" {
 | 
			
		||||
		defaultMap.Firstname = f.Firstname
 | 
			
		||||
	}
 | 
			
		||||
	if f.Lastname != "" {
 | 
			
		||||
		defaultMap.Lastname = f.Lastname
 | 
			
		||||
	}
 | 
			
		||||
	if f.Phone != "" {
 | 
			
		||||
		defaultMap.Phone = f.Phone
 | 
			
		||||
	}
 | 
			
		||||
	if f.Department != "" {
 | 
			
		||||
		defaultMap.Department = f.Department
 | 
			
		||||
	}
 | 
			
		||||
	if f.IsAdmin != "" {
 | 
			
		||||
		defaultMap.IsAdmin = f.IsAdmin
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return defaultMap
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/go-ldap/ldap/v3"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Auth struct {
 | 
			
		||||
@@ -23,7 +25,67 @@ type BaseFields struct {
 | 
			
		||||
 | 
			
		||||
type OauthFields struct {
 | 
			
		||||
	BaseFields `yaml:",inline"`
 | 
			
		||||
	IsAdmin    string `yaml:"is_admin"` // If the value is "true", the user is an admin.
 | 
			
		||||
	IsAdmin    string `yaml:"is_admin"`    // If the value is "true", the user is an admin.
 | 
			
		||||
	UserGroups string `yaml:"user_groups"` // This value specifies the claim name that contains the users groups.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OauthAdminMapping contains all necessary information to extract information about administrative privileges
 | 
			
		||||
// from the user info fields.
 | 
			
		||||
//
 | 
			
		||||
// WgPortal can grant a user admin rights by matching the value of the `is_admin` claim against a regular expression.
 | 
			
		||||
// Alternatively, a regular expression can be used to check if a user is member of a specific group listed in the
 | 
			
		||||
// `user_group` claim.
 | 
			
		||||
// If one of the cases evaluates to true, the user is granted admin rights.
 | 
			
		||||
type OauthAdminMapping struct {
 | 
			
		||||
	// If the regex specified in that field matches the contents of the is_admin field, the user is an admin.
 | 
			
		||||
	AdminValueRegex string `yaml:"admin_value_regex"`
 | 
			
		||||
 | 
			
		||||
	// If any of the groups listed in the groups field matches the group specified in the admin_group_regex field, ]
 | 
			
		||||
	// the user is an admin.
 | 
			
		||||
	AdminGroupRegex string `yaml:"admin_group_regex"`
 | 
			
		||||
 | 
			
		||||
	// internal cache fields
 | 
			
		||||
 | 
			
		||||
	adminValueRegex *regexp.Regexp
 | 
			
		||||
	adminGroupRegex *regexp.Regexp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *OauthAdminMapping) GetAdminValueRegex() *regexp.Regexp {
 | 
			
		||||
	if o.adminValueRegex != nil {
 | 
			
		||||
		return o.adminValueRegex // return cached value
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.AdminValueRegex == "" {
 | 
			
		||||
		o.adminValueRegex = regexp.MustCompile("^true$") // default value is "true"
 | 
			
		||||
		return o.adminValueRegex
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	adminRegex, err := regexp.Compile(o.AdminValueRegex)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Fatalf("failed to compile admin_value_regex: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	o.adminValueRegex = adminRegex
 | 
			
		||||
 | 
			
		||||
	return o.adminValueRegex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *OauthAdminMapping) GetAdminGroupRegex() *regexp.Regexp {
 | 
			
		||||
	if o.adminGroupRegex != nil {
 | 
			
		||||
		return o.adminGroupRegex // return cached value
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.AdminGroupRegex == "" {
 | 
			
		||||
		o.adminGroupRegex = regexp.MustCompile("^wg_portal_default_admin_group$") // default value is "wg_portal_default_admin_group"
 | 
			
		||||
		return o.adminGroupRegex
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	groupRegex, err := regexp.Compile(o.AdminGroupRegex)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logrus.Fatalf("failed to compile admin_group_regex: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	o.adminGroupRegex = groupRegex
 | 
			
		||||
 | 
			
		||||
	return o.adminGroupRegex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type LdapFields struct {
 | 
			
		||||
@@ -58,6 +120,9 @@ type LdapProvider struct {
 | 
			
		||||
 | 
			
		||||
	// If RegistrationEnabled is set to true, wg-portal will create new users that do not exist in the database.
 | 
			
		||||
	RegistrationEnabled bool `yaml:"registration_enabled"`
 | 
			
		||||
 | 
			
		||||
	// If LogUserInfo is set to true, the user info retrieved from the LDAP provider will be logged in trace level.
 | 
			
		||||
	LogUserInfo bool `yaml:"log_user_info"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type OpenIDConnectProvider struct {
 | 
			
		||||
@@ -81,8 +146,15 @@ type OpenIDConnectProvider struct {
 | 
			
		||||
	// FieldMap is used to map the names of the user-info endpoint fields to wg-portal fields
 | 
			
		||||
	FieldMap OauthFields `yaml:"field_map"`
 | 
			
		||||
 | 
			
		||||
	// AdminMapping contains all necessary information to extract information about administrative privileges
 | 
			
		||||
	// from the user info fields.
 | 
			
		||||
	AdminMapping OauthAdminMapping `yaml:"admin_mapping"`
 | 
			
		||||
 | 
			
		||||
	// If RegistrationEnabled is set to true, missing users will be created in the database
 | 
			
		||||
	RegistrationEnabled bool `yaml:"registration_enabled"`
 | 
			
		||||
 | 
			
		||||
	// If LogUserInfo is set to true, the user info retrieved from the OIDC provider will be logged in trace level.
 | 
			
		||||
	LogUserInfo bool `yaml:"log_user_info"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type OAuthProvider struct {
 | 
			
		||||
@@ -108,6 +180,13 @@ type OAuthProvider struct {
 | 
			
		||||
	// FieldMap is used to map the names of the user-info endpoint fields to wg-portal fields
 | 
			
		||||
	FieldMap OauthFields `yaml:"field_map"`
 | 
			
		||||
 | 
			
		||||
	// AdminMapping contains all necessary information to extract information about administrative privileges
 | 
			
		||||
	// from the user info fields.
 | 
			
		||||
	AdminMapping OauthAdminMapping `yaml:"admin_mapping"`
 | 
			
		||||
 | 
			
		||||
	// If RegistrationEnabled is set to true, wg-portal will create new users that do not exist in the database.
 | 
			
		||||
	RegistrationEnabled bool `yaml:"registration_enabled"`
 | 
			
		||||
 | 
			
		||||
	// If LogUserInfo is set to true, the user info retrieved from the OAuth provider will be logged in trace level.
 | 
			
		||||
	LogUserInfo bool `yaml:"log_user_info"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -79,6 +79,27 @@ func MapDefaultString(m map[string]interface{}, key string, dflt string) string
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MapDefaultStringSlice returns the string slice value for the given key or a default value
 | 
			
		||||
func MapDefaultStringSlice(m map[string]interface{}, key string, dflt []string) []string {
 | 
			
		||||
	if m == nil {
 | 
			
		||||
		return dflt
 | 
			
		||||
	}
 | 
			
		||||
	if tmp, ok := m[key]; !ok {
 | 
			
		||||
		return dflt
 | 
			
		||||
	} else {
 | 
			
		||||
		switch v := tmp.(type) {
 | 
			
		||||
		case []string:
 | 
			
		||||
			return v
 | 
			
		||||
		case string:
 | 
			
		||||
			return []string{v}
 | 
			
		||||
		case nil:
 | 
			
		||||
			return dflt
 | 
			
		||||
		default:
 | 
			
		||||
			return []string{fmt.Sprintf("%v", v)}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UniqueStringSlice removes duplicates in the given string slice
 | 
			
		||||
func UniqueStringSlice(slice []string) []string {
 | 
			
		||||
	keys := make(map[string]struct{})
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user