Skip to content

Commit 4b31e28

Browse files
committed
Fix issue with expirary json
1 parent 3be7d31 commit 4b31e28

File tree

3 files changed

+80
-5
lines changed

3 files changed

+80
-5
lines changed

coderd/coderdtest/oidctest/idp.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ type FakeIDP struct {
6363
hookUserInfo func(email string) jwt.MapClaims
6464
fakeCoderd func(req *http.Request) (*http.Response, error)
6565
hookOnRefresh func(email string) error
66+
// Custom authentication for the client. This is useful if you want
67+
// to test something like PKI auth vs a client_secret.
68+
hookAuthenticateClient func(t testing.TB, req *http.Request) (url.Values, error)
6669
// Optional if you want to use a real http network request assuming
6770
// it is not directed to the IDP.
6871
defaultClient *http.Client
@@ -79,6 +82,12 @@ func WithRefreshHook(hook func(email string) error) func(*FakeIDP) {
7982
}
8083
}
8184

85+
func WithCustomClientAuth(hook func(t testing.TB, req *http.Request) (url.Values, error)) func(*FakeIDP) {
86+
return func(f *FakeIDP) {
87+
f.hookAuthenticateClient = hook
88+
}
89+
}
90+
8291
// WithLogging is optional, but will log some HTTP calls made to the IDP.
8392
func WithLogging(t testing.TB, options *slogtest.Options) func(*FakeIDP) {
8493
return func(f *FakeIDP) {
@@ -109,6 +118,12 @@ func WithServing() func(*FakeIDP) {
109118
}
110119
}
111120

121+
func WithIssuer(issuer string) func(*FakeIDP) {
122+
return func(f *FakeIDP) {
123+
f.issuer = issuer
124+
}
125+
}
126+
112127
const (
113128
authorizePath = "/oauth2/authorize"
114129
tokenPath = "/oauth2/token"
@@ -137,7 +152,6 @@ func NewFakeIDP(t testing.TB, opts ...FakeIDPOpt) *FakeIDP {
137152
hookOnRefresh: func(_ string) error { return nil },
138153
hookUserInfo: func(email string) jwt.MapClaims { return jwt.MapClaims{} },
139154
}
140-
idp.handler = idp.httpHandler(t)
141155

142156
for _, opt := range opts {
143157
opt(idp)
@@ -147,6 +161,7 @@ func NewFakeIDP(t testing.TB, opts ...FakeIDPOpt) *FakeIDP {
147161
idp.issuer = "https://coder.com"
148162
}
149163

164+
idp.handler = idp.httpHandler(t)
150165
idp.updateIssuerURL(t, idp.issuer)
151166
if idp.serve {
152167
idp.realServer(t)
@@ -355,6 +370,10 @@ func (f *FakeIDP) authenticateBearerTokenRequest(t testing.TB, req *http.Request
355370
func (f *FakeIDP) authenticateOIDClientRequest(t testing.TB, req *http.Request) (url.Values, error) {
356371
t.Helper()
357372

373+
if f.hookAuthenticateClient != nil {
374+
return f.hookAuthenticateClient(t, req)
375+
}
376+
358377
data, _ := io.ReadAll(req.Body)
359378
values, err := url.ParseQuery(string(data))
360379
if !assert.NoError(t, err, "parse token request values") {
@@ -541,7 +560,6 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
541560
"refresh_token": refreshToken,
542561
"token_type": "Bearer",
543562
"expires_in": int64(time.Minute * 5),
544-
"expiry": exp.Unix(),
545563
"id_token": f.encodeClaims(t, claims),
546564
}
547565
// Store the claims for the next refresh

coderd/oauthpki/oidcpki.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,10 @@ func (src *jwtTokenSource) Token() (*oauth2.Token, error) {
215215
}
216216

217217
var tokenRes struct {
218-
oauth2.Token
218+
AccessToken string `json:"access_token"`
219+
TokenType string `json:"token_type,omitempty"`
220+
RefreshToken string `json:"refresh_token,omitempty"`
221+
219222
// Extra fields returned by the refresh that are needed
220223
IDToken string `json:"id_token"`
221224
ExpiresIn int64 `json:"expires_in"` // relative seconds from now

coderd/oauthpki/okidcpki_test.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import (
1212
"time"
1313

1414
"github.com/coreos/go-oidc/v3/oidc"
15-
"github.com/golang-jwt/jwt"
15+
"github.com/golang-jwt/jwt/v4"
1616
"github.com/stretchr/testify/assert"
1717
"github.com/stretchr/testify/require"
1818
"golang.org/x/oauth2"
1919
"golang.org/x/xerrors"
2020

21+
"github.com/coder/coder/v2/coderd"
22+
"github.com/coder/coder/v2/coderd/coderdtest"
23+
"github.com/coder/coder/v2/coderd/coderdtest/oidctest"
2124
"github.com/coder/coder/v2/coderd/oauthpki"
2225
"github.com/coder/coder/v2/testutil"
2326
)
@@ -123,6 +126,57 @@ func TestAzureADPKIOIDC(t *testing.T) {
123126
require.Error(t, err, "error expected")
124127
}
125128

129+
// TestAzureAKPKIWithCoderd uses a fake IDP and a real Coderd to test PKI auth.
130+
func TestAzureAKPKIWithCoderd(t *testing.T) {
131+
t.Parallel()
132+
133+
scopes := []string{"openid", "email", "profile", "offline_access"}
134+
fake := oidctest.NewFakeIDP(t,
135+
oidctest.WithIssuer("https://login.microsoftonline.com/fake_app"),
136+
oidctest.WithCustomClientAuth(func(t testing.TB, req *http.Request) (url.Values, error) {
137+
values := assertJWTAuth(t, req)
138+
if values == nil {
139+
return nil, xerrors.New("authorizatin failed in request")
140+
}
141+
return values, nil
142+
}),
143+
oidctest.WithServing(),
144+
)
145+
cfg := fake.OIDCConfig(t, scopes, func(cfg *coderd.OIDCConfig) {
146+
cfg.AllowSignups = true
147+
})
148+
149+
oauthCfg := cfg.OAuth2Config.(*oauth2.Config)
150+
// Create the oauthpki config
151+
pki, err := oauthpki.NewOauth2PKIConfig(oauthpki.ConfigParams{
152+
ClientID: oauthCfg.ClientID,
153+
TokenURL: oauthCfg.Endpoint.TokenURL,
154+
Scopes: scopes,
155+
PemEncodedKey: []byte(testClientKey),
156+
PemEncodedCert: []byte(testClientCert),
157+
Config: oauthCfg,
158+
})
159+
require.NoError(t, err)
160+
cfg.OAuth2Config = pki
161+
162+
owner, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
163+
OIDCConfig: cfg,
164+
})
165+
166+
// Create a user and login
167+
const email = "alice@coder.com"
168+
claims := jwt.MapClaims{
169+
"email": email,
170+
}
171+
helper := oidctest.NewLoginHelper(owner, fake)
172+
user, _ := helper.Login(t, claims)
173+
174+
// Try refreshing the token more than once.
175+
for i := 0; i < 2; i++ {
176+
helper.ForceRefresh(t, api.Database, user, claims)
177+
}
178+
}
179+
126180
// TestSavedAzureADPKIOIDC was created by capturing actual responses from an Azure
127181
// AD instance and saving them to replay, removing some details.
128182
// The reason this is done is that this is the only way to assert values
@@ -269,7 +323,7 @@ func (f fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
269323

270324
// assertJWTAuth will assert the basic JWT auth assertions. It will return the
271325
// url.Values from the request body for any additional assertions to be made.
272-
func assertJWTAuth(t *testing.T, r *http.Request) url.Values {
326+
func assertJWTAuth(t testing.TB, r *http.Request) url.Values {
273327
body, err := io.ReadAll(r.Body)
274328
if !assert.NoError(t, err) {
275329
return nil

0 commit comments

Comments
 (0)