lyrebird 0.0.0 → 1.0.0.alpha2

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.
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ module Lyrebird
6
+ class IDTest < Minitest::Test
7
+ def test_generate_starts_with_underscore
8
+ assert ID.generate.start_with?("_")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ module Lyrebird
6
+ class ResponseTest < Minitest::Test
7
+ def setup
8
+ @response = Response.new
9
+ @root = @response.document.root
10
+ end
11
+
12
+ def test_build_with_defaults
13
+ response = Response.build
14
+ root = response.document.root
15
+ assert_equal DEFAULTS.issuer, root.elements["saml:Issuer"].text
16
+ end
17
+
18
+ def test_build_with_kwargs
19
+ issuer = "https://test.example.com"
20
+ refute_equal issuer, DEFAULTS.issuer
21
+ response = Response.build(issuer: issuer)
22
+ root = response.document.root
23
+ assert_equal issuer, root.elements["saml:Issuer"].text
24
+ end
25
+
26
+ def test_build_with_block
27
+ issuer = "https://test.example.com"
28
+ refute_equal issuer, DEFAULTS.issuer
29
+ response = Response.build { |r| r.issuer = issuer }
30
+ root = response.document.root
31
+ assert_equal issuer, root.elements["saml:Issuer"].text
32
+ end
33
+
34
+ def test_build_with_kwargs_and_block
35
+ issuer = "https://test.example.com"
36
+ email = "test@example.com"
37
+
38
+ refute_equal issuer, DEFAULTS.issuer
39
+ refute_equal email, DEFAULTS.name_id
40
+
41
+ response = Response.build(issuer: issuer) do |r|
42
+ r.name_id = email
43
+ end
44
+
45
+ root = response.document.root
46
+ assert_equal issuer, root.elements["saml:Issuer"].text
47
+ name_id = root.elements["saml:Assertion/saml:Subject/saml:NameID"]
48
+ assert_equal email, name_id.text
49
+ end
50
+
51
+ def test_build_with_attributes_block
52
+ email = "test@example.com"
53
+ role = "admin"
54
+
55
+ response = Response.build do |r|
56
+ r.attributes do |a|
57
+ a.email = email
58
+ a.role = role
59
+ end
60
+ end
61
+
62
+ root = response.document.root
63
+ statement = root.elements["saml:Assertion/saml:AttributeStatement"]
64
+ email_element = statement.elements["saml:Attribute[@Name='email']"]
65
+ role_element = statement.elements["saml:Attribute[@Name='role']"]
66
+
67
+ assert_equal email, email_element.elements["saml:AttributeValue"].text
68
+ assert_equal role, role_element.elements["saml:AttributeValue"].text
69
+ end
70
+
71
+ def test_build_with_attributes_hash
72
+ email = "user@example.com"
73
+
74
+ response = Response.build do |r|
75
+ r.attributes = { email: email }
76
+ end
77
+
78
+ root = response.document.root
79
+ statement = root.elements["saml:Assertion/saml:AttributeStatement"]
80
+ email_element = statement.elements["saml:Attribute[@Name='email']"]
81
+ assert_equal email, email_element.elements["saml:AttributeValue"].text
82
+ end
83
+
84
+ def test_mimic_returns_base64
85
+ encoded = @response.mimic
86
+ decoded = Base64.strict_decode64(encoded)
87
+ assert decoded.include?("<samlp:Response")
88
+ assert decoded.include?("<saml:Assertion")
89
+ end
90
+
91
+ def test_root_name
92
+ assert_equal "Response", @root.name
93
+ assert_equal "samlp", @root.prefix
94
+ end
95
+
96
+ def test_root_namespace
97
+ assert_equal SAML_PROTOCOL_NS, @root.namespace
98
+ end
99
+
100
+ def test_saml_namespace_declared
101
+ assert_equal SAML_ASSERTION_NS, @root.namespace("saml")
102
+ end
103
+
104
+ def test_root_id
105
+ assert @root.attributes["ID"].start_with?("_")
106
+ end
107
+
108
+ def test_root_version
109
+ assert_equal "2.0", @root.attributes["Version"]
110
+ end
111
+
112
+ def test_root_issue_instant
113
+ instant = Time.iso8601(@root.attributes["IssueInstant"])
114
+ assert_in_delta Time.now.to_i, instant.to_i, 1
115
+ end
116
+
117
+ def test_destination_default
118
+ assert_equal DEFAULTS.recipient, @root.attributes["Destination"]
119
+ end
120
+
121
+ def test_destination_override
122
+ destination = "https://test.example.com/acs"
123
+ refute_equal destination, DEFAULTS.recipient
124
+ root = Response.new(destination: destination).document.root
125
+ assert_equal destination, root.attributes["Destination"]
126
+ end
127
+
128
+ def test_in_response_to_default
129
+ assert_equal DEFAULTS.in_response_to, @root.attributes["InResponseTo"]
130
+ end
131
+
132
+ def test_in_response_to_override
133
+ in_response_to = "_test_request"
134
+ refute_equal in_response_to, DEFAULTS.in_response_to
135
+ root = Response.new(in_response_to: in_response_to).document.root
136
+ assert_equal in_response_to, root.attributes["InResponseTo"]
137
+ end
138
+
139
+ def test_destination_omitted_when_nil
140
+ root = Response.new(destination: nil).document.root
141
+ assert_nil root.attributes["Destination"]
142
+ end
143
+
144
+ def test_in_response_to_omitted_when_nil
145
+ root = Response.new(in_response_to: nil).document.root
146
+ assert_nil root.attributes["InResponseTo"]
147
+ end
148
+
149
+ def test_issuer
150
+ issuer = @root.elements["saml:Issuer"]
151
+ assert_equal "Issuer", issuer.name
152
+ assert_equal "saml", issuer.prefix
153
+ assert_equal DEFAULTS.issuer, issuer.text
154
+ end
155
+
156
+ def test_issuer_override
157
+ url = "https://test.idp.example.com"
158
+ refute_equal url, DEFAULTS.issuer
159
+ root = Response.new(issuer: url).document.root
160
+ issuer = root.elements["saml:Issuer"]
161
+ assert_equal url, issuer.text
162
+ end
163
+
164
+ def test_status
165
+ status = @root.elements["samlp:Status"]
166
+ assert_equal "Status", status.name
167
+ assert_equal "samlp", status.prefix
168
+ end
169
+
170
+ def test_status_code
171
+ status_code = @root.elements["samlp:Status/samlp:StatusCode"]
172
+ assert_equal "StatusCode", status_code.name
173
+ assert_equal "samlp", status_code.prefix
174
+ assert_equal STATUS_SUCCESS, status_code.attributes["Value"]
175
+ end
176
+
177
+ def test_assertion_embedded
178
+ assertion = @root.elements["saml:Assertion"]
179
+ assert_equal "Assertion", assertion.name
180
+ assert_equal "saml", assertion.prefix
181
+ end
182
+
183
+ def test_assertion_has_id
184
+ assertion = @root.elements["saml:Assertion"]
185
+ assert assertion.attributes["ID"].start_with?("_")
186
+ end
187
+
188
+ def test_assertion_inherits_issuer
189
+ url = "https://test.idp.example.com"
190
+ refute_equal url, DEFAULTS.issuer
191
+ root = Response.new(issuer: url).document.root
192
+ assertion = root.elements["saml:Assertion"]
193
+ issuer = assertion.elements["saml:Issuer"]
194
+ assert_equal url, issuer.text
195
+ end
196
+
197
+ def test_assertion_options_flow_through
198
+ email = "test@example.com"
199
+ refute_equal email, DEFAULTS.name_id
200
+ root = Response.new(name_id: email).document.root
201
+ assertion = root.elements["saml:Assertion"]
202
+ name_id = assertion.elements["saml:Subject/saml:NameID"]
203
+ assert_equal email, name_id.text
204
+ end
205
+
206
+ def test_unsigned_by_default
207
+ assert_nil @root.elements["ds:Signature"]
208
+ assert_nil @root.elements["saml:Assertion/ds:Signature"]
209
+ end
210
+
211
+ def test_sign_with_signs_response_and_assertion
212
+ root = Response.new(sign_with: Certificate.generate).document.root
213
+ refute_nil root.elements["ds:Signature"]
214
+ refute_nil root.elements["saml:Assertion/ds:Signature"]
215
+ end
216
+
217
+ def test_not_encrypted_by_default
218
+ assert_nil @root.elements["saml:EncryptedAssertion"]
219
+ end
220
+
221
+ def test_encrypt_with_creates_encrypted_assertion
222
+ root = Response.new(encrypt_with: Certificate.generate).document.root
223
+ ea = root.elements["saml:EncryptedAssertion"]
224
+ assert_equal "EncryptedAssertion", ea.name
225
+ assert_equal "saml", ea.prefix
226
+ end
227
+
228
+ def test_encrypt_with_removes_plain_assertion
229
+ root = Response.new(encrypt_with: Certificate.generate).document.root
230
+ assert_nil root.elements["saml:Assertion"]
231
+ end
232
+
233
+ def test_encrypt_with_contains_encrypted_data
234
+ root = Response.new(encrypt_with: Certificate.generate).document.root
235
+ ed = root.elements["saml:EncryptedAssertion/xenc:EncryptedData"]
236
+ assert_equal "EncryptedData", ed.name
237
+ end
238
+
239
+ end
240
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ module Lyrebird
6
+ class SignatureTest < Minitest::Test
7
+ def setup
8
+ @certificate = Certificate.generate
9
+ @assertion = Assertion.new.document
10
+ @element = @assertion.root
11
+ Signature.new(@element, @certificate).sign!
12
+ @signature = @element.elements["ds:Signature"]
13
+ @signed_info = @signature.elements["ds:SignedInfo"]
14
+ @reference = @signed_info.elements["ds:Reference"]
15
+ end
16
+
17
+ def test_sign_inserts_after_issuer
18
+ children = @element.elements.to_a
19
+ issuer_index = children.index { |e| e.name == "Issuer" }
20
+ signature_index = children.index { |e| e.name == "Signature" }
21
+ assert_equal issuer_index + 1, signature_index
22
+ end
23
+
24
+ def test_signature_element
25
+ assert_equal "Signature", @signature.name
26
+ assert_equal "ds", @signature.prefix
27
+ assert_equal XMLDSIG_NS, @signature.namespace
28
+ end
29
+
30
+ def test_signature_element_children
31
+ children = @signature.elements.to_a
32
+ assert_equal 3, children.size
33
+ assert_equal "SignedInfo", children[0].name
34
+ assert_equal "SignatureValue", children[1].name
35
+ assert_equal "KeyInfo", children[2].name
36
+ end
37
+
38
+ def test_signed_info_element
39
+ assert_equal "SignedInfo", @signed_info.name
40
+ assert_equal "ds", @signed_info.prefix
41
+ end
42
+
43
+ def test_canonicalization_method
44
+ cm = @signed_info.elements["ds:CanonicalizationMethod"]
45
+ assert_equal "CanonicalizationMethod", cm.name
46
+ assert_equal EXC_C14N, cm.attributes["Algorithm"]
47
+ end
48
+
49
+ def test_signature_method
50
+ sm = @signed_info.elements["ds:SignatureMethod"]
51
+ assert_equal "SignatureMethod", sm.name
52
+ assert_equal RSA_SHA256, sm.attributes["Algorithm"]
53
+ end
54
+
55
+ def test_reference_element
56
+ assert_equal "Reference", @reference.name
57
+ assert_equal "ds", @reference.prefix
58
+ end
59
+
60
+ def test_reference_uri
61
+ element_id = @element.attributes["ID"]
62
+ assert_equal "##{element_id}", @reference.attributes["URI"]
63
+ end
64
+
65
+ def test_transforms_element
66
+ transforms = @reference.elements["ds:Transforms"]
67
+ assert_equal "Transforms", transforms.name
68
+ assert_equal "ds", transforms.prefix
69
+ end
70
+
71
+ def test_enveloped_signature_transform
72
+ transforms = @reference.elements["ds:Transforms"]
73
+ transform = transforms.elements.to_a("ds:Transform")[0]
74
+ assert_equal ENVELOPED_SIG, transform.attributes["Algorithm"]
75
+ end
76
+
77
+ def test_c14n_transform
78
+ transforms = @reference.elements["ds:Transforms"]
79
+ transform = transforms.elements.to_a("ds:Transform")[1]
80
+ assert_equal EXC_C14N, transform.attributes["Algorithm"]
81
+ end
82
+
83
+ def test_digest_method_element
84
+ digest_method = @reference.elements["ds:DigestMethod"]
85
+ assert_equal "DigestMethod", digest_method.name
86
+ assert_equal "ds", digest_method.prefix
87
+ end
88
+
89
+ def test_digest_method_algorithm
90
+ digest_method = @reference.elements["ds:DigestMethod"]
91
+ assert_equal SHA256_DIGEST, digest_method.attributes["Algorithm"]
92
+ end
93
+
94
+ def test_digest_value_element
95
+ digest_value = @reference.elements["ds:DigestValue"]
96
+ assert_equal "DigestValue", digest_value.name
97
+ assert_equal "ds", digest_value.prefix
98
+ end
99
+
100
+ def test_digest_value_is_base64
101
+ digest_value = @reference.elements["ds:DigestValue"]
102
+ decoded = Base64.strict_decode64(digest_value.text)
103
+ assert_equal 32, decoded.bytesize
104
+ end
105
+
106
+ def test_signature_value_element
107
+ sv = @signature.elements["ds:SignatureValue"]
108
+ assert_equal "SignatureValue", sv.name
109
+ assert_equal "ds", sv.prefix
110
+ end
111
+
112
+ def test_signature_value_is_base64
113
+ sv = @signature.elements["ds:SignatureValue"]
114
+ decoded = Base64.strict_decode64(sv.text)
115
+ assert_equal 256, decoded.bytesize
116
+ end
117
+
118
+ def test_key_info_element
119
+ ki = @signature.elements["ds:KeyInfo"]
120
+ assert_equal "KeyInfo", ki.name
121
+ assert_equal "ds", ki.prefix
122
+ end
123
+
124
+ def test_x509_data_element
125
+ x509_data = @signature.elements["ds:KeyInfo/ds:X509Data"]
126
+ assert_equal "X509Data", x509_data.name
127
+ assert_equal "ds", x509_data.prefix
128
+ end
129
+
130
+ def test_x509_certificate_element
131
+ cert = @signature.elements["ds:KeyInfo/ds:X509Data/ds:X509Certificate"]
132
+ assert_equal "X509Certificate", cert.name
133
+ assert_equal "ds", cert.prefix
134
+ assert_equal @certificate.base64, cert.text
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "lyrebird"
4
+ require "minitest/autorun"
metadata CHANGED
@@ -1,25 +1,119 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lyrebird
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 1.0.0.alpha2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josh
8
- bindir: exe
8
+ autorequire:
9
+ bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ date: 2026-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ostruct
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rexml
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
12
85
  executables: []
