saml-kit 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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