Skip to content

Commit 5bada13

Browse files
committed
tests: add test cases for pkce
1 parent 9a6676b commit 5bada13

1 file changed

Lines changed: 179 additions & 0 deletions

File tree

internal/controller/oidc_controller_test.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package controller_test
22

33
import (
4+
"crypto/sha256"
5+
"encoding/base64"
46
"encoding/json"
57
"net/http/httptest"
68
"net/url"
@@ -431,6 +433,183 @@ func TestOIDCController(t *testing.T) {
431433
assert.False(t, ok, "Did not expect email claim in userinfo response")
432434
},
433435
},
436+
{
437+
description: "Ensure plain PKCE succeeds",
438+
middlewares: []gin.HandlerFunc{
439+
simpleCtx,
440+
},
441+
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
442+
reqBody := service.AuthorizeRequest{
443+
Scope: "openid",
444+
ResponseType: "code",
445+
ClientID: "some-client-id",
446+
RedirectURI: "https://test.example.com/callback",
447+
State: "some-state",
448+
Nonce: "some-nonce",
449+
CodeChallenge: "some-challenge",
450+
// Not setting a code challenge method should default to "plain"
451+
CodeChallengeMethod: "",
452+
}
453+
reqBodyBytes, err := json.Marshal(reqBody)
454+
assert.NoError(t, err)
455+
456+
req := httptest.NewRequest("POST", "/api/oidc/authorize", strings.NewReader(string(reqBodyBytes)))
457+
req.Header.Set("Content-Type", "application/json")
458+
router.ServeHTTP(recorder, req)
459+
assert.Equal(t, 200, recorder.Code)
460+
461+
var res map[string]any
462+
err = json.Unmarshal(recorder.Body.Bytes(), &res)
463+
assert.NoError(t, err)
464+
465+
redirectURI := res["redirect_uri"].(string)
466+
url, err := url.Parse(redirectURI)
467+
assert.NoError(t, err)
468+
469+
queryParams := url.Query()
470+
assert.Equal(t, queryParams.Get("state"), "some-state")
471+
472+
code := queryParams.Get("code")
473+
assert.NotEmpty(t, code)
474+
475+
// Now exchange the code for a token
476+
tokenReqBody := controller.TokenRequest{
477+
GrantType: "authorization_code",
478+
Code: code,
479+
RedirectURI: "https://test.example.com/callback",
480+
CodeVerifier: "some-challenge",
481+
}
482+
reqBodyEncoded, err := query.Values(tokenReqBody)
483+
assert.NoError(t, err)
484+
485+
req = httptest.NewRequest("POST", "/api/oidc/token", strings.NewReader(reqBodyEncoded.Encode()))
486+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
487+
req.SetBasicAuth("some-client-id", "some-client-secret")
488+
router.ServeHTTP(recorder, req)
489+
490+
assert.Equal(t, 200, recorder.Code)
491+
},
492+
},
493+
{
494+
description: "Ensure S256 PKCE succeeds",
495+
middlewares: []gin.HandlerFunc{
496+
simpleCtx,
497+
},
498+
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
499+
hasher := sha256.New()
500+
hasher.Write([]byte("some-challenge"))
501+
codeChallenge := hasher.Sum(nil)
502+
codeChallengeEncoded := base64.URLEncoding.EncodeToString(codeChallenge)
503+
reqBody := service.AuthorizeRequest{
504+
Scope: "openid",
505+
ResponseType: "code",
506+
ClientID: "some-client-id",
507+
RedirectURI: "https://test.example.com/callback",
508+
State: "some-state",
509+
Nonce: "some-nonce",
510+
CodeChallenge: codeChallengeEncoded,
511+
CodeChallengeMethod: "S256",
512+
}
513+
reqBodyBytes, err := json.Marshal(reqBody)
514+
assert.NoError(t, err)
515+
516+
req := httptest.NewRequest("POST", "/api/oidc/authorize", strings.NewReader(string(reqBodyBytes)))
517+
req.Header.Set("Content-Type", "application/json")
518+
router.ServeHTTP(recorder, req)
519+
assert.Equal(t, 200, recorder.Code)
520+
521+
var res map[string]any
522+
err = json.Unmarshal(recorder.Body.Bytes(), &res)
523+
assert.NoError(t, err)
524+
525+
redirectURI := res["redirect_uri"].(string)
526+
url, err := url.Parse(redirectURI)
527+
assert.NoError(t, err)
528+
529+
queryParams := url.Query()
530+
assert.Equal(t, queryParams.Get("state"), "some-state")
531+
532+
code := queryParams.Get("code")
533+
assert.NotEmpty(t, code)
534+
535+
// Now exchange the code for a token
536+
tokenReqBody := controller.TokenRequest{
537+
GrantType: "authorization_code",
538+
Code: code,
539+
RedirectURI: "https://test.example.com/callback",
540+
CodeVerifier: "some-challenge",
541+
}
542+
reqBodyEncoded, err := query.Values(tokenReqBody)
543+
assert.NoError(t, err)
544+
545+
req = httptest.NewRequest("POST", "/api/oidc/token", strings.NewReader(reqBodyEncoded.Encode()))
546+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
547+
req.SetBasicAuth("some-client-id", "some-client-secret")
548+
router.ServeHTTP(recorder, req)
549+
550+
assert.Equal(t, 200, recorder.Code)
551+
},
552+
},
553+
{
554+
description: "Ensure request with invalid PKCE fails",
555+
middlewares: []gin.HandlerFunc{
556+
simpleCtx,
557+
},
558+
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
559+
hasher := sha256.New()
560+
hasher.Write([]byte("some-challenge"))
561+
codeChallenge := hasher.Sum(nil)
562+
codeChallengeEncoded := base64.URLEncoding.EncodeToString(codeChallenge)
563+
reqBody := service.AuthorizeRequest{
564+
Scope: "openid",
565+
ResponseType: "code",
566+
ClientID: "some-client-id",
567+
RedirectURI: "https://test.example.com/callback",
568+
State: "some-state",
569+
Nonce: "some-nonce",
570+
CodeChallenge: codeChallengeEncoded,
571+
CodeChallengeMethod: "S256",
572+
}
573+
reqBodyBytes, err := json.Marshal(reqBody)
574+
assert.NoError(t, err)
575+
576+
req := httptest.NewRequest("POST", "/api/oidc/authorize", strings.NewReader(string(reqBodyBytes)))
577+
req.Header.Set("Content-Type", "application/json")
578+
router.ServeHTTP(recorder, req)
579+
assert.Equal(t, 200, recorder.Code)
580+
581+
var res map[string]any
582+
err = json.Unmarshal(recorder.Body.Bytes(), &res)
583+
assert.NoError(t, err)
584+
585+
redirectURI := res["redirect_uri"].(string)
586+
url, err := url.Parse(redirectURI)
587+
assert.NoError(t, err)
588+
589+
queryParams := url.Query()
590+
assert.Equal(t, queryParams.Get("state"), "some-state")
591+
592+
code := queryParams.Get("code")
593+
assert.NotEmpty(t, code)
594+
595+
// Now exchange the code for a token
596+
tokenReqBody := controller.TokenRequest{
597+
GrantType: "authorization_code",
598+
Code: code,
599+
RedirectURI: "https://test.example.com/callback",
600+
CodeVerifier: "some-challenge-1",
601+
}
602+
reqBodyEncoded, err := query.Values(tokenReqBody)
603+
assert.NoError(t, err)
604+
605+
req = httptest.NewRequest("POST", "/api/oidc/token", strings.NewReader(reqBodyEncoded.Encode()))
606+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
607+
req.SetBasicAuth("some-client-id", "some-client-secret")
608+
router.ServeHTTP(recorder, req)
609+
610+
assert.Equal(t, 200, recorder.Code)
611+
},
612+
},
434613
}
435614

436615
app := bootstrap.NewBootstrapApp(config.Config{})

0 commit comments

Comments
 (0)