saml_idp 0.15.0 → 1.0.0

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.
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+
2
3
  module SamlIdp
3
4
 
4
5
  metadata_1 = <<-eos
@@ -29,11 +30,52 @@ module SamlIdp
29
30
  </md:EntityDescriptor>
30
31
  eos
31
32
 
33
+ metadata_5 = <<-eos
34
+ <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
35
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
36
+ <md:KeyDescriptor>
37
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
38
+ <ds:X509Data>
39
+ <ds:X509Certificate>MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnht3GR...</ds:X509Certificate>
40
+ </ds:X509Data>
41
+ </ds:KeyInfo>
42
+ </md:KeyDescriptor>
43
+ </md:SPSSODescriptor>
44
+ </md:EntityDescriptor>
45
+ eos
46
+
47
+ metadata_6 = <<-eos
48
+ <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
49
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
50
+ <md:KeyDescriptor use="signing">
51
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
52
+ <ds:X509Data>
53
+ <ds:X509Certificate>MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmw6vGr...</ds:X509Certificate>
54
+ </ds:X509Data>
55
+ </ds:KeyInfo>
56
+ </md:KeyDescriptor>
57
+ </md:SPSSODescriptor>
58
+ </md:EntityDescriptor>
59
+ eos
60
+
61
+ metadata_7 = <<-eos
62
+ <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
63
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
64
+ <md:KeyDescriptor use="encryption">
65
+ <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
66
+ <ds:X509Data>
67
+ <ds:X509Certificate>MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1dX3Gr...</ds:X509Certificate>
68
+ </ds:X509Data>
69
+ </ds:KeyInfo>
70
+ </md:KeyDescriptor>
71
+ </md:SPSSODescriptor>
72
+ </md:EntityDescriptor>
73
+ eos
74
+
32
75
  describe IncomingMetadata do
33
76
  it 'should properly set sign_assertions to false' do
34
77
  metadata = SamlIdp::IncomingMetadata.new(metadata_1)
35
78
  expect(metadata.sign_assertions).to eq(false)
36
- expect(metadata.sign_authn_request).to eq(false)
37
79
  end
38
80
 
39
81
  it 'should properly set entity_id as https://test-saml.com/saml' do
@@ -56,5 +98,37 @@ module SamlIdp
56
98
  metadata = SamlIdp::IncomingMetadata.new(metadata_4)
57
99
  expect(metadata.sign_authn_request).to eq(false)
58
100
  end
101
+
102
+ it 'should properly set unspecified_certificate when present' do
103
+ metadata = SamlIdp::IncomingMetadata.new(metadata_5)
104
+ expect(metadata.unspecified_certificate).to eq('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnht3GR...')
105
+ end
106
+
107
+ it 'should return empty unspecified_certificate when not present' do
108
+ metadata = SamlIdp::IncomingMetadata.new(metadata_1)
109
+ expect(metadata.unspecified_certificate).to eq('')
110
+ end
111
+
112
+ it 'should properly set signing_certificate when present but not unspecified_certificate' do
113
+ metadata = SamlIdp::IncomingMetadata.new(metadata_6)
114
+ expect(metadata.signing_certificate).to eq('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmw6vGr...')
115
+ expect(metadata.unspecified_certificate).to eq('')
116
+ end
117
+
118
+ it 'should return empty signing_certificate when not present' do
119
+ metadata = SamlIdp::IncomingMetadata.new(metadata_1)
120
+ expect(metadata.signing_certificate).to eq('')
121
+ end
122
+
123
+ it 'should properly set encryption_certificate when present but not unspecified_certificate' do
124
+ metadata = SamlIdp::IncomingMetadata.new(metadata_7)
125
+ expect(metadata.encryption_certificate).to eq('MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1dX3Gr...')
126
+ expect(metadata.unspecified_certificate).to eq('')
127
+ end
128
+
129
+ it 'should return empty encryption_certificate when not present' do
130
+ metadata = SamlIdp::IncomingMetadata.new(metadata_1)
131
+ expect(metadata.encryption_certificate).to eq('')
132
+ end
59
133
  end
60
134
  end
@@ -6,7 +6,7 @@ module SamlIdp
6
6
  end
7
7
 
8
8
  it "signs valid xml" do
