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.

Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +8 -0
  5. data/LICENSE +176 -0
  6. data/README.md +182 -0
  7. data/Rakefile +12 -0
  8. data/bin/samlr +46 -0
  9. data/config/schemas/XMLSchema.xsd +2534 -0
  10. data/config/schemas/saml-schema-assertion-2.0.xsd +283 -0
  11. data/config/schemas/saml-schema-metadata-2.0.xsd +337 -0
  12. data/config/schemas/saml-schema-protocol-2.0.xsd +302 -0
  13. data/config/schemas/xenc-schema.xsd +146 -0
  14. data/config/schemas/xml.xsd +287 -0
  15. data/config/schemas/xmldsig-core-schema.xsd +318 -0
  16. data/lib/samlr.rb +52 -0
  17. data/lib/samlr/assertion.rb +91 -0
  18. data/lib/samlr/certificate.rb +23 -0
  19. data/lib/samlr/command.rb +41 -0
  20. data/lib/samlr/condition.rb +31 -0
  21. data/lib/samlr/errors.rb +22 -0
  22. data/lib/samlr/fingerprint.rb +44 -0
  23. data/lib/samlr/logout_request.rb +7 -0
  24. data/lib/samlr/reference.rb +32 -0
  25. data/lib/samlr/request.rb +37 -0
  26. data/lib/samlr/response.rb +68 -0
  27. data/lib/samlr/signature.rb +129 -0
  28. data/lib/samlr/tools.rb +108 -0
  29. data/lib/samlr/tools/certificate_builder.rb +74 -0
  30. data/lib/samlr/tools/logout_request_builder.rb +27 -0
  31. data/lib/samlr/tools/metadata_builder.rb +41 -0
  32. data/lib/samlr/tools/request_builder.rb +44 -0
  33. data/lib/samlr/tools/response_builder.rb +157 -0
  34. data/lib/samlr/tools/timestamp.rb +26 -0
  35. data/samlr.gemspec +19 -0
  36. data/test/fixtures/default_samlr_certificate.pem +11 -0
  37. data/test/fixtures/default_samlr_private_key.pem +9 -0
  38. data/test/fixtures/no_cert_response.xml +2 -0
  39. data/test/fixtures/sample_metadata.xml +7 -0
  40. data/test/fixtures/sample_response.xml +2 -0
  41. data/test/test_helper.rb +55 -0
  42. data/test/unit/test_assertion.rb +54 -0
  43. data/test/unit/test_condition.rb +71 -0
  44. data/test/unit/test_fingerprint.rb +45 -0
  45. data/test/unit/test_logout_request.rb +39 -0
  46. data/test/unit/test_reference.rb +32 -0
  47. data/test/unit/test_request.rb +34 -0
  48. data/test/unit/test_response.rb +94 -0
  49. data/test/unit/test_response_scenarios.rb +111 -0
  50. data/test/unit/test_signature.rb +54 -0
  51. data/test/unit/test_timestamp.rb +58 -0
  52. data/test/unit/test_tools.rb +100 -0
  53. data/test/unit/tools/test_certificate_builder.rb +41 -0
  54. data/test/unit/tools/test_logout_request_builder.rb +26 -0
  55. data/test/unit/tools/test_metadata_builder.rb +26 -0
  56. data/test/unit/tools/test_request_builder.rb +35 -0
  57. data/test/unit/tools/test_response_builder.rb +19 -0
  58. 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
@@ -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>
@@ -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