saml-kit 0.2.14 → 0.2.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8c247888ce2d9b7ca65605a7169e275f60094185
4
- data.tar.gz: 666425c5224ece36334d9fee1c0b8c249fabc7e1
3
+ metadata.gz: 868c7d300e79b706187c739acfc803c0a7d3535c
4
+ data.tar.gz: 5515dc46cc26271c4420b25ef56d908abf77ca2d
5
5
  SHA512:
6
- metadata.gz: 6dffef3e1532ceef9178bace64ffae38caf0a4580e0926ea5a1524d15242efc29148b47164aadaabcc14808ef2e62c7ae618c4dfd42954346a556081750161d6
7
- data.tar.gz: db069d1133acf615608f846d86d46d9ba38cddc4670ea5c45899266a25a465247e0a809f6308c98f011896de3a8f1753485ac83a801f88359ef6c7ab492201d5
6
+ metadata.gz: b63bbbdfa381acbddac50a1a3cc26a170b0e3215ba060b66a64c900b1731cb6ae7b421fb520f39cb6896883e3b31aa3d7aa718e4ce9815c5a4a1ea0c7d345467
7
+ data.tar.gz: b684aee57843123632b34b2c8194a7fa6c76f439587954a8b51c96bc23f6ea841ec78e27c548110ae4e0e7c65781055215de592c25894b38f1b4b164ea8ea764
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'saml/kit'
3
3
 
4
- puts "Enter Password:"
5
- password = STDIN.read.strip
6
- certificate, private_key = Saml::Kit::SelfSignedCertificate.new(password).create
4
+ puts "Enter Passphrase:"
5
+ passphrase = STDIN.read.strip
6
+ certificate, private_key = Saml::Kit::SelfSignedCertificate.new(passphrase).create
7
7
 
8
8
  puts "** BEGIN File Format **"
9
9
  print certificate
@@ -18,5 +18,5 @@ puts private_key.inspect
18
18
  puts "***********************"
19
19
 
20
20
  puts
21
- puts "Private Key Password:"
22
- puts password.inspect
21
+ puts "Private Key Passphrase:"
22
+ puts passphrase.inspect
@@ -18,18 +18,10 @@ module Saml
18
18
  @version = "2.0"
19
19
  @status_code = Namespaces::SUCCESS
20
20
  @issuer = configuration.issuer
21
- @embed_signature = want_assertions_signed
22
21
  @encrypt = encryption_certificate.present?
23
22
  @configuration = configuration
24
23
  end
25
24
 
26
- def want_assertions_signed
27
- request.provider.want_assertions_signed
28
- rescue => error
29
- Saml::Kit.logger.error(error)
30
- nil
31
- end
32
-
33
25
  def build
34
26
  Saml::Kit::Response.new(to_xml, request_id: request.id, configuration: configuration)
35
27
  end
@@ -3,33 +3,51 @@ module Saml
3
3
  class Certificate
4
4
  BEGIN_CERT=/-----BEGIN CERTIFICATE-----/
5
5
  END_CERT=/-----END CERTIFICATE-----/
6
- attr_reader :value, :use
6
+ # The use can be `:signing` or `:encryption`
7
+ attr_reader :use
7
8
 
8
9
  def initialize(value, use:)
9
10
  @value = value
10
11
  @use = use.downcase.to_sym
11
12
  end
12
13
 
14
+ # @return [Saml::Kit::Fingerprint] the certificate fingerprint.
13
15
  def fingerprint
14
16
  Fingerprint.new(value)
15
17
  end
16
18
 
19
+ # Returns true if this certificate is for the specified use.
20
+ #
21
+ # @param use [Symbol] `:signing` or `:encryption`.
22
+ # @return [Boolean] true or false.
17
23
  def for?(use)
18
24
  self.use == use.to_sym
19
25
  end
20
26
 
27
+ # Returns true if this certificate is used for encryption.
28
+ #
29
+ # return [Boolean] true or false.
21
30
  def encryption?
22
31
  for?(:encryption)
23
32
  end
24
33
 
34
+ # Returns true if this certificate is used for signing.
35
+ #
36
+ # return [Boolean] true or false.
25
37
  def signing?
26
38
  for?(:signing)
27
39
  end
28
40
 
41
+ # Returns the x509 form.
42
+ #
43
+ # return [OpenSSL::X509::Certificate] the OpenSSL equivalent.
29
44
  def x509
