Documentation
¶
Overview ¶
Package identityharness stands in, offline, for the external parties of the agent identity credential flow: the upstream company IdP and AS-1, Mecatl's sign-in authorization server. It follows docs/drafts/agent-identity/credentials-and-lifecycle.md for every step it covers, so identity broker tests and Mecatl composition tests share one stand-in instead of each inventing its own.
The harness covers user sign-in: authorization code with PKCE at AS-1, which signs the user in at the upstream IdP as an ordinary OIDC client, and AS-1 access tokens as design step (1) describes them. Later work adds the session-open exchange, ID-JAG issuance, refresh-token revocation, broker client authentication, and a gateway stand-in, each in its own files. AS-2 is deliberately absent: tests use the real embedded ToolHive authorization server for it.
Everything runs on loopback TLS servers that close when the test ends. The harness is test code; production packages never import it.
Index ¶
- Constants
- type AS1
- func (a *AS1) Audience() string
- func (a *AS1) AuthorizationEndpoint() string
- func (a *AS1) FailNextAuthorization(fault Fault)
- func (a *AS1) FailNextToken(fault Fault)
- func (a *AS1) HasUpstreamRefreshToken(user string) bool
- func (a *AS1) Issuer() string
- func (a *AS1) MintAccessToken(spec AccessTokenSpec) string
- func (a *AS1) Subject(user string) string
- func (a *AS1) TokenEndpoint() string
- type AccessTokenSpec
- type Client
- type Fault
- type Harness
- func (h *Harness) CACertPEM() []byte
- func (h *Harness) HTTPClient() *http.Client
- func (h *Harness) Refresh(ctx context.Context, clientID, refreshToken string) (Tokens, error)
- func (h *Harness) SignIn(ctx context.Context, req SignInRequest) (Tokens, error)
- func (h *Harness) WriteCACert(t testing.TB, dir string) string
- type IdP
- type OAuthError
- type Options
- type SignInRequest
- type Tokens
Constants ¶
const ( // DefaultAudience is the Mecatl API identifier AS-1 access tokens carry in // aud. mecak8s must accept the same value (--oidc-audience). DefaultAudience = "https://mecatl.test/api" // DefaultClientID is the public client registered when Options.Clients is // empty. DefaultClientID = "mecatui" // DefaultRedirectURI is DefaultClientID's only redirect URI. DefaultRedirectURI = "http://127.0.0.1:8765/callback" // DefaultAccessTokenLifetime is the AS-1 access token lifetime. DefaultAccessTokenLifetime = 15 * time.Minute // Alice and Bob are the company identities the upstream IdP knows by // default. Alice is the default user. Alice = "alice-204" Bob = "bob-317" )
Defaults used when Options leaves a field empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AS1 ¶
type AS1 struct {
// contains filtered or unexported fields
}
AS1 stands in for AS-1, Mecatl's sign-in authorization server. It signs users in with the authorization code flow and PKCE (S256, required), sends them to the upstream IdP as an ordinary OIDC client asking for offline access, and issues the AS-1 access token design step (1) describes:
header: typ at+jwt, alg ES256, kid
claims: iss, sub, sub_id {format iss_sub, iss, sub}, aud, client_id, iat, exp, jti
sub is AS-1's own subject for the company identity: stable across sign-ins, and never shared by two company identities. Clients also receive a refresh token they can use at the refresh_token grant.
The issuer has a path (/as), as AS-1's final issuer URL does, so discovery is served both at {issuer}/.well-known/openid-configuration and at the RFC 8414 location.
func (*AS1) AuthorizationEndpoint ¶
AuthorizationEndpoint is where clients start sign-in.
func (*AS1) FailNextAuthorization ¶
FailNextAuthorization makes the next authorization request redirect back with fault.Error instead of signing the user in.
func (*AS1) FailNextToken ¶
FailNextToken makes the next token request from a registered client fail with fault.
func (*AS1) HasUpstreamRefreshToken ¶
HasUpstreamRefreshToken reports whether AS-1 holds an upstream refresh token for user, which its later upstream re-check needs.
func (*AS1) MintAccessToken ¶
func (a *AS1) MintAccessToken(spec AccessTokenSpec) string
MintAccessToken issues an AS-1 access token without a sign-in, for tests that need a specific token or a specific defect.
func (*AS1) Subject ¶
Subject returns AS-1's subject for the company identity user, assigning one on first use. Two company identities never share a subject.
func (*AS1) TokenEndpoint ¶
TokenEndpoint is AS-1's token endpoint.
type AccessTokenSpec ¶
type AccessTokenSpec struct {
// User is the company identity (IdP subject). Empty selects the IdP's
// default user.
User string
// ClientID is the client_id claim. Empty selects the first registered
// client.
ClientID string
// Audience replaces the Mecatl API identifier in aud.
Audience string
// Issuer replaces AS-1's issuer in iss.
Issuer string
// IssuedAt replaces the current time in iat; exp follows from it.
IssuedAt time.Time
// Lifetime replaces the configured lifetime. Negative issues an expired
// token.
Lifetime time.Duration
// Type replaces at+jwt in the typ header.
Type string
// OmitType leaves typ out of the header.
OmitType bool
// OmitSubID leaves the sub_id claim out.
OmitSubID bool
// SubID, when non-nil, replaces the sub_id claim, for a malformed one.
SubID any
// ForeignKey signs with a key that is not in AS-1's JWKS, under AS-1's
// kid.
ForeignKey bool
}
AccessTokenSpec describes an AS-1 access token for MintAccessToken. Fields left empty take the values a real sign-in produces, so a test changes only the defect it checks.
type Client ¶
Client is a public OAuth client registered at AS-1, such as the TUI or Studio. Redirect URIs match exactly.
type Fault ¶
type Fault struct {
// Status is the HTTP status for a token endpoint fault. Zero selects 400.
Status int
// Error is the OAuth error code, such as access_denied or invalid_grant.
Error string
// Reason starts error_description, as the design's error reasons do.
Reason string
}
Fault is a failure a stand-in returns once instead of its normal response.
type Harness ¶
Harness is a running set of stand-ins. Create it with New.
func (*Harness) HTTPClient ¶
HTTPClient returns a client that trusts the stand-ins' TLS certificate and does not follow redirects.
type IdP ¶
type IdP struct {
// contains filtered or unexported fields
}
IdP stands in for the upstream company IdP. AS-1 is an ordinary OIDC client of it (design step (1)): the IdP signs users in with the authorization code flow and returns an ID token naming the company identity. It issues a refresh token only when the client asks for offline access the way OIDC Core §11 requires: the offline_access scope together with prompt=consent.
Sign-in is headless. The user is the one named by login_hint, or the default user when login_hint is absent; an unknown user is refused with access_denied.
func (*IdP) FailNextAuthorization ¶
FailNextAuthorization makes the next authorization request redirect back with fault.Error, such as access_denied, instead of signing the user in.
func (*IdP) FailNextToken ¶
FailNextToken makes the next token request fail with fault.
func (*IdP) RefreshTokensIssued ¶
RefreshTokensIssued reports how many upstream refresh tokens the IdP issued for subject, which shows whether AS-1 asked for offline access.
func (*IdP) RegisterClient ¶
RegisterClient registers a confidential OIDC client that authenticates to the token endpoint with client_secret_basic or client_secret_post. AS-1 registers itself this way; a test hosting a real AS-1 registers that one.
type OAuthError ¶
OAuthError is an OAuth error a sign-in ended with, from the authorization redirect or the token endpoint.
func (*OAuthError) Error ¶
func (e *OAuthError) Error() string
type Options ¶
type Options struct {
// Audience is the Mecatl API identifier. Empty selects DefaultAudience.
Audience string
// Clients are AS-1's public clients. Empty registers DefaultClientID with
// DefaultRedirectURI.
Clients []Client
// Users are the company identities (IdP subjects) the upstream IdP can
// sign in. The first is the default user. Empty selects Alice and Bob.
Users []string
// AccessTokenLifetime is the AS-1 access token lifetime. Zero selects
// DefaultAccessTokenLifetime.
AccessTokenLifetime time.Duration
// Now is the clock for every issued token and code. Nil selects time.Now.
Now func() time.Time
}
Options configures a Harness. The zero value is usable.
type SignInRequest ¶
type SignInRequest struct {
// User is the company identity to sign in. Empty selects the IdP's
// default user.
User string
// ClientID is the AS-1 client. Empty selects DefaultClientID.
ClientID string
// RedirectURI is the client's redirect URI. Empty selects
// DefaultRedirectURI.
RedirectURI string
}
SignInRequest selects who signs in, with which client.