@@ -22,6 +22,7 @@ class IdpMetadataParser
2222
2323 attr_reader :document
2424 attr_reader :response
25+ attr_reader :parse_options
2526
2627 # Parse the Identity Provider metadata and update the settings with the
2728 # IdP values
@@ -36,22 +37,24 @@ def parse_remote(url, validate_cert = true, options = {})
3637 end
3738
3839 # Parse the Identity Provider metadata and update the settings with the IdP values
39- # @param idp_metadata [String]
40+ # @param idp_metadata [String]
4041 # @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides
4142 #
42- def parse ( idp_metadata , options = { } )
43+ def parse ( idp_metadata , parse_options = { } )
4344 @document = REXML ::Document . new ( idp_metadata )
45+ @parse_options = parse_options
46+ @entity_descriptor = nil
4447
45- settings = options [ :settings ]
48+ settings = parse_options [ :settings ]
4649 if settings . nil? || settings . is_a? ( Hash )
4750 settings = OneLogin ::RubySaml ::Settings . new ( settings || { } )
4851 end
4952
5053 settings . tap do |settings |
5154 settings . idp_entity_id = idp_entity_id
5255 settings . name_identifier_format = idp_name_id_format
53- settings . idp_sso_target_url = single_signon_service_url ( options )
54- settings . idp_slo_target_url = single_logout_service_url ( options )
56+ settings . idp_sso_target_url = single_signon_service_url
57+ settings . idp_slo_target_url = single_logout_service_url
5558 settings . idp_cert = certificate_base64
5659 settings . idp_cert_fingerprint = fingerprint ( settings . idp_cert_fingerprint_algorithm )
5760 settings . idp_attribute_names = attribute_names
@@ -67,56 +70,58 @@ def parse(idp_metadata, options = {})
6770 # @raise [HttpError] Failure to fetch remote IdP metadata
6871 def get_idp_metadata ( url , validate_cert )
6972 uri = URI . parse ( url )
70- if uri . scheme == "http"
71- response = Net ::HTTP . get_response ( uri )
72- meta_text = response . body
73- elsif uri . scheme == "https"
74- http = Net ::HTTP . new ( uri . host , uri . port )
73+ raise ArgumentError . new ( "url must begin with http or https" ) unless /^https?/ =~ uri . scheme
74+ http = Net ::HTTP . new ( uri . host , uri . port )
75+
76+ if uri . scheme == "https"
7577 http . use_ssl = true
7678 # Most IdPs will probably use self signed certs
77- if validate_cert
78- http . verify_mode = OpenSSL ::SSL ::VERIFY_PEER
79-
80- # Net::HTTP in Ruby 1.8 did not set the default certificate store
81- # automatically when VERIFY_PEER was specified.
82- if RUBY_VERSION < '1.9' && !http . ca_file && !http . ca_path && !http . cert_store
83- http . cert_store = OpenSSL ::SSL ::SSLContext ::DEFAULT_CERT_STORE
84- end
85- else
86- http . verify_mode = OpenSSL ::SSL ::VERIFY_NONE
79+ http . verify_mode = validate_cert ? OpenSSL ::SSL ::VERIFY_PEER : OpenSSL ::SSL ::VERIFY_NONE
80+
81+ # Net::HTTP in Ruby 1.8 did not set the default certificate store
82+ # automatically when VERIFY_PEER was specified.
83+ if RUBY_VERSION < '1.9' && !http . ca_file && !http . ca_path && !http . cert_store
84+ http . cert_store = OpenSSL ::SSL ::SSLContext ::DEFAULT_CERT_STORE
8785 end
88- get = Net ::HTTP ::Get . new ( uri . request_uri )
89- response = http . request ( get )
90- meta_text = response . body
91- else
92- raise ArgumentError . new ( "url must begin with http or https" )
9386 end
9487
95- unless response . is_a? Net ::HTTPSuccess
96- raise OneLogin ::RubySaml ::HttpError . new ( "Failed to fetch idp metadata" )
97- end
88+ get = Net ::HTTP ::Get . new ( uri . request_uri )
89+ response = http . request ( get )
90+ return response . body if response . is_a? Net ::HTTPSuccess
91+
92+ raise OneLogin ::RubySaml ::HttpError . new (
93+ "Failed to fetch idp metadata: #{ response . code } : #{ response . message } "
94+ )
95+ end
96+
97+ def entity_descriptor
98+ @entity_descriptor ||= REXML ::XPath . first (
99+ document ,
100+ entity_descriptor_path ,
101+ namespace
102+ )
103+ end
98104
99- meta_text
105+ def entity_descriptor_path
106+ path = "//md:EntityDescriptor"
107+ entity_id = parse_options [ :entity_id ]
108+ return path unless entity_id
109+ path << "[@entityID=\" #{ entity_id } \" ]"
100110 end
101111
102112 # @return [String|nil] IdP Entity ID value if exists
103113 #
104114 def idp_entity_id
105- node = REXML ::XPath . first (
106- document ,
107- "/md:EntityDescriptor/@entityID" ,
108- { "md" => METADATA }
109- )
110- node . value if node
115+ entity_descriptor . attributes [ "entityID" ]
111116 end
112117
113118 # @return [String|nil] IdP Name ID Format value if exists
114119 #
115120 def idp_name_id_format
116121 node = REXML ::XPath . first (
117- document ,
118- "/md:EntityDescriptor/ md:IDPSSODescriptor/md:NameIDFormat" ,
119- { "md" => METADATA }
122+ entity_descriptor ,
123+ "md:IDPSSODescriptor/md:NameIDFormat" ,
124+ namespace
120125 )
121126 node . text if node
122127 end
@@ -126,9 +131,9 @@ def idp_name_id_format
126131 #
127132 def single_signon_service_binding ( binding_priority = nil )
128133 nodes = REXML ::XPath . match (
129- document ,
130- "/md:EntityDescriptor/ md:IDPSSODescriptor/md:SingleSignOnService/@Binding" ,
131- { "md" => METADATA }
134+ entity_descriptor ,
135+ "md:IDPSSODescriptor/md:SingleSignOnService/@Binding" ,
136+ namespace
132137 )
133138 if binding_priority
134139 values = nodes . map ( &:value )
@@ -145,9 +150,9 @@ def single_signon_service_url(options = {})
145150 binding = single_signon_service_binding ( options [ :sso_binding ] )
146151 unless binding . nil?
147152 node = REXML ::XPath . first (
148- document ,
149- "/md:EntityDescriptor/ md:IDPSSODescriptor/md:SingleSignOnService[@Binding=\" #{ binding } \" ]/@Location" ,
150- { "md" => METADATA }
153+ entity_descriptor ,
154+ "md:IDPSSODescriptor/md:SingleSignOnService[@Binding=\" #{ binding } \" ]/@Location" ,
155+ namespace
151156 )
152157 return node . value if node
153158 end
@@ -158,9 +163,9 @@ def single_signon_service_url(options = {})
158163 #
159164 def single_logout_service_binding ( binding_priority = nil )
160165 nodes = REXML ::XPath . match (
161- document ,
162- "/md:EntityDescriptor/ md:IDPSSODescriptor/md:SingleLogoutService/@Binding" ,
163- { "md" => METADATA }
166+ entity_descriptor ,
167+ "md:IDPSSODescriptor/md:SingleLogoutService/@Binding" ,
168+ namespace
164169 )
165170 if binding_priority
166171 values = nodes . map ( &:value )
@@ -177,9 +182,9 @@ def single_logout_service_url(options = {})
177182 binding = single_logout_service_binding ( options [ :slo_binding ] )
178183 unless binding . nil?
179184 node = REXML ::XPath . first (
180- document ,
181- "/md:EntityDescriptor/ md:IDPSSODescriptor/md:SingleLogoutService[@Binding=\" #{ binding } \" ]/@Location" ,
182- { "md" => METADATA }
185+ entity_descriptor ,
186+ "md:IDPSSODescriptor/md:SingleLogoutService[@Binding=\" #{ binding } \" ]/@Location" ,
187+ namespace
183188 )
184189 return node . value if node
185190 end
@@ -190,16 +195,16 @@ def single_logout_service_url(options = {})
190195 def certificate_base64
191196 @certificate_base64 ||= begin
192197 node = REXML ::XPath . first (
193- document ,
194- "/md:EntityDescriptor/ md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
195- { "md" => METADATA , "ds" => DSIG }
198+ entity_descriptor ,
199+ " md:IDPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
200+ namespace
196201 )
197202
198203 unless node
199204 node = REXML ::XPath . first (
200- document ,
201- "/md:EntityDescriptor/ md:IDPSSODescriptor/md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
202- { "md" => METADATA , "ds" => DSIG }
205+ entity_descriptor ,
206+ " md:IDPSSODescriptor/md:KeyDescriptor/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
207+ namespace
203208 )
204209 end
205210 node . text if node
@@ -232,12 +237,21 @@ def fingerprint(fingerprint_algorithm = XMLSecurity::Document::SHA1)
232237 #
233238 def attribute_names
234239 nodes = REXML ::XPath . match (
235- document ,
236- "/md:EntityDescriptor/ md:IDPSSODescriptor/saml:Attribute/@Name" ,
237- { "md" => METADATA , "NameFormat" => NAME_FORMAT , "saml" => SAML_ASSERTION }
240+ entity_descriptor ,
241+ "md:IDPSSODescriptor/saml:Attribute/@Name" ,
242+ namespace
238243 )
239244 nodes . map ( &:value )
240245 end
246+
247+ def namespace
248+ {
249+ "md" => METADATA ,
250+ "NameFormat" => NAME_FORMAT ,
251+ "saml" => SAML_ASSERTION ,
252+ "ds" => DSIG
253+ }
254+ end
241255 end
242256 end
243257end
0 commit comments