9
- expect(Saml::XML::Document.parse(subject.signed).valid_signature?(Default::FINGERPRINT)).to be_truthy
9
+ expect(Saml::XML::Document.parse(subject.signed).valid_signature?("", Default::FINGERPRINT)).to be_truthy
10
10
  end
11
11
 
12
12
  it "includes logout element" do
@@ -1,140 +1,195 @@
1
1
  require 'spec_helper'
2
- module SamlIdp
3
- describe Request do
4
- let(:issuer) { 'localhost:3000' }
5
- let(:raw_authn_request) do
6
- "<samlp:AuthnRequest AssertionConsumerServiceURL='http://localhost:3000/saml/consume' Destination='http://localhost:1337/saml/auth' ID='_af43d1a0-e111-0130-661a-3c0754403fdb' IssueInstant='2013-08-06T22:01:35Z' Version='2.0' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'><saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>#{issuer}</saml:Issuer><samlp:NameIDPolicy AllowCreate='true' Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'/><samlp:RequestedAuthnContext Comparison='exact'><saml:AuthnContextClassRef xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></samlp:RequestedAuthnContext></samlp:AuthnRequest>"
7
- end
8
2
 
9
- describe "deflated request" do
10
- let(:deflated_request) { Base64.encode64(Zlib::Deflate.deflate(raw_authn_request, 9)[2..-5]) }
3
+ RSpec.describe SamlIdp::Request, type: :model do
4
+ let(:valid_saml_request) { make_saml_request("https://foo.example.com/saml/consume", true) }
5
+ let(:valid_logout_request) { make_saml_sp_slo_request(security_options: { embed_sign: true })['SAMLRequest'] }
6
+ let(:invalid_saml_request) { "invalid_saml_request" }
7
+ let(:external_attributes) { { saml_request: valid_saml_request, relay_state: "state" } }
11
8
 
12
- subject { described_class.from_deflated_request deflated_request }
9
+ describe ".from_deflated_request" do
10
+ context "when request is valid and deflated" do
11
+ it "inflates and decodes the request" do
12
+ request = SamlIdp::Request.from_deflated_request(valid_saml_request)
13
13
 
14
- it "inflates" do
15
- expect(subject.request_id).to eq("_af43d1a0-e111-0130-661a-3c0754403fdb")
14
+ expect { Saml::XML::Document.parse(request.raw_xml) }.not_to raise_error
16
15
  end
16
+ end
17
17
 
18
- it "handles invalid SAML" do
19
- req = described_class.from_deflated_request "bang!"
20
- expect(req.valid?).to eq(false)
18
+ context "when request is invalid" do
19
+ it "returns an empty inflated string" do
20
+ request = SamlIdp::Request.from_deflated_request(nil)
21
+ expect(request.raw_xml).to eq("")
21
22
  end
22
23
  end
24
+ end
23
25
 
24
- describe "authn request" do
25
- subject { described_class.new raw_authn_request }
26
+ describe "#logout_request?" do
27
+ it "returns true for a valid logout request" do
28
+ request = SamlIdp::Request.from_deflated_request(valid_logout_request)
29
+ expect(request.logout_request?).to be true
30
+ end
26
31
 
27
- it "has a valid request_id" do
28
- expect(subject.request_id).to eq("_af43d1a0-e111-0130-661a-3c0754403fdb")
29
- end
32
+ it "returns false for a non-logout request" do
33
+ request = SamlIdp::Request.from_deflated_request(valid_saml_request)
34
+ expect(request.logout_request?).to be false
35
+ end
36
+ end
30
37
 
31
- it "has a valid acs_url" do
32
- expect(subject.acs_url).to eq("http://localhost:3000/saml/consume")
33
- end
38
+ describe "#authn_request?" do
39
+ it "returns true for a valid authn request" do
40
+ request = SamlIdp::Request.from_deflated_request(valid_saml_request)
41
+ expect(request.authn_request?).to be true
42
+ end
34
43
 
35
- it "has a valid service_provider" do
36
- expect(subject.service_provider).to be_a ServiceProvider
37
- end
44
+ it "returns false for a non-authn request" do
45
+ request = SamlIdp::Request.from_deflated_request(valid_logout_request)
46
+ expect(request.authn_request?).to be false
47
+ end
48
+ end
38
49
 
