saml

package module
v0.0.0-...-e0e82b4 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 19 Imported by: 0

README

go-ruby-saml/saml

saml — go-ruby-saml

ci Docs License Go coverage

A pure-Go (no cgo), MRI-faithful port of Ruby's ruby-saml gem — the SAML 2.0 Service Provider toolkit exposed as OneLogin::RubySaml.

It mirrors the ruby-saml surface while standing on the pure-Go SAML ecosystem — github.com/crewjam/saml for the SAML schema types and github.com/russellhaering/goxmldsig for XML digital-signature validation — rather than reimplementing XML signing or the SAML schema from scratch. It is the SAML backend for go-embedded-ruby but is a standalone, reusable module with no dependency on the Ruby runtime.

Ruby-faithful surface

ruby-saml (OneLogin::RubySaml) this module
Settings Settingsidp_sso_target_url, idp_cert / idp_cert_fingerprint, sp_entity_id, assertion_consumer_service_url, name_identifier_format, private_key / certificate, want_assertions_signed, signature_method, digest_method
Authrequest#create Authrequest.Create — SP-initiated SSO redirect URL + deflate+base64 SAMLRequest
Response#is_valid?, #name_id, #attributes, #sessionindex, #status_code, #issuers Response
Metadata#generate Metadata.Generate — SP metadata XML
IdpMetadataParser#parse IdpMetadataParser.Parse
Logoutrequest#create, SloLogoutresponse#create Logoutrequest, SloLogoutresponse — SP-initiated SLO
ValidationError ValidationError

Response validation

Response.IsValid mirrors ruby-saml's is_valid?, collecting each failure into Errors:

  • Signature — verified against the configured idp_cert or its idp_cert_fingerprint (SHA-1 / SHA-256), via goxmldsig.
  • ConditionsNotBefore / NotOnOrAfter with allowed_clock_drift.
  • AudienceAudienceRestriction matches sp_entity_id.
  • Destination — matches assertion_consumer_service_url.
  • InResponseTo — matches the originating AuthnRequest ID.
  • Issuer — matches idp_entity_id.
  • Status / Assertion countSuccess, exactly one assertion.

All time-dependent checks read an injectable clock, so validation is deterministic and timezone-independent (the suite runs under TZ=UTC).

Example

settings := &saml.Settings{
    IdPSSOTargetURL:             "https://idp.example.com/sso",
    IdPCert:                     idpCertPEM,
    SPEntityID:                  "https://sp.example.com/metadata",
    AssertionConsumerServiceURL: "https://sp.example.com/acs",
    NameIdentifierFormat:        saml.NameIDFormatEmail,
}

// SP-initiated SSO.
authn := &saml.Authrequest{}
redirect, _ := authn.Create(settings, "" /* relayState */)

// Consume the IdP response.
resp, _ := saml.NewResponse(base64SAMLResponse, settings)
resp.ExpectedInResponseTo = authn.UUID
if resp.IsValid() {
    fmt.Println(resp.NameID(), resp.Attributes())
}

Testing

Every fixture — RSA keys, X.509 certificates, and signed SAML documents (valid, tampered, expired, wrong-audience, wrong-destination, wrong-issuer, wrong-InResponseTo) — is embedded; no network is used. The suite runs with -race and enforces 100% statement coverage across three OSes and the six supported 64-bit architectures (amd64, arm64, riscv64, loong64, ppc64le, and big-endian s390x).

GOWORK=off TZ=UTC go test -race -cover ./...

License

BSD-3-Clause — see LICENSE. Copyright (c) 2026, the go-ruby-saml/saml authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package saml is a pure-Go (CGO=0), MRI-faithful port of Ruby's ruby-saml gem (OneLogin::RubySaml), the SAML 2.0 Service Provider toolkit.

It mirrors the surface of ruby-saml while building on the pure-Go SAML ecosystem — github.com/crewjam/saml for the SAML schema types and github.com/russellhaering/goxmldsig for XML digital-signature validation — rather than reimplementing XML signing or the SAML schema from scratch.

