lyrebird 1.0.0.alpha2 → 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.
@@ -5,83 +5,116 @@ require "test_helper"
5
5
  module Lyrebird
6
6
  class CertificateTest < Minitest::Test
7
7
  def setup
8
- @certificate = Certificate.generate
8
+ unless defined?(@@shared)
9
+ @@shared = Certificate.build
10
+ @@created_at = Time.now.utc
11
+ end
12
+
13
+ @certificate = @@shared
14
+ @created_at = @@created_at
15
+ end
16
+
17
+ def test_key_is_rsa
18
+ assert_instance_of OpenSSL::PKey::RSA, @certificate.key
19
+ end
20
+
21
+ def test_key_defaults_to_2048_bits
22
+ assert_equal 2048, @certificate.key.n.num_bits
9
23
  end
10
24
 
11
- def test_private_key_is_rsa
12
- assert_instance_of OpenSSL::PKey::RSA, @certificate.private_key
25
+ def test_key_with_custom_bits
26
+ certificate = Certificate.build(bits: 4096)
27
+ assert_equal 4096, certificate.key.n.num_bits
13
28
  end
14
29
 
15
- def test_private_key_defaults_to_2048_bits
16
- assert_equal 2048, @certificate.private_key.n.num_bits
30
+ def test_x509_is_x509
31
+ assert_instance_of OpenSSL::X509::Certificate, @certificate.x509
17
32
  end
18
33
 
19
- def test_private_key_with_custom_bits
20
- certificate = Certificate.generate(bits: 4096)
21
- assert_equal 4096, certificate.private_key.n.num_bits
34
+ def test_x509_version_is_v3
35
+ assert_equal 2, @certificate.x509.version
22
36
  end
23
37
 
24
- def test_certificate_is_x509
25
- assert_instance_of OpenSSL::X509::Certificate, @certificate.certificate
38
+ def test_x509_has_serial_number
39
+ refute_nil @certificate.x509.serial
40
+ assert @certificate.x509.serial.to_i > 0
26
41
  end
27
42
 
28
- def test_certificate_not_before_is_now
29
- assert_in_delta Time.now, @certificate.certificate.not_before, 1
43
+ def test_x509_not_before_is_now
44
+ assert_in_delta @created_at, @certificate.x509.not_before, 1
30
45
  end
31
46
 
32
- def test_certificate_not_after_is_one_year_from_now
33
- one_year = 365 * 24 * 60 * 60
34
- assert_in_delta Time.now + one_year, @certificate.certificate.not_after, 1
47
+ def test_x509_not_after_is_one_year_from_now
48
+ expected = @created_at + (365 * 24 * 60 * 60)
49
+ assert_in_delta expected, @certificate.x509.not_after, 1
35
50
  end
36
51
 
37
- def test_certificate_is_signed
38
- assert @certificate.certificate.verify(@certificate.private_key)
52
+ def test_x509_is_signed
53
+ assert @certificate.x509.verify(@certificate.key)
39
54
  end
40
55
 
41
- def test_certificate_with_custom_subject
42
- certificate = Certificate.generate(cn: "Test", o: "Acme")
43
- assert_equal "/CN=Test/O=Acme", certificate.certificate.subject.to_s
56
+ def test_sign
57
+ data = "test"
58
+ signature = @certificate.sign(data)
59
+ public_key = @certificate.x509.public_key
60
+ assert public_key.verify("SHA256", signature, data)
44
61
  end
45
62
 
46
- def test_certificate_with_custom_valid_for
47
- certificate = Certificate.generate(valid_for: 30).certificate
48
- thirty_days = 30 * 24 * 60 * 60
49
- assert_in_delta Time.now + thirty_days, certificate.not_after, 1
63
+ def test_x509_with_custom_subject
64
+ certificate = Certificate.build(cn: "Test", o: "Acme")
65
+ subject = certificate.x509.subject.to_s
66
+ assert_equal "/CN=Test/O=Acme", subject
50
67
  end
51
68
 
52
- def test_certificate_with_valid_until
69
+ def test_x509_with_custom_valid_for
70
+ certificate = Certificate.build(valid_for: 30).x509
71
+ expected = Time.now.utc + (30 * 24 * 60 * 60)
72
+ assert_in_delta expected, certificate.not_after, 1
73
+ end
74
+
75
+ def test_x509_with_valid_until
53
76
  valid_until = Time.new(2030, 1, 1)
