Skip to content

Commit

Permalink
Introduce handlers to support the code & token OAuth flow (#2)
Browse files Browse the repository at this point in the history
These test handlers allow to simulate an OAuth2 provider in a test setting.
  • Loading branch information
pdeslaur authored Mar 22, 2022
1 parent 38d3c9d commit 557d401
Showing 1 changed file with 99 additions and 12 deletions.
111 changes: 99 additions & 12 deletions pkg/oidctest/issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ package oidctest
import (
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"

"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)

// Stand up a very simple OIDC endpoint.
// Stand up a simple OIDC endpoint.
func NewIssuer(t *testing.T) (jose.Signer, string) {
t.Helper()

Expand All @@ -37,25 +41,30 @@ func NewIssuer(t *testing.T) (jose.Signer, string) {
}

// Populated below, but we need to capture it first.
var testIssuer *string

oidcMux := http.NewServeMux()
oidcServer := httptest.NewServer(oidcMux)
testIssuer := oidcServer.URL

oidcMux.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
t.Log("Handling request for openid-configuration.")
if err := json.NewEncoder(w).Encode(struct {
Issuer string `json:"issuer"`
JWKSURI string `json:"jwks_uri"`
Issuer string `json:"issuer"`
JWKSURI string `json:"jwks_uri"`
AuthzEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
}{
Issuer: *testIssuer,
JWKSURI: *testIssuer + "/keys",
Issuer: testIssuer,
JWKSURI: testIssuer + "/keys",
AuthzEndpoint: testIssuer + "/authz",
TokenEndpoint: testIssuer + "/token",
}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})

oidcMux.HandleFunc("/keys", func(w http.ResponseWriter, r *http.Request) {
t.Log("Handling request for jwks.")
w.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{
jwk.Public(),
Expand All @@ -64,11 +73,89 @@ func NewIssuer(t *testing.T) (jose.Signer, string) {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
oidcServer := httptest.NewServer(oidcMux)
t.Cleanup(oidcServer.Close)

// Setup the testIssuer, so everything uses the right URL.
testIssuer = &oidcServer.URL
// code stores information sent inside the `code` OAuth parameter.
type code struct {
ClientID string `json:"client_id"`
Nonce string `json:"nonce"`
}

oidcMux.HandleFunc("/authz", func(w http.ResponseWriter, r *http.Request) {
t.Log("Handling request for authz.")
redirectURL, err := url.Parse(r.URL.Query().Get("redirect_uri"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}

// Rely on `code` as a mechanism to encode information required by the token
// endpoint.
c, err := json.Marshal(code{
ClientID: r.URL.Query().Get("client_id"),
Nonce: r.URL.Query().Get("nonce"),
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

v := url.Values{
"state": {r.URL.Query().Get("state")},
"code": {base64.StdEncoding.EncodeToString(c)},
}
redirectURL.RawQuery = v.Encode()

http.Redirect(w, r, redirectURL.String(), http.StatusFound)
})

oidcMux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
t.Log("Handling request for token.")

rawCode, err := base64.StdEncoding.DecodeString(r.FormValue("code"))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

var c code
if err := json.Unmarshal(rawCode, &c); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

token, err := jwt.Signed(signer).Claims(struct {
jwt.Claims `json:",inline"` // nolint:revive // unknown option 'inline' in JSON tag

Nonce string `json:"nonce"`
}{
Claims: jwt.Claims{
Issuer: testIssuer,
IssuedAt: jwt.NewNumericDate(time.Now()),
Expiry: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)),
Subject: "test-subject",
Audience: jwt.Audience{c.ClientID},
},
Nonce: c.Nonce,
}).CompactSerialize()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.Header().Add("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(struct {
IdToken string `json:"id_token"`
TokenType string `json:"token_type"`
AccessToken string `json:"access_token"`
}{
IdToken: token,
TokenType: "Bearer",
AccessToken: "garbage",
}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})

t.Cleanup(oidcServer.Close)

return signer, *testIssuer
return signer, testIssuer
}

0 comments on commit 557d401

Please sign in to comment.