Skip to content

Commit a21f37b

Browse files
committed
Small clean-ups for v2.x release
1 parent 6f73a4f commit a21f37b

File tree

7 files changed

+69
-65
lines changed

7 files changed

+69
-65
lines changed

lib/ruby_saml/idp_metadata_parser.rb

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ module Vocabulary
3939
attr_reader :options
4040

4141
# fetch IdP descriptors from a metadata document
42-
def self.get_idps(metadata_document, only_entity_id=nil)
42+
def self.get_idps(metadata_document, only_entity_id = nil)
4343
path = "//md:EntityDescriptor#{"[@entityID=\"#{only_entity_id}\"]" if only_entity_id}/md:IDPSSODescriptor"
4444
REXML::XPath.match(
4545
metadata_document,
@@ -190,7 +190,7 @@ def parse_to_idp_metadata_array(idp_metadata, options = {})
190190
raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
191191
end
192192

193-
idpsso_descriptors.map {|id| IdpMetadata.new(id, id.parent.attributes["entityID"])}
193+
idpsso_descriptors.map { |id| IdpMetadata.new(id, id.parent.attributes["entityID"]) }
194194
end
195195

196196
# Retrieve the remote IdP metadata from the URL or a cached copy.
@@ -205,6 +205,7 @@ def parse_to_idp_metadata_array(idp_metadata, options = {})
205205
def get_idp_metadata(url, validate_cert, options = {})
206206
uri = URI.parse(url)
207207
raise ArgumentError.new("url must begin with http or https") unless /^https?/.match?(uri.scheme)
208+
208209
http = Net::HTTP.new(uri.host, uri.port)
209210

210211
if uri.scheme == "https"
@@ -218,13 +219,12 @@ def get_idp_metadata(url, validate_cert, options = {})
218219
http.max_retries = options[:max_retries] if options[:max_retries]
219220

220221
get = Net::HTTP::Get.new(uri.request_uri)
221-
get.basic_auth uri.user, uri.password if uri.user
222+
get.basic_auth(uri.user, uri.password) if uri.user
223+
222224
@response = http.request(get)
223-
return response.body if response.is_a? Net::HTTPSuccess
225+
return response.body if response.is_a?(Net::HTTPSuccess)
224226

225-
raise RubySaml::HttpError.new(
226-
"Failed to fetch idp metadata: #{response.code}: #{response.message}"
227-
)
227+
raise RubySaml::HttpError.new("Failed to fetch idp metadata: #{response.code}: #{response.message}")
228228
end
229229

230230
private
@@ -240,6 +240,7 @@ def initialize(idpsso_descriptor, entity_id)
240240
def to_hash(options = {})
241241
sso_binding = options[:sso_binding]
242242
slo_binding = options[:slo_binding]
243+
243244
{
244245
idp_entity_id: @entity_id,
245246
name_identifier_format: idp_name_id_format(options[:name_id_format]),
@@ -392,15 +393,13 @@ def certificates
392393

393394
# @return [String|nil] the fingerpint of the X509Certificate if it exists
394395
#
395-
def fingerprint(certificate, fingerprint_algorithm = RubySaml::XML::Document::SHA256)
396-
@fingerprint ||= begin
397-
return unless certificate
396+
def fingerprint(certificate, fingerprint_algorithm = RubySaml::XML::Crypto::SHA256)
397+
return unless certificate
398398

399-
cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate))
399+
cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate))
400400

401-
fingerprint_alg = RubySaml::XML::Crypto.hash_algorithm(fingerprint_algorithm).new
402-
fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
403-
end
401+
fingerprint_alg = RubySaml::XML::Crypto.hash_algorithm(fingerprint_algorithm).new
402+
fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
404403
end
405404

406405
# @return [Array] the names of all SAML attributes if any exist

lib/ruby_saml/logoutresponse.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,5 @@ def validate_signature
271271
end
272272
true
273273
end
274-
275274
end
276275
end

lib/ruby_saml/metadata.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def add_xml_declaration(meta_doc)
4040

4141
def add_root_element(meta_doc, settings, valid_until, cache_duration)
4242
namespaces = {
43-
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
43+
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
4444
}
4545