30
45
  self.class.to_x509(value)
31
46
  end
32
47
 
48
+ # Returns the public key.
49
+ #
50
+ # @return [OpenSSL::PKey::RSA] the RSA public key.
33
51
  def public_key
34
52
  x509.public_key
35
53
  end
@@ -68,6 +86,10 @@ module Saml
68
86
  Saml::Kit.logger.warn(error)
69
87
  OpenSSL::X509::Certificate.new(value)
70
88
  end
89
+
90
+ private
91
+
92
+ attr_reader :value
71
93
  end
72
94
  end
73
95
  end
@@ -11,20 +11,20 @@ module Saml
11
11
  # config.logger = Rails.logger
12
12
  # end
13
13
  #
14
- # To specify global configuration it is best to do this in an initialize
14
+ # To specify global configuration it is best to do this in an initializer
15
15
  # that runs at the start of the program.
16
16
  #
17
17
  # Saml::Kit.configure do |configuration|
18
18
  # configuration.issuer = "https://www.example.com/saml/metadata"
19
19
  # configuration.generate_key_pair_for(use: :signing)
20
- # configuration.add_key_pair(ENV["X509_CERTIFICATE"], ENV["PRIVATE_KEY"], password: ENV['PRIVATE_KEY_PASSWORD'], use: :encryption)
20
+ # configuration.add_key_pair(ENV["X509_CERTIFICATE"], ENV["PRIVATE_KEY"], passphrase: ENV['PRIVATE_KEY_PASSPHRASE'], use: :encryption)
21
21
  # end
22
22
  class Configuration
23
23
  # The issuer or entity_id to use.
24
24
  attr_accessor :issuer
25
- # The signature method to use when generating signatures (See {SAML::Kit::Builders::XmlSignature::SIGNATURE_METHODS})
25
+ # The signature method to use when generating signatures (See {Saml::Kit::Builders::XmlSignature::SIGNATURE_METHODS})
26
26
  attr_accessor :signature_method
27
- # The digest method to use when generating signatures (See {SAML::Kit::Builders::XmlSignature::DIGEST_METHODS})
27
+ # The digest method to use when generating signatures (See {Saml::Kit::Builders::XmlSignature::DIGEST_METHODS})
28
28
  attr_accessor :digest_method
29
29
  # The metadata registry to use for searching for metadata associated with an issuer.
30
30
  attr_accessor :registry
@@ -47,19 +47,19 @@ module Saml
47
47
  #
48
48
  # @param certificate [String] the x509 certificate with public key.
49
49
  # @param private_key [String] the plain text private key.
50
- # @param password [String] the password to decrypt the private key.
50
+ # @param passphrase [String] the password to decrypt the private key.
51
51
  # @param use [Symbol] the type of key pair, `:signing` or `:encryption`
52
- def add_key_pair(certificate, private_key, password: '', use: :signing)
53
- @key_pairs.push(KeyPair.new(certificate, private_key, password, use.to_sym))
52
+ def add_key_pair(certificate, private_key, passphrase: '', use: :signing)
53
+ @key_pairs.push(KeyPair.new(certificate, private_key, passphrase, use.to_sym))
54
54
  end
55
55
 
56
56
  # Generates a unique key pair that can be used for signing or encryption.
57
57
  #
58
58
  # @param use [Symbol] the type of key pair, `:signing` or `:encryption`
59
- # @param password [String] the private key password to use.
60
- def generate_key_pair_for(use:, password: SecureRandom.uuid)
61
- certificate, private_key = SelfSignedCertificate.new(password).create
62
- add_key_pair(certificate, private_key, password: password, use: use)
59
+ # @param passphrase [String] the private key passphrase to use.
60
+ def generate_key_pair_for(use:, passphrase: SecureRandom.uuid)
61
+ certificate, private_key = SelfSignedCertificate.new(passphrase).create
62
+ add_key_pair(certificate, private_key, passphrase: passphrase, use: use)
63
63
  end
64
64
 
65
65
  # Return each key pair for a specific use.
@@ -8,6 +8,7 @@ module Saml
8
8
  module Crypto
9
9
  DECRYPTORS = [ SimpleCipher, RsaCipher, OaepCipher, UnknownCipher ]
10
10
 
11
+ # @!visibility private
11
12
  def self.decryptor_for(algorithm, key)
12
13
  DECRYPTORS.find { |x| x.matches?(algorithm) }.new(algorithm, key)
