samlr 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of samlr might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/LICENSE +176 -0
- data/README.md +182 -0
- data/Rakefile +12 -0
- data/bin/samlr +46 -0
- data/config/schemas/XMLSchema.xsd +2534 -0
- data/config/schemas/saml-schema-assertion-2.0.xsd +283 -0
- data/config/schemas/saml-schema-metadata-2.0.xsd +337 -0
- data/config/schemas/saml-schema-protocol-2.0.xsd +302 -0
- data/config/schemas/xenc-schema.xsd +146 -0
- data/config/schemas/xml.xsd +287 -0
- data/config/schemas/xmldsig-core-schema.xsd +318 -0
- data/lib/samlr.rb +52 -0
- data/lib/samlr/assertion.rb +91 -0
- data/lib/samlr/certificate.rb +23 -0
- data/lib/samlr/command.rb +41 -0
- data/lib/samlr/condition.rb +31 -0
- data/lib/samlr/errors.rb +22 -0
- data/lib/samlr/fingerprint.rb +44 -0
- data/lib/samlr/logout_request.rb +7 -0
- data/lib/samlr/reference.rb +32 -0
- data/lib/samlr/request.rb +37 -0
- data/lib/samlr/response.rb +68 -0
- data/lib/samlr/signature.rb +129 -0
- data/lib/samlr/tools.rb +108 -0
- data/lib/samlr/tools/certificate_builder.rb +74 -0
- data/lib/samlr/tools/logout_request_builder.rb +27 -0
- data/lib/samlr/tools/metadata_builder.rb +41 -0
- data/lib/samlr/tools/request_builder.rb +44 -0
- data/lib/samlr/tools/response_builder.rb +157 -0
- data/lib/samlr/tools/timestamp.rb +26 -0
- data/samlr.gemspec +19 -0
- data/test/fixtures/default_samlr_certificate.pem +11 -0
- data/test/fixtures/default_samlr_private_key.pem +9 -0
- data/test/fixtures/no_cert_response.xml +2 -0
- data/test/fixtures/sample_metadata.xml +7 -0
- data/test/fixtures/sample_response.xml +2 -0
- data/test/test_helper.rb +55 -0
- data/test/unit/test_assertion.rb +54 -0
- data/test/unit/test_condition.rb +71 -0
- data/test/unit/test_fingerprint.rb +45 -0
- data/test/unit/test_logout_request.rb +39 -0
- data/test/unit/test_reference.rb +32 -0
- data/test/unit/test_request.rb +34 -0
- data/test/unit/test_response.rb +94 -0
- data/test/unit/test_response_scenarios.rb +111 -0
- data/test/unit/test_signature.rb +54 -0
- data/test/unit/test_timestamp.rb +58 -0
- data/test/unit/test_tools.rb +100 -0
- data/test/unit/tools/test_certificate_builder.rb +41 -0
- data/test/unit/tools/test_logout_request_builder.rb +26 -0
- data/test/unit/tools/test_metadata_builder.rb +26 -0
- data/test/unit/tools/test_request_builder.rb +35 -0
- data/test/unit/tools/test_response_builder.rb +19 -0
- metadata +184 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module Samlr
|
2
|
+
module Tools
|
3
|
+
module Timestamp
|
4
|
+
|
5
|
+
# Generate a current timestamp in ISO8601 format
|
6
|
+
def self.stamp(time = Time.now)
|
7
|
+
time.utc.iso8601
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.parse(value)
|
11
|
+
Time.iso8601(value)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Is the current time on or after the given time?
|
15
|
+
def self.not_on_or_after?(time)
|
16
|
+
Time.now.to_i <= (time.to_i + Samlr.jitter.to_i)
|
17
|
+
end
|
18
|
+
|
19
|
+
# True when the current time is not before the given time
|
20
|
+
def self.not_before?(time)
|
21
|
+
Time.now.to_i >= (time.to_i - Samlr.jitter.to_i)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/samlr.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Gem::Specification.new "samlr", "2.0.0" do |s|
|
2
|
+
s.summary = "Ruby tools for SAML"
|
3
|
+
s.description = "Helps you implement a SAML SP"
|
4
|
+
s.authors = ["Morten Primdahl"]
|
5
|
+
s.email = "primdahl@me.com"
|
6
|
+
s.homepage = "http://github.com/zendesk/samlr"
|
7
|
+
s.files = `git ls-files`.split("\n")
|
8
|
+
s.license = "Apache License Version 2.0"
|
9
|
+
|
10
|
+
s.add_runtime_dependency("nokogiri", ">= 1.5.5")
|
11
|
+
s.add_runtime_dependency("uuidtools", ">= 2.1.3")
|
12
|
+
s.add_runtime_dependency("trollop", ">= 1.16.2")
|
13
|
+
|
14
|
+
s.add_development_dependency("rake")
|
15
|
+
s.add_development_dependency("bundler")
|
16
|
+
s.add_development_dependency("minitest")
|
17
|
+
|
18
|
+
s.executables << "samlr"
|
19
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIBjTCCATegAwIBAgIBATANBgkqhkiG9w0BAQUFADBPMQswCQYDVQQGEwJVUzEU
|
3
|
+
MBIGA1UECgwLZXhhbXBsZS5vcmcxHTAbBgNVBAsMFFphbWwgUmVzcG9uc2VCdWls
|
4
|
+
ZGVyMQswCQYDVQQDDAJDQTAeFw0xMjA4MDgwMjAxMDlaFw0zMjA4MDMwMjAxMTRa
|
5
|
+
ME8xCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtleGFtcGxlLm9yZzEdMBsGA1UECwwU
|
6
|
+
WmFtbCBSZXNwb25zZUJ1aWxkZXIxCzAJBgNVBAMMAkNBMFwwDQYJKoZIhvcNAQEB
|
7
|
+
BQADSwAwSAJBALb9pPmyHrbZJMDLLkVsHzzXvP7DFcPiYdaNU50l5znRr8ZGhwRZ
|
8
|
+
FAwKroOxXwhK5e9lz06C+kGqnL1v10h1BEUCAwEAATANBgkqhkiG9w0BAQUFAANB
|
9
|
+
AKU10RznL2p7xRhO9vOh0CY+gWYmT2kbkLTVRYLApghQFAW8EzIHC/NggfEHM554
|
10
|
+
ykzbbPwjSvM7cRBBDHYuWoY=
|
11
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,9 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIBOwIBAAJBALb9pPmyHrbZJMDLLkVsHzzXvP7DFcPiYdaNU50l5znRr8ZGhwRZ
|
3
|
+
FAwKroOxXwhK5e9lz06C+kGqnL1v10h1BEUCAwEAAQJADZ4QgdhkerzsBEDaf6YN
|
4
|
+
KQzw7pB79SjKmRnJSB+C9oVo8SE5cDyaomwCCnnYFJm8ACJzCVXhA0eElTtWvkqT
|
5
|
+
wQIhAN+rx2zckCPEBH+pxJ6HOkmDG28EUOP3J2llTUA/zArxAiEA0XCgPzCnWdcH
|
6
|
+
eJN8z7QLLEGJ/JFTZpgr959RQYuBBpUCIEhrEsehZh3eYmJ/MgTt3aZdh61bJWGZ
|
7
|
+
7S3HucpanZLRAiEAzucLd8Fx4f/aYpSZXXtI+lx4m6lZkeXMsaCTHkRZn40CIQDX
|
8
|
+
fYUO1wQNBw/mXihtz+jal+kCP7xu0zrOhTQR+UXL9A==
|
9
|
+
-----END RSA PRIVATE KEY-----
|
@@ -0,0 +1,2 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="samlr-a61f02dc-c4df-11e2-ac02-a4b197fffe98" InResponseTo="samlr-a61c3746-c4df-11e2-ac02-a4b197fffe98" Version="2.0" IssueInstant="2013-05-25T02:06:01Z" Destination="https://example.org/saml/endpoint"><saml:Issuer>ResponseBuilder IdP</saml:Issuer><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#samlr-a61f02dc-c4df-11e2-ac02-a4b197fffe98"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><InclusiveNamespaces xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="#default samlp saml ds xs xsi"/></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>4Eqkol24EsDrPhY9crTX+TJ8SNM=</DigestValue></Reference></SignedInfo><SignatureValue>hXwitIsw2ZY9/vQCY9feMYf0jn22VdSBDS6ai7F9Ay8QbWQ+R6WI9+k3WatAXMzxnz8lrF3XhL8HoQPac4RCeA==</SignatureValue></Signature><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status><saml:Assertion ID="samlr-a61f0872-c4df-11e2-ac02-a4b197fffe98" IssueInstant="2013-05-25T02:06:01Z" Version="2.0"><saml:Issuer>ResponseBuilder IdP</saml:Issuer><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#samlr-a61f0872-c4df-11e2-ac02-a4b197fffe98"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><InclusiveNamespaces xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="#default samlp saml ds xs xsi"/></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>3Hd1NIIArmJNLnxjTYG9YY5T1Bw=</DigestValue></Reference></SignedInfo><SignatureValue>NFL3r1Fu0PnKQVUsG6o0l+qjYydGlxTR9w5h06ef+85EjFR4YnJJ7p5p0vSeFuOyvoJZ8OmfbJy9h+1Vbmveig==</SignatureValue></Signature><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">someone@example.org</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData InResponseTo="samlr-a61c3746-c4df-11e2-ac02-a4b197fffe98" NotOnOrAfter="2013-05-25T02:07:01Z" Recipient="https://example.org/saml/endpoint"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2013-05-25T02:05:01Z" NotOnOrAfter="2013-05-25T02:07:01Z"><saml:AudienceRestriction><saml:Audience>example.org</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2013-05-25T02:06:01Z" SessionIndex="samlr-a61f0872-c4df-11e2-ac02-a4b197fffe98"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement></saml:Assertion></samlp:Response>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://sp.example.com/saml2">
|
3
|
+
<md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
4
|
+
<md:NameIDFormat>identity_format</md:NameIDFormat>
|
5
|
+
<md:AssertionConsumerService index="0" Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://support.sp.example.com/"/>
|
6
|
+
</md:SPSSODescriptor>
|
7
|
+
</md:EntityDescriptor>
|
@@ -0,0 +1,2 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="samlr-26a4eb6c-e271-11e1-a29c-000a27020041" InResponseTo="samlr-26a4e82e-e271-11e1-a29c-000a27020041" Version="2.0" IssueInstant="2012-08-09T22:25:40Z" Destination="https://example.org/saml/endpoint"><saml:Issuer>ResponseBuilder IdP</saml:Issuer><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#samlr-26a4eb6c-e271-11e1-a29c-000a27020041"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><InclusiveNamespaces xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="#default samlp saml ds xs xsi"/></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>n4k8S7PsriEcj2en2fXnMwgWruU=</DigestValue></Reference></SignedInfo><SignatureValue>pNUUwVRL92E5tFk1+p77geJqV62PuaG5x27Dn+Xi4ff18NSMLb/XmbL2PJIakYOtwMuwQiNX9qioY3Pt1o/CMw==</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIBjTCCATegAwIBAgIBATANBgkqhkiG9w0BAQUFADBPMQswCQYDVQQGEwJVUzEUMBIGA1UECgwLZXhhbXBsZS5vcmcxHTAbBgNVBAsMFFphbWwgUmVzcG9uc2VCdWlsZGVyMQswCQYDVQQDDAJDQTAeFw0xMjA4MDgwMjAxMDlaFw0zMjA4MDMwMjAxMTRaME8xCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtleGFtcGxlLm9yZzEdMBsGA1UECwwUWmFtbCBSZXNwb25zZUJ1aWxkZXIxCzAJBgNVBAMMAkNBMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALb9pPmyHrbZJMDLLkVsHzzXvP7DFcPiYdaNU50l5znRr8ZGhwRZFAwKroOxXwhK5e9lz06C+kGqnL1v10h1BEUCAwEAATANBgkqhkiG9w0BAQUFAANBAKU10RznL2p7xRhO9vOh0CY+gWYmT2kbkLTVRYLApghQFAW8EzIHC/NggfEHM554ykzbbPwjSvM7cRBBDHYuWoY=</X509Certificate></X509Data></KeyInfo></Signature><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status><saml:Assertion ID="samlr-26a4ed1a-e271-11e1-a29c-000a27020041" IssueInstant="2012-08-09T22:25:40Z" Version="2.0"><saml:Issuer>ResponseBuilder IdP</saml:Issuer><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/><Reference URI="#samlr-26a4ed1a-e271-11e1-a29c-000a27020041"><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"><InclusiveNamespaces xmlns="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="#default samlp saml ds xs xsi"/></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><DigestValue>f6uCnv1PdZqKp/0dz6YtfSFaiHQ=</DigestValue></Reference></SignedInfo><SignatureValue>VqW1I4hlWN3ciKjZ1WUaouvita1e7CldZB0UQKtVrnIdO+6XI7R3i12jfDAKmclQ1E6VrNIdV4/D5eGTRjdTjQ==</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIBjTCCATegAwIBAgIBATANBgkqhkiG9w0BAQUFADBPMQswCQYDVQQGEwJVUzEUMBIGA1UECgwLZXhhbXBsZS5vcmcxHTAbBgNVBAsMFFphbWwgUmVzcG9uc2VCdWlsZGVyMQswCQYDVQQDDAJDQTAeFw0xMjA4MDgwMjAxMDlaFw0zMjA4MDMwMjAxMTRaME8xCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtleGFtcGxlLm9yZzEdMBsGA1UECwwUWmFtbCBSZXNwb25zZUJ1aWxkZXIxCzAJBgNVBAMMAkNBMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALb9pPmyHrbZJMDLLkVsHzzXvP7DFcPiYdaNU50l5znRr8ZGhwRZFAwKroOxXwhK5e9lz06C+kGqnL1v10h1BEUCAwEAATANBgkqhkiG9w0BAQUFAANBAKU10RznL2p7xRhO9vOh0CY+gWYmT2kbkLTVRYLApghQFAW8EzIHC/NggfEHM554ykzbbPwjSvM7cRBBDHYuWoY=</X509Certificate></X509Data></KeyInfo></Signature><saml:Subject><saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">someone@example.org</saml:NameID><saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><saml:SubjectConfirmationData InResponseTo="samlr-26a4e82e-e271-11e1-a29c-000a27020041" NotOnOrAfter="2012-08-09T22:26:40Z" Recipient="https://example.org/saml/endpoint"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore="2012-08-09T22:24:40Z" NotOnOrAfter="2012-08-09T22:26:40Z"><saml:AudienceRestriction><saml:Audience>example.org</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant="2012-08-09T22:25:40Z" SessionIndex="samlr-26a4ed1a-e271-11e1-a29c-000a27020041"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement></saml:Assertion></samlp:Response>
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require "bundler"
|
2
|
+
require "minitest/autorun"
|
3
|
+
|
4
|
+
Bundler.require
|
5
|
+
|
6
|
+
require "time"
|
7
|
+
require "base64"
|
8
|
+
require "tmpdir"
|
9
|
+
|
10
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
11
|
+
|
12
|
+
require "samlr"
|
13
|
+
require "samlr/tools/response_builder"
|
14
|
+
require "samlr/tools/certificate_builder"
|
15
|
+
|
16
|
+
FIXTURE_PATH = File.join(File.dirname(__FILE__), "fixtures")
|
17
|
+
TEST_CERTIFICATE = Samlr::Tools::CertificateBuilder.load(FIXTURE_PATH, "default_samlr")
|
18
|
+
|
19
|
+
def saml_response_document(options = {})
|
20
|
+
# Test defaults
|
21
|
+
options = {
|
22
|
+
:destination => "https://example.org/saml/endpoint",
|
23
|
+
:in_response_to => Samlr::Tools.uuid,
|
24
|
+
:name_id => "someone@example.org",
|
25
|
+
:audience => "example.org",
|
26
|
+
:not_on_or_after => Samlr::Tools::Timestamp.stamp(Time.now + 60),
|
27
|
+
:not_before => Samlr::Tools::Timestamp.stamp(Time.now - 60),
|
28
|
+
:response_id => Samlr::Tools.uuid
|
29
|
+
}.merge(options)
|
30
|
+
|
31
|
+
Samlr::Tools::ResponseBuilder.build(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def saml_response(options = {})
|
35
|
+
fingerprint = options[:fingerprint]
|
36
|
+
fingerprint ||= options[:certificate] ? Samlr::Fingerprint.x509(options[:certificate].x509) : nil
|
37
|
+
|
38
|
+
Samlr::Response.new(saml_response_document(options), :fingerprint => fingerprint)
|
39
|
+
end
|
40
|
+
|
41
|
+
# A response that never changes. Useful for digest checks etc.
|
42
|
+
def fixed_saml_response(options = {})
|
43
|
+
options = {
|
44
|
+
:certificate => TEST_CERTIFICATE,
|
45
|
+
:issue_instant => Samlr::Tools::Timestamp.stamp(Time.at(1344379365)),
|
46
|
+
:response_id => "samlr123",
|
47
|
+
:assertion_id => "samlr456",
|
48
|
+
:in_response_to => "samlr789",
|
49
|
+
:attributes => { "tags" => "mean horse", "things" => [ "one", "two", "three" ] },
|
50
|
+
:not_on_or_after => Samlr::Tools::Timestamp.stamp(Time.at(1344379365 + 60)),
|
51
|
+
:not_before => Samlr::Tools::Timestamp.stamp(Time.at(1344379365 - 60))
|
52
|
+
}.merge(options)
|
53
|
+
|
54
|
+
saml_response(options)
|
55
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require File.expand_path("test/test_helper")
|
2
|
+
require "time"
|
3
|
+
|
4
|
+
describe Samlr::Assertion do
|
5
|
+
subject { fixed_saml_response.assertion }
|
6
|
+
|
7
|
+
describe "#skip_conditions?" do
|
8
|
+
it "reflects the passed options" do
|
9
|
+
assert Samlr::Assertion.new(nil, :skip_conditions => true).send(:skip_conditions?)
|
10
|
+
refute Samlr::Assertion.new(nil, :skip_conditions => false).send(:skip_conditions?)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#attributes" do
|
15
|
+
it "returns a hash of assertion attributes" do
|
16
|
+
assert_equal subject.attributes[:tags], "mean horse"
|
17
|
+
assert_equal subject.attributes["tags"], "mean horse"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "turns multiple attribute values into an array" do
|
21
|
+
assert_equal subject.attributes["things"].sort, [ "one", "two", "three" ].sort
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#name_id" do
|
26
|
+
it "returns the body of the NameID element" do
|
27
|
+
assert_equal "someone@example.org", subject.name_id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#verify!" do
|
32
|
+
before do
|
33
|
+
@unsatisfied_condition = Samlr::Condition.new("NotBefore" => Samlr::Tools::Timestamp.stamp(Time.now + 60))
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "when conditions are not met" do
|
37
|
+
it "should raise" do
|
38
|
+
subject.stub(:conditions, @unsatisfied_condition) do
|
39
|
+
assert_raises(Samlr::ConditionsError) { subject.verify! }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "and conditions are to be skipped" do
|
44
|
+
it "should pass" do
|
45
|
+
subject.stub(:skip_conditions?, true) do
|
46
|
+
subject.stub(:conditions, @unsatisfied_condition) do
|
47
|
+
assert subject.verify!
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.expand_path("test/test_helper")
|
2
|
+
|
3
|
+
def condition(before, after)
|
4
|
+
Samlr::Condition.new(
|
5
|
+
"NotBefore" => before ? before.utc.iso8601 : nil,
|
6
|
+
"NotOnOrAfter" => after ? after.utc.iso8601 : nil
|
7
|
+
)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe Samlr::Condition do
|
11
|
+
before do
|
12
|
+
@not_before = (Time.now - 10*60)
|
13
|
+
@not_after = (Time.now + 10*60)
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "verify!" do
|
17
|
+
describe "when the lower time has not been met" do
|
18
|
+
before { @not_before = (Time.now + 5*60) }
|
19
|
+
subject { condition(@not_before, @not_after) }
|
20
|
+
|
21
|
+
it "raises an exception" do
|
22
|
+
assert subject.not_on_or_after_satisfied?
|
23
|
+
refute subject.not_before_satisfied?
|
24
|
+
|
25
|
+
begin
|
26
|
+
subject.verify!
|
27
|
+
flunk "Expected exception"
|
28
|
+
rescue Samlr::ConditionsError => e
|
29
|
+
assert_match /Not before/, e.message
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "when the upper time has been exceeded" do
|
35
|
+
before { @not_after = (Time.now - 5*60) }
|
36
|
+
subject { condition(@not_before, @not_after) }
|
37
|
+
|
38
|
+
it "raises an exception" do
|
39
|
+
refute subject.not_on_or_after_satisfied?
|
40
|
+
assert subject.not_before_satisfied?
|
41
|
+
|
42
|
+
begin
|
43
|
+
subject.verify!
|
44
|
+
flunk "Expected exception"
|
45
|
+
rescue Samlr::ConditionsError => e
|
46
|
+
assert_match /Not on or after/, e.message
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "when no time boundary has been exeeded" do
|
52
|
+
subject { condition(@not_before, @not_after) }
|
53
|
+
|
54
|
+
it "returns true" do
|
55
|
+
assert subject.verify!
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#not_before_satisfied?" do
|
61
|
+
it "returns true when passed a nil value" do
|
62
|
+
assert Samlr::Condition.new({}).not_before_satisfied?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#not_on_or_after_satisfied?" do
|
67
|
+
it "returns true when passed a nil value" do
|
68
|
+
assert Samlr::Condition.new({}).not_on_or_after_satisfied?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path("test/test_helper")
|
2
|
+
|
3
|
+
describe Samlr::Fingerprint do
|
4
|
+
describe "#new" do
|
5
|
+
it "generates an invalid fingerprint for nil" do
|
6
|
+
refute Samlr::Fingerprint.new(nil).valid?
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "::normalize!" do
|
11
|
+
it "converts input to fingerprint normal form" do
|
12
|
+
assert_equal "AF:44", Samlr::Fingerprint.normalize("aF44 :-+6t")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#==" do
|
17
|
+
it "compares two fingerprints" do
|
18
|
+
assert (Samlr::Fingerprint.new("aa:33") == Samlr::Fingerprint.new("AA:33"))
|
19
|
+
assert (Samlr::Fingerprint.new("aa:33") != Samlr::Fingerprint.new("AA:34"))
|
20
|
+
assert (Samlr::Fingerprint.new("") != Samlr::Fingerprint.new(""))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#compare!" do
|
25
|
+
it "raises when fingerprints do not equal" do
|
26
|
+
assert_raises(Samlr::FingerprintError) do
|
27
|
+
Samlr::Fingerprint.new("aa:34").compare!(Samlr::Fingerprint.new("bb:35"))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "stores fingerprints on the exception" do
|
32
|
+
begin
|
33
|
+
Samlr::Fingerprint.new("aa:34").compare!(Samlr::Fingerprint.new("bb:35"))
|
34
|
+
flunk "Exception expected"
|
35
|
+
rescue Samlr::FingerprintError => e
|
36
|
+
assert_equal "Fingerprint mismatch", e.message
|
37
|
+
assert_equal "AA:34 vs. BB:35", e.details
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "doesn't raise when fingerprints are equal" do
|
42
|
+
assert Samlr::Fingerprint.new("aa:34").compare!(Samlr::Fingerprint.new("aa:34"))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path("test/test_helper")
|
2
|
+
|
3
|
+
describe Samlr::LogoutRequest do
|
4
|
+
before do
|
5
|
+
@request = Samlr::LogoutRequest.new(
|
6
|
+
:issuer => "https://sp.example.com/saml2",
|
7
|
+
:name_id => "test@test.com"
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#body" do
|
12
|
+
it "should return the generated XML" do
|
13
|
+
document = Nokogiri::XML(@request.body) { |c| c.strict }
|
14
|
+
assert document.at("/samlp:LogoutRequest", Samlr::NS_MAP)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should delegate the building to the LogoutRequestBuilder" do
|
18
|
+
Samlr::Tools::LogoutRequestBuilder.stub(:build, "hello") do
|
19
|
+
assert_match "hello", @request.body
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#param" do
|
25
|
+
it "returns the encoded body" do
|
26
|
+
@request.stub(:body, "hello") do
|
27
|
+
assert_equal Samlr::Tools.encode("hello"), @request.param
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#url" do
|
33
|
+
it "returns a valid URL" do
|
34
|
+
@request.stub(:param, "hello") do
|
35
|
+
assert_equal("https://foo.com/?SAMLRequest=hello&foo=bar", @request.url("https://foo.com/", :foo => "bar"))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.expand_path("test/test_helper")
|
2
|
+
|
3
|
+
describe Samlr::Reference do
|
4
|
+
before do
|
5
|
+
@response = fixed_saml_response
|
6
|
+
@reference = @response.signature.send(:references).first
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#uri" do
|
10
|
+
it "should return the normalized URI" do
|
11
|
+
assert_equal "samlr123", @reference.uri
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#digest_method" do
|
16
|
+
it "should return the digest implementation" do
|
17
|
+
assert_equal OpenSSL::Digest::SHA1, @reference.digest_method
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#digest_value" do
|
22
|
+
it "should return the verbatim value" do
|
23
|
+
assert_equal "OSVXSTu8W+eGao6muxUHXcKQwZU=", @reference.digest_value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "namespaces" do
|
28
|
+
it "should return the inclusive namespaces" do
|
29
|
+
assert_equal ["#default", "samlp", "saml", "ds", "xs", "xsi"].sort, @reference.namespaces.sort
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.expand_path("test/test_helper")
|
2
|
+
|
3
|
+
describe Samlr::Request do
|
4
|
+
before { @request = Samlr::Request.new }
|
5
|
+
|
6
|
+
describe "#body" do
|
7
|
+
it "should return the generated XML" do
|
8
|
+
document = Nokogiri::XML(@request.body) { |c| c.strict }
|
9
|
+
assert document.at("/samlp:AuthnRequest", Samlr::NS_MAP)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should delegate the building to the RequestBuilder" do
|
13
|
+
Samlr::Tools::RequestBuilder.stub(:build, "hello") do
|
14
|
+
assert_match "hello", @request.body
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#param" do
|
20
|
+
it "returns the encoded body" do
|
21
|
+
@request.stub(:body, "hello") do
|
22
|
+
assert_equal Samlr::Tools.encode("hello"), @request.param
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#url" do
|
28
|
+
it "returns a valid URL" do
|
29
|
+
@request.stub(:param, "hello") do
|
30
|
+
assert_equal("https://foo.com/?SAMLRequest=hello&foo=bar", @request.url("https://foo.com/", :foo => "bar"))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require File.expand_path("test/test_helper")
|
2
|
+
|
3
|
+
describe Samlr::Response do
|
4
|
+
|
5
|
+
subject { fixed_saml_response }
|
6
|
+
|
7
|
+
describe "#name_id" do
|
8
|
+
it "delegates to the assertion" do
|
9
|
+
subject.assertion.stub(:name_id, "george") do
|
10
|
+
assert_equal("george", subject.name_id)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#attributes" do
|
16
|
+
it "delegates to the assertion" do
|
17
|
+
subject.assertion.stub(:attributes, { :name => "george" }) do
|
18
|
+
assert_equal({ :name => "george" }, subject.attributes)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#location" do
|
24
|
+
it "should return proper assertion location" do
|
25
|
+
assert_equal "//saml:Assertion[@ID='samlr456']", subject.assertion.location
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "XSW attack" do
|
30
|
+
it "should not validate if SAML response is hacked" do
|
31
|
+
document = saml_response_document(:certificate => TEST_CERTIFICATE)
|
32
|
+
|
33
|
+
modified_document = Nokogiri::XML(document)
|
34
|
+
|
35
|
+
original_assertion = modified_document.xpath("/samlp:Response/saml:Assertion", Samlr::NS_MAP).first
|
36
|
+
|
37
|
+
response_signature = modified_document.xpath("/samlp:Response/ds:Signature", Samlr::NS_MAP).first
|
38
|
+
|
39
|
+
extensions = Nokogiri::XML::Node.new "Extensions", modified_document
|
40
|
+
extensions << original_assertion.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML)
|
41
|
+
response_signature.add_next_sibling(extensions)
|
42
|
+
response_signature.remove()
|
43
|
+
|
44
|
+
modified_document.xpath("/samlp:Response/samlp:Extensions/saml:Assertion/ds:Signature", Samlr::NS_MAP).remove
|
45
|
+
modified_document.xpath("/samlp:Response/saml:Assertion/saml:Subject/saml:NameID", Samlr::NS_MAP).first.content="evil@example.org"
|
46
|
+
modified_document.xpath("/samlp:Response/saml:Assertion", Samlr::NS_MAP).first["ID"] = "evil_id"
|
47
|
+
|
48
|
+
response = Samlr::Response.new(modified_document.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML), {:certificate => TEST_CERTIFICATE.x509})
|
49
|
+
assert_raises(Samlr::FormatError) { response.verify! }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "::parse" do
|
54
|
+
before { @document = saml_response_document(:certificate => TEST_CERTIFICATE) }
|
55
|
+
|
56
|
+
describe "when given a raw XML response" do
|
57
|
+
it "constructs and XML document" do
|
58
|
+
assert_equal Nokogiri::XML::Document, Samlr::Response.parse(@document).class
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "when given a Base64 encoded response" do
|
63
|
+
subject { Base64.encode64(@document) }
|
64
|
+
|
65
|
+
it "constructs and XML document" do
|
66
|
+
assert_equal Nokogiri::XML::Document, Samlr::Response.parse(subject).class
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "when given an invalid string" do
|
71
|
+
it "fails" do
|
72
|
+
assert_raises(Samlr::FormatError) { Samlr::Response.parse("hello") }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "when given a malformed XML response" do
|
77
|
+
subject { saml_response_document(:certificate => TEST_CERTIFICATE).gsub("Assertion", "AyCaramba") }
|
78
|
+
after { Samlr.validation_mode = :reject }
|
79
|
+
|
80
|
+
describe "and Samlr.validation_mode == :log" do
|
81
|
+
before { Samlr.validation_mode = :log }
|
82
|
+
it "does not raise" do
|
83
|
+
assert Samlr::Response.parse(subject)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe "and Samlr.validation_mode != :log" do
|
88
|
+
it "raises" do
|
89
|
+
assert_raises(Samlr::FormatError) { Samlr::Response.parse(subject) }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|