saml

package
v0.2.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 25, 2026 License: MIT Imports: 13 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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")
)

AllRejectReasons enumerates every rejection Reason (excludes ReasonOK). Used by tests to ensure exhaustive bucket coverage.

View Source
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

type AuthnRequest struct {
	ID          string
	RedirectURL string // fully signed, ready to 302
}

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

type Clock interface {
	Now() time.Time
	Since(t time.Time) time.Duration
}

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.

func (*FakeClock) Advance added in v0.2.11

func (c *FakeClock) Advance(d time.Duration)

Advance moves the fake clock forward by d.

func (*FakeClock) Now added in v0.2.11

func (c *FakeClock) Now() time.Time

Now returns the fake current time.

func (*FakeClock) Since added in v0.2.11

func (c *FakeClock) Since(t time.Time) time.Duration

Since returns the duration elapsed on the fake clock since t.

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

type LogoutRequest struct {
	ID          string
	RedirectURL string
}

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.

func (Reason) IsAccept added in v0.2.11

func (r Reason) IsAccept() bool

IsAccept returns true only for ReasonOK.

func (Reason) String added in v0.2.11

func (r Reason) String() string

String returns the stable wire-format spelling of the reason.

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

func (s *RedisStore) GetDomain(ctx context.Context, domain string) (string, error)

GetDomain retrieves the tenant ID for an email domain.

func (*RedisStore) GetIdP added in v0.2.11

func (s *RedisStore) GetIdP(ctx context.Context, tid string) (IdPRecord, error)

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.

func (SystemClock) Since added in v0.2.11

func (SystemClock) Since(t time.Time) time.Duration

Since returns the elapsed duration since t.

type ValidateLogoutInput

type ValidateLogoutInput struct {
	TenantID   string
	IdP        IdPRecord
	RawMessage string
	Binding    string
}

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.

type VerifiedLogout

type VerifiedLogout struct {
	NameID       string
	SessionIndex string
	IsResponse   bool
	Status       string
}

VerifiedLogout is the validated output of a SAML LogoutRequest or LogoutResponse.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL