saml2 3.1.2 → 3.1.4

Sign up to get free protection for your applications and to get access to all the features.
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