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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7cd98d292836070eddac0b7f710dbd15b0d3611cc849de827b999c3a2fa12d86
4
- data.tar.gz: f49f1035b63375d5b8aebfd38484c1859d8ac35520f82df92fc698cea00dde1a
3
+ metadata.gz: 4a0fd46437c04823ad86f269552282b0252cb888294345984c5e16b88bcfa9cf
4
+ data.tar.gz: b7c56c2516f7aaf392fe5a999e98f67af0b79f799374757f3724724605616fc4
5
5
  SHA512:
6
- metadata.gz: 7ab18f32c643b5c8be43a093349ff5c1c0f3142b8156fb650f749ffde2ce1306e104d4006178f77f27055bc6683a35ffbf8aa1d79331140b9c94ae7a2a1da7ee
7
- data.tar.gz: e68e9a8f0dd08f5dd9aa31c8ff8461ca229b97cbc5e9813eed9c3575ee65bafd2c766bcfd19d9081d0956bab62b8819574b8765be692156d10967589a791cda3
6
+ metadata.gz: 558df23c6ffefed06a205df4ea72cc530f7a963c64741e0bb86cae16f7ffd26a822299793a6b4d7a587addf49428a71e3449f46650d1a26990ec563b690a6cac
7
+ data.tar.gz: ee6eec4ee019c3e01443bbc6d383e4eb6dcd422e3eb5dddcbefd10666799d9604e094e9dec22b213780a415f9efaaebfe54f8b512fef90322f6ce3cc89e4d751
data/README.md CHANGED
@@ -24,9 +24,13 @@ Add this to your Gemfile:
24
24
 
25
25
  Include `SamlIdp::Controller` and see the examples that use rails. It should be straightforward for you.
26
26
 
27
- Basically you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
28
- `saml_acs_url` to determine the source for which you need to authenticate a user. How you authenticate
29
- a user is entirely up to you.
27
+ Basically, you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
28
+ `saml_acs_url` to determine the source for which you need to authenticate a user.
29
+ If the signature (`Signature`) and signing algorithm (`SigAlg`) are provided as external parameters in the request,
30
+ you can pass those parameters as `decode_request(params[:SAMLRequest], params[:Signature], params[:SigAlg], params[:RelayState])`.
31
+ Then, you can verify the request signature with the `valid?` method.
32
+
33
+ How you authenticate a user is entirely up to you.
30
34
 
31
35
  Once a user has successfully authenticated on your system send the Service Provider a SAMLResponse by
32
36
  posting to `saml_acs_url` the parameter `SAMLResponse` with the return value from a call to
@@ -74,6 +78,11 @@ KEY DATA
74
78
  -----END RSA PRIVATE KEY-----
75
79
  CERT
76
80
 
81
+ # x509_certificate, secret_key, and password may also be set from within a proc, for example:
82
+ # config.x509_certificate = -> { File.read("cert.pem") }
83
+ # config.secret_key = -> { SecretKeyFinder.key_for(id: 1) }
84
+ # config.password = -> { "password" }
85
+
77
86
  # config.password = "secret_key_password"
78
87
  # config.algorithm = :sha256 # Default: sha1 only for development.
79
88
  # config.organization_name = "Your Organization"
@@ -25,8 +25,8 @@ module SamlIdp
25
25
  attr_accessor :logger
26
26
 
27
27
  def initialize
28
- self.x509_certificate = Default::X509_CERTIFICATE
29
- self.secret_key = Default::SECRET_KEY
28
+ self.x509_certificate = -> { Default::X509_CERTIFICATE }
29
+ self.secret_key = -> { Default::SECRET_KEY }
30
30
  self.algorithm = :sha1
31
31
  self.reference_id_generator = ->() { SecureRandom.uuid }
32
32
  self.service_provider = OpenStruct.new
@@ -35,7 +35,7 @@ module SamlIdp
35
35
  self.service_provider.persisted_metadata_getter = ->(id, service_provider) { }
