Skip to content

Commit e77c2f6

Browse files
authored
feat(oidc): take preferred_username if present (#1430)
Signed-off-by: Miguel Martinez <[email protected]>
1 parent 8b55cb9 commit e77c2f6

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

app/controlplane/internal/service/auth.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"errors"
2323
"fmt"
2424
"net/http"
25+
"net/mail"
2526
"net/url"
2627
"time"
2728

@@ -208,6 +209,24 @@ func loginHandler(svc *AuthService, w http.ResponseWriter, r *http.Request) (int
208209
// Extract custom claims
209210
type upstreamOIDCclaims struct {
210211
Email string `json:"email"`
212+
// This value is present in the case of Microsoft Entra IDp
213+
// It might show the user's proxy email used during login
214+
// https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-use-email-signin
215+
// https://learn.microsoft.com/en-us/entra/identity-platform/id-token-claims-reference
216+
PreferredUsername string `json:"preferred_username"`
217+
}
218+
219+
// Will retrieve the email from the preferred username if it's a valid email
220+
// or fallback to the email field
221+
func (c *upstreamOIDCclaims) preferredEmail() string {
222+
if c.PreferredUsername != "" {
223+
// validate that this is an email since according to the Entra spec this might be a phone or username
224+
if _, err := mail.ParseAddress(c.PreferredUsername); err == nil {
225+
return c.PreferredUsername
226+
}
227+
}
228+
229+
return c.Email
211230
}
212231

213232
type errorWithCode struct {
@@ -224,7 +243,7 @@ func callbackHandler(svc *AuthService, w http.ResponseWriter, r *http.Request) (
224243
}
225244

226245
// Create user if needed
227-
u, err := svc.userUseCase.FindOrCreateByEmail(ctx, claims.Email)
246+
u, err := svc.userUseCase.FindOrCreateByEmail(ctx, claims.preferredEmail())
228247
if err != nil {
229248
return http.StatusInternalServerError, sl.LogAndMaskErr(err, svc.log)
230249
}

app/controlplane/internal/service/auth_test.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// Copyright 2023 The Chainloop Authors.
2+
// Copyright 2024 The Chainloop Authors.
33
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
@@ -87,3 +87,40 @@ func TestGetAuthURLs(t *testing.T) {
8787
})
8888
}
8989
}
90+
91+
func TestGetPreferredEmail(t *testing.T) {
92+
testCases := []struct {
93+
claims *upstreamOIDCclaims
94+
want string
95+
}{
96+
{
97+
claims: &upstreamOIDCclaims{Email: ""},
98+
want: "",
99+
},
100+
{
101+
claims: &upstreamOIDCclaims{Email: "[email protected]"},
102+
103+
},
104+
{
105+
claims: &upstreamOIDCclaims{Email: "[email protected]", PreferredUsername: "overridden"},
106+
107+
},
108+
{
109+
claims: &upstreamOIDCclaims{Email: "[email protected]", PreferredUsername: "[email protected]"},
110+
111+
},
112+
{
113+
claims: &upstreamOIDCclaims{Email: "[email protected]", PreferredUsername: "[email protected]"},
114+
115+
},
116+
{
117+
claims: &upstreamOIDCclaims{Email: "[email protected]", PreferredUsername: "[email protected]"},
118+
119+
},
120+
}
121+
122+
for _, tc := range testCases {
123+
got := tc.claims.preferredEmail()
124+
assert.Equal(t, tc.want, got)
125+
}
126+
}

0 commit comments

Comments
 (0)