Documentation
¶
Overview ¶
Package saml provides SAML 2.0 single sign-on support.
This file anchors the SAML module dependencies in go.mod so that go mod tidy does not remove them before the first feature PR lands.
Index ¶
- Variables
- type AuthnRequest
- type BuildAuthnRequestInput
- type BuildLogoutRequestInput
- type Clock
- type ErrorBucket
- type FakeClock
- type IdPRecord
- type IndexRecord
- type IssueInput
- type LogoutRequest
- type Provider
- type Reason
- type RedisStore
- func (s *RedisStore) ConsumeRequest(ctx context.Context, id string) (RequestRecord, bool, error)
- func (s *RedisStore) DeleteDomain(ctx context.Context, domain string) error
- func (s *RedisStore) DeleteIdP(ctx context.Context, tid string) error
- func (s *RedisStore) DeleteIndex(ctx context.Context, nameID string) error
- func (s *RedisStore) GetDomain(ctx context.Context, domain string) (string, error)
- func (s *RedisStore) GetIdP(ctx context.Context, tid string) (IdPRecord, error)
- func (s *RedisStore) GetIndex(ctx context.Context, nameID string) (IndexRecord, error)
- func (s *RedisStore) ListIdPs(ctx context.Context) ([]IdPRecord, error)
- func (s *RedisStore) MarkSeen(ctx context.Context, assertionID string, ttl time.Duration) (bool, error)
- func (s *RedisStore) PutDomain(ctx context.Context, domain, tid string) error
- func (s *RedisStore) PutIdP(ctx context.Context, rec IdPRecord) error
- func (s *RedisStore) PutIndex(ctx context.Context, nameID string, rec IndexRecord) error
- func (s *RedisStore) PutRequest(ctx context.Context, rec RequestRecord) error
- type RequestRecord
- type SessionBridge
- type Store
- type SystemClock
- type ValidateLogoutInput
- type ValidateResponseInput
- type VerifiedAssertion
- type VerifiedLogout
Constants ¶
This section is empty.
Variables ¶
var ( ErrRequestNotFound = errors.New("saml: request not found") ErrRequestReplay = errors.New("saml: request replay") ErrDestinationMismatch = errors.New("saml: destination mismatch") ErrIssuerMismatch = errors.New("saml: issuer mismatch") ErrStatusNotSuccess = errors.New("saml: status not success") ErrAudienceMismatch = errors.New("saml: audience mismatch") ErrSignatureInvalid = errors.New("saml: signature invalid") ErrDecryptFailed = errors.New("saml: decrypt failed") ErrAssertionSignatureInvalid = errors.New("saml: assertion signature invalid") ErrClockSkew = errors.New("saml: clock skew") ErrSubjectConfirmation = errors.New("saml: subject confirmation mismatch") ErrMissingAttribute = errors.New("saml: missing attribute") ErrTIDUnknown = errors.New("saml: tenant id unknown") ErrIDPMetadataStale = errors.New("saml: idp metadata stale") ErrAlgorithmDisallowed = errors.New("saml: algorithm disallowed") ErrXXE = errors.New("saml: xxe detected") ErrSignatureWrapping = errors.New("saml: signature wrapping detected") ErrInternal = errors.New("saml: internal error") )
var AllRejectReasons = [...]Reason{ ReasonRequestNotFound, ReasonRequestReplay, ReasonDestinationMismatch, ReasonIssuerMismatch, ReasonStatusNotSuccess, ReasonAudienceMismatch, ReasonSignatureInvalid, ReasonDecryptFailed, ReasonAssertionSignatureInvalid, ReasonClockSkew, ReasonSubjectConfirmationMismatch, ReasonMissingAttribute, ReasonTIDUnknown, ReasonIDPMetadataStale, ReasonAlgorithmDisallowed, ReasonXXE, ReasonSignatureWrapping, ReasonInternal, }
AllRejectReasons enumerates every rejection Reason (excludes ReasonOK). Used by tests to ensure exhaustive bucket coverage.
var ErrIdPNotFound = errors.New("saml: idp not found")
ErrIdPNotFound is returned when a requested IdP record does not exist.
Functions ¶
This section is empty.
Types ¶
type AuthnRequest ¶
AuthnRequest is the output of BuildAuthnRequest.
type BuildAuthnRequestInput ¶
type BuildAuthnRequestInput struct {
TenantID string
IdP IdPRecord
RelayState string
ACSURL string
RequestID string // caller-supplied, 20 random bytes hex
IssueInstant time.Time
ForceAuthn bool
NameIDFormat string
}
BuildAuthnRequestInput carries the parameters needed to build a SAML AuthnRequest.
type BuildLogoutRequestInput ¶
type BuildLogoutRequestInput struct {
TenantID string
IdP IdPRecord
NameID string
SessionIndex string
RequestID string
IssueInstant time.Time
NameIDFormat string
}
BuildLogoutRequestInput carries the parameters needed to build a SAML LogoutRequest.
type Clock ¶ added in v0.2.11
Clock abstracts time so that consumers can be tested without real sleeps.
type ErrorBucket ¶ added in v0.2.11
type ErrorBucket string
ErrorBucket classifies a rejection reason into one of four user-facing categories. The bucket determines the HTTP status and neutral message shown.
const ( BucketBadRequest ErrorBucket = "bad_request" BucketForbidden ErrorBucket = "forbidden" BucketNotConfigured ErrorBucket = "not_configured" BucketInternal ErrorBucket = "internal" )
type FakeClock ¶ added in v0.2.11
type FakeClock struct {
// contains filtered or unexported fields
}
FakeClock is a deterministic clock for tests. It starts at 2026-01-01T00:00:00Z and only moves forward via Advance.
func NewFakeClock ¶ added in v0.2.11
func NewFakeClock() *FakeClock
NewFakeClock returns a FakeClock starting at 2026-01-01T00:00:00Z.
type IdPRecord ¶
type IdPRecord struct {
TenantID string `msgpack:"tid"`
EntityID string `msgpack:"entity_id"`
SSOURL string `msgpack:"sso_url"`
SLOURL string `msgpack:"slo_url"`
SigningCerts [][]byte `msgpack:"signing_certs"`
EncryptionCerts [][]byte `msgpack:"encryption_certs"`
NameIDFormat string `msgpack:"name_id_format"`
AttributeMap map[string][]string `msgpack:"attribute_map"`
LastFetched time.Time `msgpack:"last_fetched"`
}
IdPRecord is the trust material for 1 tenant.
type IndexRecord ¶
type IndexRecord struct {
TenantID string `msgpack:"tid"`
Subject string `msgpack:"sub"`
SessionIndex string `msgpack:"session_index"`
NotOnOrAfter time.Time `msgpack:"not_on_or_after"`
}
IndexRecord maps a session index to a subject for SLO.
type IssueInput ¶
type IssueInput struct {
TenantID string
Subject string
ExternalSubject string
Email string
Name string
Roles []string
AMR []string // ["saml"]
AuthnInstant time.Time
}
IssueInput carries the claims extracted from a verified SAML assertion that are needed to issue a local idToken.
type LogoutRequest ¶
LogoutRequest is the output of BuildLogoutRequest.
type Provider ¶
type Provider interface {
BuildAuthnRequest(ctx context.Context, in BuildAuthnRequestInput) (*AuthnRequest, error)
ValidateResponse(ctx context.Context, in ValidateResponseInput) (*VerifiedAssertion, error)
BuildLogoutRequest(ctx context.Context, in BuildLogoutRequestInput) (*LogoutRequest, error)
ValidateLogoutMessage(ctx context.Context, in ValidateLogoutInput) (*VerifiedLogout, error)
SPMetadata(ctx context.Context) ([]byte, error)
ParseIdPMetadata(ctx context.Context, raw []byte) (*IdPRecord, error)
}
Provider abstracts the SAML protocol library. 6 methods, interface segregation keeps the dependency replaceable (DIP).
type Reason ¶ added in v0.2.11
type Reason string
Reason is a closed-set string identifying why a SAML login was rejected (or accepted). The values match HLD §19 exactly and must not be changed without a schema-version bump.
const ( // ReasonOK is the only accept-path value; every other Reason is a rejection. ReasonOK Reason = "ok" // 18 rejection reasons — HLD §19. ReasonRequestNotFound Reason = "request_not_found" ReasonRequestReplay Reason = "request_replay" ReasonDestinationMismatch Reason = "destination_mismatch" ReasonIssuerMismatch Reason = "issuer_mismatch" ReasonStatusNotSuccess Reason = "status_not_success" ReasonAudienceMismatch Reason = "audience_mismatch" ReasonSignatureInvalid Reason = "signature_invalid" ReasonDecryptFailed Reason = "decrypt_failed" ReasonAssertionSignatureInvalid Reason = "assertion_signature_invalid" ReasonClockSkew Reason = "clock_skew" ReasonSubjectConfirmationMismatch Reason = "subject_confirmation_mismatch" ReasonMissingAttribute Reason = "missing_attribute" ReasonTIDUnknown Reason = "tid_unknown" ReasonIDPMetadataStale Reason = "idp_metadata_stale" ReasonAlgorithmDisallowed Reason = "algorithm_disallowed" ReasonXXE Reason = "xxe_detected" ReasonSignatureWrapping Reason = "signature_wrapping_detected" ReasonInternal Reason = "internal_error" )
func (Reason) Bucket ¶ added in v0.2.11
func (r Reason) Bucket() ErrorBucket
Bucket maps a Reason to its ErrorBucket per HLD Appendix Q. ReasonOK returns an empty bucket because it is not an error.
type RedisStore ¶ added in v0.2.11
type RedisStore struct {
// contains filtered or unexported fields
}
RedisStore implements Store using Redis with Lua-script atomics and msgpack-encoded values.
func NewRedisStore ¶ added in v0.2.11
func NewRedisStore(rdb *redis.Client) *RedisStore
NewRedisStore returns a Store backed by the given Redis client.
func (*RedisStore) ConsumeRequest ¶ added in v0.2.11
func (s *RedisStore) ConsumeRequest(ctx context.Context, id string) (RequestRecord, bool, error)
ConsumeRequest atomically retrieves and deletes a pending request. The second return value is false when no record exists for the given ID.
func (*RedisStore) DeleteDomain ¶ added in v0.2.11
func (s *RedisStore) DeleteDomain(ctx context.Context, domain string) error
DeleteDomain removes a domain→tenant mapping.
func (*RedisStore) DeleteIdP ¶ added in v0.2.11
func (s *RedisStore) DeleteIdP(ctx context.Context, tid string) error
DeleteIdP removes the IdP record for the given tenant.
func (*RedisStore) DeleteIndex ¶ added in v0.2.11
func (s *RedisStore) DeleteIndex(ctx context.Context, nameID string) error
DeleteIndex removes the session index for a NameID.
func (*RedisStore) GetDomain ¶ added in v0.2.11
GetDomain retrieves the tenant ID for an email domain.
func (*RedisStore) GetIdP ¶ added in v0.2.11
GetIdP retrieves IdP trust material for a tenant. Returns ErrIdPNotFound when the key does not exist.
func (*RedisStore) GetIndex ¶ added in v0.2.11
func (s *RedisStore) GetIndex(ctx context.Context, nameID string) (IndexRecord, error)
GetIndex retrieves the session index for a NameID.
func (*RedisStore) ListIdPs ¶ added in v0.2.11
func (s *RedisStore) ListIdPs(ctx context.Context) ([]IdPRecord, error)
ListIdPs returns all stored IdP records using SCAN to avoid blocking.
func (*RedisStore) MarkSeen ¶ added in v0.2.11
func (s *RedisStore) MarkSeen(ctx context.Context, assertionID string, ttl time.Duration) (bool, error)
MarkSeen records an assertion ID to prevent replay. Returns true if the assertion was not previously seen (SETNX succeeded), false otherwise.
func (*RedisStore) PutDomain ¶ added in v0.2.11
func (s *RedisStore) PutDomain(ctx context.Context, domain, tid string) error
PutDomain maps an email domain to a tenant ID.
func (*RedisStore) PutIdP ¶ added in v0.2.11
func (s *RedisStore) PutIdP(ctx context.Context, rec IdPRecord) error
PutIdP stores IdP trust material with a 24-hour TTL.
func (*RedisStore) PutIndex ¶ added in v0.2.11
func (s *RedisStore) PutIndex(ctx context.Context, nameID string, rec IndexRecord) error
PutIndex stores a session index with a TTL derived from NotOnOrAfter.
func (*RedisStore) PutRequest ¶ added in v0.2.11
func (s *RedisStore) PutRequest(ctx context.Context, rec RequestRecord) error
PutRequest stores a pending AuthnRequest with NX semantics (fail if the ID already exists) and a 300-second TTL.
type RequestRecord ¶
type RequestRecord struct {
ID string `msgpack:"id"`
TenantID string `msgpack:"tid"`
RelayState string `msgpack:"relay_state"`
ACSURL string `msgpack:"acs_url"`
IssueInstant time.Time `msgpack:"issue_instant"`
}
RequestRecord tracks an in-flight SAML AuthnRequest.
type SessionBridge ¶
type SessionBridge interface {
Issue(ctx context.Context, in IssueInput) (idToken string, err error)
}
SessionBridge converts a verified SAML assertion into a local idToken, decoupling the SAML protocol layer from the session-issuance layer.
type Store ¶
type Store interface {
PutRequest(ctx context.Context, rec RequestRecord) error
ConsumeRequest(ctx context.Context, id string) (RequestRecord, bool, error)
PutIdP(ctx context.Context, rec IdPRecord) error
GetIdP(ctx context.Context, tid string) (IdPRecord, error)
ListIdPs(ctx context.Context) ([]IdPRecord, error)
DeleteIdP(ctx context.Context, tid string) error
PutIndex(ctx context.Context, nameID string, rec IndexRecord) error
GetIndex(ctx context.Context, nameID string) (IndexRecord, error)
DeleteIndex(ctx context.Context, nameID string) error
MarkSeen(ctx context.Context, assertionID string, ttl time.Duration) (bool, error)
PutDomain(ctx context.Context, domain, tid string) error
GetDomain(ctx context.Context, domain string) (string, error)
DeleteDomain(ctx context.Context, domain string) error
}
Store abstracts the persistence layer for SAML state. 13 methods across four record families: requests, IdPs, session indexes, and replay marks.
type SystemClock ¶ added in v0.2.11
type SystemClock struct{}
SystemClock delegates to the standard time package.
func (SystemClock) Now ¶ added in v0.2.11
func (SystemClock) Now() time.Time
Now returns the current wall-clock time.
type ValidateLogoutInput ¶
ValidateLogoutInput carries the parameters needed to validate an incoming SAML LogoutRequest or LogoutResponse.
type ValidateResponseInput ¶
type ValidateResponseInput struct {
TenantID string
IdP IdPRecord
RawBase64 string
RelayState string
Now time.Time
ExpectedInResponseTo string
ClockSkew time.Duration
}
ValidateResponseInput carries the parameters needed to validate a SAML Response.
type VerifiedAssertion ¶
type VerifiedAssertion struct {
AssertionID string `msgpack:"assertion_id"`
NameID string `msgpack:"name_id"`
NameIDFormat string `msgpack:"name_id_format"`
SessionIndex string `msgpack:"session_index"`
NotOnOrAfter time.Time `msgpack:"not_on_or_after"`
Attributes map[string][]string `msgpack:"attributes"`
IssuerEntityID string `msgpack:"issuer_entity_id"`
}
VerifiedAssertion holds the validated claims from a SAML Response.