39
- it "has a valid service_provider" do
40
- expect(subject.service_provider).to be_truthy
41
- end
50
+ describe "#valid?" do
51
+ let(:sp_issuer) { "test_issuer" }
52
+ let(:valid_service_provider) do
53
+ instance_double(
54
+ "SamlIdp::ServiceProvider",
55
+ valid?: true,
56
+ acs_url: 'https://foo.example.com/saml/consume',
57
+ current_metadata: instance_double("Metadata", sign_authn_request?: true),
58
+ assertion_consumer_logout_service_url: 'https://foo.example.com/saml/logout',
59
+ sign_authn_request: true,
60
+ acceptable_response_hosts: ["foo.example.com"],
61
+ cert: sp_x509_cert,
62
+ fingerprint: SamlIdp::Fingerprint.certificate_digest(sp_x509_cert, :sha256),
63
+ )
64
+ end
65
+
66
+ before do
67
+ allow_any_instance_of(SamlIdp::Request).to receive(:service_provider).and_return(valid_service_provider)
68
+ allow_any_instance_of(SamlIdp::Request).to receive(:issuer).and_return(sp_issuer)
69
+ end
42
70
 
43
- it "has a valid issuer" do
44
- expect(subject.issuer).to eq("localhost:3000")
71
+ context "when the request is valid" do
72
+ it "returns true for a valid authn request" do
73
+ request = SamlIdp::Request.from_deflated_request(valid_saml_request)
74
+ expect(request.errors).to be_empty
75
+ expect(request.valid?).to be true
45
76
  end
46
77
 
47
- it "has a valid valid_signature" do
48
- expect(subject.valid_signature?).to be_truthy
78
+ it "returns true for a valid logout request" do
79
+ request = SamlIdp::Request.from_deflated_request(valid_logout_request)
80
+ expect(request.errors).to be_empty
81
+ expect(request.valid?).to be true
49
82
  end
83
+ end
50
84
 
51
- it "should return acs_url for response_url" do
52
- expect(subject.response_url).to eq(subject.acs_url)
53
- end
85
+ context 'when signature provided as external param' do
86
+ let!(:uri_query) { make_saml_sp_slo_request(security_options: { embed_sign: false }) }
87
+ let(:raw_saml_request) { uri_query['SAMLRequest'] }
88
+ let(:relay_state) { uri_query['RelayState'] }
89
+ let(:siging_algorithm) { uri_query['SigAlg'] }
90
+ let(:signature) { uri_query['Signature'] }
54
91
 
55
- it "is a authn request" do
56
- expect(subject.authn_request?).to eq(true)
92
+ subject do
93
+ described_class.from_deflated_request(
94
+ raw_saml_request,
95
+ saml_request: raw_saml_request,
96
+ relay_state: relay_state,
97
+ sig_algorithm: siging_algorithm,
98
+ signature: signature
99
+ )
57
100
  end
58
101
 
59
- it "fetches internal request" do
60
- expect(subject.request['ID']).to eq(subject.request_id)
102
+ it "should validate the request" do
103
+ expect(subject.valid_external_signature?).to be true
104
+ expect(subject.errors).to be_empty
61
105
  end
62
106
 
63
- it 'has a valid authn context' do
64
- expect(subject.requested_authn_context).to eq('urn:oasis:names:tc:SAML:2.0:ac:classes:Password')
107
+ it "should collect errors when the signature is invalid" do
108
+ allow(subject).to receive(:valid_external_signature?).and_return(false)
109
+ expect(subject.valid?).to eq(false)
110
+ expect(subject.errors).to include(:invalid_external_signature)
65
111
  end
112
+ end
66
113
 
67
- context 'the issuer is empty' do
68
- let(:issuer) { nil }
69
- let(:logger) { ->(msg) { puts msg } }
70
-
71
- before do
72
- allow(SamlIdp.config).to receive(:logger).and_return(logger)
73
- end
74
-
75
- it 'is invalid' do
76
- expect(subject.issuer).to_not eq('')
77
- expect(subject.issuer).to be_nil
78
- expect(subject.valid?).to eq(false)
79
- end
80
-
81
- context 'a Ruby Logger is configured' do
82
- let(:logger) { Logger.new($stdout) }
83
-
84
- before do
85
- allow(logger).to receive(:info)
86
- end
87
-
88
- it 'logs an error message' do
89
- expect(subject.valid?).to be false
90
- expect(logger).to have_received(:info).with('Unable to find service provider for issuer ')
91
- end
92
- end
93
-
94
- context 'a logger lambda is configured' do
95
- let(:logger) { double }
96
-
97
- before { allow(logger).to receive(:call) }
114
+ context "when the service provider is invalid" do
115
+ it "returns false and logs an error" do
116
+ allow_any_instance_of(SamlIdp::Request).to receive(:service_provider?).and_return(false)
117
+ request = SamlIdp::Request.from_deflated_request(valid_saml_request)
98
118
 
