Skip to content

Commit 12ac581

Browse files
kradalbysocoldkiller
authored andcommitted
relax user validation to allow emails, add tests from various oidc providers (juanfont#2364)
* relax user validation to allow emails, add tests from various oidc providers Signed-off-by: Kristoffer Dalby <[email protected]> * changelog Signed-off-by: Kristoffer Dalby <[email protected]> --------- Signed-off-by: Kristoffer Dalby <[email protected]>
1 parent 0d4eb67 commit 12ac581

File tree

3 files changed

+165
-1
lines changed

3 files changed

+165
-1
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
- `oidc.map_legacy_users` is now `false` by default
88
[#2350](https://github.com/juanfont/headscale/pull/2350)
99

10+
## 0.24.1 (2025-01-xx)
11+
12+
### Changes
13+
14+
- Relax username validation to allow emails
15+
[#2364](https://github.com/juanfont/headscale/pull/2364)
16+
1017
## 0.24.0 (2025-01-17)
1118

1219
### Security fix: OIDC changes in Headscale 0.24.0

hscontrol/types/users_test.go

+148
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package types
22

33
import (
4+
"database/sql"
45
"encoding/json"
56
"testing"
67

78
"github.com/google/go-cmp/cmp"
9+
"github.com/juanfont/headscale/hscontrol/util"
810
)
911

1012
func TestUnmarshallOIDCClaims(t *testing.T) {
@@ -73,3 +75,149 @@ func TestUnmarshallOIDCClaims(t *testing.T) {
7375
})
7476
}
7577
}
78+
79+
func TestOIDCClaimsJSONToUser(t *testing.T) {
80+
tests := []struct {
81+
name string
82+
jsonstr string
83+
want User
84+
}{
85+
{
86+
name: "normal-bool",
87+
jsonstr: `
88+
{
89+
"sub": "test",
90+
"email": "[email protected]",
91+
"email_verified": true
92+
}
93+
`,
94+
want: User{
95+
Provider: util.RegisterMethodOIDC,
96+
97+
ProviderIdentifier: sql.NullString{
98+
String: "/test",
99+
Valid: true,
100+
},
101+
},
102+
},
103+
{
104+
name: "string-bool-true",
105+
jsonstr: `
106+
{
107+
"sub": "test2",
108+
"email": "[email protected]",
109+
"email_verified": "true"
110+
}
111+
`,
112+
want: User{
113+
Provider: util.RegisterMethodOIDC,
114+
115+
ProviderIdentifier: sql.NullString{
116+
String: "/test2",
117+
Valid: true,
118+
},
119+
},
120+
},
121+
{
122+
name: "string-bool-false",
123+
jsonstr: `
124+
{
125+
"sub": "test3",
126+
"email": "[email protected]",
127+
"email_verified": "false"
128+
}
129+
`,
130+
want: User{
131+
Provider: util.RegisterMethodOIDC,
132+
ProviderIdentifier: sql.NullString{
133+
String: "/test3",
134+
Valid: true,
135+
},
136+
},
137+
},
138+
{
139+
// From https://github.com/juanfont/headscale/issues/2333
140+
name: "okta-oidc-claim-20250121",
141+
jsonstr: `
142+
{
143+
"sub": "00u7dr4qp7XXXXXXXXXX",
144+
"name": "Tim Horton",
145+
"email": "[email protected]",
146+
"ver": 1,
147+
"iss": "https://sso.company.com/oauth2/default",
148+
"aud": "0oa8neto4tXXXXXXXXXX",
149+
"iat": 1737455152,
150+
"exp": 1737458752,
151+
"jti": "ID.zzJz93koTunMKv5Bq-XXXXXXXXXXXXXXXXXXXXXXXXX",
152+
"amr": [
153+
"pwd"
154+
],
155+
"idp": "00o42r3s2cXXXXXXXX",
156+
"nonce": "nonce",
157+
"preferred_username": "[email protected]",
158+
"auth_time": 1000,
159+
"at_hash": "preview_at_hash"
160+
}
161+
`,
162+
want: User{
163+
Provider: util.RegisterMethodOIDC,
164+
DisplayName: "Tim Horton",
165+
166+
ProviderIdentifier: sql.NullString{
167+
String: "https://sso.company.com/oauth2/default/00u7dr4qp7XXXXXXXXXX",
168+
Valid: true,
169+
},
170+
},
171+
},
172+
{
173+
// From https://github.com/juanfont/headscale/issues/2333
174+
name: "okta-oidc-claim-20250121",
175+
jsonstr: `
176+
{
177+
"aud": "79xxxxxx-xxxx-xxxx-xxxx-892146xxxxxx",
178+
"iss": "https://login.microsoftonline.com//v2.0",
179+
"iat": 1737346441,
180+
"nbf": 1737346441,
181+
"exp": 1737350341,
182+
"aio": "AWQAm/8ZAAAABKne9EWr6ygVO2DbcRmoPIpRM819qqlP/mmK41AAWv/C2tVkld4+znbG8DaXFdLQa9jRUzokvsT7rt9nAT6Fg7QC+/ecDWsF5U+QX11f9Ox7ZkK4UAIWFcIXpuZZvRS7",
183+
"email": "[email protected]",
184+
"name": "XXXXXX XXXX",
185+
"oid": "54c2323d-5052-4130-9588-ad751909003f",
186+
"preferred_username": "[email protected]",
187+
"rh": "1.AXUAXdg0Rfc11UifLDJv67ChfSluoXmD9z1EmK-JIUYuSK9cAQl1AA.",
188+
"sid": "5250a0a2-0b4e-4e68-8652-b4e97866411d",
189+
"sub": "I-70OQnj3TogrNSfkZQqB3f7dGwyBWSm1dolHNKrMzQ",
190+
"tid": "<redacted>",
191+
"uti": "zAuXeEtMM0GwcTAcOsBZAA",
192+
"ver": "2.0"
193+
}
194+
`,
195+
want: User{
196+
Provider: util.RegisterMethodOIDC,
197+
DisplayName: "XXXXXX XXXX",
198+
199+
ProviderIdentifier: sql.NullString{
200+
String: "https://login.microsoftonline.com//v2.0/I-70OQnj3TogrNSfkZQqB3f7dGwyBWSm1dolHNKrMzQ",
201+
Valid: true,
202+
},
203+
},
204+
},
205+
}
206+
207+
for _, tt := range tests {
208+
t.Run(tt.name, func(t *testing.T) {
209+
var got OIDCClaims
210+
if err := json.Unmarshal([]byte(tt.jsonstr), &got); err != nil {
211+
t.Errorf("TestOIDCClaimsJSONToUser() error = %v", err)
212+
return
213+
}
214+
215+
var user User
216+
217+
user.FromClaim(&got)
218+
if diff := cmp.Diff(user, tt.want); diff != "" {
219+
t.Errorf("TestOIDCClaimsJSONToUser() mismatch (-want +got):\n%s", diff)
220+
}
221+
})
222+
}
223+
}

hscontrol/util/dns.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ var invalidCharsInUserRegex = regexp.MustCompile("[^a-z0-9-.]+")
2626

2727
var ErrInvalidUserName = errors.New("invalid user name")
2828

29+
// ValidateUsername checks if a username is valid.
30+
// It must be at least 2 characters long, start with a letter, and contain
31+
// only letters, numbers, hyphens, dots, and underscores.
32+
// It cannot contain more than one '@'.
33+
// It cannot contain invalid characters.
2934
func ValidateUsername(username string) error {
3035
// Ensure the username meets the minimum length requirement
3136
if len(username) < 2 {
@@ -40,7 +45,11 @@ func ValidateUsername(username string) error {
4045
atCount := 0
4146
for _, char := range username {
4247
switch {
43-
case unicode.IsLetter(char), unicode.IsDigit(char), char == '-':
48+
case unicode.IsLetter(char),
49+
unicode.IsDigit(char),
50+
char == '-',
51+
char == '.',
52+
char == '_':
4453
// Valid characters
4554
case char == '@':
4655
atCount++

0 commit comments

Comments
 (0)