- ES256 keys, PKCS#1/SEC1 parsing fallbacks, EC JWKs, mixed RS256/ES256 rings - Pairwise subjects per sector (SubjectSector, ErrSectorRequired) - Opt-in refresh_token grant (OIDCOptions.RefreshEnabled): OfflineAccess clients only, rotation, lineage revocation on replay, fresh id_token without nonce - IssueOIDCClientCredentials with the PHP oas_/oas_cs_ shapes - Smoke tests: token errors, replay revocation, userinfo 401s, mixed key ring, pairwise subjects, refresh opt-in
69 lines
2.0 KiB
Go
69 lines
2.0 KiB
Go
package wristband
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/url"
|
|
"strings"
|
|
"uuid"
|
|
)
|
|
|
|
// The subject types a client can have (D-15).
|
|
const (
|
|
// SubjectTypePublic gives a user the same sub at every public client.
|
|
SubjectTypePublic = "public"
|
|
// SubjectTypePairwise gives a user a different sub per sector.
|
|
SubjectTypePairwise = "pairwise"
|
|
)
|
|
|
|
// ErrSectorRequired is returned by SubjectSector for a pairwise client
|
|
// without a sector identifier whose redirect URIs do not share one host:
|
|
// an administrator must set OIDCClientRecord.SectorIdentifier (D-15).
|
|
var ErrSectorRequired = errors.New("wristband: pairwise client needs a sector identifier")
|
|
|
|
// SubjectSector returns the subject sector of a client: "" for a public
|
|
// client; for a pairwise client its trimmed SectorIdentifier when set,
|
|
// otherwise the host of its redirect URIs when they all share one host,
|
|
// otherwise ErrSectorRequired. No remote sector_identifier_uri is fetched.
|
|
func SubjectSector(c *OIDCClientRecord) (string, error) {
|
|
if c == nil {
|
|
return "", errors.New("wristband: no client")
|
|
}
|
|
switch c.SubjectType {
|
|
case "", SubjectTypePublic:
|
|
return "", nil
|
|
case SubjectTypePairwise:
|
|
if sector := strings.TrimSpace(c.SectorIdentifier); sector != "" {
|
|
return sector, nil
|
|
}
|
|
host := ""
|
|
for _, uri := range c.RedirectURIs {
|
|
u, err := url.Parse(uri)
|
|
if err != nil || u.Hostname() == "" {
|
|
return "", ErrSectorRequired
|
|
}
|
|
h := strings.ToLower(u.Hostname())
|
|
if host != "" && h != host {
|
|
return "", ErrSectorRequired
|
|
}
|
|
host = h
|
|
}
|
|
if host == "" {
|
|
return "", ErrSectorRequired
|
|
}
|
|
return host, nil
|
|
}
|
|
return "", errors.New("wristband: unknown subject type")
|
|
}
|
|
|
|
// subjectFor allocates (or reads) the subject of userID at client: a random
|
|
// UUIDv4 per (user, sector), never derived from an account attribute
|
|
// (T-19.4-13).
|
|
func subjectFor(ctx context.Context, tx OIDCTx, client *OIDCClientRecord, userID uint) (string, error) {
|
|
sector, err := SubjectSector(client)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return tx.SubjectFor(ctx, userID, sector, uuid.NewV4().String())
|
|
}
|