Surface

  • Settings mirrors OneLogin::RubySaml::Settings: IdP and SP configuration (idp_sso_target_url, idp_cert / idp_cert_fingerprint, sp_entity_id, assertion_consumer_service_url, name_identifier_format, private_key / certificate) plus security options (want_assertions_signed, signature_method, digest_method).
  • Authrequest mirrors OneLogin::RubySaml::Authrequest: Create builds the SP-initiated SSO redirect URL and the deflate+base64 SAMLRequest.
  • Response mirrors OneLogin::RubySaml::Response: IsValid validates the XML signature (via the IdP certificate or its fingerprint), the Conditions window (NotBefore / NotOnOrAfter with clock drift), the audience, the destination, the InResponseTo correlation and the issuer; NameID, Attributes (multi-valued), SessionIndex, StatusCode and Issuers extract the assertion contents.
  • Metadata mirrors OneLogin::RubySaml::Metadata (SP metadata generation) and IdpMetadataParser ingests IdP metadata into a Settings.
  • Logoutrequest and SloLogoutresponse implement SP-initiated Single Logout.
  • ValidationError mirrors OneLogin::RubySaml::ValidationError.

All time-dependent validation reads the clock through an injectable seam so the Conditions checks are deterministic and timezone-independent.

Index

Constants

View Source
const (
	HTTPRedirectBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
	HTTPPostBinding     = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"

	NameIDFormatEmail       = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
	NameIDFormatTransient   = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
	NameIDFormatPersistent  = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
	NameIDFormatUnspecified = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"

	SignatureMethodRSASHA1   = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
	SignatureMethodRSASHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
	DigestMethodSHA1         = "http://www.w3.org/2000/09/xmldsig#sha1"
	DigestMethodSHA256       = "http://www.w3.org/2001/04/xmlenc#sha256"
)

Common SAML binding, format and algorithm identifiers, mirroring the constants ruby-saml exposes.

Variables

This section is empty.

Functions

func DecodeSAMLRequest

func DecodeSAMLRequest(encoded string) ([]byte, error)

DecodeSAMLRequest reverses SAMLRequest (base64 + inflate), returning the AuthnRequest XML. It is exposed so callers (and tests) can verify the encoded payload round-trips to well-formed XML.

Types

type Authrequest

type Authrequest struct {
	// UUID holds the ID of the most recently created request, matching the
	// ruby-saml attribute of the same name (used to correlate InResponseTo).
	UUID string
}

Authrequest mirrors OneLogin::RubySaml::Authrequest, the builder for an SP-initiated SSO AuthnRequest.

func (*Authrequest) Create

func (a *Authrequest) Create(s *Settings, relayState string) (string, error)

Create mirrors OneLogin::RubySaml::Authrequest#create: it returns the full IdP redirect URL carrying the SAMLRequest (and RelayState, when non-empty).

func (*Authrequest) SAMLRequest

func (a *Authrequest) SAMLRequest(s *Settings) (string, error)

SAMLRequest returns the deflate+base64 encoded AuthnRequest, the value placed in the SAMLRequest query parameter of the HTTP-Redirect binding.

type IdpMetadataParser

type IdpMetadataParser struct{}

IdpMetadataParser mirrors OneLogin::RubySaml::IdpMetadataParser: it ingests an IdP EntityDescriptor and populates the IdP-facing fields of a Settings.

func (IdpMetadataParser) Parse

func (IdpMetadataParser) Parse(metadataXML []byte) (*Settings, error)

Parse reads IdP metadata XML and returns a Settings with idp_entity_id, idp_sso_target_url, idp_slo_target_url and idp_cert filled in from the first IDPSSODescriptor.

type Logoutrequest

type Logoutrequest struct {
	// UUID holds the ID of the most recently created request.
	UUID string
}

Logoutrequest mirrors OneLogin::RubySaml::Logoutrequest, the builder for an SP-initiated Single Logout request.

func (*Logoutrequest) Create

func (l *Logoutrequest) Create(s *Settings, nameID, relayState string) (string, error)

Create mirrors OneLogin::RubySaml::Logoutrequest#create: it returns the IdP SLO redirect URL carrying the deflate+base64 SAMLRequest.

type Metadata

type Metadata struct{}

Metadata mirrors OneLogin::RubySaml::Metadata, the generator for SP metadata.

func (Metadata) Generate

func (Metadata) Generate(s *Settings) ([]byte, error)

Generate returns the SP EntityDescriptor XML for the given settings, matching the document ruby-saml's Metadata#generate produces (SPSSODescriptor with the Assertion Consumer Service, NameIDFormat and, when configured, a signing key and WantAssertionsSigned).

type Response