13
86
  extensions: []
14
87
  extra_rdoc_files: []
15
88
  files:
89
+ - ".github/workflows/ci.yml"
90
+ - ".github/workflows/publish.yml"
16
91
  - README.md
17
92
  - Rakefile
18
93
  - lib/lyrebird.rb
94
+ - lib/lyrebird/assertion.rb
95
+ - lib/lyrebird/certificate.rb
96
+ - lib/lyrebird/defaults.rb
97
+ - lib/lyrebird/encryption.rb
98
+ - lib/lyrebird/id.rb
99
+ - lib/lyrebird/namespaces.rb
100
+ - lib/lyrebird/response.rb
101
+ - lib/lyrebird/signature.rb
19
102
  - lib/lyrebird/version.rb
103
+ - lyrebird.gemspec
20
104
  - sig/lyrebird.rbs
105
+ - test/lyrebird/assertion_test.rb
106
+ - test/lyrebird/certificate_test.rb
107
+ - test/lyrebird/defaults_test.rb
108
+ - test/lyrebird/encryption_test.rb
109
+ - test/lyrebird/id_test.rb
110
+ - test/lyrebird/response_test.rb
111
+ - test/lyrebird/signature_test.rb
112
+ - test/test_helper.rb
113
+ homepage:
21
114
  licenses: []
22
115
  metadata: {}
116
+ post_install_message:
23
117
  rdoc_options: []
24
118
  require_paths:
25
119
  - lib
@@ -30,11 +124,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
30
124
  version: 3.2.0
31
125
  required_rubygems_version: !ruby/object:Gem::Requirement
32
126
  requirements:
33
- - - ">="
127
+ - - ">"
34
128
  - !ruby/object:Gem::Version
35
- version: '0'
129
+ version: 1.3.1
36
130
  requirements: []
37
- rubygems_version: 4.0.3
131
+ rubygems_version: 3.4.19
132
+ signing_key:
38
133
  specification_version: 4
39
- summary: Mimics a SAML IdP for testing
134
+ summary: Mimics SAML Identity Provider (IdP) responses for testing
40
135
  test_files: []