13
14
  end
@@ -2,6 +2,31 @@ module Saml
2
2
  module Kit
3
3
  # The default metadata registry is used to fetch the metadata associated with an issuer or entity id.
4
4
  # The metadata associated with an issuer is used to verify trust for any SAML documents that are received.
5
+ #
6
+ # You can replace the default registry with your own at startup.
7
+ #
8
+ # Example:
9
+ #
10
+ # class OnDemandRegistry
11
+ # def initialize(original)
12
+ # @original = original
13
+ # end
14
+ #
15
+ # def metadata_for(entity_id)
16
+ # found = @original.metadata_for(entity_id)
17
+ # return found if found
18
+ #
19
+ # @original.register_url(entity_id, verify_ssl: Rails.env.production?)
20
+ # @original.metadata_for(entity_id)
21
+ # end
22
+ # end
23
+ #
24
+ # Saml::Kit.configure do |configuration|
25
+ # configuration.issuer = ENV['ISSUER']
26
+ # configuration.registry = OnDemandRegistry.new(configuration.registry)
27
+ # configuration.logger = Rails.logger
28
+ # end
29
+
5
30
  class DefaultRegistry
6
31
  def initialize(items = {})
7
32
  @items = items
@@ -1,6 +1,11 @@
1
1
  module Saml
2
2
  module Kit
3
3
  # This generates a fingerprint for an X509 Certificate.
4
+ #
5
+ # certificate, _ = Saml::Kit::SelfSignedCertificate.new("password").create
6
+ #
7
+ # puts Saml::Kit::Fingerprint.new(certificate).to_s
8
+ # # B7:AB:DC:BD:4D:23:58:65:FD:1A:99:0C:5F:89:EA:87:AD:F1:D7:83:34:7A:E9:E4:88:12:DD:46:1F:38:05:93
4
9
  class Fingerprint
5
10
  # The OpenSSL::X509::Certificate
6
11
  attr_reader :x509
data/lib/saml/kit/id.rb CHANGED
@@ -1,6 +1,11 @@
1
1
  module Saml
2
2
  module Kit
3
+ # This class is used primary for generating ID.
4
+ #https://www.w3.org/2001/XMLSchema.xsd
3
5
  class Id
6
+
7
+ # Generate an ID that conforms to the XML Schema.
8
+ # https://www.w3.org/2001/XMLSchema.xsd
4
9
  def self.generate
5
10
  "_#{SecureRandom.uuid}"
6
11
  end
@@ -2,6 +2,7 @@ module Saml
2
2
  module Kit
3
3
  # This class is used to parse the IDPSSODescriptor from a SAML metadata document.
4
4
  #
5
+ # raw_xml = <<-XML
5
6
  # <?xml version="1.0" encoding="UTF-8"?>
6
7
  # <EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="_cfa24e2f-0ec0-4ee3-abb8-b2fcfe394c1c" entityID="">
7
8
  # <IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
@@ -12,6 +13,19 @@ module Saml
12
13
  # <saml:Attribute Name="id"/>
13
14
  # </IDPSSODescriptor>
14
15
  # </EntityDescriptor>
16
+ # XML
17
+ #
18
+ # metadata = Saml::Kit::IdentityProviderMetadata.new(raw_xml)
19
+ # puts metadata.entity_id
20
+ #
21
+ # It can also be used to generate IDP metadata.
22
+ #
23
+ # metadata = Saml::Kit::IdentityProviderMetadata.build do |builder|
24
+ # builder.entity_id = "my-entity-id"
25
+ # end
26
+ # puts metadata.to_xml
27
+ #
28
+ # For more details on generating metadata see {Saml::Kit::Metadata}.
15
29
  class IdentityProviderMetadata < Metadata
16
30
  def initialize(xml)
17
31
  super("IDPSSODescriptor", xml)
@@ -9,10 +9,17 @@ module Saml
9
9
  @private_key = OpenSSL::PKey::RSA.new(private_key, passphrase)
10
10
  end
11
11
 
12
+ # Returns true if the key pair is the designated use.
13
+ #
14
+ # @param use [Symbol] Can be either `:signing` or `:encryption`.
12
15
  def for?(use)
13
16
  @use == use
14
17
  end
15
18
 