99
- it 'logs an error message' do
100
- expect(subject.valid?).to be false
101
- expect(logger).to have_received(:call).with('Unable to find service provider for issuer ')
102
- end
103
- end
119
+ expect(request.valid?).to be false
120
+ expect(request.errors).to include(:sp_not_found)
104
121
  end
105
122
  end
106
123
 
107
- describe "logout request" do
108
- let(:raw_logout_request) { "<LogoutRequest ID='_some_response_id' Version='2.0' IssueInstant='2010-06-01T13:00:00Z' Destination='http://localhost:3000/saml/logout' xmlns='urn:oasis:names:tc:SAML:2.0:protocol'><Issuer xmlns='urn:oasis:names:tc:SAML:2.0:assertion'>http://example.com</Issuer><NameID xmlns='urn:oasis:names:tc:SAML:2.0:assertion' Format='urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'>some_name_id</NameID><SessionIndex>abc123index</SessionIndex></LogoutRequest>" }
109
-
110
- subject { described_class.new raw_logout_request }
111
-
112
- it "has a valid request_id" do
113
- expect(subject.request_id).to eq('_some_response_id')
124
+ context "when empty certificate for authn request validation" do
125
+ let(:valid_service_provider) do
126
+ instance_double(
127
+ "SamlIdp::ServiceProvider",
128
+ valid?: true,
129
+ acs_url: 'https://foo.example.com/saml/consume',
130
+ current_metadata: instance_double("Metadata", sign_authn_request?: true),
131
+ assertion_consumer_logout_service_url: 'https://foo.example.com/saml/logout',
132
+ sign_authn_request: true,
133
+ acceptable_response_hosts: ["foo.example.com"],
134
+ cert: nil,
135
+ fingerprint: nil,
136
+ )
137
+ end
138
+ it "returns false and logs an error" do
139
+ request = SamlIdp::Request.from_deflated_request(valid_saml_request)
140
+
141
+ expect(request.valid?).to be false
142
+ expect(request.errors).to include(:empty_certificate)
114
143
  end
144
+ end
115
145
 
116
- it "should be flagged as a logout_request" do
117
- expect(subject.logout_request?).to eq(true)
146
+ context "when empty certificate for logout validation" do
147
+ let(:valid_service_provider) do
148
+ instance_double(
149
+ "SamlIdp::ServiceProvider",
150
+ valid?: true,
151
+ acs_url: 'https://foo.example.com/saml/consume',
152
+ current_metadata: instance_double("Metadata", sign_authn_request?: true),
153
+ assertion_consumer_logout_service_url: 'https://foo.example.com/saml/logout',
154
+ sign_authn_request: true,
155
+ acceptable_response_hosts: ["foo.example.com"],
156
+ cert: nil,
157
+ fingerprint: nil,
158
+ )
118
159
  end
119
160
 
120
- it "should have a valid name_id" do
121
- expect(subject.name_id).to eq('some_name_id')
161
+ before do
162
+ allow_any_instance_of(SamlIdp::Request).to receive(:authn_request?).and_return(false)
163
+ allow_any_instance_of(SamlIdp::Request).to receive(:logout_request?).and_return(true)
122
164
  end
123
165
 
124
- it "should have a session index" do
125
- expect(subject.session_index).to eq('abc123index')
126
- end
166
+ it "returns false and logs an error" do
167
+ request = SamlIdp::Request.from_deflated_request(valid_saml_request)
127
168
 
128
- it "should have a valid issuer" do
129
- expect(subject.issuer).to eq('http://example.com')
169
+ expect(request.valid?).to be false
170
+ expect(request.errors).to include(:empty_certificate)
130
171
  end
