Files
coder/coderd/coderdtest/oidctest/idp_test.go
T
Spike Curtis bddb808b25 chore: arrange imports in a standard way (#21452)
Fixes all our Go file imports to match the preferred spec that we've _mostly_ been using. For example:

```
import (
	"context"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"golang.org/x/xerrors"
	"gopkg.in/natefinch/lumberjack.v2"

	"cdr.dev/slog/v3"
	"github.com/coder/coder/v2/codersdk/agentsdk"
	"github.com/coder/serpent"
)
```

3 groups: standard library, 3rd partly libs, Coder libs.

This PR makes the change across the codebase. The PR in the stack above modifies our formatting to maintain this state of affairs, and is a separate PR so it's possible to review that one in detail.
2026-01-08 15:24:11 +04:00

151 lines
4.6 KiB
Go

package oidctest_test
import (
"context"
"crypto"
"net/http"
"testing"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/golang-jwt/jwt/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd"
"github.com/coder/coder/v2/coderd/coderdtest/oidctest"
"github.com/coder/coder/v2/testutil"
)
// TestFakeIDPBasicFlow tests the basic flow of the fake IDP.
// It is done all in memory with no actual network requests.
// nolint:bodyclose
func TestFakeIDPBasicFlow(t *testing.T) {
t.Parallel()
fake := oidctest.NewFakeIDP(t,
oidctest.WithLogging(t, nil),
)
cfg := fake.OIDCConfig(t, nil)
cli := fake.HTTPClient(nil)
ctx := oidc.ClientContext(context.Background(), cli)
const expectedState = "random-state"
var token *oauth2.Token
// This is the Coder callback using an actual network request.
fake.SetCoderdCallbackHandler(func(w http.ResponseWriter, r *http.Request) {
// Emulate OIDC flow
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
assert.Equal(t, expectedState, state, "state mismatch")
oauthToken, err := cfg.Exchange(ctx, code)
if assert.NoError(t, err, "failed to exchange code") {
assert.NotEmpty(t, oauthToken.AccessToken, "access token is empty")
assert.NotEmpty(t, oauthToken.RefreshToken, "refresh token is empty")
}
token = oauthToken
})
//nolint:bodyclose
resp := fake.OIDCCallback(t, expectedState, jwt.MapClaims{})
require.Equal(t, http.StatusOK, resp.StatusCode)
// Test the user info
_, err := cfg.Provider.UserInfo(ctx, oauth2.StaticTokenSource(token))
require.NoError(t, err)
// Now test it can refresh
refreshed, err := cfg.TokenSource(ctx, &oauth2.Token{
AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken,
Expiry: time.Now().Add(time.Minute * -1),
}).Token()
require.NoError(t, err, "failed to refresh token")
require.NotEmpty(t, refreshed.AccessToken, "access token is empty on refresh")
}
// TestIDPIssuerMismatch emulates a situation where the IDP issuer url does
// not match the one in the well-known config and claims.
// This can happen in some edge cases and in some azure configurations.
//
// This test just makes sure a fake IDP can set up this scenario.
func TestIDPIssuerMismatch(t *testing.T) {
t.Parallel()
const proxyURL = "https://proxy.com"
const primaryURL = "https://primary.com"
fake := oidctest.NewFakeIDP(t,
oidctest.WithIssuer(proxyURL),
oidctest.WithDefaultIDClaims(jwt.MapClaims{
"iss": primaryURL,
}),
oidctest.WithHookWellKnown(func(r *http.Request, j *oidctest.ProviderJSON) error {
// host should be proxy.com, but we return the primaryURL
if r.Host != "proxy.com" {
return xerrors.Errorf("unexpected host: %s", r.Host)
}
j.Issuer = primaryURL
return nil
}),
oidctest.WithLogging(t, nil),
)
ctx := testutil.Context(t, testutil.WaitMedium)
// Do not use real network requests
cli := fake.HTTPClient(nil)
ctx = oidc.ClientContext(ctx, cli)
// Allow the issuer mismatch
verifierContext := oidc.InsecureIssuerURLContext(ctx, "this field does not matter")
p, err := oidc.NewProvider(verifierContext, "https://proxy.com")
require.NoError(t, err, "failed to create OIDC provider")
oauthConfig := fake.OauthConfig(t, nil)
cfg := &coderd.OIDCConfig{
OAuth2Config: oauthConfig,
Provider: p,
Verifier: oidc.NewVerifier(fake.WellknownConfig().Issuer, &oidc.StaticKeySet{
PublicKeys: []crypto.PublicKey{fake.PublicKey()},
}, &oidc.Config{
SkipIssuerCheck: true,
ClientID: oauthConfig.ClientID,
SupportedSigningAlgs: []string{
"RS256",
},
}),
UsernameField: "preferred_username",
EmailField: "email",
AuthURLParams: map[string]string{"access_type": "offline"},
}
const expectedState = "random-state"
var token *oauth2.Token
fake.SetCoderdCallbackHandler(func(w http.ResponseWriter, r *http.Request) {
// Emulate OIDC flow
code := r.URL.Query().Get("code")
state := r.URL.Query().Get("state")
assert.Equal(t, expectedState, state, "state mismatch")
oauthToken, err := cfg.Exchange(ctx, code)
if assert.NoError(t, err, "failed to exchange code") {
assert.NotEmpty(t, oauthToken.AccessToken, "access token is empty")
assert.NotEmpty(t, oauthToken.RefreshToken, "refresh token is empty")
}
token = oauthToken
})
//nolint:bodyclose
resp := fake.OIDCCallback(t, expectedState, nil) // Use default claims
require.Equal(t, http.StatusOK, resp.StatusCode)
idToken, err := cfg.Verifier.Verify(ctx, token.Extra("id_token").(string))
require.NoError(t, err)
require.Equal(t, primaryURL, idToken.Issuer)
}