git.iko.soy/typeable/oauth2-proxy
on master help sign in
extract email from id_token for azure provider (#914)

* extract email from id_token for azure provider this change fixes a bug when --resource is specified with non-Graph api and the access token destined to --resource is used to call Graph api * fixed typo * refactor GetEmailAddress to EnrichSessionState * make getting email from idtoken best effort and fall back to previous behavior when it's absent * refactor to use jwt package to extract claims * fix lint * refactor unit tests to use test table refactor the get email logic from profile api * addressing feedback * added oidc verifier to azure provider and extract email from id_token if present * fix lint and codeclimate * refactor to use oidc verifier to verify id_token if oidc is configured * fixed UT * addressed comments * minor refactor * addressed feedback * extract email from id_token first and fallback to access token * fallback to access token as well when id_token doesn't have email claim * address feedbacks * updated change log!

commitf3209a40e16defdab442ef87765e837d90ec4f9a authorWeinong Wang <weinong@outlook.com> dateTue 9 Mar 2021, 20:53 (6 years ago) parent6894738
3 files changed+356−156
CHANGELOG.md+1 providers/azure.go+131−49 providers/azure_test.go+224−107
CHANGELOG.md+1
@@ -8,6 +8,7 @@
88
99## Changes since v7.0.1
1010
11+- [#914](https://github.com/oauth2-proxy/oauth2-proxy/pull/914) Extract email from id_token for azure provider when oidc is configured
1112- [#1047](https://github.com/oauth2-proxy/oauth2-proxy/pull/1047) Refactor HTTP Server and add ServerGroup to handle graceful shutdown of multiple servers (@JoelSpeed)
1213- [#1070](https://github.com/oauth2-proxy/oauth2-proxy/pull/1070) Refactor logging middleware to middleware package (@NickMeves)
1314- [#1064](https://github.com/oauth2-proxy/oauth2-proxy/pull/1064) Add support for setting groups on session when using basic auth (@stefansedich)
providers/azure.go+131−49
@@ -107,26 +107,22 @@ func overrideTenantURL(current, defaultURL *url.URL, tenant, path string) {
107107 }
108108}
109109
110+func (p *AzureProvider) GetLoginURL(redirectURI, state string) string {
111+ extraParams := url.Values{}
112+ if p.ProtectedResource != nil && p.ProtectedResource.String() != "" {
113+ extraParams.Add("resource", p.ProtectedResource.String())
114+ }
115+ a := makeLoginURL(p.ProviderData, redirectURI, state, extraParams)
116+ return a.String()
117+}
118+
110119// Redeem exchanges the OAuth2 authentication token for an ID token
111120func (p *AzureProvider) Redeem(ctx context.Context, redirectURL, code string) (*sessions.SessionState, error) {
112− if code == "" {
113− return nil, ErrMissingCode
114− }
115− clientSecret, err := p.GetClientSecret()
121+ params, err := p.prepareRedeem(redirectURL, code)
116122 if err != nil {
117123 return nil, err
118124 }
119125
120− params := url.Values{}
121− params.Add("redirect_uri", redirectURL)
122− params.Add("client_id", p.ClientID)
123− params.Add("client_secret", clientSecret)
124− params.Add("code", code)
125− params.Add("grant_type", "authorization_code")
126− if p.ProtectedResource != nil && p.ProtectedResource.String() != "" {
127− params.Add("resource", p.ProtectedResource.String())
128− }
129−
130126 // blindly try json and x-www-form-urlencoded
131127 var jsonResponse struct {
132128 AccessToken string `json:"access_token"`
@@ -149,13 +145,98 @@ func (p *AzureProvider) Redeem(ctx context.Context, redirectURL, code string) (*
149145 created := time.Now()
150146 expires := time.Unix(jsonResponse.ExpiresOn, 0)
151147
152− return &sessions.SessionState{
148+ session := &sessions.SessionState{
153149 AccessToken: jsonResponse.AccessToken,
154150 IDToken: jsonResponse.IDToken,
155151 CreatedAt: &created,
156152 ExpiresOn: &expires,
157153 RefreshToken: jsonResponse.RefreshToken,
158− }, nil
154+ }
155+
156+ email, err := p.verifyTokenAndExtractEmail(ctx, session.IDToken)
157+
158+ // https://github.com/oauth2-proxy/oauth2-proxy/pull/914#issuecomment-782285814
159+ // https://github.com/AzureAD/azure-activedirectory-library-for-java/issues/117
160+ // due to above issues, id_token may not be signed by AAD
161+ // in that case, we will fallback to access token
162+ if err == nil && email != "" {
163+ session.Email = email
164+ } else {
165+ logger.Printf("unable to get email claim from id_token: %v", err)
166+ }
167+
168+ if session.Email == "" {
169+ email, err = p.verifyTokenAndExtractEmail(ctx, session.AccessToken)
170+ if err == nil && email != "" {
171+ session.Email = email
172+ } else {
173+ logger.Printf("unable to get email claim from access token: %v", err)
174+ }
175+ }
176+
177+ return session, nil
178+}
179+
180+// EnrichSession finds the email to enrich the session state
181+func (p *AzureProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
182+ if s.Email != "" {
183+ return nil
184+ }
185+
186+ email, err := p.getEmailFromProfileAPI(ctx, s.AccessToken)
187+ if err != nil {
188+ return fmt.Errorf("unable to get email address: %v", err)
189+ }
190+ if email == "" {
191+ return errors.New("unable to get email address")
192+ }
193+ s.Email = email
194+
195+ return nil
196+}
197+
198+func (p *AzureProvider) prepareRedeem(redirectURL, code string) (url.Values, error) {
199+ params := url.Values{}
200+ if code == "" {
201+ return params, ErrMissingCode
202+ }
203+ clientSecret, err := p.GetClientSecret()
204+ if err != nil {
205+ return params, err
206+ }
207+
208+ params.Add("redirect_uri", redirectURL)
209+ params.Add("client_id", p.ClientID)
210+ params.Add("client_secret", clientSecret)
211+ params.Add("code", code)
212+ params.Add("grant_type", "authorization_code")
213+ if p.ProtectedResource != nil && p.ProtectedResource.String() != "" {
214+ params.Add("resource", p.ProtectedResource.String())
215+ }
216+ return params, nil
217+}
218+
219+// verifyTokenAndExtractEmail tries to extract email claim from either id_token or access token
220+// when oidc verifier is configured
221+func (p *AzureProvider) verifyTokenAndExtractEmail(ctx context.Context, token string) (string, error) {
222+ email := ""
223+
224+ if token != "" && p.Verifier != nil {
225+ token, err := p.Verifier.Verify(ctx, token)
226+ // due to issues mentioned above, id_token may not be signed by AAD
227+ if err == nil {
228+ claims, err := p.getClaims(token)
229+ if err == nil {
230+ email = claims.Email
231+ } else {
232+ logger.Printf("unable to get claims from token: %v", err)
233+ }
234+ } else {
235+ logger.Printf("unable to verify token: %v", err)
236+ }
237+ }
238+
239+ return email, nil
159240}
160241
161242// RefreshSessionIfNeeded checks if the session has expired and uses the
@@ -209,6 +290,28 @@ func (p *AzureProvider) redeemRefreshToken(ctx context.Context, s *sessions.Sess
209290 s.RefreshToken = jsonResponse.RefreshToken
210291 s.CreatedAt = &now
211292 s.ExpiresOn = &expires
293+
294+ email, err := p.verifyTokenAndExtractEmail(ctx, s.IDToken)
295+
296+ // https://github.com/oauth2-proxy/oauth2-proxy/pull/914#issuecomment-782285814
297+ // https://github.com/AzureAD/azure-activedirectory-library-for-java/issues/117
298+ // due to above issues, id_token may not be signed by AAD
299+ // in that case, we will fallback to access token
300+ if err == nil && email != "" {
301+ s.Email = email
302+ } else {
303+ logger.Printf("unable to get email claim from id_token: %v", err)
304+ }
305+
306+ if s.Email == "" {
307+ email, err = p.verifyTokenAndExtractEmail(ctx, s.AccessToken)
308+ if err == nil && email != "" {
309+ s.Email = email
310+ } else {
311+ logger.Printf("unable to get email claim from access token: %v", err)
312+ }
313+ }
314+
212315 return
213316}
214317
@@ -230,53 +333,32 @@ func getEmailFromJSON(json *simplejson.Json) (string, error) {
230333 err = otherMailsErr
231334 }
232335
336+ if err != nil || email == "" {
337+ email, err = json.Get("userPrincipalName").String()
338+ if err != nil {
339+ logger.Errorf("unable to find userPrincipalName: %s", err)
340+ return "", err
341+ }
342+ }
343+
233344 return email, err
234345}
235346
236−// GetEmailAddress returns the Account email address
237−func (p *AzureProvider) GetEmailAddress(ctx context.Context, s *sessions.SessionState) (string, error) {
238− var email string
239− var err error
240−
241− if s.AccessToken == "" {
347+func (p *AzureProvider) getEmailFromProfileAPI(ctx context.Context, accessToken string) (string, error) {
348+ if accessToken == "" {
242349 return "", errors.New("missing access token")
243350 }
244351
245352 json, err := requests.New(p.ProfileURL.String()).
246353 WithContext(ctx).
247− WithHeaders(makeAzureHeader(s.AccessToken)).
354+ WithHeaders(makeAzureHeader(accessToken)).
248355 Do().
249356 UnmarshalJSON()
250357 if err != nil {
251358 return "", err
252359 }
253360
254− email, err = getEmailFromJSON(json)
255− if err == nil && email != "" {
256− return email, err
257− }
258−
259− email, err = json.Get("userPrincipalName").String()
260− if err != nil {
261− logger.Errorf("failed making request %s", err)
262− return "", err
263− }
264−
265− if email == "" {
266− logger.Errorf("failed to get email address")
267− return "", err
268− }
269−
270− return email, err
271−}
272−
273−func (p *AzureProvider) GetLoginURL(redirectURI, state string) string {
274− extraParams := url.Values{}
275− if p.ProtectedResource != nil && p.ProtectedResource.String() != "" {
276− extraParams.Add("resource", p.ProtectedResource.String())
277− }
278− a := makeLoginURL(p.ProviderData, redirectURI, state, extraParams)
279− return a.String()
361+ return getEmailFromJSON(json)
280362}
281363
282364// ValidateSession validates the AccessToken
providers/azure_test.go+224−107
@@ -2,18 +2,41 @@ package providers
22
33import (
44 "context"
5+ "encoding/base64"
6+ "encoding/json"
7+ "errors"
8+ "fmt"
59 "net/http"
610 "net/http/httptest"
711 "net/url"
12+ "strings"
813 "testing"
914 "time"
1015
16+ oidc "github.com/coreos/go-oidc"
1117 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
1218
1319 . "github.com/onsi/gomega"
1420 "github.com/stretchr/testify/assert"
1521)
1622
23+type fakeAzureKeySetStub struct{}
24+
25+func (fakeAzureKeySetStub) VerifySignature(_ context.Context, jwt string) (payload []byte, err error) {
26+ decodeString, err := base64.RawURLEncoding.DecodeString(strings.Split(jwt, ".")[1])
27+ if err != nil {
28+ return nil, err
29+ }
30+ return decodeString, nil
31+}
32+
33+type azureOAuthPayload struct {
34+ AccessToken string `json:"access_token,omitempty"`
35+ RefreshToken string `json:"refresh_token,omitempty"`
36+ ExpiresOn int64 `json:"expires_on,omitempty,string"`
37+ IDToken string `json:"id_token,omitempty"`
38+}
39+
1740func testAzureProvider(hostname string) *AzureProvider {
1841 p := NewAzureProvider(
1942 &ProviderData{
@@ -23,7 +46,19 @@ func testAzureProvider(hostname string) *AzureProvider {
2346 ProfileURL: &url.URL{},
2447 ValidateURL: &url.URL{},
2548 ProtectedResource: &url.URL{},
26− Scope: ""})
49+ Scope: "",
50+ EmailClaim: "email",
51+ Verifier: oidc.NewVerifier(
52+ "https://issuer.example.com",
53+ fakeAzureKeySetStub{},
54+ &oidc.Config{
55+ ClientID: "cd6d4fae-f6a6-4a34-8454-2c6b598e9532",
56+ SkipClientIDCheck: true,
57+ SkipIssuerCheck: true,
58+ SkipExpiryCheck: true,
59+ },
60+ ),
61+ })
2762
2863 if hostname != "" {
2964 updateURL(p.Data().LoginURL, hostname)
@@ -104,6 +139,10 @@ func TestAzureSetTenant(t *testing.T) {
104139}
105140
106141func testAzureBackend(payload string) *httptest.Server {
142+ return testAzureBackendWithError(payload, false)
143+}
144+
145+func testAzureBackendWithError(payload string, injectError bool) *httptest.Server {
107146 path := "/v1.0/me"
108147
109148 return httptest.NewServer(http.HandlerFunc(
@@ -111,7 +150,11 @@ func testAzureBackend(payload string) *httptest.Server {
111150 if (r.URL.Path != path) && r.Method != http.MethodPost {
112151 w.WriteHeader(404)
113152 } else if r.Method == http.MethodPost && r.Body != nil {
114− w.WriteHeader(200)
153+ if injectError {
154+ w.WriteHeader(400)
155+ } else {
156+ w.WriteHeader(200)
157+ }
115158 w.Write([]byte(payload))
116159 } else if !IsAuthorizedInHeader(r.Header) {
117160 w.WriteHeader(403)
@@ -122,98 +165,172 @@ func testAzureBackend(payload string) *httptest.Server {
122165 }))
123166}
124167
125−func TestAzureProviderGetEmailAddress(t *testing.T) {
126− b := testAzureBackend(`{ "mail": "user@windows.net" }`)
127− defer b.Close()
128−
129− bURL, _ := url.Parse(b.URL)
130− p := testAzureProvider(bURL.Host)
131−
132− session := CreateAuthorizedSession()
133− email, err := p.GetEmailAddress(context.Background(), session)
134− assert.Equal(t, nil, err)
135− assert.Equal(t, "user@windows.net", email)
136−}
137−
138−func TestAzureProviderGetEmailAddressMailNull(t *testing.T) {
139− b := testAzureBackend(`{ "mail": null, "otherMails": ["user@windows.net", "altuser@windows.net"] }`)
140− defer b.Close()
141−
142− bURL, _ := url.Parse(b.URL)
143− p := testAzureProvider(bURL.Host)
144−
145− session := CreateAuthorizedSession()
146− email, err := p.GetEmailAddress(context.Background(), session)
147− assert.Equal(t, nil, err)
148− assert.Equal(t, "user@windows.net", email)
149−}
150−
151−func TestAzureProviderGetEmailAddressGetUserPrincipalName(t *testing.T) {
152− b := testAzureBackend(`{ "mail": null, "otherMails": [], "userPrincipalName": "user@windows.net" }`)
153− defer b.Close()
154−
155− bURL, _ := url.Parse(b.URL)
156− p := testAzureProvider(bURL.Host)
157−
158− session := CreateAuthorizedSession()
159− email, err := p.GetEmailAddress(context.Background(), session)
160− assert.Equal(t, nil, err)
161− assert.Equal(t, "user@windows.net", email)
162−}
163−
164−func TestAzureProviderGetEmailAddressFailToGetEmailAddress(t *testing.T) {
165− b := testAzureBackend(`{ "mail": null, "otherMails": [], "userPrincipalName": null }`)
166− defer b.Close()
167−
168− bURL, _ := url.Parse(b.URL)
169− p := testAzureProvider(bURL.Host)
170−
171− session := CreateAuthorizedSession()
172− email, err := p.GetEmailAddress(context.Background(), session)
173− assert.Equal(t, "type assertion to string failed", err.Error())
174− assert.Equal(t, "", email)
175−}
176−
177−func TestAzureProviderGetEmailAddressEmptyUserPrincipalName(t *testing.T) {
178− b := testAzureBackend(`{ "mail": null, "otherMails": [], "userPrincipalName": "" }`)
179− defer b.Close()
180−
181− bURL, _ := url.Parse(b.URL)
182− p := testAzureProvider(bURL.Host)
168+func TestAzureProviderEnrichSession(t *testing.T) {
169+ testCases := []struct {
170+ Description string
171+ Email string
172+ PayloadFromAzureBackend string
173+ ExpectedEmail string
174+ ExpectedError error
175+ }{
176+ {
177+ Description: "should return email using mail property from Azure backend",
178+ PayloadFromAzureBackend: `{ "mail": "user@windows.net" }`,
179+ ExpectedEmail: "user@windows.net",
180+ },
181+ {
182+ Description: "should return email using otherMails property returned from Azure backend",
183+ PayloadFromAzureBackend: `{ "mail": null, "otherMails": ["user@windows.net", "altuser@windows.net"] }`,
184+ ExpectedEmail: "user@windows.net",
185+ },
186+ {
187+ Description: "should return email using userPrincipalName from Azure backend",
188+ PayloadFromAzureBackend: `{ "mail": null, "otherMails": [], "userPrincipalName": "user@windows.net" }`,
189+ ExpectedEmail: "user@windows.net",
190+ },
191+ {
192+ Description: "should return error when Azure backend doesn't return email information",
193+ PayloadFromAzureBackend: `{ "mail": null, "otherMails": [], "userPrincipalName": null }`,
194+ ExpectedError: fmt.Errorf("unable to get email address: %v", errors.New("type assertion to string failed")),
195+ },
196+ {
197+ Description: "should return specific error when unable to get email",
198+ PayloadFromAzureBackend: `{ "mail": null, "otherMails": [], "userPrincipalName": "" }`,
199+ ExpectedError: errors.New("unable to get email address"),
200+ },
201+ {
202+ Description: "should return error when otherMails from Azure backend is not a valid type",
203+ PayloadFromAzureBackend: `{ "mail": null, "otherMails": "", "userPrincipalName": null }`,
204+ ExpectedError: fmt.Errorf("unable to get email address: %v", errors.New("type assertion to string failed")),
205+ },
206+ {
207+ Description: "should not query profile api when email is already set in session",
208+ Email: "user@windows.net",
209+ ExpectedEmail: "user@windows.net",
210+ },
211+ }
183212
184− session := CreateAuthorizedSession()
185− email, err := p.GetEmailAddress(context.Background(), session)
186− assert.Equal(t, nil, err)
187− assert.Equal(t, "", email)
213+ for _, testCase := range testCases {
214+ t.Run(testCase.Description, func(t *testing.T) {
215+ var (
216+ b *httptest.Server
217+ host string
218+ )
219+ if testCase.PayloadFromAzureBackend != "" {
220+ b = testAzureBackend(testCase.PayloadFromAzureBackend)
221+ defer b.Close()
222+
223+ bURL, _ := url.Parse(b.URL)
224+ host = bURL.Host
225+ }
226+ p := testAzureProvider(host)
227+ session := CreateAuthorizedSession()
228+ session.Email = testCase.Email
229+ err := p.EnrichSession(context.Background(), session)
230+ assert.Equal(t, testCase.ExpectedError, err)
231+ assert.Equal(t, testCase.ExpectedEmail, session.Email)
232+ })
233+ }
188234}
189235
190−func TestAzureProviderGetEmailAddressIncorrectOtherMails(t *testing.T) {
191− b := testAzureBackend(`{ "mail": null, "otherMails": "", "userPrincipalName": null }`)
192− defer b.Close()
236+func TestAzureProviderRedeem(t *testing.T) {
237+ testCases := []struct {
238+ Name string
239+ RefreshToken string
240+ ExpiresOn time.Time
241+ EmailFromIDToken string
242+ EmailFromAccessToken string
243+ IsIDTokenMalformed bool
244+ InjectRedeemURLError bool
245+ }{
246+ {
247+ Name: "with id_token returned",
248+ EmailFromIDToken: "foo1@example.com",
249+ RefreshToken: "some_refresh_token",
250+ ExpiresOn: time.Now().Add(time.Hour),
251+ },
252+ {
253+ Name: "without id_token returned, fallback to access token",
254+ EmailFromAccessToken: "foo2@example.com",
255+ RefreshToken: "some_refresh_token",
256+ ExpiresOn: time.Now().Add(time.Hour),
257+ },
258+ {
259+ Name: "id_token malformed, fallback to access token",
260+ EmailFromAccessToken: "foo3@example.com",
261+ RefreshToken: "some_refresh_token",
262+ ExpiresOn: time.Now().Add(time.Hour),
263+ IsIDTokenMalformed: true,
264+ },
265+ {
266+ Name: "both id_token and access tokens are valid, return email from id_token",
267+ EmailFromIDToken: "foo1@example.com",
268+ EmailFromAccessToken: "foo3@example.com",
269+ RefreshToken: "some_refresh_token",
270+ ExpiresOn: time.Now().Add(time.Hour),
271+ },
272+ {
273+ Name: "redeem URL failed, should return error",
274+ EmailFromIDToken: "foo1@example.com",
275+ EmailFromAccessToken: "foo3@example.com",
276+ RefreshToken: "some_refresh_token",
277+ ExpiresOn: time.Now().Add(time.Hour),
278+ InjectRedeemURLError: true,
279+ },
280+ }
193281
194− bURL, _ := url.Parse(b.URL)
195− p := testAzureProvider(bURL.Host)
282+ for _, testCase := range testCases {
283+ t.Run(testCase.Name, func(t *testing.T) {
284+ idTokenString := ""
285+ accessTokenString := ""
286+ if testCase.EmailFromIDToken != "" {
287+ var err error
288+ token := idTokenClaims{Email: testCase.EmailFromIDToken}
289+ idTokenString, err = newSignedTestIDToken(token)
290+ assert.NoError(t, err)
291+ }
292+ if testCase.EmailFromAccessToken != "" {
293+ var err error
294+ token := idTokenClaims{Email: testCase.EmailFromAccessToken}
295+ accessTokenString, err = newSignedTestIDToken(token)
296+ assert.NoError(t, err)
297+ }
298+ if testCase.IsIDTokenMalformed {
299+ idTokenString = "this is a malformed id_token"
300+ }
301+ payload := azureOAuthPayload{
302+ IDToken: idTokenString,
303+ RefreshToken: testCase.RefreshToken,
304+ AccessToken: accessTokenString,
305+ ExpiresOn: testCase.ExpiresOn.Unix(),
306+ }
196307
197− session := CreateAuthorizedSession()
198− email, err := p.GetEmailAddress(context.Background(), session)
199− assert.Equal(t, "type assertion to string failed", err.Error())
200− assert.Equal(t, "", email)
201−}
308+ payloadBytes, err := json.Marshal(payload)
309+ assert.NoError(t, err)
202310
203−func TestAzureProviderRedeemReturnsIdToken(t *testing.T) {
204− b := testAzureBackend(`{ "id_token": "testtoken1234", "expires_on": "1136239445", "refresh_token": "refresh1234" }`)
205− defer b.Close()
206− timestamp, err := time.Parse(time.RFC3339, "2006-01-02T22:04:05Z")
207− assert.Equal(t, nil, err)
311+ b := testAzureBackendWithError(string(payloadBytes), testCase.InjectRedeemURLError)
312+ defer b.Close()
208313
209− bURL, _ := url.Parse(b.URL)
210− p := testAzureProvider(bURL.Host)
211− p.Data().RedeemURL.Path = "/common/oauth2/token"
212− s, err := p.Redeem(context.Background(), "https://localhost", "1234")
213− assert.Equal(t, nil, err)
214− assert.Equal(t, "testtoken1234", s.IDToken)
215− assert.Equal(t, timestamp, s.ExpiresOn.UTC())
216− assert.Equal(t, "refresh1234", s.RefreshToken)
314+ bURL, _ := url.Parse(b.URL)
315+ p := testAzureProvider(bURL.Host)
316+ p.Data().RedeemURL.Path = "/common/oauth2/token"
317+ s, err := p.Redeem(context.Background(), "https://localhost", "1234")
318+ if testCase.InjectRedeemURLError {
319+ assert.NotNil(t, err)
320+ } else {
321+ assert.NoError(t, err)
322+ assert.Equal(t, idTokenString, s.IDToken)
323+ assert.Equal(t, accessTokenString, s.AccessToken)
324+ assert.Equal(t, testCase.ExpiresOn.Unix(), s.ExpiresOn.Unix())
325+ assert.Equal(t, testCase.RefreshToken, s.RefreshToken)
326+ if testCase.EmailFromIDToken != "" {
327+ assert.Equal(t, testCase.EmailFromIDToken, s.Email)
328+ } else {
329+ assert.Equal(t, testCase.EmailFromAccessToken, s.Email)
330+ }
331+ }
332+ })
333+ }
217334}
218335
219336func TestAzureProviderProtectedResourceConfigured(t *testing.T) {
@@ -223,22 +340,6 @@ func TestAzureProviderProtectedResourceConfigured(t *testing.T) {
223340 assert.Contains(t, result, "resource="+url.QueryEscape("http://my.resource.test"))
224341}
225342
226−func TestAzureProviderGetsTokensInRedeem(t *testing.T) {
227− b := testAzureBackend(`{ "access_token": "some_access_token", "refresh_token": "some_refresh_token", "expires_on": "1136239445", "id_token": "some_id_token" }`)
228− defer b.Close()
229− timestamp, _ := time.Parse(time.RFC3339, "2006-01-02T22:04:05Z")
230− bURL, _ := url.Parse(b.URL)
231− p := testAzureProvider(bURL.Host)
232−
233− session, err := p.Redeem(context.Background(), "http://redirect/", "code1234")
234− assert.Equal(t, nil, err)
235− assert.NotEqual(t, session, nil)
236− assert.Equal(t, "some_access_token", session.AccessToken)
237− assert.Equal(t, "some_refresh_token", session.RefreshToken)
238− assert.Equal(t, "some_id_token", session.IDToken)
239− assert.Equal(t, timestamp, session.ExpiresOn.UTC())
240−}
241−
242343func TestAzureProviderNotRefreshWhenNotExpired(t *testing.T) {
243344 p := testAzureProvider("")
244345
@@ -250,19 +351,35 @@ func TestAzureProviderNotRefreshWhenNotExpired(t *testing.T) {
250351}
251352
252353func TestAzureProviderRefreshWhenExpired(t *testing.T) {
253− b := testAzureBackend(`{ "access_token": "new_some_access_token", "refresh_token": "new_some_refresh_token", "expires_on": "32693148245", "id_token": "new_some_id_token" }`)
354+ email := "foo@example.com"
355+ idToken := idTokenClaims{Email: email}
356+ idTokenString, err := newSignedTestIDToken(idToken)
357+ assert.NoError(t, err)
358+ timestamp, err := time.Parse(time.RFC3339, "3006-01-02T22:04:05Z")
359+ assert.NoError(t, err)
360+ payload := azureOAuthPayload{
361+ IDToken: idTokenString,
362+ RefreshToken: "new_some_refresh_token",
363+ AccessToken: "new_some_access_token",
364+ ExpiresOn: timestamp.Unix(),
365+ }
366+
367+ payloadBytes, err := json.Marshal(payload)
368+ assert.NoError(t, err)
369+ b := testAzureBackend(string(payloadBytes))
254370 defer b.Close()
255− timestamp, _ := time.Parse(time.RFC3339, "3006-01-02T22:04:05Z")
256371 bURL, _ := url.Parse(b.URL)
257372 p := testAzureProvider(bURL.Host)
258373
259374 expires := time.Now().Add(time.Duration(-1) * time.Hour)
260375 session := &sessions.SessionState{AccessToken: "some_access_token", RefreshToken: "some_refresh_token", IDToken: "some_id_token", ExpiresOn: &expires}
261− _, err := p.RefreshSessionIfNeeded(context.Background(), session)
376+ refreshNeeded, err := p.RefreshSessionIfNeeded(context.Background(), session)
262377 assert.Equal(t, nil, err)
378+ assert.True(t, refreshNeeded)
263379 assert.NotEqual(t, session, nil)
264380 assert.Equal(t, "new_some_access_token", session.AccessToken)
265381 assert.Equal(t, "new_some_refresh_token", session.RefreshToken)
266− assert.Equal(t, "new_some_id_token", session.IDToken)
382+ assert.Equal(t, idTokenString, session.IDToken)
383+ assert.Equal(t, email, session.Email)
267384 assert.Equal(t, timestamp, session.ExpiresOn.UTC())
268385}