4646
if settings.attribute_consuming_service.configured?

lib/ruby_saml/response.rb

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
require "ruby_saml/xml"
44
require "ruby_saml/attributes"
5-
65
require "time"
76
require "nokogiri"
87

@@ -166,16 +165,16 @@ def attributes
166165
raise ValidationError.new("Found an Attribute element with duplicated Name")
167166
end
168167

169-
values = node.elements.collect do |e|
168+
values = node.elements.map do |e|
170169
if e.elements.nil? || e.elements.empty?
171170
# SAMLCore requires that nil AttributeValues MUST contain xsi:nil XML attribute set to "true" or "1"
172171
# otherwise the value is to be regarded as empty.
173172
%w[true 1].include?(e.attributes['xsi:nil']) ? nil : Utils.element_text(e)
174-
# explicitly support saml2:NameID with saml2:NameQualifier if supplied in attributes
175-
# this is useful for allowing eduPersonTargetedId to be passed as an opaque identifier to use to
176-
# identify the subject in an SP rather than email or other less opaque attributes
177-
# NameQualifier, if present is prefixed with a "/" to the value
178173
else
174+
# Explicitly support saml2:NameID with saml2:NameQualifier if supplied in attributes
175+
# this is useful for allowing eduPersonTargetedId to be passed as an opaque identifier to use to
176+
# identify the subject in an SP rather than email or other less opaque attributes
177+
# NameQualifier, if present is prefixed with a "/" to the value
179178
REXML::XPath.match(e,'a:NameID', { "a" => ASSERTION }).collect do |n|
180179
base_path = n.attributes['NameQualifier'] ? "#{n.attributes['NameQualifier']}/" : ''
181180
"#{base_path}#{Utils.element_text(n)}"
@@ -186,6 +185,7 @@ def attributes
186185
attributes.add(name, values.flatten)
187186
end
188187
end
188+
189189
attributes
190190
end
191191
end
@@ -594,7 +594,7 @@ def validate_signed_elements
594594
signed_elements << signed_element
595595
end
596596

597-
unless signature_nodes.length < 3 && !signed_elements.empty?
597+
unless signature_nodes.size < 3 && !signed_elements.empty?
598598
return append_error("Found an unexpected number of Signature Element. SAML Response rejected")
599599
end
600600

@@ -619,10 +619,10 @@ def validate_in_response_to
619619
append_error(error_msg)
620620
end
621621

622-
# Validates the Audience, (If the Audience match the Service Provider EntityID)
622+
# Validates the Audience, (If the Audience matches the Service Provider EntityID)
623623
# If the response was initialized with the :skip_audience option, this validation is skipped,
624624
# If fails, the error is added to the errors array
625-
# @return [Boolean] True if there is an Audience Element that match the Service Provider EntityID, otherwise False if soft=True
625+
# @return [Boolean] True if there is an Audience Element that matches the Service Provider EntityID, otherwise False if soft=True
626626
# @raise [ValidationError] if soft == false and validation fails
627627
#
628628
def validate_audience
@@ -726,7 +726,7 @@ def validate_conditions
726726

727727
# Validates the Issuer (Of the SAML Response and the SAML Assertion)
728728
# @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the response is invalid or not)
729-
# @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True
729+
# @return [Boolean] True if the Issuer matches the IdP entityId, otherwise False if soft=True
730730
# @raise [ValidationError] if soft == false and validation fails
731731
#
732732
def validate_issuer
@@ -863,9 +863,9 @@ def validate_signature
863863

864864
if sig_elements.size != 1
865865
if sig_elements.empty?
866-
append_error("Signed element id ##{doc.signed_element_id} is not found")
866+
append_error("Signed element id ##{doc.signed_element_id} is not found")
867867
else
868-
append_error("Signed element id ##{doc.signed_element_id} is found more than once")
868+
append_error("Signed element id ##{doc.signed_element_id} is found more than once")
869869
end
870870
return append_error(error_msg)
871871
end
@@ -874,16 +874,16 @@ def validate_signature
874874