19
+ # Returns a generated self signed certificate with private key.
20
+ #
21
+ # @param use [Symbol] Can be either `:signing` or `:encryption`.
22
+ # @param passphrase [String] the passphrase to use to encrypt the private key.
16
23
  def self.generate(use:, passphrase: SecureRandom.uuid)
17
24
  certificate, private_key = SelfSignedCertificate.new(passphrase).create
18
25
  new(certificate, private_key, passphrase, use)
@@ -1,10 +1,33 @@
1
1
  module Saml
2
2
  module Kit
3
- # This class parses a LogoutRequest SAML document.
3
+ # This class can be used to parse a LogoutRequest SAML document.
4
+ #
5
+ # document = Saml::Kit::LogoutRequest.new(raw_xml)
6
+ #
7
+ # It can also be used to generate a new LogoutRequest.
8
+ #
9
+ # document = Saml::Kit::LogoutRequest.build do |builder|
10
+ # builder.issuer = "issuer"
11
+ # end
12
+ #
13
+ # puts document.to_xml(pretty: true)
14
+ #
15
+ # See {Saml::Kit::Builders::LogoutRequest} for a list of available settings.
16
+ #
17
+ # This class can also be used to generate the correspondong LogoutResponse for a LogoutRequest.
18
+ #
19
+ # document = Saml::Kit::LogoutRequest.new(raw_xml)
20
+ # url, saml_params = document.response_for(binding: :http_post)
21
+ #
22
+ # See {#response_for} for more information.
4
23
  class LogoutRequest < Document
5
24
  include Requestable
6
25
  validates_presence_of :single_logout_service, if: :expected_type?
7
26
 
27
+ # A new instance of LogoutRequest
28
+ #
29
+ # @param xml [String] The raw xml string.
30
+ # @param configuration [Saml::Kit::Configuration] the configuration to use.
8
31
  def initialize(xml, configuration: Saml::Kit.configuration)
9
32
  super(xml, name: "LogoutRequest", configuration: configuration)
10
33
  end
@@ -1,6 +1,8 @@
1
1
  module Saml
2
2
  module Kit
3
3
  # This class is used to parse a LogoutResponse SAML document.
4
+ #
5
+ # document = Saml::Kit::LogoutResponse.new(raw_xml)
4
6
  class LogoutResponse < Document
5
7
  include Respondable
6
8
 
@@ -1,5 +1,26 @@
1
1
  module Saml
2
2
  module Kit
3
+ # The Metadata object can be used to parse an XML string of metadata.
4
+ #
5
+ # metadata = Saml::Kit::Metadata.from(raw_xml)
6
+ #
7
+ # It can also be used to generate a new metadata string.
8
+ #
9
+ # metadata = Saml::Kit::Metadata.build do |builder|
10
+ # builder.entity_id = "my-issuer"
11
+ # builder.build_service_provider do |x|
12
+ # x.add_assertion_consumer_service(assertions_url, binding: :http_post)
13
+ # x.add_single_logout_service(logout_url, binding: :http_post)
14
+ # end
15
+ # builder.build_identity_provider do |x|
16
+ # x.add_single_sign_on_service(login_url, binding: :http_redirect)
17
+ # x.add_single_logout_service(logout_url, binding: :http_post)
18
+ # end
19
+ # end
20
+ # puts metadata.to_xml(pretty: true)
21
+ #
22
+ # See {Saml::Kit::Builders::ServiceProviderMetadata} and {Saml::Kit::Builders::IdentityProviderMetadata}
23
+ # for a list of options that can be specified.
3
24
  class Metadata
4
25
  METADATA_XSD = File.expand_path("./xsd/saml-schema-metadata-2.0.xsd", File.dirname(__FILE__)).freeze
5
26
  include ActiveModel::Validations
@@ -12,35 +33,39 @@ module Saml
12
33
  validate :must_match_xsd
13
34
  validate :must_have_valid_signature
14
35
 
15
- attr_reader :xml, :name
16
- attr_accessor :hash_algorithm
36
+ attr_reader :name
17
37
 
18
38
  def initialize(name, xml)
19
39
  @name = name
20
40
  @xml = xml
21
- @hash_algorithm = OpenSSL::Digest::SHA256
22
41
  end
23
42
 
43
+ # Returns the /EntityDescriptor/@entityID
24
44
  def entity_id
25
45
  document.find_by("/md:EntityDescriptor/@entityID").value
26
46
  end
27
47
 
48
+ # Returns the supported NameIDFormats.
28
49
  def name_id_formats