172
+ end
173
+
174
+ context "when both authn and logout requests are present" do
175
+ it "returns false and logs an error" do
176
+ allow_any_instance_of(SamlIdp::Request).to receive(:authn_request?).and_return(true)
177
+ allow_any_instance_of(SamlIdp::Request).to receive(:logout_request?).and_return(true)
178
+ request = SamlIdp::Request.from_deflated_request(valid_saml_request)
131
179
 
132
- it "fetches internal request" do
133
- expect(subject.request['ID']).to eq(subject.request_id)
180
+ expect(request.valid?).to be false
181
+ expect(request.errors).to include(:unaccepted_request)
134
182
  end
183
+ end
184
+
185
+ context "when the signature is invalid" do
186
+ it "returns false and logs an error" do
187
+ allow_any_instance_of(SamlIdp::Request).to receive(:valid_signature?).and_return(false)
188
+ allow_any_instance_of(SamlIdp::Request).to receive(:log)
189
+ request = SamlIdp::Request.from_deflated_request(valid_saml_request)
135
190
 
136
- it "should return logout_url for response_url" do
137
- expect(subject.response_url).to eq(subject.logout_url)
191
+ expect(request.valid?).to be false
192
+ expect(request.errors).to include(:invalid_embedded_signature)
138
193
  end
139
194
  end
140
195
  end
@@ -192,6 +192,25 @@ module SamlIdp
192
192
  expect(saml_resp.is_valid?).to eq(true)
193
193
  end
194
194
 
195
+ it "will pass reference_id as SessionIndex" do
196
+ expect { subject.build }.not_to raise_error
197
+ signed_encoded_xml = subject.build
198
+ resp_settings = saml_settings(saml_acs_url)
199
+ resp_settings.private_key = Default::SECRET_KEY
200
+ resp_settings.issuer = audience_uri
201
+ saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
202
+
203
+ expect(
204
+ Nokogiri::XML(saml_resp.response).at_xpath(
205
+ "//saml:AuthnStatement/@SessionIndex",
206
+ {
207
+ "samlp" => "urn:oasis:names:tc:SAML:2.0:protocol",
208
+ "saml" => "urn:oasis:names:tc:SAML:2.0:assertion"
209
+ }
210
+ ).value
211
+ ).to eq("_#{reference_id}")
212
+ end
213
+
195
214
  it "sets session expiration" do
196
215
  saml_resp = OneLogin::RubySaml::Response.new(subject.build)
197
216
  expect(saml_resp.session_expires_at).to eq Time.local(1990, "jan", 2).iso8601
@@ -4,6 +4,9 @@
4
4
  <%= form_tag do %>
5
5
  <%= hidden_field_tag("SAMLRequest", params[:SAMLRequest]) %>
6
6
  <%= hidden_field_tag("RelayState", params[:RelayState]) %>
7
+ <%= hidden_field_tag("SigAlg", params[:SigAlg]) %>
8
+ <%= hidden_field_tag("Signature", params[:Signature]) %>
9
+
7
10
  <p>
8
11
  <%= label_tag :email %>
9
12
  <%= email_field_tag :email, params[:email], :autocapitalize => "off", :autocorrect => "off", :autofocus => "autofocus", :spellcheck => "false", :size => 30, :class => "email_pwd txt" %>
@@ -3,8 +3,8 @@ require 'saml_idp/logout_request_builder'
3
3
  module SamlRequestMacros
4
4
  def make_saml_request(requested_saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false)
5
5
  auth_request = OneLogin::RubySaml::Authrequest.new
6
- auth_url = auth_request.create(saml_settings(requested_saml_acs_url, enable_secure_options))
7
- CGI.unescape(auth_url.split("=").last)
6
+ auth_url = auth_request.create_params(saml_settings(requested_saml_acs_url, enable_secure_options))
7
+ auth_url['SAMLRequest']
8
8
  end
9
9
 
10
10
  def make_saml_logout_request(requested_saml_logout_url = 'https://foo.example.com/saml/logout')
@@ -18,39 +18,46 @@ module SamlRequestMacros
18
18
  Base64.strict_encode64(request_builder.signed)
19
19
  end
20
20
 
