mirror of
https://github.com/juanfont/headscale.git
synced 2025-12-24 01:16:10 +00:00
oidc: make email verification configurable
Co-authored-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
@@ -41,6 +41,7 @@ var (
|
||||
errOIDCAllowedUsers = errors.New(
|
||||
"authenticated principal does not match any allowed user",
|
||||
)
|
||||
errOIDCUnverifiedEmail = errors.New("authenticated principal has an unverified email")
|
||||
)
|
||||
|
||||
// RegistrationInfo contains both machine key and verifier information for OIDC validation.
|
||||
@@ -264,17 +265,8 @@ func (a *AuthProviderOIDC) OIDCCallbackHandler(
|
||||
|
||||
// The user claims are now updated from the userinfo endpoint so we can verify the user
|
||||
// against allowed emails, email domains, and groups.
|
||||
if err := validateOIDCAllowedDomains(a.cfg.AllowedDomains, &claims); err != nil {
|
||||
httpError(writer, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateOIDCAllowedGroups(a.cfg.AllowedGroups, &claims); err != nil {
|
||||
httpError(writer, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateOIDCAllowedUsers(a.cfg.AllowedUsers, &claims); err != nil {
|
||||
err = doOIDCAuthorization(a.cfg, &claims)
|
||||
if err != nil {
|
||||
httpError(writer, err)
|
||||
return
|
||||
}
|
||||
@@ -434,17 +426,13 @@ func validateOIDCAllowedGroups(
|
||||
allowedGroups []string,
|
||||
claims *types.OIDCClaims,
|
||||
) error {
|
||||
if len(allowedGroups) > 0 {
|
||||
for _, group := range allowedGroups {
|
||||
if slices.Contains(claims.Groups, group) {
|
||||
return nil
|
||||
}
|
||||
for _, group := range allowedGroups {
|
||||
if slices.Contains(claims.Groups, group) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return NewHTTPError(http.StatusUnauthorized, "unauthorised group", errOIDCAllowedGroups)
|
||||
}
|
||||
|
||||
return nil
|
||||
return NewHTTPError(http.StatusUnauthorized, "unauthorised group", errOIDCAllowedGroups)
|
||||
}
|
||||
|
||||
// validateOIDCAllowedUsers checks that if AllowedUsers is provided,
|
||||
@@ -453,14 +441,62 @@ func validateOIDCAllowedUsers(
|
||||
allowedUsers []string,
|
||||
claims *types.OIDCClaims,
|
||||
) error {
|
||||
if len(allowedUsers) > 0 &&
|
||||
!slices.Contains(allowedUsers, claims.Email) {
|
||||
if !slices.Contains(allowedUsers, claims.Email) {
|
||||
return NewHTTPError(http.StatusUnauthorized, "unauthorised user", errOIDCAllowedUsers)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// doOIDCAuthorization applies authorization tests to claims.
|
||||
//
|
||||
// The following tests are always applied:
|
||||
//
|
||||
// - validateOIDCAllowedGroups
|
||||
//
|
||||
// The following tests are applied if cfg.EmailVerifiedRequired=false
|
||||
// or claims.email_verified=true:
|
||||
//
|
||||
// - validateOIDCAllowedDomains
|
||||
// - validateOIDCAllowedUsers
|
||||
//
|
||||
// NOTE that, contrary to the function name, validateOIDCAllowedUsers
|
||||
// only checks the email address -- not the username.
|
||||
func doOIDCAuthorization(
|
||||
cfg *types.OIDCConfig,
|
||||
claims *types.OIDCClaims,
|
||||
) error {
|
||||
if len(cfg.AllowedGroups) > 0 {
|
||||
err := validateOIDCAllowedGroups(cfg.AllowedGroups, claims)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
trustEmail := !cfg.EmailVerifiedRequired || bool(claims.EmailVerified)
|
||||
|
||||
hasEmailTests := len(cfg.AllowedDomains) > 0 || len(cfg.AllowedUsers) > 0
|
||||
if !trustEmail && hasEmailTests {
|
||||
return NewHTTPError(http.StatusUnauthorized, "unverified email", errOIDCUnverifiedEmail)
|
||||
}
|
||||
|
||||
if len(cfg.AllowedDomains) > 0 {
|
||||
err := validateOIDCAllowedDomains(cfg.AllowedDomains, claims)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(cfg.AllowedUsers) > 0 {
|
||||
err := validateOIDCAllowedUsers(cfg.AllowedUsers, claims)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRegistrationIDFromState retrieves the registration ID from the state.
|
||||
func (a *AuthProviderOIDC) getRegistrationIDFromState(state string) *types.RegistrationID {
|
||||
regInfo, ok := a.registrationCache.Get(state)
|
||||
@@ -493,7 +529,7 @@ func (a *AuthProviderOIDC) createOrUpdateUserFromClaim(
|
||||
user = &types.User{}
|
||||
}
|
||||
|
||||
user.FromClaim(claims)
|
||||
user.FromClaim(claims, a.cfg.EmailVerifiedRequired)
|
||||
|
||||
if newUser {
|
||||
user, c, err = a.h.state.CreateUser(*user)
|
||||
|
||||
Reference in New Issue
Block a user