saml2 3.1.2 → 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +6 -4
  3. data/exe/bulk_verify_responses +94 -0
  4. data/lib/saml2/assertion.rb +7 -7
  5. data/lib/saml2/attribute/x500.rb +31 -28
  6. data/lib/saml2/attribute.rb +53 -49
  7. data/lib/saml2/attribute_consuming_service.rb +29 -31
  8. data/lib/saml2/authn_request.rb +54 -47
  9. data/lib/saml2/authn_statement.rb +31 -20
  10. data/lib/saml2/base.rb +72 -63
  11. data/lib/saml2/bindings/http_post.rb +7 -7
  12. data/lib/saml2/bindings/http_redirect.rb +37 -33
  13. data/lib/saml2/bindings.rb +1 -1
  14. data/lib/saml2/conditions.rb +19 -16
  15. data/lib/saml2/contact.rb +19 -18
  16. data/lib/saml2/endpoint.rb +14 -11
  17. data/lib/saml2/entity.rb +27 -27
  18. data/lib/saml2/identity_provider.rb +13 -10
  19. data/lib/saml2/indexed_object.rb +15 -12
  20. data/lib/saml2/key.rb +43 -34
  21. data/lib/saml2/localized_name.rb +11 -10
  22. data/lib/saml2/logout_request.rb +8 -8
  23. data/lib/saml2/logout_response.rb +4 -4
  24. data/lib/saml2/message.rb +24 -20
  25. data/lib/saml2/name_id.rb +45 -41
  26. data/lib/saml2/namespaces.rb +8 -8
  27. data/lib/saml2/organization.rb +11 -10
  28. data/lib/saml2/organization_and_contacts.rb +5 -5
  29. data/lib/saml2/request.rb +3 -3
  30. data/lib/saml2/requested_authn_context.rb +4 -4
  31. data/lib/saml2/response.rb +45 -33
  32. data/lib/saml2/role.rb +11 -11
  33. data/lib/saml2/schemas.rb +13 -10
  34. data/lib/saml2/service_provider.rb +11 -12
  35. data/lib/saml2/signable.rb +23 -18
  36. data/lib/saml2/sso.rb +5 -5
  37. data/lib/saml2/status.rb +9 -7
  38. data/lib/saml2/status_response.rb +5 -5
  39. data/lib/saml2/subject.rb +28 -28
  40. data/lib/saml2/version.rb +1 -1
  41. data/lib/saml2.rb +7 -7
  42. metadata +78 -122
  43. data/spec/fixtures/FederationMetadata.xml +0 -670
  44. data/spec/fixtures/authnrequest.xml +0 -12
  45. data/spec/fixtures/certificate.pem +0 -24
  46. data/spec/fixtures/entities.xml +0 -13
  47. data/spec/fixtures/external-uri-reference-response.xml +0 -48
  48. data/spec/fixtures/identity_provider.xml +0 -46
  49. data/spec/fixtures/noconditions_response.xml +0 -1
  50. data/spec/fixtures/othercertificate.pem +0 -25
  51. data/spec/fixtures/privatekey.key +0 -27
  52. data/spec/fixtures/response_assertion_signed_reffed_from_response.xml +0 -6
  53. data/spec/fixtures/response_signed.xml +0 -46
  54. data/spec/fixtures/response_tampered_certificate.xml +0 -25
  55. data/spec/fixtures/response_tampered_signature.xml +0 -46
  56. data/spec/fixtures/response_with_attribute_signed.xml +0 -46
  57. data/spec/fixtures/response_with_encrypted_assertion.xml +0 -58
  58. data/spec/fixtures/response_with_rsa_key_value.xml +0 -1
  59. data/spec/fixtures/response_with_signed_assertion_and_encrypted_subject.xml +0 -116
  60. data/spec/fixtures/response_without_keyinfo.xml +0 -1
  61. data/spec/fixtures/service_provider.xml +0 -79
  62. data/spec/fixtures/test3-response.xml +0 -9
  63. data/spec/fixtures/test6-response.xml +0 -10
  64. data/spec/fixtures/test7-response.xml +0 -10
  65. data/spec/fixtures/xml_missigned_assertion.xml +0 -84
  66. data/spec/fixtures/xml_signature_wrapping_attack_duplicate_ids.xml +0 -11
  67. data/spec/fixtures/xml_signature_wrapping_attack_response_attributes.xml +0 -45
  68. data/spec/fixtures/xml_signature_wrapping_attack_response_nameid.xml +0 -44
  69. data/spec/fixtures/xslt-transform-response.xml +0 -57
  70. data/spec/lib/attribute_consuming_service_spec.rb +0 -129
  71. data/spec/lib/attribute_spec.rb +0 -149
  72. data/spec/lib/authn_request_spec.rb +0 -52
  73. data/spec/lib/bindings/http_redirect_spec.rb +0 -183
  74. data/spec/lib/conditions_spec.rb +0 -74
  75. data/spec/lib/entity_spec.rb +0 -58
  76. data/spec/lib/identity_provider_spec.rb +0 -43
  77. data/spec/lib/indexed_object_spec.rb +0 -71
  78. data/spec/lib/key_spec.rb +0 -23
  79. data/spec/lib/logout_request_spec.rb +0 -33
  80. data/spec/lib/logout_response_spec.rb +0 -33
  81. data/spec/lib/message_spec.rb +0 -23
  82. data/spec/lib/response_spec.rb +0 -293
  83. data/spec/lib/service_provider_spec.rb +0 -76
  84. data/spec/lib/signable_spec.rb +0 -15
  85. data/spec/spec_helper.rb +0 -8
