lyrebird 0.0.0 → 1.0.0.alpha1

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: 9634bd5e47288132993ae694000573ebe7a74035a43628f74053a68eb26ea785
4
- data.tar.gz: 0d733419004764bdf5b5d60cd2ed55fd6b7d4c7531c5fc37725e5c13a6e5eae9
3
+ metadata.gz: 01f50b61e7c1cf3b9417c22f495bfc6cd37c15cdd5bb6989147a4462c10ce7ca
4
+ data.tar.gz: ee3ff1a24a14319f978685696a73df8f6dd0a321a9907ef0e0c47cb5b2344fdf
5
5
  SHA512:
6
- metadata.gz: 6053439212105edd8ab7b977c42d38a95678ed2f692f6fa19048b0e3dc3b8f822e0896e3a557fc59b3248d0040c71bc766252e2dcfe388b604a0feb0e953a8ad
7
- data.tar.gz: 75421cfad59640abbbe5f73729dca6df1dc14b400066cc6e281e0cd6432c8c94fe42cdc647adb25adccf4ffc232a240f6c069d27c6be7bc5008205d6675a8a24
6
+ metadata.gz: 98f2d39495b3f5db08e9f27e8663e9b9924ff55ee41b1b9a81a707aa91f6bfa452e823fb577702990bb05e9fdcd439e36573700f62ef30cbd4b48202cf7f4b98
7
+ data.tar.gz: c526d72406244c4da1ba6cc9abe80abdd11191c7cba023db2a1a2b1bbd647a519aeb1531e49d4e09bd6701e1c6164a5b7969f141d005ff3109fe213bd0de0f1d
@@ -0,0 +1,25 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ ruby-version: ["3.2", "3.3", "3.4", "4.0"]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - name: Set up Ruby ${{ matrix.ruby-version }}
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: ${{ matrix.ruby-version }}
23
+ bundler-cache: true
24
+ - name: Run tests
25
+ run: bundle exec rake test
@@ -0,0 +1,20 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: ruby/setup-ruby@v1
13
+ with:
14
+ ruby-version: "3.2"
15
+ - name: Build gem
16
+ run: gem build lyrebird.gemspec
17
+ - name: Publish to RubyGems
18
+ run: gem push lyrebird-*.gem
19
+ env:
20
+ GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
data/README.md CHANGED
@@ -1,35 +1,134 @@
1
1
  # Lyrebird
2
+ A Ruby gem for mimicking SAML Identity Provider (IdP) responses in test
3
+ environments.
2
4
 
3
- TODO: Delete this and the text below, and describe your gem
5
+ ## Installation
6
+ ```ruby
7
+ gem "lyrebird", group: :test
8
+ ```
4
9
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/lyrebird`. To experiment with that code, run `bin/console` for an interactive prompt.
10
+ ## Basic example
11
+ ```ruby
12
+ # test/integration/saml_test.rb
13
+ class SAMLTest < ActionDispatch::IntegrationTest
14
+ test "consume creates a session" do
15
+ user = users(:alice)
6
16
 
7
- ## Installation
17
+ response = Lyrebird::Response.new(
18
+ issuer: "https://idp.example.com",
19
+ destination: saml_consume_url,
20
+ recipient: saml_consume_url,
21
+ audience: root_url,
22
+ name_id: user.email,
23
+ attributes: {
24
+ email: user.email,
25
+ first_name: user.first_name,
26
+ last_name: user.last_name
27
+ }
28
+ )
8
29
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
30
+ post saml_consume_path, params: { SAMLResponse: response.mimic }
10
31
 