875875
idp_certs = settings.get_idp_cert_multi
876876
if idp_certs.nil? || idp_certs[:signing].empty?
877-
opts = {}
878-
opts[:fingerprint_alg] = settings.idp_cert_fingerprint_algorithm
879877
idp_cert = settings.get_idp_cert
880878
fingerprint = settings.get_fingerprint
881-
opts[:cert] = idp_cert
879+
opts = {
880+
cert: idp_cert,
881+
fingerprint_alg: settings.idp_cert_fingerprint_algorithm
882+
}
882883

883884
if fingerprint && doc.validate_document(fingerprint, @soft, opts)
884885
if settings.security[:check_idp_cert_expiration] && RubySaml::Utils.is_cert_expired(idp_cert)
885-
error_msg = "IdP x509 certificate expired"
886-
return append_error(error_msg)
886+
return append_error("IdP x509 certificate expired")
887887
end
888888
else
889889
return append_error(error_msg)
@@ -894,18 +894,20 @@ def validate_signature
894894
idp_certs[:signing].each do |idp_cert|
895895
valid = doc.validate_document_with_cert(idp_cert, true)
896896
next unless valid
897+
897898
if settings.security[:check_idp_cert_expiration] && RubySaml::Utils.is_cert_expired(idp_cert)
898-
expired = true
899+
expired = true
899900
end
900901

