@@ -2,13 +2,17 @@ package providers
22
33import (
44 "context"
5+ "crypto/rand"
6+ "crypto/rsa"
57 "encoding/base64"
8+ "errors"
69 "net/http"
710 "net/http/httptest"
811 "net/url"
912 "strings"
1013
1114 "github.com/coreos/go-oidc/v3/oidc"
15+ "github.com/golang-jwt/jwt"
1216 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
1317 . "github.com/onsi/ginkgo"
1418 . "github.com/onsi/ginkgo/extensions/table"
@@ -25,8 +29,18 @@ func (fakeADFSJwks) VerifySignature(_ context.Context, jwt string) (payload []by
2529 return decodeString, nil
2630}
2731
28−func testADFSProvider(hostname string) *ADFSProvider {
32+type adfsClaims struct {
33+ UPN string `json:"upn,omitempty"`
34+ idTokenClaims
35+}
2936
37+func newSignedTestADFSToken(tokenClaims adfsClaims) (string, error) {
38+ key, _ := rsa.GenerateKey(rand.Reader, 2048)
39+ standardClaims := jwt.NewWithClaims(jwt.SigningMethodRS256, tokenClaims)
40+ return standardClaims.SignedString(key)
41+}
42+
43+func testADFSProvider(hostname string) *ADFSProvider {
3044 o := oidc.NewVerifier(
3145 "https://issuer.example.com",
3246 fakeADFSJwks{},
@@ -41,6 +55,7 @@ func testADFSProvider(hostname string) *ADFSProvider {
4155 ValidateURL: &url.URL{},
4256 Scope: "",
4357 Verifier: o,
58+ EmailClaim: OIDCEmailClaim,
4459 })
4560
4661 if hostname != "" {
@@ -54,7 +69,6 @@ func testADFSProvider(hostname string) *ADFSProvider {
5469}
5570
5671func testADFSBackend() *httptest.Server {
57−
5872 authResponse := `
5973 {
6074 "access_token": "my_access_token",
@@ -129,13 +143,12 @@ var _ = Describe("ADFS Provider Tests", func() {
129143
130144 Context("with valid token", func() {
131145 It("should not throw an error", func() {
132− p.EmailClaim = "email"
133146 rawIDToken, _ := newSignedTestIDToken(defaultIDToken)
134147 idToken, err := p.Verifier.Verify(context.Background(), rawIDToken)
135148 Expect(err).To(BeNil())
136149 session, err := p.buildSessionFromClaims(idToken)
137− session.IDToken = rawIDToken
138150 Expect(err).To(BeNil())
151+ session.IDToken = rawIDToken
139152 err = p.EnrichSession(context.Background(), session)
140153 Expect(session.Email).To(Equal("janed@me.com"))
141154 Expect(err).To(BeNil())
@@ -149,7 +162,7 @@ var _ = Describe("ADFS Provider Tests", func() {
149162 ProtectedResource: resource,
150163 Scope: "",
151164 })
152− p.SkipScope = true
165+ p.skipScope = true
153166
154167 result := p.GetLoginURL("https://example.com/adfs/oauth2/", "", "")
155168 Expect(result).NotTo(ContainSubstring("scope="))
@@ -202,4 +215,78 @@ var _ = Describe("ADFS Provider Tests", func() {
202215 }),
203216 )
204217 })
218+
219+ Context("UPN Fallback", func() {
220+ var idToken string
221+ var session *sessions.SessionState
222+
223+ BeforeEach(func() {
224+ var err error
225+ idToken, err = newSignedTestADFSToken(adfsClaims{
226+ UPN: "upn@company.com",
227+ idTokenClaims: minimalIDToken,
228+ })
229+ Expect(err).ToNot(HaveOccurred())
230+
231+ session = &sessions.SessionState{
232+ IDToken: idToken,
233+ }
234+ })
235+
236+ Describe("EnrichSession", func() {
237+ It("uses email claim if present", func() {
238+ p.oidcEnrichFunc = func(_ context.Context, s *sessions.SessionState) error {
239+ s.Email = "person@company.com"
240+ return nil
241+ }
242+
243+ err := p.EnrichSession(context.Background(), session)
244+ Expect(err).ToNot(HaveOccurred())
245+ Expect(session.Email).To(Equal("person@company.com"))
246+ })
247+
248+ It("falls back to UPN claim if Email is missing", func() {
249+ p.oidcEnrichFunc = func(_ context.Context, s *sessions.SessionState) error {
250+ return nil
251+ }
252+
253+ err := p.EnrichSession(context.Background(), session)
254+ Expect(err).ToNot(HaveOccurred())
255+ Expect(session.Email).To(Equal("upn@company.com"))
256+ })
257+
258+ It("falls back to UPN claim on errors", func() {
259+ p.oidcEnrichFunc = func(_ context.Context, s *sessions.SessionState) error {
260+ return errors.New("neither the id_token nor the profileURL set an email")
261+ }
262+
263+ err := p.EnrichSession(context.Background(), session)
264+ Expect(err).ToNot(HaveOccurred())
265+ Expect(session.Email).To(Equal("upn@company.com"))
266+ })
267+ })
268+
269+ Describe("RefreshSession", func() {
270+ It("uses email claim if present", func() {
271+ p.oidcRefreshFunc = func(_ context.Context, s *sessions.SessionState) (bool, error) {
272+ s.Email = "person@company.com"
273+ return true, nil
274+ }
275+
276+ _, err := p.RefreshSession(context.Background(), session)
277+ Expect(err).ToNot(HaveOccurred())
278+ Expect(session.Email).To(Equal("person@company.com"))
279+ })
280+
281+ It("falls back to UPN claim if Email is missing", func() {
282+ p.oidcRefreshFunc = func(_ context.Context, s *sessions.SessionState) (bool, error) {
283+ return true, nil
284+ }
285+
286+ _, err := p.RefreshSession(context.Background(), session)
287+ Expect(err).ToNot(HaveOccurred())
288+ Expect(session.Email).To(Equal("upn@company.com"))
289+ })
290+ })
291+ })
205292})