saml-kit 0.2.3 → 0.2.4

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -1
  3. data/exe/saml-kit-create-self-signed-certificate +1 -0
  4. data/lib/saml/kit.rb +1 -0
  5. data/lib/saml/kit/assertion.rb +73 -0
  6. data/lib/saml/kit/authentication_request.rb +2 -2
  7. data/lib/saml/kit/bindings/http_redirect.rb +1 -1
  8. data/lib/saml/kit/bindings/url_builder.rb +9 -5
  9. data/lib/saml/kit/buildable.rb +2 -4
  10. data/lib/saml/kit/builders/assertion.rb +2 -2
  11. data/lib/saml/kit/builders/authentication_request.rb +2 -3
  12. data/lib/saml/kit/builders/identity_provider_metadata.rb +2 -3
  13. data/lib/saml/kit/builders/logout_request.rb +3 -4
  14. data/lib/saml/kit/builders/logout_response.rb +2 -3
  15. data/lib/saml/kit/builders/response.rb +11 -17
  16. data/lib/saml/kit/builders/service_provider_metadata.rb +3 -4
  17. data/lib/saml/kit/builders/templates/assertion.builder +23 -21
  18. data/lib/saml/kit/builders/templates/identity_provider_metadata.builder +4 -4
  19. data/lib/saml/kit/builders/templates/nil_class.builder +0 -0
  20. data/lib/saml/kit/builders/templates/response.builder +1 -3
  21. data/lib/saml/kit/builders/templates/service_provider_metadata.builder +4 -4
  22. data/lib/saml/kit/builders/xml_signature.rb +2 -3
  23. data/lib/saml/kit/composite_metadata.rb +13 -8
  24. data/lib/saml/kit/configuration.rb +36 -11
  25. data/lib/saml/kit/document.rb +3 -2
  26. data/lib/saml/kit/locales/en.yml +3 -0
  27. data/lib/saml/kit/logout_request.rb +2 -2
  28. data/lib/saml/kit/metadata.rb +2 -2
  29. data/lib/saml/kit/response.rb +9 -62
  30. data/lib/saml/kit/self_signed_certificate.rb +0 -9
  31. data/lib/saml/kit/signature.rb +8 -21
  32. data/lib/saml/kit/signatures.rb +6 -12
  33. data/lib/saml/kit/templatable.rb +8 -2
  34. data/lib/saml/kit/template.rb +2 -1
  35. data/lib/saml/kit/trustable.rb +1 -1
  36. data/lib/saml/kit/version.rb +1 -1
  37. data/lib/saml/kit/xml.rb +1 -0
  38. data/lib/saml/kit/xml_decryption.rb +2 -2
  39. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 68d768bf760de038c1d2509056882bdd822d6d9d81449e9b6b3ee55b1f0061b0
4
- data.tar.gz: 36ddb0f1d8f7692518e43604bed5505ff5503235b99644e83923049cbc86cf0d
3
+ metadata.gz: 13c5ee20e28acd63c6ec3dde99cd1fea092be3db550dca1bd129a4b0e1fb83d3
4
+ data.tar.gz: 0337c7d33845d4fbfb9dd6cc817cfde1dc620c3745cfd83b96f246e29ec30dc3
5
5
  SHA512:
6
- metadata.gz: 03dec61510539ae84a0fa52e64f0b522d75ae2388a289f657a47480d716913e16d2c52e369b761581c32467e812aec22eb78b50e311518d877a9a891d1d0a2a0
7
- data.tar.gz: 687f187faeb6e73c31abe59ec6734f30cb41213262b4c5fdd0fd4dd4ada9d712a7bce27959e92a03ee8ccbe6efc75d2ce8d9ff75d8595e5e87c15e54da0525ba
6
+ metadata.gz: cc09a60391991a964b3f6114e6c039da70eb917ba268787abe8940bc00a8883449f5e751aeb2365b9cae5f693d177ded5fd2d9d152ae766569a9feb5a8198ad2
7
+ data.tar.gz: e6cd00b9f963348a4b9cce442d09fe7b5819c0b970334ccff01c6fcfcce54e60cfff7692878f5603a6e54061c2b75b0df61364e2eaa1717547fdc079efbe588c
data/.rspec CHANGED
@@ -1,2 +1,2 @@
1
- --format documentation
2
1
  --color
2
+ --require spec_helper
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'saml/kit'
3
3
 
4
+ puts "Enter Password:"
4
5
  password = STDIN.read.strip
5
6
  certificate, private_key = Saml::Kit::SelfSignedCertificate.new(password).create
6
7
 
data/lib/saml/kit.rb CHANGED
@@ -26,6 +26,7 @@ require "saml/kit/requestable"
26
26
  require "saml/kit/trustable"
27
27
  require "saml/kit/document"
28
28
 
29
+ require "saml/kit/assertion"
29
30
  require "saml/kit/authentication_request"
30
31
  require "saml/kit/bindings"
31
32
  require "saml/kit/certificate"
