saml_idp 0.16.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,157 +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
-
27
- it "has a valid request_id" do
28
- expect(subject.request_id).to eq("_af43d1a0-e111-0130-661a-3c0754403fdb")
29
- end
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
30
31
 
31
- it "has a valid acs_url" do
32
- expect(subject.acs_url).to eq("http://localhost:3000/saml/consume")
33
- 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
34
37
 
35
- it "has a valid service_provider" do
36
- expect(subject.service_provider).to be_a ServiceProvider
37
- 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
38
43
 
39
- it "has a valid service_provider" do
40
- expect(subject.service_provider).to be_truthy
41
- 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
42
49
 
43
- it "has a valid issuer" do
44
- expect(subject.issuer).to eq("localhost:3000")
45
- 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
46
70
 
47
- it "has a valid valid_signature" do
48
- expect(subject.valid_signature?).to be_truthy
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
49
76
  end
50
77
 
51
- it "should return acs_url for response_url" do
52
- expect(subject.response_url).to eq(subject.acs_url)
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
53
82
  end
83
+ end
54
84
 
55
- it "is a authn request" do
56
- expect(subject.authn_request?).to eq(true)
57
- 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'] }
58
91
 
59
- it "fetches internal request" do
60
- expect(subject.request['ID']).to eq(subject.request_id)
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
+ )
61
100
  end
62
101
 
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')
102
+ it "should validate the request" do
103
+ expect(subject.valid_external_signature?).to be true
104
+ expect(subject.errors).to be_empty
65
105
  end
66
106
 
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-like logger is configured' do
95
- let(:logger) do
96
- Class.new {
97
- def info(msg); end
98
- }.new
99
- end
100
-
101
- before do
102
- allow(logger).to receive(:info)
103
- end
104
-
105
- it 'logs an error message' do
106
- expect(subject.valid?).to be false
107
- expect(logger).to have_received(:info).with('Unable to find service provider for issuer ')
108
- end
109
- end
110
-
111
- context 'a logger lambda is configured' do
112
- let(:logger) { double }
113
-
114
- before { allow(logger).to receive(:call) }
115
-
116
- it 'logs an error message' do
117
- expect(subject.valid?).to be false
118
- expect(logger).to have_received(:call).with('Unable to find service provider for issuer ')
119
- end
120
- end
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)
121
111
  end
122
112
  end
123
113
 
124
- describe "logout request" do
125
- 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>" }
126
-
127
- subject { described_class.new raw_logout_request }
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)
128
118
 
129
- it "has a valid request_id" do
130
- expect(subject.request_id).to eq('_some_response_id')
119
+ expect(request.valid?).to be false
120
+ expect(request.errors).to include(:sp_not_found)
131
121
  end
122
+ end
132
123
 
133
- it "should be flagged as a logout_request" do
134
- expect(subject.logout_request?).to eq(true)
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)
135
143
  end
144
+ end
136
145
 
137
- it "should have a valid name_id" do
138
- expect(subject.name_id).to eq('some_name_id')
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
+ )
139
159
  end
140
160
 
141
- it "should have a session index" do
142
- expect(subject.session_index).to eq('abc123index')
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)
143
164
  end
144
165
 
145
- it "should have a valid issuer" do
146
- expect(subject.issuer).to eq('http://example.com')
166
+ it "returns false and logs an error" do
167
+ request = SamlIdp::Request.from_deflated_request(valid_saml_request)
168
+
169
+ expect(request.valid?).to be false
170
+ expect(request.errors).to include(:empty_certificate)
147
171
  end
172
+ end
148
173
 
149
- it "fetches internal request" do
150
- expect(subject.request['ID']).to eq(subject.request_id)
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)
179
+
180
+ expect(request.valid?).to be false
181
+ expect(request.errors).to include(:unaccepted_request)
151
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)
152
190
 
153
- it "should return logout_url for response_url" do
154
- 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)
155
193
  end
156
194
  end
157
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,41 +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,
44
- assertions_signed: true)
49
+ def add_securty_options(settings, options = default_sp_security_options)
45
50
  # Security section
46
51
  settings.idp_cert = SamlIdp::Default::X509_CERTIFICATE
47
52
  # Signed embedded singature
48
- settings.security[:authn_requests_signed] = authn_requests_signed
49
- settings.security[:embed_sign] = embed_sign
50
- settings.security[:logout_requests_signed] = logout_requests_signed
51
- settings.security[:logout_responses_signed] = logout_responses_signed
52
- settings.security[:metadata_signed] = digest_method
53
- settings.security[:digest_method] = digest_method
54
- settings.security[:signature_method] = signature_method
55
- settings.security[:want_assertions_signed] = assertions_signed
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]
56
61
  settings.private_key = sp_pv_key
57
62
  settings.certificate = sp_x509_cert
58
63
  end
@@ -84,16 +89,51 @@ module SamlRequestMacros
84
89
  response_hosts: [URI(saml_acs_url).host],
85
90
  acs_url: saml_acs_url,
86
91
  cert: sp_x509_cert,
87
- 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'
88
94
  }
89
95
  }
90
96
  end
91
97
  end
92
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
+
93
110
  def print_pretty_xml(xml_string)
94
111
  doc = REXML::Document.new xml_string
95
112
  outbuf = ""
96
113
  doc.write(outbuf, 1)
97
114
  puts outbuf
98
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
99
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