type Response struct {

	// ExpectedInResponseTo, when set, is compared against the response's
	// InResponseTo attribute during validation (mirrors ruby-saml's
	// matches_request_id option).
	ExpectedInResponseTo string

	// Errors accumulates the validation failures found by IsValid, mirroring
	// ruby-saml's @errors array.
	Errors []string
	// contains filtered or unexported fields
}

Response mirrors OneLogin::RubySaml::Response, the parsed and validatable SAML 2.0 authentication response received at the SP Assertion Consumer Service.

func NewResponse

func NewResponse(input string, settings *Settings) (*Response, error)

NewResponse parses a SAML Response. The input is either the base64-encoded response as delivered on the HTTP-POST binding, or the raw XML document.

func (*Response) Attributes

func (r *Response) Attributes() map[string][]string

Attributes returns the assertion's attributes as a multi-valued map, mirroring ruby-saml's OneLogin::RubySaml::Attributes (each key maps to all its values).

func (*Response) Destination

func (r *Response) Destination() string

Destination returns the Response Destination attribute.

func (*Response) InResponseTo

func (r *Response) InResponseTo() string

InResponseTo returns the Response InResponseTo attribute.

func (*Response) IsValid

func (r *Response) IsValid() bool

IsValid mirrors OneLogin::RubySaml::Response#is_valid?. It runs every validation, appends each failure to Errors, and reports whether the response is valid. It never depends on the local timezone: all comparisons use UTC instants read through the injectable clock.

func (*Response) Issuers

func (r *Response) Issuers() []string

Issuers returns the unique issuer values from the Response and its Assertion.

func (*Response) NameID

func (r *Response) NameID() string

NameID returns the value of the assertion's Subject/NameID.

func (*Response) SessionIndex

func (r *Response) SessionIndex() string

SessionIndex returns the AuthnStatement SessionIndex, if present.

func (*Response) StatusCode

func (r *Response) StatusCode() string

StatusCode returns the top-level samlp:Status/StatusCode Value.

func (*Response) Validate

func (r *Response) Validate() error

Validate is the fail-fast counterpart of IsValid: it returns the first validation error as a *ValidationError, or nil when the response is valid.

type Settings

type Settings struct {
	// IdP configuration.
	IdPEntityID                 string // idp_entity_id (expected issuer)
	IdPSSOTargetURL             string // idp_sso_target_url
	IdPSLOTargetURL             string // idp_slo_target_url
	IdPCert                     string // idp_cert (PEM)
	IdPCertFingerprint          string // idp_cert_fingerprint (hex, ':'-separated allowed)
	IdPCertFingerprintAlgorithm string // sha1 (default) or sha256

	// SP configuration.
	SPEntityID                  string // sp_entity_id / issuer
	AssertionConsumerServiceURL string // assertion_consumer_service_url
	NameIdentifierFormat        string // name_identifier_format
	Certificate                 string // certificate (PEM)
	PrivateKey                  string // private_key (PEM)

	// Security options (subset of ruby-saml's security hash).
	WantAssertionsSigned bool
	SignatureMethod      string
	DigestMethod         string

	// AllowedClockDrift is the tolerance applied to condition timestamps,
	// mirroring ruby-saml's allowed_clock_drift.
	AllowedClockDrift time.Duration

	// Audience is the expected audience URI; defaults to SPEntityID when blank.
	Audience string
}

Settings mirrors OneLogin::RubySaml::Settings: the SP and IdP configuration that drives request generation and response validation.

type SloLogoutresponse

type SloLogoutresponse struct {
	// UUID holds the ID of the most recently created response.
	UUID string
}

SloLogoutresponse mirrors OneLogin::RubySaml::SloLogoutresponse, the builder for the SP's response to an IdP-initiated logout request.

func (*SloLogoutresponse) Create

func (l *SloLogoutresponse) Create(s *Settings, requestID, relayState string) (string, error)

Create mirrors OneLogin::RubySaml::SloLogoutresponse#create: it returns the IdP SLO redirect URL carrying the deflate+base64 SAMLResponse.

type ValidationError

type ValidationError struct {
	Message string
}

ValidationError mirrors OneLogin::RubySaml::ValidationError, the exception ruby-saml raises when a SAML document fails a validation check. In this pure-Go port it is a plain error value; callers inspect Message (or use it via the standard error interface). It corresponds to the single message that aborts a fail-fast validation in the Ruby gem.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error implements the error interface.

Jump to

Keyboard shortcuts

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