36
36
  self.session_expiry = 0
37
37
  self.attributes = {}
38
- self.logger = defined?(::Rails) ? Rails.logger : ->(msg) { puts msg }
38
+ self.logger = (defined?(::Rails) && Rails.respond_to?(:logger)) ? Rails.logger : ->(msg) { puts msg }
39
39
  end
40
40
 
41
41
  # formats
@@ -33,15 +33,18 @@ module SamlIdp
33
33
  end
34
34
 
35
35
  def validate_saml_request(raw_saml_request = params[:SAMLRequest])
36
- decode_request(raw_saml_request)
37
- return true if valid_saml_request?
38
-
39
- head :forbidden if defined?(::Rails)
40
- false
36
+ decode_request(raw_saml_request, params[:Signature], params[:SigAlg], params[:RelayState])
37
+ valid_saml_request?
41
38
  end
42
39
 
43
- def decode_request(raw_saml_request)
44
- @saml_request = Request.from_deflated_request(raw_saml_request)
40
+ def decode_request(raw_saml_request, signature, sig_algorithm, relay_state)
41
+ @saml_request = Request.from_deflated_request(
42
+ raw_saml_request,
43
+ saml_request: raw_saml_request,
44
+ signature: signature,
45
+ sig_algorithm: sig_algorithm,
46
+ relay_state: relay_state
47
+ )
45
48
  end
46
49
 
47
50
  def authn_context_classref
@@ -63,7 +66,7 @@ module SamlIdp
63
66
  signed_message_opts = opts[:signed_message] || false
64
67
  name_id_formats_opts = opts[:name_id_formats] || nil
65
68
  asserted_attributes_opts = opts[:attributes] || nil
66
- signed_assertion_opts = opts[:signed_assertion] || true
69
+ signed_assertion_opts = opts[:signed_assertion].nil? ? true : opts[:signed_assertion]
67
70
  compress_opts = opts[:compress] || false
68
71
 
