A Ruby gem for mimicking SAML Identity Provider (IdP) responses in test environments.
gem "lyrebird", group: :test# test/integration/saml_test.rb
class SAMLTest < ActionDispatch::IntegrationTest
test "consume creates a session" do
user = users(:alice)
response = Lyrebird::Response.build do |r|
r.issuer = "https://idp.example.com"
r.destination = saml_consume_url
r.recipient = saml_consume_url
r.audience = root_url
r.name_id = user.email
r.attributes do |a|
a.email = user.email
a.first_name = user.first_name
a.last_name = user.last_name
end
end
post saml_consume_path, params: { SAMLResponse: response.mimic }
assert_redirected_to dashboard_path
assert_equal user.id, session[:user_id]
end
endBuilds complete SAML responses with embedded assertions.
Defaults produce an SP-initiated response. See
IdP-initiated SSO to omit InResponseTo and
Destination.
# With defaults (SP-initiated)
response = Lyrebird::Response.build
# With options
response = Lyrebird::Response.build do |r|
r.issuer = "https://idp.example.com"
r.destination = "https://sp.example.com/acs"
r.in_response_to = "_request_id"
r.name_id = "user@example.com"
r.name_id_format = Lyrebird::NAMEID_EMAIL
r.recipient = "https://sp.example.com/acs"
r.audience = "https://sp.example.com"
r.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
r.not_before = Time.now.utc
r.valid_for = 300 # seconds
r.sign_with = idp_cert
r.encrypt_with = sp_cert
r.attributes do |a|
a.email = "user@example.com"
a.groups = ["admin", "users"]
end
endFor unsolicited (IdP-initiated) flows where there is no AuthnRequest,
set in_response_to and destination to nil to omit them from the
XML entirely:
response = Lyrebird::Response.build do |r|
r.in_response_to = nil
r.destination = nil
r.name_id = "user@example.com"
endresponse.mimic # Base64-encoded SAML response (for POST binding)
response.document # Nokogiri::XML::Document for inspectionSign both the assertion and response with an IdP certificate:
idp_cert = Lyrebird::Certificate.build
response = Lyrebird::Response.build(sign_with: idp_cert)Encrypt assertions using the SP's certificate so only the SP can decrypt them:
sp_cert = Lyrebird::Certificate.build
response = Lyrebird::Response.build(encrypt_with: sp_cert)Signing and encryption can be combined:
response = Lyrebird::Response.build do |r|
r.sign_with = idp_cert
r.encrypt_with = sp_cert
endLyrebird::NAMEID_EMAIL # emailAddress (default)
Lyrebird::NAMEID_PERSISTENT # persistent
Lyrebird::NAMEID_TRANSIENT # transient
Lyrebird::NAMEID_UNSPECIFIED # unspecifiedOverride defaults globally for all responses/assertions:
# test/test_helper.rb
Lyrebird.configure do |d|
d.issuer = "https://custom.example.com"
d.recipient = "https://custom.example.com/acs"
d.audience = "https://custom.example.com"
d.name_id = "default@example.com"
d.valid_for = 600 # 10 minutes
d.attributes = { role: "user" }
endDefaults are frozen after configuration for thread safety.
Generates and manages X.509 certificates for signing SAML responses.
# With defaults
cert = Lyrebird::Certificate.build
# With options
cert = Lyrebird::Certificate.build do |c|
c.bits = 4096 # RSA key size (default: 2048)
c.cn = "example.com" # Common Name
c.o = "Acme" # Organization
c.valid_for = 30 # Days (default: 365)
c.valid_until = Time.new(2999, 12, 31) # Overrides valid_for
endcert = Lyrebird::Certificate.load(
key_pem: File.read("private_key.pem"),
x509_pem: File.read("certificate.pem")
)cert.key # OpenSSL::PKey::RSA private key
cert.x509 # OpenSSL::X509::Certificate object
cert.key_pem # PEM-encoded private key
cert.x509_pem # PEM-encoded certificate
cert.sign(data) # Sign data with RSA-SHA256
cert.base64 # Base64-encoded certificate (for SAML metadata)
cert.fingerprint # SHA256 fingerprint