mirror of
https://github.com/juanfont/headscale.git
synced 2026-04-22 16:48:40 +02:00
oidc: make email verification configurable
Co-authored-by: Kristoffer Dalby <kristoffer@tailscale.com>
This commit is contained in:
@@ -185,6 +185,7 @@ type OIDCConfig struct {
|
||||
AllowedDomains []string
|
||||
AllowedUsers []string
|
||||
AllowedGroups []string
|
||||
EmailVerifiedRequired bool
|
||||
Expiry time.Duration
|
||||
UseExpiryFromToken bool
|
||||
PKCE PKCEConfig
|
||||
@@ -384,6 +385,7 @@ func LoadConfig(path string, isFile bool) error {
|
||||
viper.SetDefault("oidc.use_expiry_from_token", false)
|
||||
viper.SetDefault("oidc.pkce.enabled", false)
|
||||
viper.SetDefault("oidc.pkce.method", "S256")
|
||||
viper.SetDefault("oidc.email_verified_required", true)
|
||||
|
||||
viper.SetDefault("logtail.enabled", false)
|
||||
viper.SetDefault("randomize_client_port", false)
|
||||
@@ -1022,14 +1024,15 @@ func LoadServerConfig() (*Config, error) {
|
||||
OnlyStartIfOIDCIsAvailable: viper.GetBool(
|
||||
"oidc.only_start_if_oidc_is_available",
|
||||
),
|
||||
Issuer: viper.GetString("oidc.issuer"),
|
||||
ClientID: viper.GetString("oidc.client_id"),
|
||||
ClientSecret: oidcClientSecret,
|
||||
Scope: viper.GetStringSlice("oidc.scope"),
|
||||
ExtraParams: viper.GetStringMapString("oidc.extra_params"),
|
||||
AllowedDomains: viper.GetStringSlice("oidc.allowed_domains"),
|
||||
AllowedUsers: viper.GetStringSlice("oidc.allowed_users"),
|
||||
AllowedGroups: viper.GetStringSlice("oidc.allowed_groups"),
|
||||
Issuer: viper.GetString("oidc.issuer"),
|
||||
ClientID: viper.GetString("oidc.client_id"),
|
||||
ClientSecret: oidcClientSecret,
|
||||
Scope: viper.GetStringSlice("oidc.scope"),
|
||||
ExtraParams: viper.GetStringMapString("oidc.extra_params"),
|
||||
AllowedDomains: viper.GetStringSlice("oidc.allowed_domains"),
|
||||
AllowedUsers: viper.GetStringSlice("oidc.allowed_users"),
|
||||
AllowedGroups: viper.GetStringSlice("oidc.allowed_groups"),
|
||||
EmailVerifiedRequired: viper.GetBool("oidc.email_verified_required"),
|
||||
Expiry: func() time.Duration {
|
||||
// if set to 0, we assume no expiry
|
||||
if value := viper.GetString("oidc.expiry"); value == "0" {
|
||||
|
||||
@@ -353,7 +353,7 @@ type OIDCUserInfo struct {
|
||||
|
||||
// FromClaim overrides a User from OIDC claims.
|
||||
// All fields will be updated, except for the ID.
|
||||
func (u *User) FromClaim(claims *OIDCClaims) {
|
||||
func (u *User) FromClaim(claims *OIDCClaims, emailVerifiedRequired bool) {
|
||||
err := util.ValidateUsername(claims.Username)
|
||||
if err == nil {
|
||||
u.Name = claims.Username
|
||||
@@ -361,7 +361,7 @@ func (u *User) FromClaim(claims *OIDCClaims) {
|
||||
log.Debug().Caller().Err(err).Msgf("Username %s is not valid", claims.Username)
|
||||
}
|
||||
|
||||
if claims.EmailVerified {
|
||||
if claims.EmailVerified || !FlexibleBoolean(emailVerifiedRequired) {
|
||||
_, err = mail.ParseAddress(claims.Email)
|
||||
if err == nil {
|
||||
u.Email = claims.Email
|
||||
|
||||
@@ -291,12 +291,14 @@ func TestCleanIdentifier(t *testing.T) {
|
||||
|
||||
func TestOIDCClaimsJSONToUser(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
jsonstr string
|
||||
want User
|
||||
name string
|
||||
jsonstr string
|
||||
emailVerifiedRequired bool
|
||||
want User
|
||||
}{
|
||||
{
|
||||
name: "normal-bool",
|
||||
name: "normal-bool",
|
||||
emailVerifiedRequired: true,
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "test",
|
||||
@@ -314,7 +316,8 @@ func TestOIDCClaimsJSONToUser(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string-bool-true",
|
||||
name: "string-bool-true",
|
||||
emailVerifiedRequired: true,
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "test2",
|
||||
@@ -332,7 +335,8 @@ func TestOIDCClaimsJSONToUser(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "string-bool-false",
|
||||
name: "string-bool-false",
|
||||
emailVerifiedRequired: true,
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "test3",
|
||||
@@ -348,9 +352,29 @@ func TestOIDCClaimsJSONToUser(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "allow-unverified-email",
|
||||
emailVerifiedRequired: false,
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "test4",
|
||||
"email": "test4@test.no",
|
||||
"email_verified": "false"
|
||||
}
|
||||
`,
|
||||
want: User{
|
||||
Provider: util.RegisterMethodOIDC,
|
||||
Email: "test4@test.no",
|
||||
ProviderIdentifier: sql.NullString{
|
||||
String: "/test4",
|
||||
Valid: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// From https://github.com/juanfont/headscale/issues/2333
|
||||
name: "okta-oidc-claim-20250121",
|
||||
name: "okta-oidc-claim-20250121",
|
||||
emailVerifiedRequired: true,
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "00u7dr4qp7XXXXXXXXXX",
|
||||
@@ -375,6 +399,7 @@ func TestOIDCClaimsJSONToUser(t *testing.T) {
|
||||
want: User{
|
||||
Provider: util.RegisterMethodOIDC,
|
||||
DisplayName: "Tim Horton",
|
||||
Email: "",
|
||||
Name: "tim.horton@company.com",
|
||||
ProviderIdentifier: sql.NullString{
|
||||
String: "https://sso.company.com/oauth2/default/00u7dr4qp7XXXXXXXXXX",
|
||||
@@ -384,7 +409,8 @@ func TestOIDCClaimsJSONToUser(t *testing.T) {
|
||||
},
|
||||
{
|
||||
// From https://github.com/juanfont/headscale/issues/2333
|
||||
name: "okta-oidc-claim-20250121",
|
||||
name: "okta-oidc-claim-20250121",
|
||||
emailVerifiedRequired: true,
|
||||
jsonstr: `
|
||||
{
|
||||
"aud": "79xxxxxx-xxxx-xxxx-xxxx-892146xxxxxx",
|
||||
@@ -409,6 +435,7 @@ func TestOIDCClaimsJSONToUser(t *testing.T) {
|
||||
Provider: util.RegisterMethodOIDC,
|
||||
DisplayName: "XXXXXX XXXX",
|
||||
Name: "user@domain.com",
|
||||
Email: "",
|
||||
ProviderIdentifier: sql.NullString{
|
||||
String: "https://login.microsoftonline.com/v2.0/I-70OQnj3TogrNSfkZQqB3f7dGwyBWSm1dolHNKrMzQ",
|
||||
Valid: true,
|
||||
@@ -417,7 +444,8 @@ func TestOIDCClaimsJSONToUser(t *testing.T) {
|
||||
},
|
||||
{
|
||||
// From https://github.com/juanfont/headscale/issues/2333
|
||||
name: "casby-oidc-claim-20250513",
|
||||
name: "casby-oidc-claim-20250513",
|
||||
emailVerifiedRequired: true,
|
||||
jsonstr: `
|
||||
{
|
||||
"sub": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
||||
@@ -458,7 +486,7 @@ func TestOIDCClaimsJSONToUser(t *testing.T) {
|
||||
|
||||
var user User
|
||||
|
||||
user.FromClaim(&got)
|
||||
user.FromClaim(&got, tt.emailVerifiedRequired)
|
||||
if diff := cmp.Diff(user, tt.want); diff != "" {
|
||||
t.Errorf("TestOIDCClaimsJSONToUser() mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user