2023-08-04 11:34:18 +00:00
|
|
|
package auth
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2025-01-18 10:55:56 +00:00
|
|
|
"encoding/json"
|
2023-08-04 11:34:18 +00:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"github.com/coreos/go-oidc/v3/oidc"
|
|
|
|
"github.com/h44z/wg-portal/internal/config"
|
|
|
|
"github.com/h44z/wg-portal/internal/domain"
|
2025-01-18 10:55:56 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2023-08-04 11:34:18 +00:00
|
|
|
"golang.org/x/oauth2"
|
|
|
|
)
|
|
|
|
|
|
|
|
type OidcAuthenticator struct {
|
|
|
|
name string
|
|
|
|
provider *oidc.Provider
|
|
|
|
verifier *oidc.IDTokenVerifier
|
|
|
|
cfg *oauth2.Config
|
|
|
|
userInfoMapping config.OauthFields
|
2025-01-18 10:55:56 +00:00
|
|
|
userAdminMapping *config.OauthAdminMapping
|
2023-08-04 11:34:18 +00:00
|
|
|
registrationEnabled bool
|
2025-01-18 10:55:56 +00:00
|
|
|
userInfoLogging bool
|
2023-08-04 11:34:18 +00:00
|
|
|
}
|
|
|
|
|
2025-01-18 10:55:56 +00:00
|
|
|
func newOidcAuthenticator(
|
|
|
|
_ context.Context,
|
|
|
|
callbackUrl string,
|
|
|
|
cfg *config.OpenIDConnectProvider,
|
|
|
|
) (*OidcAuthenticator, error) {
|
2023-08-04 11:34:18 +00:00
|
|
|
var err error
|
|
|
|
var provider = &OidcAuthenticator{}
|
|
|
|
|
|
|
|
provider.name = cfg.ProviderName
|
2025-01-18 10:55:56 +00:00
|
|
|
provider.provider, err = oidc.NewProvider(context.Background(),
|
|
|
|
cfg.BaseUrl) // use new context here, see https://github.com/coreos/go-oidc/issues/339
|
2023-08-04 11:34:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to create new oidc provider: %w", err)
|
|
|
|
}
|
|
|
|
provider.verifier = provider.provider.Verifier(&oidc.Config{
|
|
|
|
ClientID: cfg.ClientID,
|
|
|
|
})
|
|
|
|
|
|
|
|
scopes := []string{oidc.ScopeOpenID}
|
|
|
|
scopes = append(scopes, cfg.ExtraScopes...)
|
|
|
|
provider.cfg = &oauth2.Config{
|
|
|
|
ClientID: cfg.ClientID,
|
|
|
|
ClientSecret: cfg.ClientSecret,
|
|
|
|
Endpoint: provider.provider.Endpoint(),
|
|
|
|
RedirectURL: callbackUrl,
|
|
|
|
Scopes: scopes,
|
|
|
|
}
|
|
|
|
provider.userInfoMapping = getOauthFieldMapping(cfg.FieldMap)
|
2025-01-18 10:55:56 +00:00
|
|
|
provider.userAdminMapping = &cfg.AdminMapping
|
2023-08-04 11:34:18 +00:00
|
|
|
provider.registrationEnabled = cfg.RegistrationEnabled
|
2025-01-18 10:55:56 +00:00
|
|
|
provider.userInfoLogging = cfg.LogUserInfo
|
2023-08-04 11:34:18 +00:00
|
|
|
|
|
|
|
return provider, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o OidcAuthenticator) GetName() string {
|
|
|
|
return o.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o OidcAuthenticator) RegistrationEnabled() bool {
|
|
|
|
return o.registrationEnabled
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o OidcAuthenticator) GetType() domain.AuthenticatorType {
|
|
|
|
return domain.AuthenticatorTypeOidc
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o OidcAuthenticator) AuthCodeURL(state string, opts ...oauth2.AuthCodeOption) string {
|
|
|
|
return o.cfg.AuthCodeURL(state, opts...)
|
|
|
|
}
|
|
|
|
|
2025-01-18 10:55:56 +00:00
|
|
|
func (o OidcAuthenticator) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (
|
|
|
|
*oauth2.Token,
|
|
|
|
error,
|
|
|
|
) {
|
2023-08-04 11:34:18 +00:00
|
|
|
return o.cfg.Exchange(ctx, code, opts...)
|
|
|
|
}
|
|
|
|
|
2025-01-18 10:55:56 +00:00
|
|
|
func (o OidcAuthenticator) GetUserInfo(ctx context.Context, token *oauth2.Token, nonce string) (
|
|
|
|
map[string]interface{},
|
|
|
|
error,
|
|
|
|
) {
|
2023-08-04 11:34:18 +00:00
|
|
|
rawIDToken, ok := token.Extra("id_token").(string)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("token does not contain id_token")
|
|
|
|
}
|
|
|
|
idToken, err := o.verifier.Verify(ctx, rawIDToken)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to validate id_token: %w", err)
|
|
|
|
}
|
|
|
|
if idToken.Nonce != nonce {
|
|
|
|
return nil, errors.New("nonce mismatch")
|
|
|
|
}
|
|
|
|
|
|
|
|
var tokenFields map[string]interface{}
|
|
|
|
if err = idToken.Claims(&tokenFields); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to parse extra claims: %w", err)
|
|
|
|
}
|
|
|
|
|
2025-01-18 10:55:56 +00:00
|
|
|
if o.userInfoLogging {
|
|
|
|
contents, _ := json.Marshal(tokenFields)
|
|
|
|
logrus.Tracef("User info from OIDC source %s: %v", o.name, string(contents))
|
|
|
|
}
|
|
|
|
|
2023-08-04 11:34:18 +00:00
|
|
|
return tokenFields, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o OidcAuthenticator) ParseUserInfo(raw map[string]interface{}) (*domain.AuthenticatorUserInfo, error) {
|
2025-01-18 10:55:56 +00:00
|
|
|
return parseOauthUserInfo(o.userInfoMapping, o.userAdminMapping, raw)
|
2023-08-04 11:34:18 +00:00
|
|
|
}
|