@@ -1,149 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../spec_helper'
4
-
5
- module SAML2
6
- describe Attribute do
7
- def serialize(attribute)
8
- doc = Nokogiri::XML::Builder.new do |builder|
9
- builder['saml'].Root('xmlns:saml' => Namespaces::SAML) do |root|
10
- attribute.build(root)
11
- root.parent.child['xmlns:saml'] = Namespaces::SAML
12
- end
13
- end.doc
14
- doc.root.child.to_s
15
- end
16
-
17
- let(:eduPersonPrincipalNameXML) { <<XML.strip
18
- <saml:Attribute xmlns:x500="urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" FriendlyName="eduPersonPrincipalName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" x500:Encoding="LDAP" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
19
- <saml:AttributeValue xsi:type="xs:string">user@domain</saml:AttributeValue>
20
- </saml:Attribute>
21
- XML
22
- }
23
-
24
- it "should auto-parse X500 attributes" do
25
- attr = Attribute.from_xml(Nokogiri::XML(eduPersonPrincipalNameXML).root)
26
- expect(attr).to be_instance_of Attribute::X500
27
- expect(attr.value).to eq "user@domain"
28
- expect(attr.name).to eq Attribute::X500::EduPerson::PRINCIPAL_NAME
29
- expect(attr.friendly_name).to eq 'eduPersonPrincipalName'
30
- expect(attr.name_format).to eq Attribute::NameFormats::URI
31
- end
32
-
33
- it "recognizes and X500 attribute without a NameFormat" do
34
- xml = <<-XML
35
- <saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"><saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">user@domain</saml:AttributeValue></saml:Attribute>
36
- XML
37
- attr = Attribute.from_xml(Nokogiri::XML(xml).root)
38
- expect(attr).to be_instance_of Attribute::X500
39
- expect(attr.value).to eq "user@domain"
40
- expect(attr.name).to eq Attribute::X500::EduPerson::PRINCIPAL_NAME
41
- expect(attr.friendly_name).to eq 'eduPersonPrincipalName'
42
- expect(attr.name_format).to eq nil
43
- end
44
-
45
- it "should serialize an X500 attribute correctly" do
46
- attr = Attribute.create('eduPersonPrincipalName', 'user@domain')
47
- expect(attr).to be_instance_of Attribute::X500
48
- expect(attr.value).to eq "user@domain"
49
- expect(attr.name).to eq Attribute::X500::EduPerson::PRINCIPAL_NAME
50
- expect(attr.friendly_name).to eq 'eduPersonPrincipalName'
51
- expect(attr.name_format).to eq Attribute::NameFormats::URI
52
-
53
- expect(serialize(attr)).to eq eduPersonPrincipalNameXML
54
- end
55
-
56
- it "should parse and serialize boolean values" do
57
- xml = <<XML.strip
58
- <saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
59
- <saml:Attribute Name="attr">
60
- <saml:AttributeValue xsi:type="xs:boolean">1</saml:AttributeValue>
61
- </saml:Attribute>
62
- </saml:AttributeStatement>
63
- XML
64
-
65
- stmt = AttributeStatement.from_xml(Nokogiri::XML(xml).root)
66
- expect(stmt.attributes.first.value).to eq true
67
-
68
- # serializes canonically
69
- expect(serialize(stmt)).to eq(xml.sub('>1<', '>true<'))
70
- end
71
-
72
- it "should parse and serialize dateTime values" do
73
- xml = <<XML.strip
74
- <saml:AttributeStatement xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
75
- <saml:Attribute Name="attr">
76
- <saml:AttributeValue xsi:type="xs:dateTime">2015-06-29T18:37:03Z</saml:AttributeValue>
77
- </saml:Attribute>
78
- </saml:AttributeStatement>
79
- XML
80
-
81
- stmt = AttributeStatement.from_xml(Nokogiri::XML(xml).root)
82
- expect(stmt.attributes.first.value).to eq Time.at(1435603023)
83
-
84
- # serializes canonically
85
- expect(serialize(stmt)).to eq xml
86
- end
87
-
88
- it "should parse values with different namespace prefixes" do
89
- xml = <<XML.strip
90
- <saml:Attribute Name="attr" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xssi="http://www.w3.org/2001/XMLSchema-instance">
91
- <saml:AttributeValue xssi:type="xsd:boolean">0</saml:AttributeValue>
92
- </saml:Attribute>
93
- XML
94
-
95
- attr = Attribute.from_xml(Nokogiri::XML(xml).root)
96
- expect(attr.value).to eq false
97
- end
98
-
99
- it "should parse untagged values" do
100
- xml = <<XML.strip
101
- <saml:Attribute Name="attr" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
102
- <saml:AttributeValue>something</saml:AttributeValue>
103
- </saml:Attribute>
104
- XML
105
-
106
- attr = Attribute.from_xml(Nokogiri::XML(xml).root)
107
- expect(attr.value).to eq "something"
108
- end
109
-
110
- end
111
-
112
- describe AttributeStatement do
113
- describe "#to_h" do
114
- it "works" do
115
- attr_statement = Response.parse(fixture("response_with_attribute_signed.xml")).assertions.first.attribute_statements.first
116
- expect(attr_statement.to_h(:friendly_name)).to eq('givenName' => 'cody')
117
- expect(attr_statement.to_h(:name)).to eq("urn:oid:2.5.4.42" => 'cody')
118
- expect(attr_statement.to_h(:both)).to eq('givenName' => 'cody', "urn:oid:2.5.4.42" => 'cody')
119
- end
120
-
121
- it "infers friendly names if possible" do
122
- attr_statement = Response.parse(fixture("test3-response.xml")).assertions.first.attribute_statements.first
123
- expect(attr_statement.to_h).to eq({
124
- 'urn:oid:1.3.6.1.4.1.5923.1.1.1.1' => 'member',
125
- 'urn:oid:1.3.6.1.4.1.5923.1.1.1.6' => 'student@example.edu',
126
- 'eduPersonAffiliation' => 'member',
127
- 'eduPersonPrincipalName' => 'student@example.edu'})
128
- end
129
-
130
- it "properly combines repeated attributes" do
131
- attr_statement = AttributeStatement.from_xml(Nokogiri::XML(<<-XML).root)
132
- <saml2:AttributeStatement xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
133
- <saml2:Attribute FriendlyName="eduPersonScopedAffiliation" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.9" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
134
- <saml2:AttributeValue>02</saml2:AttributeValue>
135
- </saml2:Attribute>
136
- <saml2:Attribute FriendlyName="eduPersonScopedAffiliation" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.9" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
137
- <saml2:AttributeValue>employee@school.edu</saml2:AttributeValue>
138
- <saml2:AttributeValue>students@school.edu</saml2:AttributeValue>
139
- </saml2:Attribute>
140
- </saml2:AttributeStatement>
141
- XML
142
-
143
- expect(attr_statement.to_h(:friendly_name)).to eq({
144
- 'eduPersonScopedAffiliation' => ['02', 'employee@school.edu', 'students@school.edu']
145
- })
146
- end
147
- end
148
- end
149
- end
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../spec_helper'
4
-
5
- module SAML2
6
- describe AuthnRequest do
7
- let(:sp) { Entity.parse(fixture('service_provider.xml')).roles.first }
8
- let(:request) { AuthnRequest.parse(fixture('authnrequest.xml')) }
9
-
10
- it "should be valid" do
11
- expect(request.valid_schema?).to eq true
12
- expect(request.resolve(sp)).to eq true
13
- expect(request.assertion_consumer_service.location).to eq "https://siteadmin.test.instructure.com/saml_consume"
14
- end
15
-
16
- it "should not be valid if the ACS url is not in the SP" do
17
- allow(request).to receive(:assertion_consumer_service_url).and_return("garbage")
18
- expect(request.resolve(sp)).to eq false
19
- end
20
-
21
- it "should use the default ACS if not specified" do
22
- allow(request).to receive(:assertion_consumer_service_url).and_return(nil)
23
- expect(request.resolve(sp)).to eq true
24
- expect(request.assertion_consumer_service.location).to eq "https://siteadmin.instructure.com/saml_consume"
25
- end
26
-
27
- it "should find the ACS by index" do
28
- allow(request).to receive(:assertion_consumer_service_url).and_return(nil)
29
- allow(request).to receive(:assertion_consumer_service_index).and_return(2)
30
- expect(request.resolve(sp)).to eq true
31
- expect(request.assertion_consumer_service.location).to eq "https://siteadmin.beta.instructure.com/saml_consume"
32
- end
33
-
34
- it "should find the NameID policy" do
35
- expect(request.name_id_policy).to eq NameID::Policy.new(true, NameID::Format::PERSISTENT, "moodle.bridge.feide.no")
36
- end
37
-
38
- it 'serializes valid XML' do
39
- authn_request = AuthnRequest.initiate(NameID.new("entity"),
40
- assertion_consumer_service: Endpoint.new('https://somewhere/'))
41
- authn_request.requested_authn_context = RequestedAuthnContext.new
42
- authn_request.requested_authn_context.class_ref = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
43
- authn_request.requested_authn_context.comparison = :exact
44
- authn_request.passive = true
45
- xml = authn_request.to_s
46
- authn_request = AuthnRequest.parse(xml)
47
- expect(authn_request).to be_valid_schema
48
- expect(authn_request.force_authn?).to eq nil
49
- expect(authn_request.passive?).to eq true
50
- end
51
- end
52
- end
@@ -1,183 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../spec_helper'
4
-
5
- require 'openssl'
6
-
7
- module SAML2
8
- describe Bindings::HTTPRedirect do
9
- describe '.decode' do
10
- def check_error(wrapped, cause)
11
- error = nil
12
- begin
13
- yield
14
- rescue => e
15
- error = e
16
- end
17
- expect(error).not_to be_nil
18
- expect(error).to be_a(wrapped)
19
- expect(error.cause).to be_a(cause)
20
- end
21
-
22
- it "complains about invalid URIs" do
23
- check_error(CorruptMessage, URI::InvalidURIError) { Bindings::HTTPRedirect.decode(" ") }
24
- end
25
-
26
- it "complains about missing message" do
27
- expect { Bindings::HTTPRedirect.decode("http://somewhere/") }.to raise_error(MissingMessage)
28
- expect { Bindings::HTTPRedirect.decode("http://somewhere/?RelayState=bob") }.to raise_error(MissingMessage)
29
- end
30
-
31
- it "complains about malformed Base64" do
32
- check_error(CorruptMessage, ArgumentError) { Bindings::HTTPRedirect.decode("http://somewhere/?SAMLRequest=%^") }
33
- end
34
-
35
- it "doesn't allow deflate bombs" do
36
- message = double()
37
- allow(message).to receive(:destination).and_return("http://somewhere/")
38
- allow(message).to receive(:to_s).and_return("\0" * 2 * 1024 * 1024)
39
- url = Bindings::HTTPRedirect.encode(message)
40
-
41
- expect { Bindings::HTTPRedirect.decode(url) }.to raise_error(MessageTooLarge)
42
- end
43
-
44
- it "complains about malformed deflated data" do
45
- check_error(CorruptMessage, Zlib::BufError) { Bindings::HTTPRedirect.decode("http://somewhere/?SAMLRequest=abcd") }
46
- end
47
-
48
- it "complains about zlibbed data" do
49
- # SAML uses just Deflate, which has no header/footer; Zlib adds a simple header/footer
50
- message = Base64.strict_encode64(Zlib::Deflate.deflate('abcd'))
51
- check_error(CorruptMessage, Zlib::DataError) { Bindings::HTTPRedirect.decode("http://somewhere/?SAMLRequest=#{message}") }
52
- end
53
-
54
- it "validates encoding" do
55
- message = double()
56
- allow(message).to receive(:destination).and_return("http://somewhere/")
57
- allow(message).to receive(:to_s).and_return("hi")
58
- url = Bindings::HTTPRedirect.encode(message, relay_state: "abc")
59
- url << "&SAMLEncoding=garbage"
60
- expect { Bindings::HTTPRedirect.decode(url) }.to raise_error(UnsupportedEncoding)
61
- end
62
-
63
- it "returns relay state" do
64
- message = double()
65
- allow(message).to receive(:destination).and_return("http://somewhere/")
66
- allow(message).to receive(:to_s).and_return("hi")
67
- url = Bindings::HTTPRedirect.encode(message, relay_state: "abc")
68
- allow(Message).to receive(:parse).with("hi").and_return("parsed")
69
- message, relay_state = Bindings::HTTPRedirect.decode(url)
70
- expect(message).to eq "parsed"
71
- expect(relay_state).to eq "abc"
72
- end
73
-
74
- context "signature validation" do
75
- # taken from logs of another system. It's a good one to test because the Signature
76
- # has to be taken out of the middle, so you can't just use the whole query string
77
- let(:url) { '/login/saml/logout?SAMLResponse=fZLNasMwEIRfxehuy7KVOhaOobSXQHtpSg%2b9FP2sE4EjGa9U2rev7JBDoOQmLTO7s5%2fUoTyPk3jxRx%2fDG%2bDkHUK2f96Rr%2fpBN4pXMtdcq5xXbJurbb3JN1JCZZpSqQ0n2QfMaL3bkaooSbZHjLB3GKQLqVSyJi95zup3xgVvRdkWVdN%2bkuwZMFgnw%2bo8hTChoNR%2b%2fwbQp8Im%2fxx1iDMU2p%2fp6I%2fW0SXockw5Sfa0xFxGxNkJL9GicPIMKIIWh8fXF5HSCH0RiehwAm0HCyYldNct3%2f2OaK3Yw8AkSKVqXrJqYIpVst4ao0xtymZbD21bbXja7ec8OhQrr%2ftzp9kHr%2f1I%2bm7lMV%2bs900SEeaFB%2bkXHgmHNAMWVyZg4lqgSfVtNSBNiDB09DKh7y7veAgyRLy9PXkD2YccI9xPgKtaHKJO7ZFktO%2fobVf632fp%2fwA%3d&Signature=eZejccsvxyLEZvkNdL3shazIPyBIfRwk0Ny5INfrwzhE40N%2bH4neEo7xoHi2ncJ0LQG6A71oxviVkqPWXUaePuAt3fbxTf%2bLkWPiXo0D6GVebSgPSZIMrsU%2fawfou68yX%2bI5dk8KtFiMOPCf5818oJvCdYjszN5o%2fU80UlJdjiDK%2bMF2rtzx6ZEXs04MoMDWKgouTZUFxNZP3KJeFQumLbK99ZfJVl8Wl4XDTs1DKq7eBJc1IgyH13LELxhHgCZMpARrCX65gCYhjnJWhmyTU4YFzdcwKeulYcP0eTbMEqRy9s1sEaOH%2fTx3I46Fl%2bv7j3GbRiRTwqF9%2bqjAKbJjpw%3d%3d&SigAlg=http%3a%2f%2fwww.w3.org%2f2000%2f09%2fxmldsig%23rsa-sha1' }
78
- let(:certificate) { OpenSSL::X509::Certificate.new(Base64.decode64('MIIFnzCCBIegAwIBAgIQItX5wssh0ecd46K65PkSNDANBgkqhkiG9w0BAQsFADCBkDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxNjA0BgNVBAMTLUNPTU9ETyBSU0EgRG9tYWluIFZhbGlkYXRpb24gU2VjdXJlIFNlcnZlciBDQTAeFw0xNjA5MDgwMDAwMDBaFw0xOTEwMjUyMzU5NTlaMIGeMSEwHwYDVQQLExhEb21haW4gQ29udHJvbCBWYWxpZGF0ZWQxSTBHBgNVBAsTQElzc3VlZCB0aHJvdWdoIEl2eSBUZWNoIENvbW11bml0eSBDb2xsZWdlIG9mIEluZGlhbmEgRS1QS0kgTWFuYWcxEzARBgNVBAsTCkNPTU9ETyBTU0wxGTAXBgNVBAMTEGFkZnMuaXZ5dGVjaC5lZHUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC58zHz7VsV9S2XZMRjgqiWxBZ6M9y6/3zkrbObJ9hZqO7giCoonNDuELUiNt8pBqF8aHef8qbDOecBBXkz8rPAJL1S6lzvbxHIBuvEy+xOpVdUNMoyOaAYHOI5T6ueL1Q4iGMKfnWuXSvVTyB+9wAF/aWVFSoz+alUOiQtqTYyfgIKzHIAmFX7/SjFA9UjKVtqatcvzWsSWZHL4imeTmPosXXjmJVZnl+jaeFsnmW59o66sdGR+NYkhsBcVRnuP3MdxVgr5xSJMN+/BgZwCncX+4LJq5664eeQcJM5Km9kbQ/jMFhYy765ejszcL0vWe/fS7tdXQCfoKjRZ5LzNEb3AgMBAAGjggHjMIIB3zAfBgNVHSMEGDAWgBSQr2o6lFoL2JDqElZz30O0Oija5zAdBgNVHQ4EFgQUdFr6SnHaXUqLAEdOL9qrTJS/3AYwDgYDVR0PAQH/BAQDAgWgMAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCME8GA1UdIARIMEYwOgYLKwYBBAGyMQECAgcwKzApBggrBgEFBQcCARYdaHR0cHM6Ly9zZWN1cmUuY29tb2RvLmNvbS9DUFMwCAYGZ4EMAQIBMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9ET1JTQURvbWFpblZhbGlkYXRpb25TZWN1cmVTZXJ2ZXJDQS5jcmwwgYUGCCsGAQUFBwEBBHkwdzBPBggrBgEFBQcwAoZDaHR0cDovL2NydC5jb21vZG9jYS5jb20vQ09NT0RPUlNBRG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZlckNBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuY29tb2RvY2EuY29tMDEGA1UdEQQqMCiCEGFkZnMuaXZ5dGVjaC5lZHWCFHd3dy5hZGZzLml2eXRlY2guZWR1MA0GCSqGSIb3DQEBCwUAA4IBAQA0dXP0leDcdrr/iKk4nDSCofllPAWE8LE3mD9Yb9K+/oVymxpqNIVJesDPLtf1HqWk6S6eafcYvfzl9aTMcvwEkL27g2l9UQuICkQgqSEY5qTsK//u/2S98JqXep2oRyvxo3UHX+3Ouc3i49hQ0v05Faoeap/ZT3JEsMV2Go9UKRJbYBG9Nqq/CDBuTgyopKJ7fvCtsGxwsvlUAz/NMuNoUphPQ2S+O/SjabjR4XsAGU78Hji2tqJyvPyKPanxc0ioDdnL5lvrk4uZ/6Dy159C5FOFeLU2ZfiNLXRR85KFfhtX954qvX6jmM7CPmcidhzEnZV8fQv9G6XYPfrNL7bh')).public_key }
79
- let(:incorrect_certificate) { OpenSSL::X509::Certificate.new(fixture('certificate.pem')).public_key }
80
-
81
- it "validates the signature" do
82
- # no exception raised
83
- Bindings::HTTPRedirect.decode(url, public_key: certificate)
84
- end
85
-
86
- it "raises on invalid signature" do
87
- expect { Bindings::HTTPRedirect.decode(url, public_key: incorrect_certificate) }.to raise_error(InvalidSignature)
88
- end
89
-
90
- it "raises on unsupported signature algorithm" do
91
- x = url.dup
92
- # SigAlg is now sha10
93
- x << "0"
94
- expect { Bindings::HTTPRedirect.decode(x, public_key: certificate) }.to raise_error(UnsupportedSignatureAlgorithm)
95
- end
96
-
97
- it "allows the caller to detect an unsigned message" do
98
- message = double()
99
- allow(message).to receive(:destination).and_return("http://somewhere/")
100
- allow(message).to receive(:to_s).and_return("hi")
101
- url = Bindings::HTTPRedirect.encode(message)
102
- allow(Message).to receive(:parse).with("hi").and_return("parsed")
103
-
104
- expect do
105
- Bindings::HTTPRedirect.decode(url) do |_message, sig_alg|
106
- expect(sig_alg).to be_nil
107
- raise "no signature"
108
- end
109
- end.to raise_error("no signature")
110
- end
111
-
112
- it "requires a signature if a key is passed" do
113
- message = double()
114
- allow(message).to receive(:destination).and_return("http://somewhere/")
115
- allow(message).to receive(:to_s).and_return("hi")
116
- url = Bindings::HTTPRedirect.encode(message)
117
- allow(Message).to receive(:parse).with("hi").and_return("parsed")
118
-
119
- expect { Bindings::HTTPRedirect.decode(url, public_key: certificate) } .to raise_error(UnsignedMessage)
120
- end
121
-
122
- it "notifies the caller which key was used" do
123
- called = 0
124
- key_used = ->(key) do
125
- expect(key).to eq certificate
126
- called += 1
127
- end
128
- Bindings::HTTPRedirect.decode(url,
129
- public_key: [incorrect_certificate,
130
- certificate],
131
- public_key_used: key_used)
132
- expect(called).to eq 1
133
- end
134
- end
135
- end
136
-
137
- describe '.encode' do
138
- it 'works' do
139
- message = double()
140
- allow(message).to receive(:destination).and_return("http://somewhere/")
141
- allow(message).to receive(:to_s).and_return("hi")
142
- url = Bindings::HTTPRedirect.encode(message, relay_state: "abc")
143
- expect(url).to match(%r{^http://somewhere/\?SAMLResponse=(?:.*)&RelayState=abc})
144
- end
145
-
146
- it 'signs a message' do
147
- message = double()
148
- allow(message).to receive(:destination).and_return("http://somewhere/")
149
- allow(message).to receive(:to_s).and_return("hi")
150
- key = OpenSSL::PKey::RSA.new(fixture('privatekey.key'))
151
- url = Bindings::HTTPRedirect.encode(message,
152
- relay_state: "abc",
153
- private_key: key)
154
-
155
- # verify the signature
156
- allow(Message).to receive(:parse).with("hi").and_return("parsed")
157
- Bindings::HTTPRedirect.decode(url) do |_message, sig_alg|
158
- expect(sig_alg).to eq Bindings::HTTPRedirect::SigAlgs::RSA_SHA1
159
- OpenSSL::X509::Certificate.new(fixture('certificate.pem')).public_key
160
- end
161
- end
162
-
163
- it 'signs a message with RSA-SHA256' do
164
- message = double()
165
- allow(message).to receive(:destination).and_return("http://somewhere/")
166
- allow(message).to receive(:to_s).and_return("hi")
167
- key = OpenSSL::PKey::RSA.new(fixture('privatekey.key'))
168
- url = Bindings::HTTPRedirect.encode(message,
169
- relay_state: "abc",
170
- private_key: key,
171
- sig_alg: Bindings::HTTPRedirect::SigAlgs::RSA_SHA256)
172
-
173
- # verify the signature
174
- allow(Message).to receive(:parse).with("hi").and_return("parsed")
175
- Bindings::HTTPRedirect.decode(url) do |_message, sig_alg|
176
- expect(sig_alg).to eq Bindings::HTTPRedirect::SigAlgs::RSA_SHA256
177
- OpenSSL::X509::Certificate.new(fixture('certificate.pem')).public_key
178
- end
179
- end
180
-
181
- end
182
- end
183
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../spec_helper'
4
-
5
- module SAML2
6
- describe Conditions do
7
- it "empty should be valid" do
8
- expect(Conditions.new.valid?).to eq true
9
- end
10
-
11
- it "should be invalid with unknown condition" do
12
- conditions = Conditions.new
13
- conditions << Conditions::Condition.new
14
- expect(conditions.valid?).to eq false
15
- end
16
-
17
- it "should be valid with timestamps" do
18
- conditions = Conditions.new
19
- now = Time.now.utc
20
- conditions.not_before = now - 5
21
- conditions.not_on_or_after = now + 30
22
- expect(conditions.valid?).to eq true
23
- end
24
-
25
- it "should be invalid with out of range timestamps" do
26
- conditions = Conditions.new
27
- now = Time.now.utc
28
- conditions.not_before = now - 35
29
- conditions.not_on_or_after = now - 5
30
- expect(conditions.valid?).to eq false
31
- end
32
-
33
- it "should allow passing now" do
34
- conditions = Conditions.new
35
- now = Time.now.utc
36
- conditions.not_before = now - 35
37
- conditions.not_on_or_after = now - 5
38
- expect(conditions.valid?(now: now - 10)).to eq true
39
- end
40
-
41
- it "should be invalid before indeterminate" do
42
- conditions = Conditions.new
43
- now = Time.now.utc
44
- conditions.not_before = now + 5
45
- conditions << Conditions::Condition.new
46
- expect(conditions.valid?).to eq false
47
- end
48
-
49
- it "should be invalid before indeterminate (actual conditions)" do
50
- conditions = Conditions.new
51
- conditions << Conditions::Condition.new
52
- conditions << Conditions::AudienceRestriction.new('audience')
53
- expect(conditions.valid?).to eq false
54
- end
55
- end
56
-
57
- describe Conditions::AudienceRestriction do
58
- it "should be invalid" do
59
- expect(Conditions::AudienceRestriction.new('expected').valid?(audience: 'actual')).to eq false
60
- end
61
-
62
- it "should be valid" do
63
- expect(Conditions::AudienceRestriction.new('expected').valid?(audience: 'expected')).to eq true
64
- end
65
-
66
- it "should be valid with an array" do
67
- expect(Conditions::AudienceRestriction.new(['expected', 'actual']).valid?(audience: 'actual')).to eq true
68
- end
69
-
70
- it "is valid when ignored" do
71
- expect(Conditions::AudienceRestriction.new('expected').valid?(audience: 'actual', ignore_audience_condition: true)).to eq true
72
- end
73
- end
74
- end
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../spec_helper'
4
-
5
- module SAML2
6
- describe Entity do
7
- it "should parse and validate" do
8
- entity = Entity.parse(fixture('service_provider.xml'))
9
- expect(entity.valid_schema?).to eq true
10
- end
11
-
12
- it "should return nil when not valid schema" do
13
- entity = Entity.parse("<xml></xml>")
14
- expect(entity).to be_nil
15
- end
16
-
17
- it "should return nil on non-XML" do
18
- entity = Entity.parse("garbage")
19
- expect(entity).to be_nil
20
- end
21
-
22
- describe "valid schema" do
23
- let(:entity) { Entity.parse(fixture('service_provider.xml')) }
24
-
25
- it "should find the id" do
26
- expect(entity.entity_id).to eq "http://siteadmin.instructure.com/saml2"
27
- end
28
-
29
- it "should parse the organization" do
30
- expect(entity.organization.display_name.to_s).to eq 'Canvas'
31
- expect(entity.organization.display_name['en']).to eq 'Canvas'
32
- expect(entity.organization.display_name['es']).to be_nil
33
- expect(entity.organization.display_name[:all]).to eq en: 'Canvas'
34
- end
35
-
36
- it "validates metadata from ADFS containing lots of non-SAML schemas" do
37
- expect(Entity.parse(fixture('FederationMetadata.xml')).valid_schema?).to eq true
38
- end
39
- end
40
-
41
- it "should sign correctly" do
42
- entity = Entity.parse(fixture('service_provider.xml'))
43
- entity.sign(fixture('certificate.pem'), fixture('privatekey.key'))
44
- entity2 = Entity.parse(entity.to_s)
45
- expect(entity2.valid_schema?).to eq true
46
- end
47
-
48
- describe Entity::Group do
49
- it "should parse and validate" do
50
- group = Entity.parse(fixture('entities.xml'))
51
- expect(group).to be_instance_of(Entity::Group)
52
- expect(group.valid_schema?).to eq true
53
-
54
- expect(group.map(&:entity_id)).to eq ['urn:entity1', 'urn:entity2']
55
- end
56
- end
57
- end
58
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../spec_helper'
4
-
5
- module SAML2
6
- describe IdentityProvider do
7
- it "should serialize valid xml" do
8
- entity = Entity.new
9
- entity.entity_id = 'http://sso.canvaslms.com/SAML2'
10
- entity.organization = Organization.new('Canvas', 'Canvas by Instructure', 'https://www.canvaslms.com/')
11
- contact = Contact.new(Contact::Type::TECHNICAL)
12
- contact.company = 'Instructure'
13
- contact.email_addresses << 'mailto:ops@instructure.com'
14
- entity.contacts << contact
15
-
16
- idp = IdentityProvider.new
17
- idp.name_id_formats << NameID::Format::PERSISTENT
18
- idp.single_sign_on_services << Endpoint.new('https://sso.canvaslms.com/SAML2/Login')
19
- idp.keys << KeyDescriptor.new('somedata', KeyDescriptor::Type::SIGNING)
20
-
21
- entity.roles << idp
22
- expect(Schemas.metadata.validate(Nokogiri::XML(entity.to_s))).to eq []
23
- end
24
-
25
- describe "valid metadata" do
26
- let(:entity) { Entity.parse(fixture('identity_provider.xml')) }
27
- let(:idp) { entity.roles.first }
28
-
29
- it "should create the single_sign_on_services array" do
30
- expect(idp.single_sign_on_services.length).to eq 3
31
- expect(idp.single_sign_on_services.first.location).to eq 'https://sso.school.edu/idp/profile/Shibboleth/SSO'
32
- end
33
-
34
- it "should find the signing certificate" do
35
- expect(idp.keys.first.x509).to match(/MIIE8TCCA9mgAwIBAgIJAITusxON60cKMA0GCSqGSIb3DQEBBQUAMIGrMQswCQYD/)
36
- end
37
-
38
- it "loads identity provider attributes" do
39
- expect(idp.want_authn_requests_signed?).to be_truthy
40
- end
41
- end
42
- end
43
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../spec_helper'
4
-
5
- module SAML2
6
- describe IndexedObject do
7
- describe "#default?" do
8
- it "always returns a boolean" do
9
- acs = Endpoint::Indexed.new('a', 0)
10
- expect(acs.default?).to eq false
11
- expect(acs.default_defined?).to eq false
12
- end
13
-
14
- it "#default_defined? works" do
15
- acs = Endpoint::Indexed.new('a', 0, false)
16
- expect(acs.default?).to eq false
17
- expect(acs.default_defined?).to eq true
18
- end
19
- end
20
-
21
- context "serialization" do
22
- it "doesn't include isDefault when it's nil" do
23
- acs = Endpoint::Indexed.new('a', 0)
24
- builder = double()
25
- expect(builder).to receive(:[]).and_return(builder).ordered
26
- expect(builder).to receive(:"AssertionConsumerService").ordered
27
- expect(builder).to receive(:parent).and_return(builder).ordered
28
- expect(builder).to receive(:children).and_return(builder).ordered
29
- expect(builder).to receive(:last).and_return(builder).ordered
30
- expect(builder).to receive(:[]=).with("index", 0).ordered
31
-
32
- acs.build(builder,"AssertionConsumerService")
33
- end
34
- end
35
- end
36
-
37
- describe IndexedObject::Array do
38
- it "should sort by index" do
39
- acses = Endpoint::Indexed::Array.new(
40
- [Endpoint::Indexed.new('b', 1),
41
- Endpoint::Indexed.new('a', 0)])
42
- expect(acses.map(&:location)).to eq ['a', 'b']
43
- end
44
-
45
- it "should be accessible by index" do
46
- acses = Endpoint::Indexed::Array.new(
47
- [Endpoint::Indexed.new('b', 3),
48
- Endpoint::Indexed.new('a', 1)])
49
- expect(acses.map(&:location)).to eq ['a', 'b']
50
- expect(acses[1].location).to eq 'a'
51
- expect(acses[3].location).to eq 'b'
52
- expect(acses[0]).to be_nil
53
- end
54
-
55
- describe "#default" do
56
- it "should default to first entry if not otherwise specified" do
57
- acses = Endpoint::Indexed::Array.new(
58
- [Endpoint::Indexed.new('a', 0),
59
- Endpoint::Indexed.new('b', 1)])
60
- expect(acses.default.location).to eq 'a'
61
- end
62
-
63
- it "should default to a tagged default" do
64
- acses = Endpoint::Indexed::Array.new(
65
- [Endpoint::Indexed.new('a', 0),
66
- Endpoint::Indexed.new('b', 1, true)])
67
- expect(acses.default.location).to eq 'b'
68
- end
69
- end
70
- end
71
- end
data/spec/lib/key_spec.rb DELETED
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../spec_helper'
4
-
5
- module SAML2
6
- describe KeyInfo do
7
- describe '.format_fingerprint' do
8
- it 'strips non-hexadecimal characters' do
9
- expect(KeyInfo.format_fingerprint("\u200F abcdefghijklmnop 1234567890-\n a1")).to eq("ab:cd:ef:12:34:56:78:90:a1")
10
- end
11
- end
12
-
13
- describe '#certificate' do
14
- it "doesn't asplode if the keyinfo is just an rsa key value" do
15
- response = Nokogiri::XML(fixture("response_with_rsa_key_value.xml"))
16
- key = KeyInfo.from_xml(response.at_xpath('//dsig:KeyInfo', Namespaces::ALL))
17
- expect(key.certificate).to be_nil
18
- expect(key.fingerprint).to be_nil
19
- expect(key.key).not_to be_nil
20
- end
21
- end
22
- end
23
- end