Skip to content

Commit 571e9ee

Browse files
committed
SAML-Toolkits#331. Allow multiple authn_context_decl_ref in settings
1 parent dab0227 commit 571e9ee

File tree

5 files changed

+78
-33
lines changed

5 files changed

+78
-33
lines changed

lib/onelogin/ruby-saml/authrequest.rb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,19 @@ def create_xml_document(settings)
136136
}
137137

138138
if settings.authn_context != nil
139-
authn_contexts = settings.authn_context.is_a?(Array) ? settings.authn_context : [settings.authn_context]
140-
authn_contexts.each do |authn_context|
139+
authn_contexts_class_ref = settings.authn_context.is_a?(Array) ? settings.authn_context : [settings.authn_context]
140+
authn_contexts_class_ref.each do |authn_context_class_ref|
141141
class_ref = requested_context.add_element "saml:AuthnContextClassRef"
142-
class_ref.text = authn_context
142+
class_ref.text = authn_context_class_ref
143143
end
144144
end
145-
# add saml:AuthnContextDeclRef element
145+
146146
if settings.authn_context_decl_ref != nil
147-
class_ref = requested_context.add_element "saml:AuthnContextDeclRef"
148-
class_ref.text = settings.authn_context_decl_ref
147+
authn_contexts_decl_refs = settings.authn_context_decl_ref.is_a?(Array) ? settings.authn_context_decl_ref : [settings.authn_context_decl_ref]
148+
authn_contexts_decl_refs.each do |authn_context_decl_ref|
149+
decl_ref = requested_context.add_element "saml:AuthnContextDeclRef"
150+
decl_ref.text = authn_context_decl_ref
151+
end
149152
end
150153
end
151154

lib/onelogin/ruby-saml/idp_metadata_parser.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class IdpMetadataParser
2727
# IdP values
2828
#
2929
# @param (see IdpMetadataParser#get_idp_metadata)
30-
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
30+
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides
3131
# @return (see IdpMetadataParser#get_idp_metadata)
3232
# @raise (see IdpMetadataParser#get_idp_metadata)
3333
def parse_remote(url, validate_cert = true, options = {})
@@ -37,12 +37,17 @@ def parse_remote(url, validate_cert = true, options = {})
3737

3838
# Parse the Identity Provider metadata and update the settings with the IdP values
3939
# @param idp_metadata [String]
40-
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
40+
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides
4141
#
4242
def parse(idp_metadata, options = {})
4343
@document = REXML::Document.new(idp_metadata)
4444

45-
(options[:settings] || OneLogin::RubySaml::Settings.new).tap do |settings|
45+
settings = options[:settings]
46+
if settings.nil? || settings.is_a?(Hash)
47+
settings = OneLogin::RubySaml::Settings.new(settings || {})
48+
end
49+
50+
settings.tap do |settings|
4651
settings.idp_entity_id = idp_entity_id
4752
settings.name_identifier_format = idp_name_id_format
4853
settings.idp_sso_target_url = single_signon_service_url(options)

lib/onelogin/ruby-saml/response.rb

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,39 @@ def not_on_or_after
248248
@not_on_or_after ||= parse_time(conditions, "NotOnOrAfter")
249249
end
250250

251+
# Gets the Issuers (from Response and Assertion).
252+
# (returns the first node that matches the supplied xpath from the Response and from the Assertion)
253+
# @return [Array] Array with the Issuers (REXML::Element)
254+
#
255+
def issuers
256+
@issuers ||= begin
257+
issuers = []
258+
issuer_response_nodes = REXML::XPath.match(
259+
document,
260+
"/p:Response/a:Issuer",
261+
{ "p" => PROTOCOL, "a" => ASSERTION }
262+
)
263+
264+
unless issuer_response_nodes.size == 1
265+
error_msg = "Issuer of the Response not found or multiple."
266+
ValidationError.new(error_msg)
267+
end
268+
269+
doc = decrypted_document.nil? ? document : decrypted_document
270+
issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
271+
unless issuer_assertion_nodes.size == 1
272+
error_msg = "Issuer of the Assertion not found or multiple."
273+
ValidationError.new(error_msg)
274+
end
275+
276+
nodes = issuer_response_nodes + issuer_assertion_nodes
277+
nodes.each do |node|
278+
issuers << node.text if node.text
279+
end
280+
issuers.uniq
281+
end
282+
end
283+
251284
# @return [String|nil] The InResponseTo attribute from the SAML Response.
252285
#
253286
def in_response_to
@@ -635,32 +668,13 @@ def validate_conditions
635668
def validate_issuer
636669
return true if settings.idp_entity_id.nil?
637670

638-
issuers = []
639-
issuer_response_nodes = REXML::XPath.match(
640-
document,
641-
"/p:Response/a:Issuer",
642-
{ "p" => PROTOCOL, "a" => ASSERTION }
643-
)
644-
645-
unless issuer_response_nodes.size == 1
646-
error_msg = "Issuer of the Response not found or multiple."
647-
return append_error(error_msg)
648-
end
649-
650-
doc = decrypted_document.nil? ? document : decrypted_document
651-
issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
652-
unless issuer_assertion_nodes.size == 1
653-
error_msg = "Issuer of the Assertion not found or multiple."
654-
return append_error(error_msg)
655-
end
656-
657-
nodes = issuer_response_nodes + issuer_assertion_nodes
658-
nodes.each do |node|
659-
issuers << node.text if node.text
671+
begin
672+
obtained_issuers = issuers
673+
rescue ValidationError => e
674+
return append_error(e.message)
660675
end
661-
issuers.uniq
662676

663-
issuers.each do |issuer|
677+
obtained_issuers.each do |issuer|
664678
unless URI.parse(issuer) == URI.parse(settings.idp_entity_id)
665679
error_msg = "Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>"
666680
return append_error(error_msg)

test/idp_metadata_parser_test.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,22 @@ def initialize; end
5656
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
5757
end
5858

59+
it "uses settings options as hash for overrides" do
60+
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
61+
idp_metadata = read_response("idp_descriptor.xml")
62+
settings = idp_metadata_parser.parse(idp_metadata, {
63+
:settings => {
64+
:security => {
65+
:digest_method => XMLSecurity::Document::SHA256,
66+
:signature_method => XMLSecurity::Document::RSA_SHA256
67+
}
68+
}
69+
})
70+
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint
71+
assert_equal XMLSecurity::Document::SHA256, settings.security[:digest_method]
72+
assert_equal XMLSecurity::Document::RSA_SHA256, settings.security[:signature_method]
73+
end
74+
5975
end
6076

6177
describe "download and parse IdP descriptor file" do

test/request_test.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,5 +285,12 @@ class RequestTest < Minitest::Test
285285
auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
286286
assert auth_doc.to_s =~ /<saml:AuthnContextDeclRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport<\/saml:AuthnContextDeclRef>/
287287
end
288+
289+
it "create multiple saml:AuthnContextDeclRef elements correctly " do
290+
settings.authn_context_decl_ref = ['name/password/uri', 'example/decl/ref']
291+
auth_doc = OneLogin::RubySaml::Authrequest.new.create_authentication_xml_doc(settings)
292+
assert auth_doc.to_s =~ /<saml:AuthnContextDeclRef>name\/password\/uri<\/saml:AuthnContextDeclRef>/
293+
assert auth_doc.to_s =~ /<saml:AuthnContextDeclRef>example\/decl\/ref<\/saml:AuthnContextDeclRef>/
294+
end
288295
end
289296
end

0 commit comments

Comments
 (0)