29
50
  document.find_all("/md:EntityDescriptor/md:#{name}/md:NameIDFormat").map(&:text)
30
51
  end
31
52
 
53
+ # Returns the Organization Name
32
54
  def organization_name
33
55
  document.find_by("/md:EntityDescriptor/md:Organization/md:OrganizationName").try(:text)
34
56
  end
35
57
 
58
+ # Returns the Organization URL
36
59
  def organization_url
37
60
  document.find_by("/md:EntityDescriptor/md:Organization/md:OrganizationURL").try(:text)
38
61
  end
39
62
 
63
+ # Returns the Company
40
64
  def contact_person_company
41
65
  document.find_by("/md:EntityDescriptor/md:ContactPerson/md:Company").try(:text)
42
66
  end
43
67
 
68
+ # Returns each of the X509 certificates.
44
69
  def certificates
45
70
  @certificates ||= document.find_all("/md:EntityDescriptor/md:#{name}/md:KeyDescriptor").map do |item|
46
71
  cert = item.at_xpath("./ds:KeyInfo/ds:X509Data/ds:X509Certificate", Xml::NAMESPACES).text
@@ -48,14 +73,19 @@ module Saml
48
73
  end
49
74
  end
50
75
 
76
+ # Returns the encryption certificates
51
77
  def encryption_certificates
52
78
  certificates.find_all(&:encryption?)
53
79
  end
54
80
 
81
+ # Returns the signing certificates.
55
82
  def signing_certificates
56
83
  certificates.find_all(&:signing?)
57
84
  end
58
85
 
86
+ # Returns each of the service endpoints supported by this metadata.
87
+ #
88
+ # @param type [String] the type of service. .E.g. `AssertionConsumerServiceURL`
59
89
  def services(type)
60
90
  document.find_all("/md:EntityDescriptor/md:#{name}/md:#{type}").map do |item|
61
91
  binding = item.attribute("Binding").value
@@ -64,19 +94,33 @@ module Saml
64
94
  end
65
95
  end
66
96
 
97
+ # Returns a specifing service binding.
98
+ #
99
+ # @param binding [Symbol] can be `:http_post` or `:http_redirect`.
100
+ # @param type [Symbol] can be on the service element like `AssertionConsumerServiceURL`, `SingleSignOnService` or `SingleLogoutService`.
67
101
  def service_for(binding:, type:)
68
102
  binding = Saml::Kit::Bindings.binding_for(binding)
69
103
  services(type).find { |x| x.binding?(binding) }
70
104
  end
71
105
 
106
+ # Returns each of the SingleLogoutService bindings
72
107
  def single_logout_services
73
108
  services('SingleLogoutService')
74
109
  end
75
110
 
111
+ # Returns the SingleLogoutService that matches the specified binding.
112
+ #
113
+ # @param binding [Symbol] can be `:http_post` or `:http_redirect`.
76
114
  def single_logout_service_for(binding:)
77
115
  service_for(binding: binding, type: 'SingleLogoutService')
78
116
  end
79
117
 
118
+ # Creates a serialized LogoutRequest.
119
+ #
120
+ # @param user [Object] a user object that responds to `name_id_for` and `assertion_attributes_for`.
121
+ # @param binding [Symbol] can be `:http_post` or `:http_redirect`.
122
+ # @param relay_state [String] the relay state to have echo'd back.
123
+ # @return [Array] Returns an array with a url and Hash of parameters to send to the other party.
80
124
  def logout_request_for(user, binding: :http_post, relay_state: nil)
81
125
  builder = Saml::Kit::LogoutRequest.builder(user) do |x|
82
126
  yield x if block_given?
@@ -85,30 +129,50 @@ module Saml
85
129
  request_binding.serialize(builder, relay_state: relay_state)
86
130
  end
87
131
 
132
+ # Returns the certificate that matches the fingerprint
133
+ #
134
+ # @param fingerprint [Saml::Kit::Fingerprint] the fingerprint to search for.
135
+ # @param use [Symbol] the type of certificates to look at. Can be `:signing` or `:encryption`.
136
+ # @return [Saml::Kit::Certificate] returns the matching `{Saml::Kit::Certificate}`
88
137
  def matches?(fingerprint, use: :signing)
89
138
  certificates.find do |certificate|
90
139
  certificate.for?(use) && certificate.fingerprint == fingerprint
91
140
  end
92
141
  end
93
142
 