@@ -0,0 +1,73 @@
1
+ module Saml
2
+ module Kit
3
+ class Assertion
4
+ def initialize(xml_hash, configuration:)
5
+ @xml_hash = xml_hash
6
+ @configuration = configuration
7
+ end
8
+
9
+ def name_id
10
+ assertion.fetch('Subject', {}).fetch('NameID', nil)
11
+ end
12
+
13
+ def signed?
14
+ assertion.fetch('Signature', nil).present?
15
+ end
16
+
17
+ def attributes
18
+ @attributes ||=
19
+ begin
20
+ attrs = assertion.fetch('AttributeStatement', {}).fetch('Attribute', [])
21
+ items = if attrs.is_a? Hash
22
+ [[attrs["Name"], attrs["AttributeValue"]]]
23
+ else
24
+ attrs.map { |item| [item['Name'], item['AttributeValue']] }
25
+ end
26
+ Hash[items].with_indifferent_access
27
+ end
28
+ end
29
+
30
+ def started_at
31
+ parse_date(assertion.fetch('Conditions', {}).fetch('NotBefore', nil))
32
+ end
33
+
34
+ def expired_at
35
+ parse_date(assertion.fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
36
+ end
37
+
38
+ def certificate
39
+ assertion.fetch('Signature', {}).fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
40
+ end
41
+
42
+ def audiences
43
+ Array(assertion['Conditions']['AudienceRestriction']['Audience'])
44
+ rescue => error
45
+ Saml::Kit.logger.error(error)
46
+ []
47
+ end
48
+
49
+ private
50
+
51
+ def encrypted?
52
+ @xml_hash.fetch('Response', {}).fetch('EncryptedAssertion', nil).present?
53
+ end
54
+
55
+ def assertion
56
+ if encrypted?
57
+ decrypted = XmlDecryption.new(configuration: @configuration).decrypt(@xml_hash['Response']['EncryptedAssertion'])
58
+ Saml::Kit.logger.debug(decrypted)
59
+ Hash.from_xml(decrypted)['Assertion']
60
+ else
61
+ @xml_hash.fetch('Response', {}).fetch('Assertion', {})
62
+ end
63
+ end
64
+
65
+ def parse_date(value)
66
+ DateTime.parse(value)
67
+ rescue => error
68
+ Saml::Kit.logger.error(error)
69
+ Time.at(0).to_datetime
70
+ end
71
+ end
72
+ end
73
+ end
@@ -3,8 +3,8 @@ module Saml
3
3
  class AuthenticationRequest < Document
4
4
  include Requestable
5
5
 
6
- def initialize(xml)
7
- super(xml, name: "AuthnRequest")
6
+ def initialize(xml, configuration: Saml::Kit.configuration)
7
+ super(xml, name: "AuthnRequest", configuration: configuration)
8
8
  end
9
9
 
10
10
  def assertion_consumer_service_url
@@ -12,7 +12,7 @@ module Saml
12
12
  builder.sign = false
13
13
  builder.destination = location
14
14
  document = builder.build
15
- [UrlBuilder.new.build(document, relay_state: relay_state), {}]
15
+ [UrlBuilder.new(configuration: builder.configuration).build(document, relay_state: relay_state), {}]
16
16
  end
17
17
 
18
18
  def deserialize(params)
@@ -3,21 +3,25 @@ module Saml
3
3
  module Bindings
4
4
  class UrlBuilder
5
5
  include Serializable
6
+ attr_reader :configuration
6
7
 
7
- def initialize(private_key: Saml::Kit.configuration.signing_private_key)
8
- @private_key = private_key
8
+ def initialize(configuration: Saml::Kit.configuration)
9
+ @configuration = configuration
9
10
  end
10
11
 
11
12
  def build(saml_document, relay_state: nil)
12
13
  payload = canonicalize(saml_document, relay_state)
13
- "#{saml_document.destination}?#{payload}&Signature=#{signature_for(payload)}"
14
+ if configuration.sign?
15
+ "#{saml_document.destination}?#{payload}&Signature=#{signature_for(payload)}"
16
+ else
17
+ "#{saml_document.destination}?#{payload}"
18
+ end
14
19
  end
15
20
 
16
21
  private
17
22
 
18
- attr_reader :private_key
19
-
20
23
  def signature_for(payload)
24
+ private_key = configuration.private_keys(use: :signing).sample
21
25
  encode(private_key.sign(OpenSSL::Digest::SHA256.new, payload))
22
26
  end
23
27
 
@@ -4,10 +4,8 @@ module Saml
4
4
  extend ActiveSupport::Concern
5
5
 
6
6
  class_methods do
7
- def build(*args)
8
- builder(*args).tap do |x|
9
- yield x if block_given?
10
- end.build
7
+ def build(*args, &block)
8
+ builder(*args, &block).build
11
9
  end
12
10
 
13
11
  def builder(*args)
@@ -5,7 +5,7 @@ module Saml
5
5
  include Templatable
6
6
  extend Forwardable
7
7
 
8
- def_delegators :@response_builder, :encrypt, :sign, :request, :issuer, :reference_id, :now, :configuration, :user, :version
8
+ def_delegators :@response_builder, :encrypt, :sign, :request, :issuer, :reference_id, :now, :configuration, :user, :version, :destination, :encryption_certificate
9
9
 
10
10
  def initialize(response_builder)
11
11
  @response_builder = response_builder
@@ -38,7 +38,7 @@ module Saml
38
38
  {
39
39
  InResponseTo: request.id,
40
40
  NotOnOrAfter: 3.hours.since(now).utc.iso8601,
41
- Recipient: request.assertion_consumer_service_url,
41
+ Recipient: destination,
42
42
  }
43
43
  end
44
44
 
@@ -3,17 +3,16 @@ module Saml
3
3
  module Builders
4
4
  class AuthenticationRequest
5
5
  include Saml::Kit::Templatable
6
- attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :sign, :destination
6
+ attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :destination
7
7
  attr_accessor :version
8
8
  attr_reader :configuration
9
9
 
10
- def initialize(configuration: Saml::Kit.configuration, sign: true)
10
+ def initialize(configuration: Saml::Kit.configuration)
11
11
  @configuration = configuration
12
12
  @id = Id.generate
13
13
  @issuer = configuration.issuer
14
14
  @name_id_format = Namespaces::PERSISTENT
15
15
  @now = Time.now.utc
16
- @sign = sign
17
16
  @version = "2.0"
18
17
  end
19
18
 
@@ -4,18 +4,17 @@ module Saml
4
4
  class IdentityProviderMetadata
5
5
  include Saml::Kit::Templatable
6
6
  attr_accessor :id, :organization_name, :organization_url, :contact_email, :entity_id, :attributes, :name_id_formats
7
- attr_accessor :want_authn_requests_signed, :sign
7
+ attr_accessor :want_authn_requests_signed
8
8
  attr_reader :logout_urls, :single_sign_on_urls
9
9
  attr_reader :configuration
10
10
 
11
- def initialize(configuration = Saml::Kit.configuration)
11
+ def initialize(configuration: Saml::Kit.configuration)
12
12
  @attributes = []
13
13
  @configuration = configuration
14
14
  @entity_id = configuration.issuer
15
15
  @id = Id.generate
16
16
  @logout_urls = []
17
17
  @name_id_formats = [Namespaces::PERSISTENT]
18
- @sign = true
19
18
  @single_sign_on_urls = []
20
19
  @want_authn_requests_signed = true
21
20
  end
@@ -4,10 +4,10 @@ module Saml
4
4
  class LogoutRequest
5
5
  include Saml::Kit::Templatable
6
6
  attr_accessor :id, :destination, :issuer, :name_id_format, :now
7
- attr_accessor :sign, :version
7
+ attr_accessor :version
8
8
  attr_reader :user, :configuration
9
9
 
10
- def initialize(user, configuration: Saml::Kit.configuration, sign: true)
10
+ def initialize(user, configuration: Saml::Kit.configuration)
11
11
  @configuration = configuration
12
12
  @user = user
13
13
  @id = "_#{SecureRandom.uuid}"
@@ -15,11 +15,10 @@ module Saml
15
15
  @name_id_format = Saml::Kit::Namespaces::PERSISTENT
16
16
  @now = Time.now.utc
17
17
  @version = "2.0"
18
- @sign = sign
19
18
  end
20
19
 
21
20
  def build
22
- Saml::Kit::LogoutRequest.new(to_xml)
21
+ Saml::Kit::LogoutRequest.new(to_xml, configuration: configuration)
23
22
  end
24
23
 
25
24
  private
@@ -3,17 +3,16 @@ module Saml
3
3
  module Builders
4
4
  class LogoutResponse
5
5
  include Saml::Kit::Templatable
6
- attr_accessor :id, :issuer, :version, :status_code, :sign, :now, :destination
6
+ attr_accessor :id, :issuer, :version, :status_code, :now, :destination
7
7
  attr_reader :request
8
8
  attr_reader :configuration
9
9
 
10
- def initialize(user, request, configuration: Saml::Kit.configuration, sign: true)
10
+ def initialize(user, request, configuration: Saml::Kit.configuration)
11
11
  @configuration = configuration
12
12
  @id = Id.generate
13
13
  @issuer = configuration.issuer
14
14
  @now = Time.now.utc
15
15
  @request = request
16
- @sign = sign
17
16
  @status_code = Namespaces::SUCCESS
18
17
  @user = user
19
18
  @version = "2.0"
@@ -6,7 +6,7 @@ module Saml
6
6
  attr_reader :user, :request
7
7
  attr_accessor :id, :reference_id, :now
8
8
  attr_accessor :version, :status_code
9
- attr_accessor :issuer, :sign, :destination, :encrypt
9
+ attr_accessor :issuer, :destination, :encrypt
10
10
  attr_reader :configuration
11
11
 
12
12
  def initialize(user, request, configuration: Saml::Kit.configuration)
@@ -18,9 +18,8 @@ module Saml
18
18
  @version = "2.0"
19
19
  @status_code = Namespaces::SUCCESS
20
20
  @issuer = configuration.issuer
21
- @destination = destination_for(request)
22
21
  @sign = want_assertions_signed
23
- @encrypt = false
22
+ @encrypt = encryption_certificate.present?
24
23
  @configuration = configuration
25
24
  end
26
25
 
@@ -28,29 +27,24 @@ module Saml
28
27
  request.provider.want_assertions_signed
29
28
  rescue => error
30
29
  Saml::Kit.logger.error(error)
31
- true
30
+ nil
32
31
  end
33
32
 
34
33
  def build
35
- Saml::Kit::Response.new(to_xml, request_id: request.id)
36
- end
37
-
38
- private
39
-
40
- def assertion
41
- @assertion ||= Saml::Kit::Builders::Assertion.new(self)
34
+ Saml::Kit::Response.new(to_xml, request_id: request.id, configuration: configuration)
42
35
  end
43
36
 
44
37
  def encryption_certificate
45
38
  request.provider.encryption_certificates.first
39
+ rescue => error
40
+ Saml::Kit.logger.error(error)
41
+ nil
46
42
  end
47
43
 
48
- def destination_for(request)
49
- if request.signed? && request.trusted?
50
- request.assertion_consumer_service_url || request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
51
- else
52
- request.provider.assertion_consumer_service_for(binding: :http_post).try(:location)
53
- end
44
+ private
45
+
46
+ def assertion
47
+ @assertion ||= Saml::Kit::Builders::Assertion.new(self)
54
48
  end
55
49
 
56
50
  def response_options
@@ -3,19 +3,18 @@ module Saml
3
3
  module Builders
4
4
  class ServiceProviderMetadata
5
5
  include Saml::Kit::Templatable
6
- attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats, :sign
6
+ attr_accessor :id, :entity_id, :acs_urls, :logout_urls, :name_id_formats
7
7
  attr_accessor :organization_name, :organization_url, :contact_email
8
8
  attr_accessor :want_assertions_signed
9
9
  attr_reader :configuration
10
10
 
11
- def initialize(configuration = Saml::Kit.configuration)
11
+ def initialize(configuration: Saml::Kit.configuration)
12
12
  @acs_urls = []
13
13
  @configuration = configuration
14
14
  @entity_id = configuration.issuer
15
15
  @id = Id.generate
16
16
  @logout_urls = []
17
17
  @name_id_formats = [Namespaces::PERSISTENT]
18
- @sign = true
19
18
  @want_assertions_signed = true
20
19
  end
21
20
 
@@ -43,7 +42,7 @@ module Saml
43
42
 
44
43
  def descriptor_options
45
44
  {
46
- AuthnRequestsSigned: sign,
45
+ AuthnRequestsSigned: sign?,
47
46
  WantAssertionsSigned: want_assertions_signed,
48
47
  protocolSupportEnumeration: Namespaces::PROTOCOL,
49
48
  }
@@ -1,27 +1,29 @@
1
- xml.Assertion(assertion_options) do
2
- xml.Issuer issuer
3
- signature_for(reference_id: reference_id, xml: xml) unless encrypt
4
- xml.Subject do
5
- xml.NameID name_id, Format: name_id_format
6
- xml.SubjectConfirmation Method: Saml::Kit::Namespaces::BEARER do
7
- xml.SubjectConfirmationData "", subject_confirmation_data_options
1
+ encryption_for(xml: xml) do |xml|
2
+ xml.Assertion(assertion_options) do
3
+ xml.Issuer issuer
4
+ signature_for(reference_id: reference_id, xml: xml) unless encrypt
5
+ xml.Subject do
6
+ xml.NameID name_id, Format: name_id_format
7
+ xml.SubjectConfirmation Method: Saml::Kit::Namespaces::BEARER do
8
+ xml.SubjectConfirmationData "", subject_confirmation_data_options
9
+ end
8
10
  end
9
- end
10
- xml.Conditions conditions_options do
11
- xml.AudienceRestriction do
12
- xml.Audience request.issuer
11
+ xml.Conditions conditions_options do
12
+ xml.AudienceRestriction do
13
+ xml.Audience request.issuer
14
+ end
13
15
  end
14
- end
15
- xml.AuthnStatement authn_statement_options do
16
- xml.AuthnContext do
17
- xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
16
+ xml.AuthnStatement authn_statement_options do
17
+ xml.AuthnContext do
18
+ xml.AuthnContextClassRef Saml::Kit::Namespaces::PASSWORD
19
+ end
18
20
  end
19
- end
20
- if assertion_attributes.any?
21
- xml.AttributeStatement do
22
- assertion_attributes.each do |key, value|
23
- xml.Attribute Name: key, NameFormat: Saml::Kit::Namespaces::URI, FriendlyName: key do
24
- xml.AttributeValue value.to_s
21
+ if assertion_attributes.any?
22
+ xml.AttributeStatement do
23
+ assertion_attributes.each do |key, value|
24
+ xml.Attribute Name: key, NameFormat: Saml::Kit::Namespaces::URI, FriendlyName: key do
25
+ xml.AttributeValue value.to_s
26
+ end
25
27
  end
26
28
  end
27
29
  end
@@ -2,11 +2,11 @@ xml.instruct!
2
2
  xml.EntityDescriptor entity_descriptor_options do
3
3
  signature_for(reference_id: id, xml: xml)
4
4
  xml.IDPSSODescriptor idp_sso_descriptor_options do
5
- if configuration.signing_certificate_pem.present?
6
- render configuration.signing_certificate, xml: xml
5
+ configuration.certificates(use: :signing).each do |certificate|
6
+ render certificate, xml: xml
7
7
  end
8
- if configuration.encryption_certificate_pem.present?
9
- render configuration.encryption_certificate, xml: xml
8
+ configuration.certificates(use: :encryption).each do |certificate|
9
+ render certificate, xml: xml
10
10
  end
11
11
  logout_urls.each do |item|
12
12
  xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
File without changes
@@ -5,7 +5,5 @@ xml.Response response_options do
5
5
  xml.Status do
6
6
  xml.StatusCode Value: status_code
7
7
  end
8
- encryption_for(xml: xml) do |xml|
9
- render assertion, xml: xml
10
- end
8
+ render assertion, xml: xml
11
9
  end
@@ -2,11 +2,11 @@ xml.instruct!
2
2
  xml.EntityDescriptor entity_descriptor_options do
3
3
  signature_for(reference_id: id, xml: xml)
4
4
  xml.SPSSODescriptor descriptor_options do
5
- if configuration.signing_certificate_pem.present?
6
- render configuration.signing_certificate, xml: xml
5
+ configuration.certificates(use: :signing).each do |certificate|
6
+ render certificate, xml: xml
7
7
  end
8
- if configuration.encryption_certificate_pem.present?
9
- render configuration.encryption_certificate, xml: xml
8
+ configuration.certificates(use: :encryption).each do |certificate|
9
+ render certificate, xml: xml
10
10
  end
11
11
  logout_urls.each do |item|
12
12
  xml.SingleLogoutService Binding: item[:binding], Location: item[:location]
@@ -21,11 +21,10 @@ module Saml
21
21
  attr_reader :reference_id
22
22
  attr_reader :x509_certificate
23
23
 
24
- def initialize(reference_id, configuration:, sign: true)
24
+ def initialize(reference_id, configuration:)
25
25
  @configuration = configuration
26
26
  @reference_id = reference_id
27
- @sign = sign
28
- @x509_certificate = configuration.signing_certificate.stripped
27
+ @x509_certificate = configuration.certificates(use: :signing).sample.stripped
29
28
  end
30
29
 
31
30
  def signature_method
@@ -1,16 +1,19 @@
1
1
  module Saml
2
2
  module Kit
3
3
  class CompositeMetadata < Metadata
4
+ include Enumerable
4
5
  attr_reader :service_provider, :identity_provider
5
6
 
6
7
  def initialize(xml)
7
8
  super("IDPSSODescriptor", xml)
8
- @service_provider = Saml::Kit::ServiceProviderMetadata.new(xml)
9
- @identity_provider = Saml::Kit::IdentityProviderMetadata.new(xml)
9
+ @metadatum = [
10
+ Saml::Kit::ServiceProviderMetadata.new(xml),
11
+ Saml::Kit::IdentityProviderMetadata.new(xml),
12
+ ]
10
13
  end
11
14
 
12
15
  def services(type)
13
- xpath = "//md:EntityDescriptor/md:SPSSODescriptor/md:#{type}|//md:EntityDescriptor/md:IDPSSODescriptor/md:#{type}"
16
+ xpath = map { |x| "//md:EntityDescriptor/md:#{x.name}/md:#{type}" }.join("|")
14
17
  document.find_all(xpath).map do |item|
15
18
  binding = item.attribute("Binding").value
16
19
  location = item.attribute("Location").value
@@ -19,14 +22,16 @@ module Saml
19
22
  end
20
23
 
21
24
  def certificates
22
- identity_provider.certificates + service_provider.certificates
25
+ flat_map(&:certificates)
26
+ end
27
+
28
+ def each(&block)
29
+ @metadatum.each(&block)
23
30
  end
24
31
 
25
32
  def method_missing(name, *args)
26
- if identity_provider.respond_to?(name)
27
- identity_provider.public_send(name, *args)
28
- elsif service_provider.respond_to?(name)
29
- service_provider.public_send(name, *args)
33
+ if target = find { |x| x.respond_to?(name) }
34
+ target.public_send(name, *args)
30
35
  else
31
36
  super
32
37
  end
@@ -3,37 +3,62 @@ module Saml
3
3
  class Configuration
4
4
  attr_accessor :issuer
5
5
  attr_accessor :signature_method, :digest_method
6
- attr_accessor :signing_certificate_pem, :signing_private_key_pem, :signing_private_key_password
7
- attr_accessor :encryption_certificate_pem, :encryption_private_key_pem, :encryption_private_key_password
8
6
  attr_accessor :registry, :session_timeout
9
7
  attr_accessor :logger
10
8
 
11
9
  def initialize
12
10
  @signature_method = :SHA256
13
11
  @digest_method = :SHA256
14
- @signing_private_key_password = SecureRandom.uuid
15
- @encryption_private_key_password = SecureRandom.uuid
16
- @signing_certificate_pem, @signing_private_key_pem = SelfSignedCertificate.new(@signing_private_key_password).create
17
- @encryption_certificate_pem, @encryption_private_key_pem = SelfSignedCertificate.new(@encryption_private_key_password).create
18
12
  @registry = DefaultRegistry.new
19
13
  @session_timeout = 3.hours
20
14
  @logger = Logger.new(STDOUT)
15
+ yield self if block_given?
21
16
  end
22
17
 
23
- def signing_certificate
24
- Saml::Kit::Certificate.new(signing_certificate_pem, use: :signing)
18
+ def add_key_pair(certificate, private_key, password:, use: :signing)
19
+ key_pairs.push({
20
+ certificate: Saml::Kit::Certificate.new(certificate, use: use),
21
+ private_key: OpenSSL::PKey::RSA.new(private_key, password)
22
+ })
23
+ end
24
+
25
+ def generate_key_pair_for(use:, password: SecureRandom.uuid)
26
+ certificate, private_key = SelfSignedCertificate.new(password).create
27
+ add_key_pair(certificate, private_key, password: password, use: use)
28
+ end
29
+
30
+ def certificates(use: nil)
31
+ certificates = key_pairs.map { |x| x[:certificate] }
32
+ use.present? ? certificates.find_all { |x| x.for?(use) } : certificates
33
+ end
34
+
35
+ def private_keys(use: :signing)
36
+ key_pairs.find_all { |x| x[:certificate].for?(use) }.map { |x| x[:private_key] }
25
37
  end
26
38
 
27
39
  def encryption_certificate
28
- Saml::Kit::Certificate.new(encryption_certificate_pem, use: :encryption)
40
+ Saml::Kit.deprecate("encryption_certificate is deprecated. Use certificates(use: :encryption) instead")
41
+ certificates(use: :encryption).last
29
42
  end
30
43
 
31
44
  def signing_private_key
32
- OpenSSL::PKey::RSA.new(signing_private_key_pem, signing_private_key_password)
45
+ Saml::Kit.deprecate("signing_private_key is deprecated. Use private_keys(use: :signing) instead")
46
+ private_keys(use: :signing).last
33
47
  end
34
48
 
35
49
  def encryption_private_key
36
- OpenSSL::PKey::RSA.new(encryption_private_key_pem, encryption_private_key_password)
50
+ Saml::Kit.deprecate("encryption_private_key is deprecated. Use private_keys(use: :encryption) instead")
51
+ private_keys(use: :encryption).last
52
+ end
53
+
54
+ def sign?
55
+ certificates(use: :signing).any?
56
+ end
57
+
58
+ private
59
+
60
+ def key_pairs
61
+ @key_pairs ||= []
37
62
  end
38
63
  end
39
64
  end
@@ -12,9 +12,10 @@ module Saml
12
12
  validate :must_be_expected_type
13
13
  validate :must_be_valid_version
14
14
 
15
- attr_reader :content, :name
15
+ attr_reader :content, :name, :configuration
16
16
 
17
- def initialize(xml, name:)
17
+ def initialize(xml, name:, configuration: Saml::Kit.configuration)
18
+ @configuration = configuration
18
19
  @content = xml
19
20
  @name = name
20
21
  @xml_hash = Hash.from_xml(xml) || {}
@@ -11,6 +11,9 @@ en:
11
11
  invalid_signature: "invalid signature."
12
12
  InvalidDocument:
13
13
  invalid: "must contain valid SAMLRequest"
14
+ LogoutRequest:
15
+ invalid_fingerprint: "does not match."
16
+ unregistered: "is unregistered."
14
17
  LogoutResponse:
15
18
  unregistered: "is unregistered."
16
19
  Response:
@@ -4,8 +4,8 @@ module Saml
4
4
  include Requestable
5
5
  validates_presence_of :single_logout_service, if: :expected_type?
6
6
 
7
- def initialize(xml)
8
- super(xml, name: "LogoutRequest")
7
+ def initialize(xml, configuration: Saml::Kit.configuration)
8
+ super(xml, name: "LogoutRequest", configuration: configuration)
9
9
  end
10
10
 
11
11
  def name_id
@@ -91,8 +91,8 @@ module Saml
91
91
  end
92
92
 
93
93
  def verify(algorithm, signature, data)
94
- signing_certificates.find do |cert|
95
- cert.public_key.verify(algorithm, signature, data)
94
+ signing_certificates.find do |certificate|
95
+ certificate.public_key.verify(algorithm, signature, data)
96
96
  end
97
97
  end
98
98
 
@@ -2,42 +2,16 @@ module Saml
2
2
  module Kit
3
3
  class Response < Document
4
4
  include Respondable
5
+ extend Forwardable
6
+
7
+ def_delegators :assertion, :name_id, :[], :attributes, :started_at, :expired_at, :audiences
5
8
 
6
9
  validate :must_be_active_session
7
10
  validate :must_match_issuer
8
11
 
9
- def initialize(xml, request_id: nil)
12
+ def initialize(xml, request_id: nil, configuration: Saml::Kit.configuration)
10
13
  @request_id = request_id
11
- super(xml, name: "Response")
12
- end
13
-
14
- def name_id
15
- assertion.fetch('Subject', {}).fetch('NameID', nil)
16
- end
17
-
18
- def [](key)
19
- attributes[key]
20
- end
21
-
22
- def attributes
23
- @attributes ||=
24
- begin
25
- attrs = assertion.fetch('AttributeStatement', {}).fetch('Attribute', [])
26
- items = if attrs.is_a? Hash
27
- [[attrs["Name"], attrs["AttributeValue"]]]
28
- else
29
- attrs.map { |item| [item['Name'], item['AttributeValue']] }
30
- end
31
- Hash[items].with_indifferent_access
32
- end
33
- end
34
-
35
- def started_at
36
- parse_date(assertion.fetch('Conditions', {}).fetch('NotBefore', nil))
37
- end
38
-
39
- def expired_at
40
- parse_date(assertion.fetch('Conditions', {}).fetch('NotOnOrAfter', nil))
14
+ super(xml, name: "Response", configuration: configuration)
41
15
  end
42
16
 
43
17
  def expired?
@@ -48,29 +22,16 @@ module Saml
48
22
  Time.current > started_at && !expired?
49
23
  end
50
24
 
51
- def encrypted?
52
- to_h[name]['EncryptedAssertion'].present?
53
- end
54
-
55
25
  def assertion
56
- @assertion =
57
- begin
58
- if encrypted?
59
- decrypted = XmlDecryption.new.decrypt(to_h.fetch(name, {}).fetch('EncryptedAssertion', {}))
60
- Saml::Kit.logger.debug(decrypted)
61
- Hash.from_xml(decrypted)['Assertion']
62
- else
63
- to_h.fetch(name, {}).fetch('Assertion', {})
64
- end
65
- end
26
+ @assertion = Saml::Kit::Assertion.new(to_h, configuration: @configuration)
66
27
  end
67
28
 
68
29
  def signed?
69
- super || assertion.fetch('Signature', nil).present?
30
+ super || assertion.signed?
70
31
  end
71
32
 
72
33
  def certificate
73
- super || assertion.fetch('Signature', {}).fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
34
+ super || assertion.certificate
74
35
  end
75
36
 
76
37
  private
@@ -85,25 +46,11 @@ module Saml
85
46
  return unless expected_type?
86
47
  return unless success?
87
48
 
88
- unless audiences.include?(Saml::Kit.configuration.issuer)
49
+ unless audiences.include?(configuration.issuer)
89
50
  errors[:audience] << error_message(:must_match_issuer)
90
51
  end
91
52
  end
92
53
 
93
- def audiences
94
- Array(assertion['Conditions']['AudienceRestriction']['Audience'])
95
- rescue => error
96
- Saml::Kit.logger.error(error)
97
- []
98
- end
99
-
100
- def parse_date(value)
101
- DateTime.parse(value)
102
- rescue => error
103
- Saml::Kit.logger.error(error)
104
- Time.at(0).to_datetime
105
- end
106
-
107
54
  Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::Response::Builder', 'Saml::Kit::Builders::Response')
108
55
  end
109
56
  end
@@ -17,15 +17,6 @@ module Saml
17
17
  certificate.public_key = public_key
18
18
  certificate.serial = 0x0
19
19
  certificate.version = 2
20
- factory = OpenSSL::X509::ExtensionFactory.new
21
- factory.subject_certificate = factory.issuer_certificate = certificate
22
- certificate.extensions = [
23
- factory.create_extension("basicConstraints","CA:TRUE", true),
24
- factory.create_extension("subjectKeyIdentifier", "hash"),
25
- ]
26
- certificate.add_extension(
27
- factory.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
28
- )
29
20
  certificate.sign(rsa_key, OpenSSL::Digest::SHA256.new)
30
21
  [
31
22
  certificate.to_pem,
@@ -1,36 +1,23 @@
1
1
  module Saml
2
2
  module Kit
3
3
  class Signature
4
- attr_reader :sign, :xml
5
- attr_reader :configuration
4
+ attr_reader :signatures
5
+ attr_reader :xml
6
6
 
7
- def initialize(xml, configuration:, sign: true)
8
- @configuration = configuration
9
- @sign = sign
7
+ def initialize(xml, signatures)
8
+ @signatures = signatures
10
9
  @xml = xml
11
10
  end
12
11
 
13
12
  def template(reference_id)
14
- return unless sign
15
- signature = signatures.build(reference_id)
16
- Template.new(signature).to_xml(xml: xml)
13
+ Template.new(signatures.build(reference_id)).to_xml(xml: xml)
17
14
  end
18
15
 
19
- def finalize
16
+ def self.sign(xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
17
+ signatures = Saml::Kit::Signatures.new(configuration: configuration)
18
+ yield xml, new(xml, signatures)
20
19
  signatures.complete(xml.target!)
21
20
  end
22
-
23
- def self.sign(sign: true, xml: ::Builder::XmlMarkup.new, configuration: Saml::Kit.configuration)
24
- signature = new(xml, sign: sign, configuration: configuration)
25
- yield xml, signature
26
- signature.finalize
27
- end
28
-
29
- private
30
-
31
- def signatures
32
- @signatures ||= Saml::Kit::Signatures.new(configuration: configuration, sign: sign)
33
- end
34
21
  end
35
22
  end
36
23
  end
@@ -1,28 +1,22 @@
1
1
  module Saml
2
2
  module Kit
3
3
  class Signatures
4
- attr_reader :sign, :configuration
4
+ attr_reader :configuration
5
5
 
6
- def initialize(configuration:, sign: true)
6
+ def initialize(configuration:)
7
7
  @configuration = configuration
8
- @sign = sign
9
8
  end
10
9
 
11
10
  def build(reference_id)
12
- Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration, sign: sign)
11
+ return nil unless configuration.sign?
12
+ Saml::Kit::Builders::XmlSignature.new(reference_id, configuration: configuration)
13
13
  end
14
14
 
15
15
  def complete(raw_xml)
16
- return raw_xml unless sign
17
-
16
+ return raw_xml unless configuration.sign?
17
+ private_key = configuration.private_keys(use: :signing).sample
18
18
  Xmldsig::SignedDocument.new(raw_xml).sign(private_key)
19
19
  end
20
-
21
- private
22
-
23
- def private_key
24
- configuration.signing_private_key
25
- end
26
20
  end
27
21
  end
28
22
  end
@@ -1,17 +1,23 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Templatable
4
+ attr_accessor :sign
5
+
4
6
  def to_xml(xml: ::Builder::XmlMarkup.new)
5
7
  signatures.complete(render(self, xml: xml))
6
8
  end
7
9
 
8
10
  def signature_for(reference_id:, xml:)
9
- return unless sign
11
+ return unless sign?
10
12
  render(signatures.build(reference_id), xml: xml)
11
13
  end
12
14
 
15
+ def sign?
16
+ sign.nil? ? configuration.sign? : sign && configuration.sign?
17
+ end
18
+
13
19
  def signatures
14
- @signatures ||= Saml::Kit::Signatures.new(configuration: configuration, sign: sign)
20
+ @signatures ||= Saml::Kit::Signatures.new(configuration: configuration)
15
21
  end
16
22
 
17
23
  def encryption_for(xml:)
@@ -18,7 +18,8 @@ module Saml
18
18
  end
19
19
 
20
20
  def template_path
21
- File.join(File.expand_path(File.dirname(__FILE__)), "builders/templates/#{template_name}")
21
+ root_path = File.expand_path(File.dirname(__FILE__))
22
+ File.join(root_path, "builders/templates/", template_name)
22
23
  end
23
24
 
24
25
  def template
@@ -30,7 +30,7 @@ module Saml
30
30
  end
31
31
 
32
32
  def provider
33
- Saml::Kit.registry.metadata_for(issuer)
33
+ configuration.registry.metadata_for(issuer)
34
34
  end
35
35
 
36
36
  def signature_verified!
@@ -1,5 +1,5 @@
1
1
  module Saml
2
2
  module Kit
3
- VERSION = "0.2.3"
3
+ VERSION = "0.2.4"
4
4
  end
5
5
  end
data/lib/saml/kit/xml.rb CHANGED
@@ -7,6 +7,7 @@ module Saml
7
7
  "ds": Namespaces::XMLDSIG,
8
8
  "md": Namespaces::METADATA,
9
9
  "saml": Namespaces::ASSERTION,
10
+ "pro": Namespaces::PROTOCOL,
10
11
  }.freeze
11
12
 
12
13
  attr_reader :raw_xml, :document
@@ -3,8 +3,8 @@ module Saml
3
3
  class XmlDecryption
4
4
  attr_reader :private_key
5
5
 
6
- def initialize(private_key = Saml::Kit.configuration.encryption_private_key)
7
- @private_key = private_key
6
+ def initialize(configuration: Saml::Kit.configuration)
7
+ @private_key = configuration.private_keys(use: :encryption).sample
8
8
  end
9
9
 
10
10
  def decrypt(data)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saml-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - mo khan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-12 00:00:00.000000000 Z
11
+ date: 2017-12-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -188,6 +188,7 @@ files:
188
188
  - exe/saml-kit-decode-http-post
189
189
  - exe/saml-kit-decode-http-redirect
190
190
  - lib/saml/kit.rb
191
+ - lib/saml/kit/assertion.rb
191
192
  - lib/saml/kit/authentication_request.rb
192
193
  - lib/saml/kit/bindings.rb
193
194
  - lib/saml/kit/bindings/binding.rb
@@ -209,6 +210,7 @@ files:
209
210
  - lib/saml/kit/builders/templates/identity_provider_metadata.builder
210
211
  - lib/saml/kit/builders/templates/logout_request.builder
211
212
  - lib/saml/kit/builders/templates/logout_response.builder
213
+ - lib/saml/kit/builders/templates/nil_class.builder
212
214
  - lib/saml/kit/builders/templates/response.builder
213
215
  - lib/saml/kit/builders/templates/service_provider_metadata.builder
214
216
  - lib/saml/kit/builders/templates/xml_encryption.builder