21
+ def make_saml_sp_slo_request(param_type: true, security_options: {})
22
+ logout_request = OneLogin::RubySaml::Logoutrequest.new
23
+ saml_sp_setting = saml_settings("https://foo.example.com/saml/consume", true, security_options: security_options)
24
+ if param_type
25
+ logout_request.create_params(saml_sp_setting, 'RelayState' => 'https://foo.example.com/home')
26
+ else
27
+ logout_request.create(saml_sp_setting, 'RelayState' => 'https://foo.example.com/home')
28
+ end
29
+ end
30
+
21
31
  def generate_sp_metadata(saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false)
22
32
  sp_metadata = OneLogin::RubySaml::Metadata.new
23
33
  sp_metadata.generate(saml_settings(saml_acs_url, enable_secure_options), true)
24
34
  end
25
35
 
26
- def saml_settings(saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false)
36
+ def saml_settings(saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false, security_options: {})
27
37
  settings = OneLogin::RubySaml::Settings.new
28
38
  settings.assertion_consumer_service_url = saml_acs_url
29
39
  settings.issuer = "http://example.com/issuer"
30
40
  settings.idp_sso_target_url = "http://idp.com/saml/idp"
41
+ settings.idp_slo_target_url = "http://idp.com/saml/slo"
31
42
  settings.assertion_consumer_logout_service_url = 'https://foo.example.com/saml/logout'
32
43
  settings.idp_cert_fingerprint = SamlIdp::Default::FINGERPRINT
33
44
  settings.name_identifier_format = SamlIdp::Default::NAME_ID_FORMAT
34
- add_securty_options(settings) if enable_secure_options
45
+ add_securty_options(settings, default_sp_security_options.merge!(security_options)) if enable_secure_options
35
46
  settings
36
47
  end
37
48
 
38
- def add_securty_options(settings, authn_requests_signed: true,
39
- embed_sign: true,
40
- logout_requests_signed: true,
41
- logout_responses_signed: true,
42
- digest_method: XMLSecurity::Document::SHA256,
43
- signature_method: XMLSecurity::Document::RSA_SHA256)
49
+ def add_securty_options(settings, options = default_sp_security_options)
44
50
  # Security section
45
51
  settings.idp_cert = SamlIdp::Default::X509_CERTIFICATE
46
52
  # Signed embedded singature
47
- settings.security[:authn_requests_signed] = authn_requests_signed
48
- settings.security[:embed_sign] = embed_sign
49
- settings.security[:logout_requests_signed] = logout_requests_signed
50
- settings.security[:logout_responses_signed] = logout_responses_signed
51
- settings.security[:metadata_signed] = digest_method
52
- settings.security[:digest_method] = digest_method
53
- settings.security[:signature_method] = signature_method
53
+ settings.security[:authn_requests_signed] = options[:authn_requests_signed]
54
+ settings.security[:embed_sign] = options[:embed_sign]
55
+ settings.security[:logout_requests_signed] = options[:logout_requests_signed]
56
+ settings.security[:logout_responses_signed] = options[:logout_responses_signed]
57
+ settings.security[:metadata_signed] = options[:digest_method]
58
+ settings.security[:digest_method] = options[:digest_method]
59
+ settings.security[:signature_method] = options[:signature_method]
60
+ settings.security[:want_assertions_signed] = options[:assertions_signed]
54
61
  settings.private_key = sp_pv_key
55
62
  settings.certificate = sp_x509_cert
56
63
  end
@@ -82,16 +89,51 @@ module SamlRequestMacros
82
89
  response_hosts: [URI(saml_acs_url).host],
83
90
  acs_url: saml_acs_url,
84
91
  cert: sp_x509_cert,
85
- fingerprint: SamlIdp::Fingerprint.certificate_digest(sp_x509_cert)
92
+ fingerprint: SamlIdp::Fingerprint.certificate_digest(sp_x509_cert),
93
+ assertion_consumer_logout_service_url: 'https://foo.example.com/saml/logout'
86
94
  }
87
95
  }
88
96
  end
89
97
  end
90
98
 
99
+ def decode_saml_request(saml_request)
100
+ decoded_request = Base64.decode64(saml_request)
101
+ begin
102
+ # Try to decompress, since SAMLRequest might be compressed
103
+ Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(decoded_request)
104
+ rescue Zlib::DataError
105
+ # If it's not compressed, just return the decoded request
106
+ decoded_request
107
+ end
108
+ end
109
+
91
110
  def print_pretty_xml(xml_string)