901902
# At least one certificate is valid, restore the old accumulated errors
902903
@errors = old_errors
903904
break
904905
end
906+
905907
if expired
906-
error_msg = "IdP x509 certificate expired"
907-
return append_error(error_msg)
908+
return append_error("IdP x509 certificate expired")
908909
end
910+
909911
unless valid
910912
# Remove duplicated errors
911913
@errors = @errors.uniq
@@ -933,7 +935,7 @@ def name_id_node
933935
# @param subelt [String] The XPath pattern
934936
# @return [REXML::Element | nil] If any matches, return the Element
935937
#
936-
def xpath_first_from_signed_assertion(subelt=nil)
938+
def xpath_first_from_signed_assertion(subelt = nil)
937939
doc = decrypted_document.nil? ? document : decrypted_document
938940
node = REXML::XPath.first(
939941
doc,
@@ -1035,14 +1037,14 @@ def decrypt_attribute(encrypted_attribute_node)
10351037
#
10361038
def decrypt_element(encrypt_node, regexp)
10371039
if settings.nil? || settings.get_sp_decryption_keys.empty?
1038-
raise ValidationError.new('An ' + encrypt_node.name + ' found and no SP private key found on the settings to decrypt it')
1040+
raise ValidationError.new("An #{encrypt_node.name} found and no SP private key found on the settings to decrypt it.")
10391041
end
10401042

1041-
if encrypt_node.name == 'EncryptedAttribute'
1042-
node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
1043-
else
1044-
node_header = '<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">'
1045-
end
1043+
node_header = if encrypt_node.name == 'EncryptedAttribute'
1044+
'<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
1045+
else
1046+
'<node xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">'
1047+
end
10461048

10471049
elem_plaintext = RubySaml::Utils.decrypt_multi(encrypt_node, settings.get_sp_decryption_keys)
10481050

@@ -1063,8 +1065,9 @@ def decrypt_element(encrypt_node, regexp)
10631065
# @return [Time|nil] The parsed value
10641066
#
10651067
def parse_time(node, attribute)
1066-
return unless node && node.attributes[attribute]
1067-
Time.parse(node.attributes[attribute])
1068+
return unless (value = node&.attributes&.[](attribute))
1069+
1070+
Time.parse(value)
10681071
end
10691072
end
10701073
end

lib/ruby_saml/saml_message.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class SamlMessage
1919
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
2020
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
2121

22-
BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
22+
BASE64_FORMAT = %r{\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z}
2323
@@mutex = Mutex.new
2424

2525
# @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
@@ -74,10 +74,12 @@ def valid_saml?(document, soft = true)
7474
raise ValidationError.new("XML load failed: #{error.message}")
7575
end
7676

77-
SamlMessage.schema.validate(xml).map do |schema_error|
77+
SamlMessage.schema.validate(xml).each do |schema_error|
7878
return false if soft
7979
raise ValidationError.new("#{schema_error.message}\n\n#{xml}")
8080
end
81+
82+
true
8183
end
8284

8385
private
@@ -89,9 +91,9 @@ def valid_saml?(document, soft = true)
8991
def decode_raw_saml(saml, settings = nil)
9092
return saml unless base64_encoded?(saml)
9193

92-
settings = RubySaml::Settings.new if settings.nil?
94+
settings ||= RubySaml::Settings.new
9395
if saml.bytesize > settings.message_max_bytesize
94-
raise ValidationError.new("Encoded SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected")
96+
raise ValidationError.new("Encoded SAML Message exceeds #{settings.message_max_bytesize} bytes, so was rejected")
9597
end
9698

9799
decoded = decode(saml)
@@ -157,7 +159,7 @@ def inflate(deflated)
157159
# @return [String] The deflated string
158160
#
159161
def deflate(inflated)
160-
Zlib::Deflate.deflate(inflated, 9)[2..-5]
162+
Zlib::Deflate.deflate(inflated, Zlib::BEST_COMPRESSION)[2..-5]
161163
end
162164
end
163165
end

lib/ruby_saml/settings.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ def get_binding(value)
226226
DEFAULTS = {
227227
assertion_consumer_service_binding: Utils::BINDINGS[:post],
228228
single_logout_service_binding: Utils::BINDINGS[:redirect],
229-
idp_cert_fingerprint_algorithm: RubySaml::XML::Document::SHA256,
229+
idp_cert_fingerprint_algorithm: RubySaml::XML::Crypto::SHA256,
230230
message_max_bytesize: 250_000,
231231
soft: true,
232232
double_quote_xml_attribute_values: false,
@@ -238,8 +238,8 @@ def get_binding(value)
238238
want_assertions_encrypted: false,
239239
want_name_id: false,
240240
metadata_signed: false,
241-
digest_method: RubySaml::XML::Document::SHA256,
242-
signature_method: RubySaml::XML::Document::RSA_SHA256,
241+
digest_method: RubySaml::XML::Crypto::SHA256,
242+
signature_method: RubySaml::XML::Crypto::RSA_SHA256,
243243
check_idp_cert_expiration: false,
244244
check_sp_cert_expiration: false,
245245
strict_audience_validation: false,

lib/ruby_saml/slo_logoutrequest.rb

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ def name_id_node
9595
# @return [REXML::Document] The decrypted EncrypedtID element
9696
#
9797
def decrypt_nameid(encrypted_id_node)
98-
9998
if settings.nil? || settings.get_sp_decryption_keys.empty?
100-
raise ValidationError.new('An ' + encrypted_id_node.name + ' found and no SP private key found on the settings to decrypt it')
99+
raise ValidationError.new("An #{encrypted_id_node.name} found and no SP private key found on the settings to decrypt it")
101100
end
102101

103102
elem_plaintext = RubySaml::Utils.decrypt_multi(encrypted_id_node, settings.get_sp_decryption_keys)
103+
104104
# If we get some problematic noise in the plaintext after decrypting.
105105
# This quick regexp parse will grab only the Element and discard the noise.
106106
elem_plaintext = elem_plaintext.match(/(.*<\/(\w+:)?NameID>)/m)[0]
@@ -135,16 +135,17 @@ def issuer
135135
# @return [Time|nil] Gets the NotOnOrAfter Attribute value if exists.
136136
#
137137
def not_on_or_after
138-
@not_on_or_after ||= begin
139-
node = REXML::XPath.first(
140-
document,
141-
"/p:LogoutRequest",
142-
{ "p" => PROTOCOL }
143-
)
144-
if node && node.attributes["NotOnOrAfter"]
145-
Time.parse(node.attributes["NotOnOrAfter"])
146-
end
147-
end
138+
return @not_on_or_after if defined?(@not_on_or_after)
139+
140+
node = REXML::XPath.first(
141+
document,
142+
"/p:LogoutRequest",
143+
{ "p" => PROTOCOL }
144+
)
145+
146+
@not_on_or_after = if (value = node&.attributes&.[]("NotOnOrAfter"))
147+
Time.parse(value)
148+
end
148149
end
149150

150151
# @return [Array] Gets the SessionIndex if exists (Supported multiple values). Empty Array if none found

0 commit comments

Comments
 (0)