11
- Install the gem and add to the application's Gemfile by executing:
32
+ assert_redirected_to dashboard_path
33
+ assert_equal user.id, session[:user_id]
34
+ end
35
+ end
36
+ ```
12
37
 
13
- ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
38
+ ## Response
39
+ Creates complete SAML responses with embedded assertions.
40
+
41
+ ### Creating a response
42
+ ```ruby
43
+ # With defaults
44
+ response = Lyrebird::Response.new
45
+
46
+ # With options
47
+ response = Lyrebird::Response.new(
48
+ issuer: "https://idp.example.com",
49
+ destination: "https://sp.example.com/acs",
50
+ in_response_to: "_request_id",
51
+ name_id: "user@example.com",
52
+ name_id_format: Lyrebird::NAMEID_EMAIL,
53
+ recipient: "https://sp.example.com/acs",
54
+ audience: "https://sp.example.com",
55
+ valid_for: 300, # seconds
56
+ attributes: {
57
+ email: "user@example.com",
58
+ groups: ["admin", "users"]
59
+ }
60
+ )
15
61
  ```
16
62
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
63
+ ### Getting the encoded response
64
+ ```ruby
65
+ response.mimic # Base64-encoded SAML response (for POST binding)
66
+ response.document # REXML::Document for inspection
67
+ ```
18
68
 
19
- ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
69
+ ### Signing
70
+ ```ruby
71
+ cert = Lyrebird::Certificate.generate
72
+
73
+ response = Lyrebird::Response.new(
74
+ certificate: cert,
75
+ sign_assertion: true, # Sign the assertion (default: false)
76
+ sign_response: true # Sign the response (default: false)
77
+ )
21
78
  ```
22
79
 
23
- ## Usage
80
+ ### NameID Formats
81
+ ```ruby
82
+ Lyrebird::NAMEID_EMAIL # emailAddress (default)
83
+ Lyrebird::NAMEID_PERSISTENT # persistent
84
+ Lyrebird::NAMEID_TRANSIENT # transient
85
+ Lyrebird::NAMEID_UNSPECIFIED # unspecified
86
+ ```
24
87
 
25
- TODO: Write usage instructions here
88
+ ## Configuring defaults
89
+ Override defaults globally for all responses/assertions:
90
+ ```ruby
91
+ # test/test_helper.rb
92
+ Lyrebird::DEFAULTS.issuer = "https://custom.example.com"
93
+ Lyrebird::DEFAULTS.recipient = "https://custom.example.com/acs"
94
+ Lyrebird::DEFAULTS.audience = "https://custom.example.com"
95
+ Lyrebird::DEFAULTS.name_id = "default@example.com"
96
+ Lyrebird::DEFAULTS.valid_for = 600 # 10 minutes
97
+ Lyrebird::DEFAULTS.attributes = { role: "user" }
98
+ ```
26
99
 
27
- ## Development
100
+ ## Certificate
101
+ Generates and manages X.509 certificates for signing SAML responses.
28
102
 
29
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
103
+ ### Generating a new certificate
104
+ ```ruby
105
+ # With defaults
106
+ cert = Lyrebird::Certificate.generate
30
107
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
108
+ # With options
109
+ cert = Lyrebird::Certificate.generate(
110
+ bits: 4096, # RSA key size (default: 2048)
111
+ cn: "example.com", # Common Name
112
+ o: "Acme", # Organization
113
+ valid_for: 30, # Validity in days (default: 365)
114
+ valid_until: Time.new(2026, 12, 31) # Specific expiration (overrides valid_for)
115
+ )
116
+ ```
32
117
 
33
- ## Contributing
118
+ ### Loading an existing certificate
119
+ ```ruby
120
+ cert = Lyrebird::Certificate.load(
121
+ private_key_pem: File.read("private_key.pem"),
122
+ certificate_pem: File.read("certificate.pem")
123
+ )
124
+ ```
34
125
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/lyrebird.
126
+ ### Exporting
127
+ ```ruby
128
+ cert.private_key # OpenSSL::PKey::RSA object
129
+ cert.certificate # OpenSSL::X509::Certificate object
130
+ cert.private_key_pem # PEM-encoded private key
131
+ cert.certificate_pem # PEM-encoded certificate
132
+ cert.base64 # Base64-encoded certificate (for SAML metadata)
133
+ cert.fingerprint # SHA256 fingerprint
134
+ ```
data/Rakefile CHANGED
@@ -1,4 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- task default: %i[]
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs.push("test")
8
+ t.test_files = FileList["test/**/*_test.rb"]
9
+ end
10
+
11
+ task default: :test
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lyrebird
4
+ class Assertion
5
+ def initialize(
6
+ issuer: DEFAULTS.issuer,
7
+ name_id: DEFAULTS.name_id,
8
+ name_id_format: DEFAULTS.name_id_format,
9
+ recipient: DEFAULTS.recipient,
10
+ in_response_to: DEFAULTS.in_response_to,
11
+ valid_for: DEFAULTS.valid_for,
12
+ audience: DEFAULTS.audience,
13
+ authn_context: DEFAULTS.authn_context,
14
+ attributes: DEFAULTS.attributes
15
+ )
16
+ @id = ID.generate
17
+ @session_index = ID.generate
18
+ @issue_instant = Time.now.utc
19
+ @issuer = issuer
20
+ @name_id = name_id
21
+ @name_id_format = name_id_format
22
+ @recipient = recipient
23
+ @in_response_to = in_response_to
24
+ @not_on_or_after = @issue_instant + valid_for
25
+ @audience = audience
26
+ @authn_context = authn_context
27
+ @attributes = attributes
28
+ end
29
+
30
+ def document
31
+ REXML::Document.new.tap do |d|
32
+ d.add_element(root)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def root
39
+ REXML::Element.new("saml:Assertion").tap do |r|
40
+ r.add_namespace("saml", SAML_ASSERTION_NS)
41
+ r.add_attribute("ID", @id)
42
+ r.add_attribute("Version", "2.0")
43
+ r.add_attribute("IssueInstant", @issue_instant.iso8601)
44
+ r.add_element("saml:Issuer").text = @issuer
45
+ r.add_element(subject)
46
+ r.add_element(conditions)
47
+ r.add_element(authn_statement)
48
+ r.add_element(attribute_statement) if @attributes.any?
49
+ end
50
+ end
51
+
52
+ def subject
53
+ REXML::Element.new("saml:Subject").tap do |s|
54
+ name_id = s.add_element("saml:NameID")
55
+ name_id.add_attribute("Format", @name_id_format)
56
+ name_id.text = @name_id
57
+ s.add_element(subject_confirmation)
58
+ end
59
+ end
60
+
61
+ def subject_confirmation
62
+ REXML::Element.new("saml:SubjectConfirmation").tap do |sc|
63
+ sc.add_attribute("Method", CM_BEARER)
64
+ data = sc.add_element("saml:SubjectConfirmationData")
65
+ data.add_attribute("NotOnOrAfter", @not_on_or_after.iso8601)
66
+ data.add_attribute("Recipient", @recipient)
67
+ data.add_attribute("InResponseTo", @in_response_to)
68
+ end
69
+ end
70
+
71
+ def conditions
72
+ REXML::Element.new("saml:Conditions").tap do |c|
73
+ c.add_attribute("NotBefore", @issue_instant.iso8601)
74
+ c.add_attribute("NotOnOrAfter", @not_on_or_after.iso8601)
75
+ ar = c.add_element("saml:AudienceRestriction")
76
+ ar.add_element("saml:Audience").text = @audience
77
+ end
78
+ end
79
+
80
+ def authn_statement
81
+ REXML::Element.new("saml:AuthnStatement").tap do |as|
82
+ as.add_attribute("AuthnInstant", @issue_instant.iso8601)
83
+ as.add_attribute("SessionIndex", @session_index)
84
+ ac = as.add_element("saml:AuthnContext")
85
+ cr = ac.add_element("saml:AuthnContextClassRef")
86
+ cr.text = @authn_context
87
+ end
88
+ end
89
+
90
+ def attribute_statement
91
+ REXML::Element.new("saml:AttributeStatement").tap do |as|
92
+ @attributes.each do |name, values|
93
+ a = as.add_element("saml:Attribute")
94
+ a.add_attribute("Name", name)
95
+ a.add_attribute("NameFormat", ATTR_NAME_FORMAT)
96
+
97
+ Array(values).each do |value|
98
+ a.add_element("saml:AttributeValue").text = value
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lyrebird
4
+ class Certificate
5
+ attr_reader :private_key, :certificate
6
+
7
+ def self.generate(bits: 2048, **options)
8
+ new(OpenSSL::PKey::RSA.new(bits), **options)
9
+ end
10
+
11
+ def self.load(private_key_pem:, certificate_pem:)
12
+ private_key = OpenSSL::PKey::RSA.new(private_key_pem)
13
+ certificate = OpenSSL::X509::Certificate.new(certificate_pem)
14
+ new(private_key, certificate: certificate)
15
+ end
16
+
17
+ def initialize(
18
+ private_key,
19
+ cn: nil,
20
+ o: nil,
21
+ valid_for: 365,
22
+ valid_until: nil,
23
+ certificate: nil
24
+ )
25
+ @private_key = private_key
26
+ @common_name = cn
27
+ @organization = o
28
+ @not_after = valid_until || Time.now + (valid_for * 24 * 60 * 60)
29
+ @certificate = certificate || build_certificate
30
+ end
31
+
32
+ def private_key_pem
33
+ @private_key.to_pem
34
+ end
35
+
36
+ def certificate_pem
37
+ @certificate.to_pem
38
+ end
39
+
40
+ def fingerprint
41
+ OpenSSL::Digest::SHA256.hexdigest(@certificate.to_der)
42
+ end
43
+
44
+ def base64
45
+ Base64.strict_encode64(@certificate.to_der)
46
+ end
47
+
48
+ private
49
+
50
+ def build_certificate
51
+ OpenSSL::X509::Certificate.new.tap do |c|
52
+ c.public_key = @private_key.public_key
53
+ c.subject = build_subject
54
+ c.issuer = c.subject
55
+ c.not_before = Time.now
56
+ c.not_after = @not_after
57
+ c.sign(@private_key, OpenSSL::Digest::SHA256.new)
58
+ end
59
+ end
60
+
61
+ def build_subject
62
+ OpenSSL::X509::Name.new.tap do |name|
63
+ name.add_entry("CN", @common_name) if @common_name
64
+ name.add_entry("O", @organization) if @organization
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lyrebird
4
+ NAMEID_EMAIL = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
5
+ NAMEID_PERSISTENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
6
+ NAMEID_TRANSIENT = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
7
+ NAMEID_UNSPECIFIED = "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
8
+
9
+ class Defaults
10
+ attr_accessor :issuer
11
+ attr_accessor :name_id
12
+ attr_accessor :name_id_format
13
+ attr_accessor :recipient
14
+ attr_accessor :in_response_to
15
+ attr_accessor :valid_for
16
+ attr_accessor :audience
17
+ attr_accessor :authn_context
18
+ attr_accessor :attributes
19
+
20
+ def initialize
21
+ @issuer = "https://idp.example.com"
22
+ @name_id = "user@example.com"
23
+ @name_id_format = NAMEID_EMAIL
24
+ @recipient = "https://sp.example.com/acs"
25
+ @in_response_to = "_request_id"
26
+ @valid_for = 300 # 5 minutes
27
+ @audience = "https://sp.example.com"
28
+
29
+ @authn_context =
30
+ "urn:oasis:names:tc:SAML:2.0:ac:classes:" \
31
+ "PasswordProtectedTransport"
32
+
33
+ @attributes = {
34
+ first_name: "Test",
35
+ last_name: "User",
36
+ }
37
+ end
38
+ end
39
+
40
+ DEFAULTS = Defaults.new
41
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lyrebird
4
+ module ID
5
+ def self.generate
6
+ "_#{SecureRandom.uuid}"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lyrebird
4
+ SAML_ASSERTION_NS = "urn:oasis:names:tc:SAML:2.0:assertion"
5
+ SAML_PROTOCOL_NS = "urn:oasis:names:tc:SAML:2.0:protocol"
6
+ XMLDSIG_NS = "http://www.w3.org/2000/09/xmldsig#"
7
+ ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
8
+ EXC_C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
9
+ SHA256_DIGEST = "http://www.w3.org/2001/04/xmlenc#sha256"
10
+ RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
11
+ CM_BEARER = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
12
+ ATTR_NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified"
13
+ STATUS_SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
14
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lyrebird
4
+ class Response
5
+ def initialize(
6
+ issuer: DEFAULTS.issuer,
7
+ destination: DEFAULTS.recipient,
8
+ in_response_to: DEFAULTS.in_response_to,
9
+ certificate: nil,
10
+ sign_assertion: false,
11
+ sign_response: false,
12
+ **assertion_options
13
+ )
14
+ @id = ID.generate
15
+ @issue_instant = Time.now.utc
16
+ @issuer = issuer
17
+ @destination = destination
18
+ @in_response_to = in_response_to
19
+ @certificate = certificate
20
+ @sign_assertion = sign_assertion
21
+ @sign_response = sign_response
22
+
23
+ @assertion = Assertion.new(
24
+ issuer: issuer,
25
+ in_response_to: in_response_to,
26
+ **assertion_options
27
+ )
28
+ end
29
+
30
+ def mimic
31
+ Base64.strict_encode64(document.to_s)
32
+ end
33
+
34
+ def document
35
+ REXML::Document.new.tap do |d|
36
+ d.add_element(root)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def root
43
+ REXML::Element.new("samlp:Response").tap do |r|
44
+ r.add_namespace("samlp", SAML_PROTOCOL_NS)
45
+ r.add_namespace("saml", SAML_ASSERTION_NS)
46
+ r.add_attribute("ID", @id)
47
+ r.add_attribute("Version", "2.0")
48
+ r.add_attribute("IssueInstant", @issue_instant.iso8601)
49
+ r.add_attribute("Destination", @destination)
50
+ r.add_attribute("InResponseTo", @in_response_to)
51
+ r.add_element("saml:Issuer").text = @issuer
52
+ r.add_element(status)
53
+ a = r.add_element(@assertion.document.root)
54
+ Signature.new(a, certificate: @certificate).sign! if @sign_assertion
55
+ Signature.new(r, certificate: @certificate).sign! if @sign_response
56
+ end
57
+ end
58
+
59
+ def status
60
+ REXML::Element.new("samlp:Status").tap do |s|
61
+ sc = s.add_element("samlp:StatusCode")
62
+ sc.add_attribute("Value", STATUS_SUCCESS)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lyrebird
4
+ class Signature
5
+ def initialize(element, certificate:)
6
+ @element = element
7
+ @certificate = certificate
8
+ @element_id = @element.attributes["ID"]
9
+ end
10
+
11
+ def sign!
12
+ issuer = @element.elements["saml:Issuer"]
13
+ @element.insert_after(issuer, signature_element)
14
+ end
15
+
16
+ private
17
+
18
+ def signature_element
19
+ REXML::Element.new("ds:Signature").tap do |sig|
20
+ sig.add_namespace("ds", XMLDSIG_NS)
21
+ sig.add_element(signed_info)
22
+ sig.add_element(signature_value)
23
+ sig.add_element(key_info)
24
+ end
25
+ end
26
+
27
+ def signed_info
28
+ REXML::Element.new("ds:SignedInfo").tap do |si|
29
+ cm = si.add_element("ds:CanonicalizationMethod")
30
+ cm.add_attribute("Algorithm", EXC_C14N)
31
+ sm = si.add_element("ds:SignatureMethod")
32
+ sm.add_attribute("Algorithm", RSA_SHA256)
33
+ si.add_element(reference)
34
+ end
35
+ end
36
+
37
+ def signature_value
38
+ REXML::Element.new("ds:SignatureValue").tap do |sv|
39
+ sig = @certificate.private_key.sign("SHA256", signed_info.to_s)
40
+ sv.text = Base64.strict_encode64(sig)
41
+ end
42
+ end
43
+
44
+ def key_info
45
+ REXML::Element.new("ds:KeyInfo").tap do |ki|
46
+ x = ki.add_element("ds:X509Data")
47
+ x.add_element("ds:X509Certificate").text = @certificate.base64
48
+ end
49
+ end
50
+
51
+ def reference
52
+ REXML::Element.new("ds:Reference").tap do |ref|
53
+ ref.add_attribute("URI", "##{@element_id}")
54
+ ref.add_element(transforms)
55
+ dm = ref.add_element("ds:DigestMethod")
56
+ dm.add_attribute("Algorithm", SHA256_DIGEST)
57
+ ref.add_element("ds:DigestValue").text = compute_digest(@element)
58
+ end
59
+ end
60
+
61
+ def transforms
62
+ REXML::Element.new("ds:Transforms").tap do |t|
63
+ enveloped = t.add_element("ds:Transform")
64
+ enveloped.add_attribute("Algorithm", ENVELOPED_SIG)
65
+ c14n = t.add_element("ds:Transform")
66
+ c14n.add_attribute("Algorithm", EXC_C14N)
67
+ end
68
+ end
69
+
70
+ def compute_digest(element)
71
+ digest = OpenSSL::Digest::SHA256.digest(element.to_s)
72
+ Base64.strict_encode64(digest)
73
+ end
74
+ end
75
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lyrebird
4
- VERSION = "0.0.0"
4
+ VERSION = "1.0.0.alpha1"
5
5
  end
data/lib/lyrebird.rb CHANGED
@@ -1,8 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "base64"
4
+ require "openssl"
5
+ require "rexml"
6
+ require "securerandom"
7
+ require "time"
8
+
9
+ require_relative "lyrebird/assertion"
10
+ require_relative "lyrebird/certificate"
11
+ require_relative "lyrebird/defaults"
12
+ require_relative "lyrebird/id"
13
+ require_relative "lyrebird/namespaces"
14
+ require_relative "lyrebird/response"
15
+ require_relative "lyrebird/signature"
3
16
  require_relative "lyrebird/version"
4
17
 
5
18
  module Lyrebird
6
19
  class Error < StandardError; end
7
- # Your code goes here...
8
20
  end
data/lyrebird.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/lyrebird/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "lyrebird"
7
+ spec.version = Lyrebird::VERSION
8
+ spec.authors = ["Josh"]
9
+
10
+ spec.summary = "Mimics SAML Identity Provider (IdP) responses for testing"
11
+ spec.required_ruby_version = ">= 3.2.0"
12
+
13
+ spec.files = `git ls-files -z`.split("\x0")
14
+ spec.files.delete("Gemfile")
15
+ spec.files.delete(".gitignore")
16
+ spec.files.reject! { |f| f.start_with?("bin/") }
17
+
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "base64"
21
+ spec.add_dependency "rexml"
22
+
23
+ spec.add_development_dependency "minitest"
24
+ spec.add_development_dependency "rake"
25
+ end