92
111
  doc = REXML::Document.new xml_string
93
112
  outbuf = ""
94
113
  doc.write(outbuf, 1)
95
114
  puts outbuf
96
115
  end
116
+
117
+ def decode_saml_request(saml_request)
118
+ decoded_request = Base64.decode64(saml_request)
119
+ begin
120
+ # Try to decompress, since SAMLRequest might be compressed
121
+ Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(decoded_request)
122
+ rescue Zlib::DataError
123
+ # If it's not compressed, just return the decoded request
124
+ decoded_request
125
+ end
126
+ end
127
+
128
+ def default_sp_security_options
129
+ {
130
+ authn_requests_signed: true,
131
+ embed_sign: true,
132
+ logout_requests_signed: true,
133
+ logout_responses_signed: true,
134
+ digest_method: XMLSecurity::Document::SHA256,
135
+ signature_method: XMLSecurity::Document::RSA_SHA256,
136
+ assertions_signed: true
137
+ }
138
+ end
97
139
  end
@@ -51,8 +51,8 @@ module SecurityHelpers
51
51
  @signature_fingerprint1 ||= "C5:19:85:D9:47:F1:BE:57:08:20:25:05:08:46:EB:27:F6:CA:B7:83"
52
52
  end
53
53
 
54
- def signature_1
55
- @signature1 ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'certificate1'))
54
+ def certificate_1
55
+ @certificate_1 ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'certificate1'))
56
56
  end
57
57
 
58
58
  def r1_signature_2
@@ -19,7 +19,7 @@ module SamlIdp
19
19
  end
20
20
 
21
21
  it "it raise Fingerprint mismatch" do
22
- expect { document.validate("no:fi:ng:er:pr:in:t", false) }.to(
22
+ expect { document.validate("", "no:fi:ng:er:pr:in:t", false) }.to(
23
23
  raise_error(SamlIdp::XMLSecurity::SignedDocument::ValidationError, "Fingerprint mismatch")
24
24
  )
25
25
  end
@@ -45,10 +45,10 @@ module SamlIdp
45
45
  response = Base64.decode64(response_document)
46
46
  response.sub!(/<ds:X509Certificate>.*<\/ds:X509Certificate>/, "")
47
47
  document = XMLSecurity::SignedDocument.new(response)
48
- expect { document.validate("a fingerprint", false) }.to(
48
+ expect { document.validate("", "a fingerprint", false) }.to(
49
49
  raise_error(
50
50
  SamlIdp::XMLSecurity::SignedDocument::ValidationError,
51
- "Certificate element missing in response (ds:X509Certificate)"
51
+ "Certificate validation is required, but it doesn't exist."
52
52
  )
53
53
  )
54
54
  end
@@ -57,22 +57,26 @@ module SamlIdp
57
57
  describe "Algorithms" do
58
58
  it "validate using SHA1" do
59
59
  document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha1, false))
60
- expect(document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")).to be_truthy
60
+ base64cert = document.elements["//ds:X509Certificate"].text
61
+ expect(document.validate(base64cert, "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")).to be_truthy
61
62
  end
62
63
 
63
64
  it "validate using SHA256" do
64
65
  document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha256, false))
65
- expect(document.validate("28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA")).to be_truthy
66
+ base64cert = document.elements["//ds:X509Certificate"].text
67
+ expect(document.validate(base64cert, "28:74:9B:E8:1F:E8:10:9C:A8:7C:A9:C3:E3:C5:01:6C:92:1C:B4:BA")).to be_truthy
66
68
  end
67
69
 
68
70
  it "validate using SHA384" do
69
71
  document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha384, false))
70
- expect(document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")).to be_truthy
72
+ base64cert = document.elements["//ds:X509Certificate"].text
73
+ expect(document.validate(base64cert, "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")).to be_truthy
71
74
  end
72
75
 
73
76
  it "validate using SHA512" do
74
77
  document = XMLSecurity::SignedDocument.new(fixture(:adfs_response_sha512, false))
75
- expect(document.validate("F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")).to be_truthy
78
+ base64cert = document.elements["//ds:X509Certificate"].text
79
+ expect(document.validate(base64cert, "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72")).to be_truthy
76
80
  end
77
81
  end
78
82