69
72
  SamlResponse.new(
@@ -63,6 +63,15 @@ module SamlIdp
63
63
  end
64
64
  hashable :contact_person
65
65
 
66
+ def unspecified_certificate
67
+ xpath(
68
+ "//md:SPSSODescriptor/md:KeyDescriptor[not(@use)]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
69
+ ds: signature_namespace,
70
+ md: metadata_namespace
71
+ ).first.try(:content).to_s
72
+ end
73
+ hashable :unspecified_certificate
74
+
66
75
  def signing_certificate
67
76
  xpath(
68
77
  "//md:SPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
@@ -152,7 +152,8 @@ module SamlIdp
152
152
  private :raw_algorithm
153
153
 
154
154
  def x509_certificate
155
- SamlIdp.config.x509_certificate
155
+ certificate = SamlIdp.config.x509_certificate.is_a?(Proc) ? SamlIdp.config.x509_certificate.call : SamlIdp.config.x509_certificate
156
+ certificate
156
157
  .to_s
157
158
  .gsub(/-----BEGIN CERTIFICATE-----/,"")
158
159
  .gsub(/-----END CERTIFICATE-----/,"")
@@ -3,7 +3,9 @@ require 'saml_idp/service_provider'
3
3
  require 'logger'
4
4
  module SamlIdp
5
5
  class Request
6
- def self.from_deflated_request(raw)
6
+ attr_accessor :errors
7
+
8
+ def self.from_deflated_request(raw, external_attributes = {})
7
9
  if raw
8
10
  decoded = Base64.decode64(raw)
9
11
  zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
@@ -18,18 +20,23 @@ module SamlIdp
18
20
  else
19
21
  inflated = ""
20
22
  end
21
- new(inflated)
23
+ new(inflated, external_attributes)
22
24
  end
23
25
 
24
- attr_accessor :raw_xml
26
+ attr_accessor :raw_xml, :saml_request, :signature, :sig_algorithm, :relay_state
25
27
 
26
28
  delegate :config, to: :SamlIdp
27
29
  private :config
28
30
  delegate :xpath, to: :document
29
31
  private :xpath
30
32
 
31
- def initialize(raw_xml = "")
33
+ def initialize(raw_xml = "", external_attributes = {})
32
34
  self.raw_xml = raw_xml
35
+ self.saml_request = external_attributes[:saml_request]
36
+ self.relay_state = external_attributes[:relay_state]
37
+ self.sig_algorithm = external_attributes[:sig_algorithm]
38
+ self.signature = external_attributes[:signature]
39
+ self.errors = []
33
40
  end
34
41
 
35
42
  def logout_request?
@@ -85,30 +92,53 @@ module SamlIdp
85
92
  end
86
93
  end
87
94
 
88
- def valid?
95
+ def collect_errors(error_type)
96
+ errors.push(error_type)
97
+ end
98
+
99
+ def valid?(external_attributes = {})
89
100
  unless service_provider?
90
101
  log "Unable to find service provider for issuer #{issuer}"
102
+ collect_errors(:sp_not_found)
91
103
  return false
92
104
  end
93
105
 
94
106
  unless (authn_request? ^ logout_request?)
95
107
  log "One and only one of authnrequest and logout request is required. authnrequest: #{authn_request?} logout_request: #{logout_request?} "
108
+ collect_errors(:unaccepted_request)
96
109
  return false
97
110
  end
98
111
 
99
- unless valid_signature?
100
- log "Signature is invalid in #{raw_xml}"
112
+ if (logout_request? || validate_auth_request_signature?) && (service_provider.cert.to_s.empty? || !!service_provider.fingerprint.to_s.empty?)
113
+ log "Verifying request signature is required. But certificate and fingerprint was empty."
114
+ collect_errors(:empty_certificate)
115
+ return false
116
+ end
117
+
118
+ # XML embedded signature
119
+ if signature.nil? && !valid_signature?
120
+ log "Requested document signature is invalid in #{raw_xml}"
121
+ collect_errors(:invalid_embedded_signature)
122
+ return false
123
+ end
124
+
125
+ # URI query signature
126
+ if signature.present? && !valid_external_signature?
127
+ log "Requested URI signature is invalid in #{raw_xml}"
128
+ collect_errors(:invalid_external_signature)
101
129
  return false
102
130
  end
103
131
 
104
132
  if response_url.nil?
105
133
  log "Unable to find response url for #{issuer}: #{raw_xml}"
134
+ collect_errors(:empty_response_url)
106
135
  return false
107
136
  end
108
137
 
109
138
  if !service_provider.acceptable_response_hosts.include?(response_host)
110
139
  log "#{service_provider.acceptable_response_hosts} compare to #{response_host}"
111
140
  log "No acceptable AssertionConsumerServiceURL, either configure them via config.service_provider.response_hosts or match to your metadata_url host"
141
+ collect_errors(:not_allowed_host)
112
142
  return false
113
143
  end
114
144
 
@@ -117,15 +147,39 @@ module SamlIdp
117
147
 
118
148
  def valid_signature?
119
149
  # Force signatures for logout requests because there is no other protection against a cross-site DoS.
120
- # Validate signature when metadata specify AuthnRequest should be signed
121
- metadata = service_provider.current_metadata
122
- if logout_request? || authn_request? && metadata.respond_to?(:sign_authn_request?) && metadata.sign_authn_request?
123
- document.valid_signature?(service_provider.fingerprint)
150
+ if logout_request? || authn_request? && validate_auth_request_signature?
151
+ document.valid_signature?(service_provider.cert, service_provider.fingerprint)
124
152
  else
125
153
  true
126
154
  end
127
155
  end
128
156
 
157
+ def valid_external_signature?
158
+ return true if authn_request? && !validate_auth_request_signature?
159
+
160
+ cert = OpenSSL::X509::Certificate.new(service_provider.cert)
161
+
162
+ sha_version = sig_algorithm =~ /sha(.*?)$/i && $1.to_i
163
+ raw_signature = Base64.decode64(signature)
164
+
165
+ signature_algorithm = case sha_version
166
+ when 256 then OpenSSL::Digest::SHA256
167
+ when 384 then OpenSSL::Digest::SHA384
168
+ when 512 then OpenSSL::Digest::SHA512
169
+ else
170
+ OpenSSL::Digest::SHA1
171
+ end
172
+
173
+ result = cert.public_key.verify(signature_algorithm.new, raw_signature, query_request_string)
174
+ # Match all percent-encoded sequences (e.g., %20, %2B) and convert them to lowercase
175
+ # Upper case is recommended for consistency but some services such as MS Entra Id not follows it
176
+ # https://datatracker.ietf.org/doc/html/rfc3986#section-2.1
177
+ result || cert.public_key.verify(signature_algorithm.new, raw_signature, query_request_string.gsub(/%[A-F0-9]{2}/) { |match| match.downcase })
178
+ rescue OpenSSL::X509::CertificateError => e
179
+ log e.message
180
+ collect_errors(:cert_format_error)
181
+ end
182
+
129
183
  def service_provider?
130
184
  service_provider && service_provider.valid?
131
185
  end
@@ -148,6 +202,13 @@ module SamlIdp
148
202
  @_session_index ||= xpath("//samlp:SessionIndex", samlp: samlp).first.try(:content)
149
203
  end
150
204
 
205
+ def query_request_string
206
+ url_string = "SAMLRequest=#{CGI.escape(saml_request)}"
207
+ url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
208
+ url_string << "&SigAlg=#{CGI.escape(sig_algorithm)}"
209
+ end
210
+ private :query_request_string
211
+
151
212
  def response_host
152
213
  uri = URI(response_url)
153
214
  if uri
@@ -197,5 +258,14 @@ module SamlIdp
197
258
  config.service_provider.finder
198
259
  end
199
260
  private :service_provider_finder
261
+
262
+ def validate_auth_request_signature?
263
+ # Validate signature when metadata specify AuthnRequest should be signed
264
+ metadata = service_provider.current_metadata
265
+ sign_authn_request = metadata.respond_to?(:sign_authn_request?) && metadata.sign_authn_request?
266
+ sign_authn_request = service_provider.sign_authn_request unless service_provider.sign_authn_request.nil?
267
+ sign_authn_request
268
+ end
269
+ private :validate_auth_request_signature?
200
270
  end
201
271
  end
@@ -98,7 +98,7 @@ module SamlIdp
98
98
 
99
99
  def assertion_builder
100
100
  @assertion_builder ||=
101
- AssertionBuilder.new SecureRandom.uuid,
101
+ AssertionBuilder.new(reference_id || SecureRandom.uuid,
102
102
  issuer_uri,
103
103
  principal,
104
104
  audience_uri,
@@ -110,7 +110,7 @@ module SamlIdp
110
110
  encryption_opts,
111
111
  session_expiry,
112
112
  name_id_formats_opts,
113
- asserted_attributes_opts
113
+ asserted_attributes_opts)
114
114
  end
115
115
  private :assertion_builder
116
116
  end
@@ -11,6 +11,7 @@ module SamlIdp
11
11
  attribute :fingerprint
12
12
  attribute :metadata_url
13
13
  attribute :validate_signature
14
+ attribute :sign_authn_request
14
15
  attribute :acs_url
15
16
  attribute :assertion_consumer_logout_service_url
16
17
  attribute :response_hosts
@@ -21,7 +21,8 @@ module SamlIdp
21
21
  end
22
22
 
23
23
  def x509_certificate
24
- SamlIdp.config.x509_certificate
24
+ certificate = SamlIdp.config.x509_certificate.is_a?(Proc) ? SamlIdp.config.x509_certificate.call : SamlIdp.config.x509_certificate
25
+ certificate
25
26
  .to_s
26
27
  .gsub(/-----BEGIN CERTIFICATE-----/,"")
27
28
  .gsub(/-----END CERTIFICATE-----/,"")
@@ -65,12 +65,12 @@ module SamlIdp
65
65
  private :clean_algorithm_name
66
66
 
67
67
  def secret_key
68
- SamlIdp.config.secret_key
68
+ SamlIdp.config.secret_key.is_a?(Proc) ? SamlIdp.config.secret_key.call : SamlIdp.config.secret_key
69
69
  end
70
70
  private :secret_key
71
71
 
72
72
  def password
73
- SamlIdp.config.password
73
+ SamlIdp.config.password.is_a?(Proc) ? SamlIdp.config.password.call : SamlIdp.config.password
74
74
  end
75
75
  private :password
76
76
 
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module SamlIdp
3
- VERSION = '0.16.0'
3
+ VERSION = '1.0.0'
4
4
  end
@@ -43,24 +43,29 @@ module SamlIdp
43
43
  extract_signed_element_id
44
44
  end
45
45
 
46
- def validate(idp_cert_fingerprint, soft = true)
46
+ def validate(idp_base64_cert, idp_cert_fingerprint, soft = true)
47
47
  # get cert from response
48
48
  cert_element = REXML::XPath.first(self, "//ds:X509Certificate", { "ds"=>DSIG })
49
- raise ValidationError.new("Certificate element missing in response (ds:X509Certificate)") unless cert_element
50
- base64_cert = cert_element.text
51
- cert_text = Base64.decode64(base64_cert)
52
- cert = OpenSSL::X509::Certificate.new(cert_text)
53
-
54
- # check cert matches registered idp cert
55
- fingerprint = fingerprint_cert(cert)
56
- sha1_fingerprint = fingerprint_cert_sha1(cert)
57
- plain_idp_cert_fingerprint = idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
58
-
59
- if fingerprint != plain_idp_cert_fingerprint && sha1_fingerprint != plain_idp_cert_fingerprint
60
- return soft ? false : (raise ValidationError.new("Fingerprint mismatch"))
49
+ if cert_element
50
+ idp_base64_cert = cert_element.text
51
+ cert_text = Base64.decode64(idp_base64_cert)
52
+ cert = OpenSSL::X509::Certificate.new(cert_text)
53
+
54
+ # check cert matches registered idp cert
55
+ fingerprint = fingerprint_cert(cert)
56
+ sha1_fingerprint = fingerprint_cert_sha1(cert)
57
+ plain_idp_cert_fingerprint = idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
58
+
59
+ if fingerprint != plain_idp_cert_fingerprint && sha1_fingerprint != plain_idp_cert_fingerprint
60
+ return soft ? false : (raise ValidationError.new("Fingerprint mismatch"))
61
+ end
62
+ end
63
+
64
+ if idp_base64_cert.nil? || idp_base64_cert.empty?
65
+ raise ValidationError.new("Certificate validation is required, but it doesn't exist.")
61
66
  end
62
67
 
63
- validate_doc(base64_cert, soft)
68
+ validate_doc(idp_base64_cert, soft)
64
69
  end
65
70
 
66
71
  def fingerprint_cert(cert)
data/lib/saml_idp.rb CHANGED
@@ -9,7 +9,7 @@ module SamlIdp
9
9
  require 'saml_idp/metadata_builder'
10
10
  require 'saml_idp/version'
11
11
  require 'saml_idp/fingerprint'
12
- require 'saml_idp/engine' if defined?(::Rails)
12
+ require 'saml_idp/engine' if defined?(::Rails::Engine)
13
13
 
14
14
  def self.config
15
15
  @config ||= SamlIdp::Configurator.new
@@ -70,9 +70,9 @@ module Saml
70
70
  !!xpath("//ds:Signature", ds: signature_namespace).first
71
71
  end
72
72
 
73
- def valid_signature?(fingerprint)
73
+ def valid_signature?(certificate, fingerprint)
74
74
  signed? &&
75
- signed_document.validate(fingerprint, :soft)
75
+ signed_document.validate(certificate, fingerprint, :soft)
76
76
  end
77
77
 
78
78
  def signed_document
data/saml_idp.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.authors = ['Jon Phenow']
11
11
  s.email = 'jon.phenow@sportngin.com'
12
12
  s.homepage = 'https://github.com/saml-idp/saml_idp'
13
- s.summary = 'SAML Indentity Provider for Ruby'
13
+ s.summary = 'SAML Identity Provider for Ruby'
14
14
  s.description = 'SAML IdP (Identity Provider) Library for Ruby'
15
15
  s.date = Time.now.utc.strftime('%Y-%m-%d')
16
16
  s.files = Dir['lib/**/*', 'LICENSE', 'README.md', 'Gemfile', 'saml_idp.gemspec']
@@ -46,14 +46,15 @@ Gem::Specification.new do |s|
46
46
  s.add_dependency('activesupport', '>= 5.2')
47
47
  s.add_dependency('builder', '>= 3.0')
48
48
  s.add_dependency('nokogiri', '>= 1.6.2')
49
+ s.add_dependency('ostruct')
49
50
  s.add_dependency('rexml')
50
51
  s.add_dependency('xmlenc', '>= 0.7.1')
51
52
 
52
- s.add_development_dependency('activeresource', '>= 5.1')
53
+ s.add_development_dependency('activeresource', '~> 6.1')
53
54
  s.add_development_dependency('appraisal')
54
- s.add_development_dependency('byebug')
55
55
  s.add_development_dependency('capybara', '>= 2.16')
56
56
  s.add_development_dependency('rails', '>= 5.2')
57
+ s.add_development_dependency('debug')
57
58
  s.add_development_dependency('rake')
58
59
  s.add_development_dependency('rspec', '>= 3.7.0')
59
60
  s.add_development_dependency('ruby-saml', '>= 1.7.2')
@@ -20,11 +20,11 @@ module SamlIdp
20
20
  it { should respond_to :logger }
21
21
 
22
22
  it "has a valid x509_certificate" do
23
- expect(subject.x509_certificate).to eq(Default::X509_CERTIFICATE)
23
+ expect(subject.x509_certificate.call).to eq(Default::X509_CERTIFICATE)
24
24
  end
25
25
 
26
26
  it "has a valid secret_key" do
27
- expect(subject.secret_key).to eq(Default::SECRET_KEY)
27
+ expect(subject.secret_key.call).to eq(Default::SECRET_KEY)
28
28
  end
29
29
 
30
30
  it "has a valid algorithm" do
@@ -47,5 +47,41 @@ module SamlIdp
47
47
  it 'has a valid session_expiry' do
48
48
  expect(subject.session_expiry).to eq(0)
49
49
  end
50
+
51
+ context "logger initialization" do
52
+ context 'when Rails has been properly initialized' do
53
+ before do
54
+ stub_const("Rails", double(logger: double("Rails.logger")))
55
+ end
56
+
57
+ it 'sets logger to Rails.logger' do
58
+ expect(subject.logger).to eq(Rails.logger)
59
+ end
60
+ end
61
+
62
+ context 'when Rails is not fully initialized' do
63
+ before do
64
+ stub_const("Rails", Class.new)
65
+ end
66
+
67
+ it 'sets logger to a lambda' do
68
+ expect(subject.logger).to be_a(Proc)
69
+ expect { subject.logger.call("test") }.to output("test\n").to_stdout
70
+ end
71
+ end
72
+
73
+ context 'when Rails is not defined' do
74
+ it 'sets logger to a lambda' do
75
+ hide_const("Rails")
76
+
77
+ expect(subject.logger).to be_a(Proc)
78
+ expect { subject.logger.call("test") }.to output("test\n").to_stdout
79
+ end
80
+ end
81
+
82
+ after do
83
+ hide_const("Rails")
84
+ end
85
+ end
50
86
  end
51
87
  end
@@ -33,7 +33,7 @@ describe SamlIdp::Controller do
33
33
  end
34
34
 
35
35
  it 'should call xml signature validation method' do
36
- signed_doc = SamlIdp::XMLSecurity::SignedDocument.new(params[:SAMLRequest])
36
+ signed_doc = SamlIdp::XMLSecurity::SignedDocument.new(decode_saml_request(params[:SAMLRequest]))
37
37
  allow(signed_doc).to receive(:validate).and_return(true)
38
38
  allow(SamlIdp::XMLSecurity::SignedDocument).to receive(:new).and_return(signed_doc)
39
39
  validate_saml_request
@@ -66,6 +66,16 @@ describe SamlIdp::Controller do
66
66
  end
67
67
  end
68
68
 
69
+ context '#encode_authn_response' do
70
+ it 'uses default values when opts are not provided' do
71
+ saml_response = encode_authn_response(principal, { audience_uri: 'http://example.com/issuer', issuer_uri: 'http://example.com', acs_url: 'https://foo.example.com/saml/consume', signed_assertion: false })
72
+
73
+ response = OneLogin::RubySaml::Response.new(saml_response)
74
+ response.settings = saml_settings
75
+ expect(response.document.to_s).to_not include("<ds:Signature>")
76
+ end
77
+ end
78
+
69
79
  context "solicited Response" do
70
80
  before(:each) do
71
81
  params[:SAMLRequest] = make_saml_request
@@ -81,16 +91,6 @@ describe SamlIdp::Controller do
81
91
  expect(response.is_valid?).to be_truthy
82
92
  end
83
93
 
84
- it "should create a SAML Logout Response" do
85
- params[:SAMLRequest] = make_saml_logout_request
86
- expect(validate_saml_request).to eq(true)
87
- expect(saml_request.logout_request?).to eq true
88
- saml_response = encode_response(principal)
89
- response = OneLogin::RubySaml::Logoutresponse.new(saml_response, saml_settings)
90
- expect(response.validate).to eq(true)
91
- expect(response.issuer).to eq("http://example.com")
92
- end
93
-
94
94
  it "should by default create a SAML Response with a signed assertion" do
95
95
  saml_response = encode_response(principal)
96
96
  response = OneLogin::RubySaml::Response.new(saml_response)
@@ -124,4 +124,32 @@ describe SamlIdp::Controller do
124
124
  end
125
125
  end
126
126
  end
127
+
128
+ context "Single Logout Request" do
129
+ before do
130
+ idp_configure("https://foo.example.com/saml/consume", true)
131
+ slo_request = make_saml_sp_slo_request(security_options: { embed_sign: false })
132
+ params[:SAMLRequest] = slo_request['SAMLRequest']
133
+ params[:RelayState] = slo_request['RelayState']
134
+ params[:SigAlg] = slo_request['SigAlg']
135
+ params[:Signature] = slo_request['Signature']
136
+ end
137
+
138
+ it 'should successfully validate signature' do
139
+ expect(validate_saml_request).to eq(true)
140
+ end
141
+
142
+ context "solicited Response" do
143
+ let(:principal) { double email_address: "foo@example.com" }
144
+
145
+ it "should create a SAML Logout Response" do
146
+ expect(validate_saml_request).to eq(true)
147
+ expect(saml_request.logout_request?).to eq true
148
+ saml_response = encode_response(principal)
149
+ response = OneLogin::RubySaml::Logoutresponse.new(saml_response, saml_settings)
150
+ expect(response.validate).to eq(true)
151
+ expect(response.issuer).to eq("http://idp.com/saml/idp")
152
+ end
153
+ end
154
+ end
127
155
  end