143
+ # Returns the XML document converted to a Hash.
94
144
  def to_h
95
145
  @xml_hash ||= Hash.from_xml(to_xml)
96
146
  end
97
147
 
148
+ # Returns the XML document as a String.
149
+ #
150
+ # @param pretty [Symbol] true to return a human friendly version of the XML.
98
151
  def to_xml(pretty: false)
99
152
  document.to_xml(pretty: pretty)
100
153
  end
101
154
 
155
+ # Returns the XML document as a [String].
102
156
  def to_s
103
157
  to_xml
104
158
  end
105
159
 
160
+ # Verifies the signature and data using the signing certificates.
161
+ #
162
+ # @param algorithm [OpenSSL::Digest] the digest algorithm to use. E.g. `OpenSSL::Digest::SHA256`
163
+ # @param signature [String] the signature to verify
164
+ # @param data [String] the data that is used to produce the signature.
165
+ # @return [Saml::Kit::Certificate] the certificate that was used to produce the signature.
106
166
  def verify(algorithm, signature, data)
107
167
  signing_certificates.find do |certificate|
108
168
  certificate.public_key.verify(algorithm, signature, data)
109
169
  end
110
170
  end
111
171
 
172
+ # Creates a `{Saml::Kit::Metadata}` object from a raw XML [String].
173
+ #
174
+ # @param content [String] the raw metadata XML.
175
+ # @return [Saml::Kit::Metadata] the metadata document or subclass.
112
176
  def self.from(content)
113
177
  hash = Hash.from_xml(content)
114
178
  entity_descriptor = hash["EntityDescriptor"]
@@ -121,12 +185,15 @@ module Saml
121
185
  end
122
186
  end
123
187
 
188
+ # @!visibility private
124
189
  def self.builder_class
125
190
  Saml::Kit::Builders::Metadata
126
191
  end
127
192
 
128
193
  private
129
194
 
195
+ attr_reader :xml
196
+
130
197
  def document
131
198
  @document ||= Xml.new(xml)
132
199
  end
@@ -1,28 +1,47 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Serializable
4
+ # Base 64 decodes the value.
5
+ #
6
+ # @param value [String] the string to base 64 decode.
4
7
  def decode(value)
5
8
  Base64.decode64(value)
6
9
  end
7
10
 
11
+ # Base 64 encodes the value.
12
+ #
13
+ # @param value [String] the string to base 64 encode.
8
14
  def encode(value)
9
15
  Base64.strict_encode64(value)
10
16
  end
11
17
 
18
+ # Inflates the value using zlib decompression.
19
+ #
20
+ # @param value [String] the value to inflate.
12
21
  def inflate(value)
13
22
  inflater = Zlib::Inflate.new(-Zlib::MAX_WBITS)
14
23
  inflater.inflate(value)
15
24
  end
16
25
 
17
- # drop header and checksum as per spec.
26
+ # Deflate the value and drop the header and checksum as per the SAML spec.
27
+ # https://en.wikipedia.org/wiki/SAML_2.0#HTTP_Redirect_Binding
28
+ #
29
+ # @param value [String] the value to deflate.
30
+ # @param level [Integer] the level of compression.
18
31
  def deflate(value, level: Zlib::BEST_COMPRESSION)
19
32
  Zlib::Deflate.deflate(value, level)[2..-5]
20
33
  end
21
34
 
35
+ # URL unescape a value
36
+ #
37
+ # @param value [String] the value to unescape.
22
38
  def unescape(value)
23
39
  CGI.unescape(value)
24
40
  end
25
41
 
42
+ # URL escape a value
43
+ #
44
+ # @param value [String] the value to escape.
26
45
  def escape(value)
27
46
  CGI.escape(value)
28
47
  end
@@ -5,24 +5,31 @@ module Saml
5
5
  super("SPSSODescriptor", xml)
6
6
  end
7
7
 
8
+ # Returns each of the AssertionConsumerService bindings.
8
9
  def assertion_consumer_services
9
10
  services('AssertionConsumerService')
10
11
  end
11
12
 
13
+ # Returns the AssertionConsumerService for the specified binding.
14
+ #
15
+ # @param binding [Symbol] can be either `:http_post` or `:http_redirect`
12
16
  def assertion_consumer_service_for(binding:)
13
17
  service_for(binding: binding, type: 'AssertionConsumerService')
14
18
  end
15
19
 
