chore: prevent authentication of non-unique oidc subjects (#16498)

Any IdP returning an empty field here breaks the assumption of a
unique subject id. This is defined in the OIDC spec.
This commit is contained in:
Steven Masley
2025-02-10 09:31:08 -06:00
committed by GitHub
parent 695d552cd0
commit d0a534e30d
6 changed files with 92 additions and 1 deletions
+41
View File
@@ -72,6 +72,7 @@ func TestOIDCOauthLoginWithExisting(t *testing.T) {
"email": "alice@coder.com",
"email_verified": true,
"preferred_username": username,
"sub": uuid.NewString(),
}
helper := oidctest.NewLoginHelper(client, fake)
@@ -899,10 +900,19 @@ func TestUserOIDC(t *testing.T) {
IgnoreEmailVerified bool
IgnoreUserInfo bool
}{
{
Name: "NoSub",
IDTokenClaims: jwt.MapClaims{
"email": "kyle@kwc.io",
},
AllowSignups: true,
StatusCode: http.StatusBadRequest,
},
{
Name: "EmailOnly",
IDTokenClaims: jwt.MapClaims{
"email": "kyle@kwc.io",
"sub": uuid.NewString(),
},
AllowSignups: true,
StatusCode: http.StatusOK,
@@ -915,6 +925,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "kyle@kwc.io",
"email_verified": false,
"sub": uuid.NewString(),
},
AllowSignups: true,
StatusCode: http.StatusForbidden,
@@ -924,6 +935,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": 3.14159,
"email_verified": false,
"sub": uuid.NewString(),
},
AllowSignups: true,
StatusCode: http.StatusBadRequest,
@@ -933,6 +945,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "kyle@kwc.io",
"email_verified": false,
"sub": uuid.NewString(),
},
AllowSignups: true,
StatusCode: http.StatusOK,
@@ -946,6 +959,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "kyle@kwc.io",
"email_verified": true,
"sub": uuid.NewString(),
},
AllowSignups: true,
EmailDomain: []string{
@@ -958,6 +972,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "cian@coder.com",
"email_verified": true,
"sub": uuid.NewString(),
},
AllowSignups: true,
EmailDomain: []string{
@@ -970,6 +985,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "kyle@kwc.io",
"email_verified": true,
"sub": uuid.NewString(),
},
AllowSignups: true,
EmailDomain: []string{
@@ -982,6 +998,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "kyle@KWC.io",
"email_verified": true,
"sub": uuid.NewString(),
},
AllowSignups: true,
AssertUser: func(t testing.TB, u codersdk.User) {
@@ -997,6 +1014,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "colin@gmail.com",
"email_verified": true,
"sub": uuid.NewString(),
},
AllowSignups: true,
EmailDomain: []string{
@@ -1015,6 +1033,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "kyle@kwc.io",
"email_verified": true,
"sub": uuid.NewString(),
},
StatusCode: http.StatusForbidden,
},
@@ -1023,6 +1042,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "kyle@kwc.io",
"email_verified": true,
"sub": uuid.NewString(),
},
AssertUser: func(t testing.TB, u codersdk.User) {
assert.Equal(t, "kyle", u.Username)
@@ -1036,6 +1056,7 @@ func TestUserOIDC(t *testing.T) {
"email": "kyle@kwc.io",
"email_verified": true,
"preferred_username": "hotdog",
"sub": uuid.NewString(),
},
AssertUser: func(t testing.TB, u codersdk.User) {
assert.Equal(t, "hotdog", u.Username)
@@ -1049,6 +1070,7 @@ func TestUserOIDC(t *testing.T) {
"email": "kyle@kwc.io",
"email_verified": true,
"name": "Hot Dog",
"sub": uuid.NewString(),
},
AssertUser: func(t testing.TB, u codersdk.User) {
assert.Equal(t, "Hot Dog", u.Name)
@@ -1065,6 +1087,7 @@ func TestUserOIDC(t *testing.T) {
// However, we should not fail to log someone in if their name is too long.
// Just truncate it.
"name": strings.Repeat("a", 129),
"sub": uuid.NewString(),
},
AllowSignups: true,
StatusCode: http.StatusOK,
@@ -1080,6 +1103,7 @@ func TestUserOIDC(t *testing.T) {
// Full names must not have leading or trailing whitespace, but this is a
// daft reason to fail a login.
"name": " Bobby Whitespace ",
"sub": uuid.NewString(),
},
AllowSignups: true,
StatusCode: http.StatusOK,
@@ -1096,6 +1120,7 @@ func TestUserOIDC(t *testing.T) {
"email_verified": true,
"name": "Kylium Carbonate",
"preferred_username": "kyle@kwc.io",
"sub": uuid.NewString(),
},
AssertUser: func(t testing.TB, u codersdk.User) {
assert.Equal(t, "kyle", u.Username)
@@ -1108,6 +1133,7 @@ func TestUserOIDC(t *testing.T) {
Name: "UsernameIsEmail",
IDTokenClaims: jwt.MapClaims{
"preferred_username": "kyle@kwc.io",
"sub": uuid.NewString(),
},
AssertUser: func(t testing.TB, u codersdk.User) {
assert.Equal(t, "kyle", u.Username)
@@ -1123,6 +1149,7 @@ func TestUserOIDC(t *testing.T) {
"email_verified": true,
"preferred_username": "kyle",
"picture": "/example.png",
"sub": uuid.NewString(),
},
AssertUser: func(t testing.TB, u codersdk.User) {
assert.Equal(t, "/example.png", u.AvatarURL)
@@ -1136,6 +1163,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "kyle@kwc.io",
"email_verified": true,
"sub": uuid.NewString(),
},
UserInfoClaims: jwt.MapClaims{
"preferred_username": "potato",
@@ -1155,6 +1183,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "coolin@coder.com",
"groups": []string{"pingpong"},
"sub": uuid.NewString(),
},
AllowSignups: true,
StatusCode: http.StatusOK,
@@ -1164,6 +1193,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "internaluser@internal.domain",
"email_verified": false,
"sub": uuid.NewString(),
},
UserInfoClaims: jwt.MapClaims{
"email": "externaluser@external.domain",
@@ -1182,6 +1212,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "internaluser@internal.domain",
"email_verified": false,
"sub": uuid.NewString(),
},
UserInfoClaims: jwt.MapClaims{
"email": 1,
@@ -1197,6 +1228,7 @@ func TestUserOIDC(t *testing.T) {
"email_verified": true,
"name": "User McName",
"preferred_username": "user",
"sub": uuid.NewString(),
},
UserInfoClaims: jwt.MapClaims{
"email": "user.mcname@external.domain",
@@ -1216,6 +1248,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: inflateClaims(t, jwt.MapClaims{
"email": "user@domain.tld",
"email_verified": true,
"sub": uuid.NewString(),
}, 65536),
AssertUser: func(t testing.TB, u codersdk.User) {
assert.Equal(t, "user", u.Username)
@@ -1228,6 +1261,7 @@ func TestUserOIDC(t *testing.T) {
IDTokenClaims: jwt.MapClaims{
"email": "user@domain.tld",
"email_verified": true,
"sub": uuid.NewString(),
},
UserInfoClaims: inflateClaims(t, jwt.MapClaims{}, 65536),
AssertUser: func(t testing.TB, u codersdk.User) {
@@ -1242,6 +1276,7 @@ func TestUserOIDC(t *testing.T) {
"iss": "https://mismatch.com",
"email": "user@domain.tld",
"email_verified": true,
"sub": uuid.NewString(),
},
AllowSignups: true,
StatusCode: http.StatusBadRequest,
@@ -1331,6 +1366,7 @@ func TestUserOIDC(t *testing.T) {
client, resp := fake.AttemptLogin(t, owner, jwt.MapClaims{
"email": user.Email,
"sub": uuid.NewString(),
})
require.Equal(t, http.StatusOK, resp.StatusCode)
@@ -1369,6 +1405,7 @@ func TestUserOIDC(t *testing.T) {
claims := jwt.MapClaims{
"email": userData.Email,
"sub": uuid.NewString(),
}
var err error
user.HTTPClient.Jar, err = cookiejar.New(nil)
@@ -1439,6 +1476,7 @@ func TestUserOIDC(t *testing.T) {
claims := jwt.MapClaims{
"email": userData.Email,
"sub": uuid.NewString(),
}
user.HTTPClient.Jar, err = cookiejar.New(nil)
require.NoError(t, err)
@@ -1509,6 +1547,7 @@ func TestUserOIDC(t *testing.T) {
numLogs := len(auditor.AuditLogs())
claims := jwt.MapClaims{
"email": "jon@coder.com",
"sub": uuid.NewString(),
}
userClient, _ := fake.Login(t, client, claims)
@@ -1629,6 +1668,7 @@ func TestUserOIDC(t *testing.T) {
claims := jwt.MapClaims{
"email": "user@example.com",
"email_verified": true,
"sub": uuid.NewString(),
}
// Perform the login
@@ -1794,6 +1834,7 @@ func TestOIDCSkipIssuer(t *testing.T) {
userClient, _ := fake.Login(t, owner, jwt.MapClaims{
"iss": secondaryURLString,
"email": "alice@coder.com",
"sub": uuid.NewString(),
})
found, err := userClient.User(ctx, "me")
require.NoError(t, err)