54
- certificate = Certificate.generate(valid_until: valid_until)
55
- assert_equal valid_until, certificate.certificate.not_after
77
+ certificate = Certificate.build(valid_until: valid_until)
78
+ assert_equal valid_until, certificate.x509.not_after
56
79
  end
57
80
 
58
- def test_private_key_pem
59
- assert @certificate.private_key_pem.start_with?("-----BEGIN")
81
+ def test_key_pem
82
+ assert @certificate.key_pem.start_with?("-----BEGIN")
60
83
  end
61
84
 
62
- def test_certificate_pem
63
- assert @certificate.certificate_pem.start_with?("-----BEGIN")
85
+ def test_x509_pem
86
+ assert @certificate.x509_pem.start_with?("-----BEGIN")
64
87
  end
65
88
 
66
89
  def test_fingerprint
67
- der = @certificate.certificate.to_der
90
+ der = @certificate.x509.to_der
68
91
  expected = OpenSSL::Digest::SHA256.hexdigest(der)
69
92
  assert_equal expected, @certificate.fingerprint
70
93
  end
71
94
 
72
95
  def test_base64
73
- der = @certificate.certificate.to_der
96
+ der = @certificate.x509.to_der
74
97
  expected = Base64.strict_encode64(der)
75
98
  assert_equal expected, @certificate.base64
76
99
  end
77
100
 
101
+ def test_build_with_block
102
+ certificate = Certificate.build do |c|
103
+ c.cn = "Name"
104
+ c.o = "Org"
105
+ end
106
+
107
+ subject = certificate.x509.subject.to_s
108
+ assert_equal "/CN=Name/O=Org", subject
109
+ end
110
+
78
111
  def test_load