20
+ # Returns true when the metadata demands that Assertions must be signed.
16
21
  def want_assertions_signed
17
22
  attribute = document.find_by("/md:EntityDescriptor/md:#{name}").attribute("WantAssertionsSigned")
18
23
  return true if attribute.nil?
19
24
  attribute.text.downcase == "true"
20
25
  end
21
26
 
27
+ # @!visibility private
22
28
  def self.builder_class
23
29
  Saml::Kit::Builders::ServiceProviderMetadata
24
30
  end
25
31
 
32
+ # @deprecated Use 'Saml::Kit::Builders::ServiceProviderMetadata'.
26
33
  Builder = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Saml::Kit::ServiceProviderMetadata::Builder', 'Saml::Kit::Builders::ServiceProviderMetadata')
27
34
  end
28
35
  end
@@ -5,17 +5,20 @@ module Saml
5
5
  @xml_hash = xml_hash
6
6
  end
7
7
 
8
+ # Returns the embedded X509 Certificate
8
9
  def certificate
9
10
  value = to_h.fetch('KeyInfo', {}).fetch('X509Data', {}).fetch('X509Certificate', nil)
10
11
  return if value.nil?
11
12
  Saml::Kit::Certificate.new(value, use: :signing)
12
13
  end
13
14
 
15
+ # Returns true when the fingerprint of the certificate matches one of the certificates registered in the metadata.
14
16
  def trusted?(metadata)
15
17
  return false if metadata.nil?
16
18
  metadata.matches?(certificate.fingerprint, use: :signing)
17
19
  end
18
20
 
21
+ # Returns the XML Hash.
19
22
  def to_h
20
23
  @xml_hash
21
24
  end
@@ -1,5 +1,6 @@
1
1
  module Saml
2
2
  module Kit
3
+ # @!visibility private
3
4
  class Signatures # :nodoc:
4
5
  # @!visibility private
5
6
  attr_reader :configuration
@@ -10,6 +11,7 @@ module Saml
10
11
  @key_pair = configuration.key_pairs(use: :signing).last
11
12
  end
12
13
 
14
+ # @!visibility private
13
15
  def sign_with(key_pair)
14
16
  @key_pair = key_pair
15
17
  end
@@ -1,34 +1,46 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Templatable
4
+ # Can be used to disable embeding a signature.
5
+ # By default a signature will be embedded if a signing
6
+ # certificate is available via the configuration.
4
7
  attr_accessor :embed_signature
5
8
 
9
+ # @deprecated Use {#embed_signature=} instead of this method.
6
10
  def sign=(value)
7
11
  Saml::Kit.deprecate("sign= is deprecated. Use embed_signature= instead")
8
12
  self.embed_signature = value
9
13
  end
10
14
 
15
+ # Returns the generated XML document with an XML Digital Signature and XML Encryption.
11
16
  def to_xml(xml: ::Builder::XmlMarkup.new)
12
17
  signatures.complete(render(self, xml: xml))
13
18
  end
14
19
 
20
+ # @!visibility private
15
21
  def signature_for(reference_id:, xml:)
16
22
  return unless sign?
17
23
  render(signatures.build(reference_id), xml: xml)
18
24
  end
19
25
 
26
+ # Allows you to specify which key pair to use for generating an XML digital signature.
27
+ #
28
+ # @param key_pair [Saml::Kit::KeyPair] the key pair to use for signing.
20
29
  def sign_with(key_pair)
21
30
  signatures.sign_with(key_pair)
22
31
  end
23
32
 
33
+ # Returns true if an embedded signature is requested and at least one signing certificate is available via the configuration.
24
34
  def sign?
25
35
  embed_signature.nil? ? configuration.sign? : embed_signature && configuration.sign?
26
36
  end
27
37
 
38
+ # @!visibility private
28
39
  def signatures
29
40
  @signatures ||= Saml::Kit::Signatures.new(configuration: configuration)
30
41
  end
31
42
 
43
+ # @!visibility private
32
44
  def encryption_for(xml:)
33
45
  if encrypt?
34
46
  temp = ::Builder::XmlMarkup.new
@@ -41,10 +53,12 @@ module Saml
41
53
  end
42
54
  end
43
55
 
56
+ # @!visibility private
44
57
  def encrypt?
45
58
  encrypt && encryption_certificate
46
59
  end
47
60
 
61
+ # @!visibility private
48
62
  def render(model, options)
49
63
  Saml::Kit::Template.new(model).to_xml(options)
50
64
  end
@@ -7,6 +7,9 @@ module Saml
7
7
  @target = target
8
8
  end
9
9
 
10
+ # Returns the compiled template as a [String].
11
+ #
12
+ # @param options [Hash] The options hash to pass to the template engine.
10
13
  def to_xml(options)
11
14
  template.render(target, options)
12
15
  end
@@ -9,6 +9,7 @@ module Saml
9
9
  validate :must_be_trusted
10
10
  end
11
11
 
12
+ # Returns true when the document has an embedded XML Signature or has been verified externally.
12
13
  def signed?
13
14
  signature_manually_verified || signature.present?
14
15
  end
@@ -19,6 +20,7 @@ module Saml
19
20
  xml_hash ? Signature.new(xml_hash) : nil
20
21
  end
21
22
 
23
+ # Returns true when documents is signed and the signing certificate belongs to a known service entity.
22
24
  def trusted?
23
25
  return true if signature_manually_verified
24
26
  return false unless signed?
@@ -1,5 +1,5 @@
1
1
  module Saml
2
2
  module Kit
3
- VERSION = "0.2.14"
3
+ VERSION = "0.2.15"
4
4
  end
5
5
  end
data/lib/saml/kit/xml.rb CHANGED
@@ -10,8 +10,6 @@ module Saml
10
10
  "samlp": Namespaces::PROTOCOL,
11
11
  }.freeze
12
12
 
13
- attr_reader :raw_xml, :document
14
-
15
13
  validate :validate_signatures
16
14
  validate :validate_certificates
17
15
 
@@ -20,27 +18,31 @@ module Saml
20
18
  @document = Nokogiri::XML(raw_xml)
21
19
  end
22
20
 
23
- def x509_certificates
24
- xpath = "//ds:KeyInfo/ds:X509Data/ds:X509Certificate"
25
- document.search(xpath, Xmldsig::NAMESPACES).map do |item|
26
- Certificate.to_x509(item.text)
27
- end
28
- end
29
-
21
+ # Returns the first XML node found by searching the document with the provided XPath.
22
+ #
23
+ # @param xpath [String] the XPath to use to search the document
30
24
  def find_by(xpath)
31
25
  document.at_xpath(xpath, NAMESPACES)
32
26
  end
33
27
 
28
+ # Returns all XML nodes found by searching the document with the provided XPath.
29
+ #
30
+ # @param xpath [String] the XPath to use to search the document
34
31
  def find_all(xpath)
35
32
  document.search(xpath, NAMESPACES)
36
33
  end
37
34
 
35
+ # Return the XML document as a [String].
36
+ #
37
+ # @param pretty [Boolean] return the XML string in a human readable format if true.
38
38
  def to_xml(pretty: true)
39
39
  pretty ? document.to_xml(indent: 2) : raw_xml
40
40
  end
41
41
 
42
42
  private
43
43
 
44
+ attr_reader :raw_xml, :document
45
+
44
46
  def validate_signatures
45
47
  invalid_signatures.flat_map(&:errors).uniq.each do |error|
46
48
  errors.add(error, "is invalid")
@@ -69,6 +71,13 @@ module Saml
69
71
  end
70
72
  end
71
73
  end
74
+
75
+ def x509_certificates
76
+ xpath = "//ds:KeyInfo/ds:X509Data/ds:X509Certificate"
77
+ document.search(xpath, Xmldsig::NAMESPACES).map do |item|
78
+ Certificate.to_x509(item.text)
79
+ end
80
+ end
72
81
  end
73
82
  end
74
83
  end
@@ -1,12 +1,16 @@
1
1
  module Saml
2
2
  module Kit
3
3
  class XmlDecryption
4
+ # The list of private keys to use to attempt to decrypt the document.
4
5
  attr_reader :private_keys
5
6
 
6
7
  def initialize(configuration: Saml::Kit.configuration)
7
8
  @private_keys = configuration.private_keys(use: :encryption)
8
9
  end
9
10
 
11
+ # Decrypts an EncryptedData section of an XML document.
12
+ #
13
+ # @param data [Hash] the XML document converted to a [Hash] using Hash.from_xml.
10
14
  def decrypt(data)
11
15
  encrypted_data = data['EncryptedData']
12
16
  symmetric_key = symmetric_key_from(encrypted_data)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saml-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.14
4
+ version: 0.2.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - mo khan