79
- certificate = Certificate.load(
80
- private_key_pem: @certificate.private_key_pem,
81
- certificate_pem: @certificate.certificate_pem,
112
+ loaded = Certificate.load(
113
+ key_pem: @certificate.key_pem,
114
+ x509_pem: @certificate.x509_pem,
82
115
  )
83
116
 
84
- assert_equal @certificate.fingerprint, certificate.fingerprint
117
+ assert_equal @certificate.fingerprint, loaded.fingerprint
85
118
  end
86
119
  end
87
120
  end
@@ -7,5 +7,52 @@ module Lyrebird
7
7
  def test_issuer
8
8
  assert_equal "https://idp.example.com", DEFAULTS.issuer
9
9
  end
10
+
11
+ def test_name_id
12
+ assert_equal "user@example.com", DEFAULTS.name_id
13
+ end
14
+
15
+ def test_name_id_format
16
+ assert_equal NAMEID_EMAIL, DEFAULTS.name_id_format
17
+ end
18
+
19
+ def test_recipient
20
+ assert_equal "https://sp.example.com/acs", DEFAULTS.recipient
21
+ end
22
+
23
+ def test_in_response_to
24
+ assert_equal "_request_id", DEFAULTS.in_response_to
25
+ end
26
+
27
+ def test_valid_for
28
+ assert_equal 300, DEFAULTS.valid_for
29
+ end
30
+
31
+ def test_audience
32
+ assert_equal "https://sp.example.com", DEFAULTS.audience
33
+ end
34
+
35
+ def test_authn_context
36
+ expected = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
37
+ assert_equal expected, DEFAULTS.authn_context
38
+ end
39
+
40
+ def test_attributes
41
+ expected = { first_name: "Test", last_name: "User" }
42
+ assert_equal expected, DEFAULTS.attributes
43
+ end
44
+ end
45
+
46
+ class ConfigureTest < Minitest::Test
47
+ def test_configure_yields_defaults
48
+ yielded = nil
49
+ Lyrebird.configure { |d| yielded = d }
50
+ assert_same DEFAULTS, yielded
51
+ end
52
+
53
+ def test_configure_freezes_defaults
54
+ Lyrebird.configure { |d| }
55
+ assert DEFAULTS.frozen?
56
+ end
10
57
  end
11
58
  end
@@ -4,69 +4,82 @@ require "test_helper"
4
4
 
5
5
  module Lyrebird
6
6
  class EncryptionTest < Minitest::Test
7
+ NS = {
8
+ "saml" => SAML_ASSERTION_NS,
9
+ "xenc" => XMLENC_NS,
10
+ "ds" => XMLDSIG_NS
11
+ }.freeze
12
+
7
13
  def setup
8
- @assertion = Assertion.new.document
9
- @element = @assertion.root
10
- @certificate = Certificate.generate
11
- @encrypted = Encryption.new(@element, @certificate).encrypt
12
- @ed = @encrypted.elements["xenc:EncryptedData"]
13
- @ki = @ed.elements["ds:KeyInfo"]
14
- @ek = @ki.elements["xenc:EncryptedKey"]
15
- @cd = @ed.elements["xenc:CipherData"]
14
+ unless defined?(@@certificate)
15
+ @@assertion = Assertion.new.document
16
+ @@element = @@assertion.root
17
+ @@certificate = Certificate.build
18
+ @@encrypted = Encryption.new(@@element, @@certificate).encrypt
19
+ end
20
+
21
+ @assertion = @@assertion
22
+ @element = @@element
23
+ @certificate = @@certificate
24
+ @encrypted = @@encrypted
25
+ @ed = @encrypted.at_xpath("xenc:EncryptedData", NS)
26
+ @ki = @ed.at_xpath("ds:KeyInfo", NS)
27
+ @ek = @ki.at_xpath("xenc:EncryptedKey", NS)
28
+ @cd = @ed.at_xpath("xenc:CipherData", NS)
16
29
  end
17
30
 
18
31
  def test_returns_encrypted_assertion_element
19
32
  assert_equal "EncryptedAssertion", @encrypted.name
20
- assert_equal "saml", @encrypted.prefix
33
+ assert_equal "saml", @encrypted.namespace.prefix
21
34
  end
22
35
 
23
36
  def test_encrypted_assertion_namespace
24
- assert_equal SAML_ASSERTION_NS, @encrypted.namespace
37
+ assert_equal SAML_ASSERTION_NS, @encrypted.namespace.href
25
38
  end
26
39
 
27
40
  def test_encrypted_data_element
28
41
  assert_equal "EncryptedData", @ed.name
29
- assert_equal "xenc", @ed.prefix
42
+ assert_equal "xenc", @ed.namespace.prefix
30
43
  end
31
44
 
32
45
  def test_encrypted_data_namespace
33
- assert_equal XMLENC_NS, @ed.namespace
46
+ assert_equal XMLENC_NS, @ed.namespace.href
34
47
  end
35
48
 
36
49
  def test_encrypted_data_type
37
- assert_equal "#{XMLENC_NS}Element", @ed.attributes["Type"]
50
+ assert_equal "#{XMLENC_NS}Element", @ed["Type"]
38
51
  end
39
52
 
40
53
  def test_encryption_method_element
41
- em = @ed.elements["xenc:EncryptionMethod"]
54
+ em = @ed.at_xpath("xenc:EncryptionMethod", NS)
42
55
  assert_equal "EncryptionMethod", em.name
43
- assert_equal "xenc", em.prefix
56
+ assert_equal "xenc", em.namespace.prefix
44
57
  end
45
58
 
46
59
  def test_encryption_method_algorithm
47
- em = @ed.elements["xenc:EncryptionMethod"]
48
- assert_equal AES256_CBC, em.attributes["Algorithm"]
60
+ em = @ed.at_xpath("xenc:EncryptionMethod", NS)
61
+ assert_equal AES256_CBC, em["Algorithm"]
49
62
  end
50
63
 
51
64
  def test_cipher_data_element
52
65
  assert_equal "CipherData", @cd.name
53
- assert_equal "xenc", @cd.prefix
66
+ assert_equal "xenc", @cd.namespace.prefix
54
67
  end
55
68
 
56
69
  def test_cipher_value_element
57
- cv = @cd.elements["xenc:CipherValue"]
70
+ cv = @cd.at_xpath("xenc:CipherValue", NS)
58
71
  assert_equal "CipherValue", cv.name
59
- assert_equal "xenc", cv.prefix
72
+ assert_equal "xenc", cv.namespace.prefix
60
73
  end
61
74
 
62
75
  def test_cipher_value_is_base64
63
- cv = @cd.elements["xenc:CipherValue"]
76
+ cv = @cd.at_xpath("xenc:CipherValue", NS)
64
77
  decoded = Base64.strict_decode64(cv.text)
65
78
  assert decoded.bytesize > 16
66
79
  end
67
80
 
68
81
  def test_cipher_value_starts_with_iv
69
- cv = @cd.elements["xenc:CipherValue"]
82
+ cv = @cd.at_xpath("xenc:CipherValue", NS)
70
83
  decoded = Base64.strict_decode64(cv.text)
71
84
  iv = decoded[0, 16]
72
85
  assert_equal 16, iv.bytesize
@@ -74,50 +87,50 @@ module Lyrebird
74
87
 
75
88
  def test_key_info_element
76
89
  assert_equal "KeyInfo", @ki.name
77
- assert_equal "ds", @ki.prefix
90
+ assert_equal "ds", @ki.namespace.prefix
78
91
  end
79
92
 
80
93
  def test_key_info_namespace
81
- assert_equal XMLDSIG_NS, @ki.namespace
94
+ assert_equal XMLDSIG_NS, @ki.namespace.href
82
95
  end
83
96
 
84
97
  def test_encrypted_key_element
85
98
  assert_equal "EncryptedKey", @ek.name
86
- assert_equal "xenc", @ek.prefix
99
+ assert_equal "xenc", @ek.namespace.prefix
87
100
  end
88
101
 
89
102
  def test_encrypted_key_namespace
90
- assert_equal XMLENC_NS, @ek.namespace
103
+ assert_equal XMLENC_NS, @ek.namespace.href
91
104
  end
92
105
 
93
106
  def test_encrypted_key_encryption_method
94
- em = @ek.elements["xenc:EncryptionMethod"]
107
+ em = @ek.at_xpath("xenc:EncryptionMethod", NS)
95
108
  assert_equal "EncryptionMethod", em.name
96
- assert_equal RSA_OAEP, em.attributes["Algorithm"]
109
+ assert_equal RSA_OAEP, em["Algorithm"]
97
110
  end
98
111
 
99
112
  def test_encrypted_key_cipher_data
100
- cd = @ek.elements["xenc:CipherData"]
113
+ cd = @ek.at_xpath("xenc:CipherData", NS)
101
114
  assert_equal "CipherData", cd.name
102
- assert_equal "xenc", cd.prefix
115
+ assert_equal "xenc", cd.namespace.prefix
103
116
  end
104
117
 
105
118
  def test_encrypted_key_cipher_value
106
- cv = @ek.elements["xenc:CipherData/xenc:CipherValue"]
119
+ cv = @ek.at_xpath("xenc:CipherData/xenc:CipherValue", NS)
107
120
  assert_equal "CipherValue", cv.name
108
- assert_equal "xenc", cv.prefix
121
+ assert_equal "xenc", cv.namespace.prefix
109
122
  end
110
123
 
111
124
  def test_encrypted_key_cipher_value_is_base64
112
- cv = @ek.elements["xenc:CipherData/xenc:CipherValue"]
125
+ cv = @ek.at_xpath("xenc:CipherData/xenc:CipherValue", NS)
113
126
  decoded = Base64.strict_decode64(cv.text)
114
127
  assert decoded.bytesize > 0
115
128
  end
116
129
 
117
130
  def test_encrypted_key_can_be_decrypted
118
- cv = @ek.elements["xenc:CipherData/xenc:CipherValue"]
131
+ cv = @ek.at_xpath("xenc:CipherData/xenc:CipherValue", NS)
119
132
  encrypted_key = Base64.strict_decode64(cv.text)
120
- private_key = @certificate.private_key
133
+ private_key = @certificate.key
121
134
  padding = OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
122
135
  decrypted_key = private_key.private_decrypt(encrypted_key, padding)
123
136
  assert_equal 32, decrypted_key.bytesize
@@ -7,5 +7,12 @@ module Lyrebird
7
7
  def test_generate_starts_with_underscore
8
8
  assert ID.generate.start_with?("_")
9
9
  end
10
+
11
+ def test_generate_contains_uuid
12
+ id = ID.generate
13
+ uuid = id[1..]
14
+ uuid_pattern = /\A[0-9a-f-]{36}\z/
15
+ assert_match uuid_pattern, uuid
16
+ end
10
17
  end
11
18
  end
@@ -0,0 +1,243 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require "onelogin/ruby-saml"
5
+
6
+ module Lyrebird
7
+ class IntegrationTest < Minitest::Test
8
+ def setup
9
+ unless defined?(@@idp_cert)
10
+ @@idp_cert = Certificate.build
11
+ @@sp_cert = Certificate.build
12
+ end
13
+
14
+ @idp_cert = @@idp_cert
15
+ @sp_cert = @@sp_cert
16
+ end
17
+
18
+ def test_ruby_saml_parses_signed_response
19
+ name_id = "user@example.com"
20
+
21
+ response = Response.build(sign_with: @idp_cert) do |r|
22
+ r.name_id = name_id
23
+ end
24
+
25
+ settings = OneLogin::RubySaml::Settings.new.tap do |s|
26
+ s.idp_cert = @idp_cert.x509_pem
27
+ s.sp_entity_id = DEFAULTS.audience
28
+ s.assertion_consumer_service_url = DEFAULTS.recipient
29
+ end
30
+
31
+ saml_response = OneLogin::RubySaml::Response.new(
32
+ response.mimic,
33
+ settings: settings
34
+ )
35
+
36
+ assert saml_response.is_valid?, saml_response.errors
37
+ assert_equal name_id, saml_response.nameid
38
+ end
39
+
40
+ def test_ruby_saml_decrypts_signed_and_encrypted_response
41
+ name_id = "user@example.com"
42
+ email = "test@example.com"
43
+ role = "admin"
44
+
45
+ response = Response.build(
46
+ sign_with: @idp_cert,
47
+ encrypt_with: @sp_cert
48
+ ) do |r|
49
+ r.name_id = name_id
50
+
51
+ r.attributes do |a|
52
+ a.email = email
53
+ a.role = role
54
+ end
55
+ end
56
+
57
+ settings = OneLogin::RubySaml::Settings.new.tap do |s|
58
+ s.idp_cert = @idp_cert.x509_pem
59
+ s.private_key = @sp_cert.key_pem
60
+ s.sp_entity_id = DEFAULTS.audience
61
+ s.assertion_consumer_service_url = DEFAULTS.recipient
62
+ end
63
+
64
+ saml_response = OneLogin::RubySaml::Response.new(
65
+ response.mimic,
66
+ settings: settings
67
+ )
68
+
69
+ assert saml_response.is_valid?, saml_response.errors
70
+ assert_equal name_id, saml_response.nameid
71
+ assert_equal email, saml_response.attributes["email"]
72
+ assert_equal role, saml_response.attributes["role"]
73
+ end
74
+
75
+ def test_ruby_saml_parses_idp_initiated_response
76
+ name_id = "user@example.com"
77
+ email = "test@example.com"
78
+
79
+ response = Response.build(sign_with: @idp_cert) do |r|
80
+ r.in_response_to = nil
81
+ r.destination = nil
82
+ r.name_id = name_id
83
+
84
+ r.attributes do |a|
85
+ a.email = email
86
+ end
87
+ end
88
+
89
+ settings = OneLogin::RubySaml::Settings.new.tap do |s|
90
+ s.idp_cert = @idp_cert.x509_pem
91
+ s.sp_entity_id = DEFAULTS.audience
92
+ s.assertion_consumer_service_url = DEFAULTS.recipient
93
+ end
94
+
95
+ saml_response = OneLogin::RubySaml::Response.new(
96
+ response.mimic,
97
+ settings: settings
98
+ )
99
+
100
+ assert saml_response.is_valid?, saml_response.errors
101
+ assert_equal name_id, saml_response.nameid
102
+ assert_equal email, saml_response.attributes["email"]
103
+ end
104
+
105
+ def test_ruby_saml_parses_multi_value_attributes
106
+ name_id = "user@example.com"
107
+ groups = ["admin", "users", "developers"]
108
+ roles = ["editor", "viewer"]
109
+
110
+ response = Response.build(sign_with: @idp_cert) do |r|
111
+ r.name_id = name_id
112
+
113
+ r.attributes do |a|
114
+ a.groups = groups
115
+ a.roles = roles
116
+ end
117
+ end
118
+
119
+ settings = OneLogin::RubySaml::Settings.new.tap do |s|
120
+ s.idp_cert = @idp_cert.x509_pem
121
+ s.sp_entity_id = DEFAULTS.audience
122
+ s.assertion_consumer_service_url = DEFAULTS.recipient
123
+ end
124
+
125
+ saml_response = OneLogin::RubySaml::Response.new(
126
+ response.mimic,
127
+ settings: settings
128
+ )
129
+
130
+ assert saml_response.is_valid?, saml_response.errors
131
+ assert_equal name_id, saml_response.nameid
132
+ assert_equal groups, saml_response.attributes.multi("groups")
133
+ assert_equal roles, saml_response.attributes.multi("roles")
134
+ end
135
+
136
+ def test_ruby_saml_validates_custom_validity_period
137
+ name_id = "user@example.com"
138
+ not_before = Time.now.utc - 60
139
+ valid_for = 600
140
+
141
+ response = Response.build(sign_with: @idp_cert) do |r|
142
+ r.name_id = name_id
143
+ r.not_before = not_before
144
+ r.valid_for = valid_for
145
+ end
146
+
147
+ settings = OneLogin::RubySaml::Settings.new.tap do |s|
148
+ s.idp_cert = @idp_cert.x509_pem
149
+ s.sp_entity_id = DEFAULTS.audience
150
+ s.assertion_consumer_service_url = DEFAULTS.recipient
151
+ end
152
+
153
+ saml_response = OneLogin::RubySaml::Response.new(
154
+ response.mimic,
155
+ settings: settings
156
+ )
157
+
158
+ assert saml_response.is_valid?, saml_response.errors
159
+ assert_equal name_id, saml_response.nameid
160
+ end
161
+
162
+ def test_ruby_saml_rejects_expired_assertion
163
+ name_id = "user@example.com"
164
+
165
+ response = Response.build(sign_with: @idp_cert) do |r|
166
+ r.name_id = name_id
167
+ r.valid_for = -10
168
+ end
169
+
170
+ settings = OneLogin::RubySaml::Settings.new.tap do |s|
171
+ s.idp_cert = @idp_cert.x509_pem
172
+ s.sp_entity_id = DEFAULTS.audience
173
+ s.assertion_consumer_service_url = DEFAULTS.recipient
174
+ end
175
+
176
+ saml_response = OneLogin::RubySaml::Response.new(
177
+ response.mimic,
178
+ settings: settings
179
+ )
180
+
181
+ assert_operator Time.now.utc, :>=, saml_response.not_on_or_after
182
+ refute saml_response.is_valid?
183
+ end
184
+
185
+ def test_ruby_saml_rejects_not_yet_valid_assertion
186
+ name_id = "user@example.com"
187
+ not_before = Time.now.utc + 600
188
+
189
+ response = Response.build(sign_with: @idp_cert) do |r|
190
+ r.name_id = name_id
191
+ r.not_before = not_before
192
+ end
193
+
194
+ settings = OneLogin::RubySaml::Settings.new.tap do |s|
195
+ s.idp_cert = @idp_cert.x509_pem
196
+ s.sp_entity_id = DEFAULTS.audience
197
+ s.assertion_consumer_service_url = DEFAULTS.recipient
198
+ end
199
+
200
+ saml_response = OneLogin::RubySaml::Response.new(
201
+ response.mimic,
202
+ settings: settings
203
+ )
204
+
205
+ assert_operator Time.now.utc, :<, saml_response.not_before
206
+ refute saml_response.is_valid?
207
+ end
208
+
209
+ def test_ruby_saml_rejects_tampered_signed_response
210
+ original_email = "user@example.com"
211
+ tampered_email = "attacker@evil.com"
212
+
213
+ response = Response.build(sign_with: @idp_cert) do |r|
214
+ r.name_id = "user@example.com"
215
+
216
+ r.attributes do |a|
217
+ a.email = original_email
218
+ a.role = "user"
219
+ end
220
+ end
221
+
222
+ doc = Nokogiri::XML(Base64.strict_decode64(response.mimic))
223
+ xpath = "//saml:Attribute[@Name='email']/saml:AttributeValue"
224
+ email_attr = doc.at_xpath(xpath, { "saml" => SAML_ASSERTION_NS })
225
+ email_attr.content = tampered_email
226
+ tampered = Base64.strict_encode64(doc.to_xml(save_with: 0))
227
+
228
+ settings = OneLogin::RubySaml::Settings.new.tap do |s|
229
+ s.idp_cert = @idp_cert.x509_pem
230
+ s.sp_entity_id = DEFAULTS.audience
231
+ s.assertion_consumer_service_url = DEFAULTS.recipient
232
+ end
233
+
234
+ saml_response = OneLogin::RubySaml::Response.new(
235
+ tampered,
236
+ settings: settings
237
+ )
238
+
239
+ refute saml_response.is_valid?
240
+ assert_includes saml_response.errors.join(" "), "Signature"
241
+ end
242
+ end
243
+ end