spid-es 0.0.19 → 0.0.24

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
  SHA256:
3
- metadata.gz: 451ae4ac0178b95e682424be4d797a503a7f54e9d7fb4594568f532117396551
4
- data.tar.gz: e68163f57cba1896b588ddd33e069719071c34a0fdaed1ebfa2d7e9f6026e179
3
+ metadata.gz: 26b9ca5bca45877a5b6bebc563ac36e2477449c823cf223c648abe49ab16def0
4
+ data.tar.gz: 60976ddb66cee847aa3f7bf95438590957fb137f22964b6223461d16f450138a
5
5
  SHA512:
6
- metadata.gz: d4a45064042c4523f8a528a7e959b9d9caf7baaaae88a492e2b3466f64a744a5e10ae93d070f1a6070e7b70975401329702463d511b2b0405dfec2b372ac4d98
7
- data.tar.gz: 63b464fc4a5a512a0aa37d2e95b0ebb29754a82bd96051ab12cb256dd6b3ebbca523edd0911f868137caaa850125eb264337372696c6b01c7aa35c0e613ef3a7
6
+ metadata.gz: 20e31accd525285e23c2c6ca25f6a6259605bf44ae28c0273c222d000b8abca51419471174af8800ec58169a8feea10ece60eabeca6506043147e8bce0050488
7
+ data.tar.gz: 8d7aa546d80c0cb87070b0e7cdfb1cb8fe4aea939749fccb40293cb1b28480314c1be832d8b4fb5bcb21d4912dd052a03723cbb07b0c91d3c35b472c3453b816
@@ -31,16 +31,26 @@ module Spid
31
31
  def generate(settings)
32
32
  #meta_doc = REXML::Document.new
33
33
  meta_doc = Spid::XMLSecurityNew::Document.new
34
- root = meta_doc.add_element "md:EntityDescriptor", {
34
+ if settings.aggregato
35
+ root = meta_doc.add_element "md:EntityDescriptor", {
36
+ "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata",
37
+ "xmlns:xml" => "http://www.w3.org/XML/1998/namespace",
38
+ "xmlns:spid" => "https://spid.gov.it/saml-extensions",
39
+ }
40
+ else
41
+ root = meta_doc.add_element "md:EntityDescriptor", {
35
42
  "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata",
36
43
  "xmlns:xml" => "http://www.w3.org/XML/1998/namespace"
37
- }
44
+ }
45
+ end
46
+
38
47
  if settings.issuer != nil
39
48
  root.attributes["entityID"] = settings.issuer
40
49
  end
41
- uuid = "_" + UUID.new.generate
42
- self.uuid = uuid
43
- root.attributes["ID"] = uuid
50
+ #Tolto per non far cambiare sempre il metadata
51
+ # uuid = "_" + UUID.new.generate
52
+ # self.uuid = uuid
53
+ # root.attributes["ID"] = uuid
44
54
 
45
55
  sp_sso = root.add_element "md:SPSSODescriptor", {
46
56
  "protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
@@ -119,6 +129,18 @@ module Spid
119
129
  }
120
130
  end
121
131
 
132
+ #Logout dei servizi esterni
133
+ unless settings.hash_assertion_consumer.blank?
134
+ settings.hash_assertion_consumer.each_pair{ |index, hash_service|
135
+ unless hash_service['logout'].blank?
136
+ sp_sso.add_element "md:SingleLogoutService", {
137
+ "Binding" => hash_service['logout']['binding'] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
138
+ "Location" => hash_service['logout']['location']
139
+ }
140
+ end
141
+ }
142
+ end
143
+
122
144
  name_identifier_formats = settings.name_identifier_format
123
145
  if name_identifier_formats != nil
124
146
  name_id = []
@@ -165,47 +187,30 @@ module Spid
165
187
  "xml:lang" => "it"
166
188
  }
167
189
  service_name.text = hash_service['testo']
168
- hash_service['array_campi'].each_with_index{ |attribute, index|
169
- attr_cons_service.add_element "md:RequestedAttribute", {
170
- "Name" => attribute
190
+ unless hash_service['description'].blank?
191
+ service_description = attr_cons_service.add_element "md:ServiceDescription", {
192
+ "xml:lang" => "it"
171
193
  }
172
- }
194
+ service_description.text = hash_service['description']
195
+ end
196
+
197
+ if hash_service['array_campi'].is_a?(Array)
198
+ hash_service['array_campi'].each_with_index{ |attribute, index|
199
+ attr_cons_service.add_element "md:RequestedAttribute", {
200
+ "Name" => attribute
201
+ }
202
+ }
203
+ else #hash
204
+ hash_service['array_campi'].each_pair{ |attribute, name_format|
205
+ attr_cons_service.add_element "md:RequestedAttribute", {
206
+ "Name" => attribute,
207
+ "NameFormat" => name_format
208
+ }
209
+ }
210
+ end
173
211
  }
174
212
 
175
213
 
176
-
177
-
178
- #Per EIDAS
179
- # #AttributeConsumingService
180
- # attr_cons_service = sp_sso.add_element "md:AttributeConsumingService", {
181
- # "index" => "99",
182
- # }
183
- # service_name
184
- # = attr_cons_service.add_element "md:ServiceName", {
185
- # "xml:lang" => "it"
186
- # }
187
- # service_name.text = "eIDAS Natural Person Minimum Attribute Set"
188
- # settings.requested_attribute.each_with_index{ |attribute, index|
189
- # attr_cons_service.add_element "md:RequestedAttribute", {
190
- # "Name" => attribute
191
- # }
192
- # }
193
-
194
- # #AttributeConsumingService
195
- # attr_cons_service = sp_sso.add_element "md:AttributeConsumingService", {
196
- # "index" => "100",
197
- # }
198
- # service_name = attr_cons_service.add_element "md:ServiceName", {
199
- # "xml:lang" => "it"
200
- # }
201
- # service_name.text = "eIDAS Natural Person Full Attribute Set"
202
- # settings.requested_attribute.each_with_index{ |attribute, index|
203
- # attr_cons_service.add_element "md:RequestedAttribute", {
204
- # "Name" => attribute
205
- # }
206
- # }
207
-
208
-
209
214
  end
210
215
  #organization
211
216
  organization = root.add_element "md:Organization"
@@ -216,12 +221,54 @@ module Spid
216
221
  org_display_name = organization.add_element "md:OrganizationDisplayName", {
217
222
  "xml:lang" => "it"
218
223
  }
219
- org_display_name.text = settings.organization['org_display_name']
224
+
225
+ org_display_name.text = settings.organization['org_display_name']+(settings.aggregato ? " tramite #{settings.hash_aggregatore['soggetto_aggregatore']}" : '')
220
226
  org_url = organization.add_element "md:OrganizationURL", {
221
227
  "xml:lang" => "it"
222
228
  }
223
229
  org_url.text = settings.organization['org_url']
224
230
 
231
+ #ContactPerson per sp aggregato
232
+ if settings.aggregato
233
+ contact_person_aggregatore = root.add_element "md:ContactPerson", {
234
+ "contactType" => "other",
235
+ "spid:entityType" => "spid:aggregator"
236
+ }
237
+ company = contact_person_aggregatore.add_element "md:Company"
238
+ company.text = settings.hash_aggregatore['soggetto_aggregatore']
239
+
240
+ extensions_aggregatore = contact_person_aggregatore.add_element "md:Extensions"
241
+ vat_number_aggregatore = extensions_aggregatore.add_element "spid:VATNumber"
242
+ vat_number_aggregatore.text = settings.hash_aggregatore['piva_aggregatore']
243
+
244
+ ipa_code_aggregatore = extensions_aggregatore.add_element "spid:IPACode"
245
+ ipa_code_aggregatore.text = settings.hash_aggregatore['cipa_aggregatore']
246
+
247
+ fiscal_code_aggregatore = extensions_aggregatore.add_element "spid:FiscalCode"
248
+ fiscal_code_aggregatore.text = settings.hash_aggregatore['cf_aggregatore']
249
+
250
+ contact_person_aggregato = root.add_element "md:ContactPerson", {
251
+ "contactType" => "other",
252
+ "spid:entityType" => "spid:aggregated"
253
+ }
254
+ company = contact_person_aggregato.add_element "md:Company"
255
+ company.text = settings.organization['org_name']
256
+
257
+ extensions_aggregato = contact_person_aggregato.add_element "md:Extensions"
258
+ unless settings.hash_aggregatore['soggetto_aggregato']['vat_number'].blank?
259
+ vat_number_aggregato = extensions_aggregato.add_element "spid:VATNumber"
260
+ vat_number_aggregato.text = settings.hash_aggregatore['soggetto_aggregato']['vat_number']
261
+ end
262
+ unless settings.hash_aggregatore['soggetto_aggregato']['ipa_code'].blank?
263
+ ipa_code_aggregato = extensions_aggregato.add_element "spid:IPACode"
264
+ ipa_code_aggregato.text = settings.hash_aggregatore['soggetto_aggregato']['ipa_code']
265
+ end
266
+ unless settings.hash_aggregatore['soggetto_aggregato']['fiscal_code'].blank?
267
+ fiscal_code_aggregato = extensions_aggregato.add_element "spid:FiscalCode"
268
+ fiscal_code_aggregato.text = settings.hash_aggregatore['soggetto_aggregato']['fiscal_code']
269
+ end
270
+ end
271
+
225
272
  #meta_doc << REXML::XMLDecl.new(version='1.0', encoding='UTF-8')
226
273
  meta_doc << REXML::XMLDecl.new("1.0", "UTF-8")
227
274
 
@@ -4,239 +4,682 @@ require "nokogiri"
4
4
  require "base64"
5
5
  require "openssl"
6
6
  require "digest/sha1"
7
+ require_relative "utils"
7
8
 
8
9
  # Only supports SAML 2.0
9
10
  module Spid
10
11
  module Saml
11
12
 
12
13
  class Response
13
- ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
14
- PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
15
- DSIG = "http://www.w3.org/2000/09/xmldsig#"
16
-
17
- attr_accessor :options, :response, :document, :settings
18
-
19
- def initialize(response, options = {})
20
- raise ArgumentError.new("Response cannot be nil") if response.nil?
21
- self.options = options
22
- self.response = response
23
- begin
24
- self.document = Spid::XMLSecurityNew::SignedDocument.new(Base64.decode64(response))
25
- rescue REXML::ParseException => e
26
- if response =~ /</
27
- self.document = Spid::XMLSecurityNew::SignedDocument.new(response)
28
- else
29
- raise e
14
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
15
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
16
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
17
+
18
+ attr_accessor :options, :response, :document, :settings, :attr_name_format
19
+ attr_reader :decrypted_document
20
+
21
+
22
+ def initialize(response, options = {})
23
+ raise ArgumentError.new("Response cannot be nil") if response.nil?
24
+ self.options = options
25
+ self.response = response
26
+ if assertion_encrypted?
27
+ @decrypted_document = generate_decrypted_document
28
+ end
29
+ begin
30
+ self.document = Spid::XMLSecurityNew::SignedDocument.new(Base64.decode64(response))
31
+ rescue REXML::ParseException => e
32
+ if response =~ /</
33
+ self.document = Spid::XMLSecurityNew::SignedDocument.new(response)
34
+ else
35
+ raise e
36
+ end
30
37
  end
38
+
31
39
  end
32
-
33
- end
34
40
 
35
- def is_valid?
36
- validate
37
- end
41
+ # Checks if the SAML Response contains or not an EncryptedAssertion element
42
+ # @return [Boolean] True if the SAML Response contains an EncryptedAssertion element
43
+ #
44
+ def assertion_encrypted?
45
+ false
46
+ #!REXML::XPath.first(self.document, "(/p:Response/EncryptedAssertion/)|(/p:Response/a:EncryptedAssertion/)", { "p" => PROTOCOL, "a" => ASSERTION }).nil?
47
+ end
38
48
 
39
- def validate!
40
- validate(false)
41
- end
49
+ def is_valid?
50
+ validate
51
+ end
42
52
 
43
- # The value of the user identifier as designated by the initialization request response
44
- def name_id
45
- @name_id ||= begin
46
- node = REXML::XPath.first(document, "/saml2p:Response/saml2:Assertion[@ID='#{document.signed_element_id}']/saml2:Subject/saml2:NameID")
47
- node ||= REXML::XPath.first(document, "/saml2p:Response[@ID='#{document.signed_element_id}']/saml2:Assertion/saml2:Subject/saml2:NameID")
48
- node.nil? ? nil : node.text
53
+ def validate!
54
+ validate(false)
55
+ end
56
+
57
+ # The value of the user identifier as designated by the initialization request response
58
+ def name_id
59
+ @name_id ||= begin
60
+ node = REXML::XPath.first(document, "/saml2p:Response/saml2:Assertion[@ID='#{document.signed_element_id}']/saml2:Subject/saml2:NameID")
61
+ node ||= REXML::XPath.first(document, "/saml2p:Response[@ID='#{document.signed_element_id}']/saml2:Assertion/saml2:Subject/saml2:NameID")
62
+ node.nil? ? nil : node.text
63
+ end
49
64
  end
50
- end
51
65
 
52
- # A hash of alle the attributes with the response. Assuming there is only one value for each key
53
- def attributes
54
- @attr_statements ||= begin
55
- result = {}
56
66
 
57
- stmt_element = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AttributeStatement", { "p" => PROTOCOL, "a" => ASSERTION })
58
- return {} if stmt_element.nil?
59
67
 
60
- stmt_element.elements.each do |attr_element|
61
- name = attr_element.attributes["Name"]
62
- value = attr_element.elements.first.text
63
68
 
64
- result[name] = value
69
+ # A hash of alle the attributes with the response. Assuming there is only one value for each key
70
+ def attributes
71
+ @attr_statements ||= begin
72
+ result = {}
73
+ stmt_element = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AttributeStatement", { "p" => PROTOCOL, "a" => ASSERTION })
74
+ return {} if stmt_element.nil?
75
+
76
+ @attr_name_format = []
77
+ stmt_element.elements.each do |attr_element|
78
+ name = attr_element.attributes["Name"]
79
+ #salvo i vari format per controllare poi che non ce ne siano di null
80
+ @attr_name_format << attr_element.attributes["NameFormat"].blank? ? nil : attr_element.attributes["NameFormat"].text
81
+ value = (attr_element.elements.blank? ? nil : attr_element.elements.first.text)
82
+
83
+ result[name] = value
84
+ end
85
+ #mette il symbol
86
+ result.keys.each do |key|
87
+ result[key.intern] = result[key]
88
+ end
89
+
90
+ result
65
91
  end
92
+ end
66
93
 
67
- result.keys.each do |key|
68
- result[key.intern] = result[key]
94
+ # When this user session should expire at latest
95
+ def session_expires_at
96
+ @expires_at ||= begin
97
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AuthnStatement", { "p" => PROTOCOL, "a" => ASSERTION })
98
+ parse_time(node, "SessionNotOnOrAfter")
69
99
  end
100
+ end
101
+
70
102
 
71
- result
103
+
104
+ # Checks the status of the response for a "Success" code
105
+ def success?
106
+ @status_code ||= begin
107
+ node = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION })
108
+ node.attributes["Value"] == "urn:oasis:names:tc:SAML:2.0:status:Success" unless node.blank?
109
+ end
72
110
  end
73
- end
74
111
 
75
- # When this user session should expire at latest
76
- def session_expires_at
77
- @expires_at ||= begin
78
- node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AuthnStatement", { "p" => PROTOCOL, "a" => ASSERTION })
79
- parse_time(node, "SessionNotOnOrAfter")
112
+ # Ritorno il valore dello StatusMessage
113
+ def get_status_message
114
+ node = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusMessage", { "p" => PROTOCOL, "a" => ASSERTION })
115
+ node.text unless node.blank?
80
116
  end
81
- end
82
-
83
- # Checks the status of the response for a "Success" code
84
- def success?
85
- @status_code ||= begin
86
- node = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION })
87
- node.attributes["Value"] == "urn:oasis:names:tc:SAML:2.0:status:Success"
117
+
118
+ # Conditions (if any) for the assertion to run
119
+ def conditions
120
+ @conditions ||= begin
121
+ REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id}']/a:Conditions", { "p" => PROTOCOL, "a" => ASSERTION })
122
+ end
88
123
  end
89
- end
90
124
 
91
- # Conditions (if any) for the assertion to run
92
- def conditions
93
- @conditions ||= begin
94
- REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id}']/a:Conditions", { "p" => PROTOCOL, "a" => ASSERTION })
125
+
126
+
127
+ #metodi per ricavare info per tracciatura agid
128
+
129
+
130
+ def issuer
131
+ @issuer ||= begin
132
+ node = REXML::XPath.first(document, "/p:Response/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
133
+ node ||= REXML::XPath.first(document, "/p:Response/a:Assertion/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
134
+ node.nil? ? nil : node.text
135
+ end
136
+ end
137
+
138
+ # Gets the Issuers (from Response and Assertion).
139
+ # (returns the first node that matches the supplied xpath from the Response and from the Assertion)
140
+ # @return [Array] Array with the Issuers (REXML::Element)
141
+ #
142
+ def issuers(soft=true)
143
+ @issuers ||= begin
144
+ issuer_response_nodes = REXML::XPath.match(
145
+ document,
146
+ "/p:Response/a:Issuer",
147
+ { "p" => PROTOCOL, "a" => ASSERTION }
148
+ )
149
+
150
+ unless issuer_response_nodes.size == 1
151
+ # error_msg = "Issuer of the Response not found or multiple."
152
+ # raise ValidationError.new(error_msg)
153
+ return (soft ? false : validation_error("Issuer of the Response not found or multiple."))
154
+ end
155
+
156
+ issuer_assertion_nodes = xpath_from_signed_assertion("/a:Issuer")
157
+ unless issuer_assertion_nodes.size == 1
158
+ # error_msg = "Issuer of the Assertion not found or multiple."
159
+ # raise ValidationError.new(error_msg)
160
+ return (soft ? false : validation_error("Issuer of the Assertion not found or multiple."))
161
+ end
162
+
163
+ issuer_response_nodes.each{ |iss|
164
+ #controllo: L'attributo Format di Issuer deve essere presente con il valore urn:oasis:names:tc:SAML:2.0:nameid-format:entity
165
+ return (soft ? false : validation_error("Elemento Issuer non ha formato corretto ")) if !iss.attributes['Format'].nil? && iss.attributes['Format'] != 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity'
166
+
167
+ }
168
+
169
+ issuer_assertion_nodes.each{ |iss|
170
+ #controllo: L'attributo Format di Issuer deve essere presente con il valore urn:oasis:names:tc:SAML:2.0:nameid-format:entity
171
+ return (soft ? false : validation_error("Elemento Issuer non ha formato corretto ")) if iss.attributes['Format'] != 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity'
172
+
173
+ }
174
+
175
+ nodes = issuer_response_nodes + issuer_assertion_nodes
176
+
177
+ nodes.map { |node| Utils.element_text(node) }.compact.uniq
178
+ end
95
179
  end
96
- end
97
180
 
98
-
99
181
 
100
- #metodi per ricavare info per tracciatura agid
101
182
 
102
- def issuer
103
- @issuer ||= begin
104
- node = REXML::XPath.first(document, "/p:Response/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
105
- node ||= REXML::XPath.first(document, "/p:Response/a:Assertion/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION })
106
- node.nil? ? nil : node.text
183
+ def response_to_id
184
+ node = REXML::XPath.first(document, "/p:Response", { "p" => PROTOCOL })
185
+ return node.attributes["InResponseTo"] unless node.blank?
107
186
  end
108
- end
109
187
 
110
- def response_to_id
111
- node = REXML::XPath.first(document, "/p:Response", { "p" => PROTOCOL })
112
- return node.attributes["InResponseTo"]
113
- end
188
+ def id
189
+ node = REXML::XPath.first(document, "/p:Response", { "p" => PROTOCOL })
190
+ return node.attributes["ID"] unless node.blank?
191
+ end
114
192
 
115
- def id
116
- node = REXML::XPath.first(document, "/p:Response", { "p" => PROTOCOL })
117
- return node.attributes["ID"]
118
- end
193
+ def issue_instant
194
+ node = REXML::XPath.first(document, "/p:Response", { "p" => PROTOCOL })
195
+ return node.attributes["IssueInstant"] unless node.blank?
196
+ end
119
197
 
120
- def issue_instant
121
- node = REXML::XPath.first(document, "/p:Response", { "p" => PROTOCOL })
122
- return node.attributes["IssueInstant"]
123
- end
198
+ def assertion_present?
199
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/", { "p" => PROTOCOL, "a" => ASSERTION })
200
+ return !node.blank?
201
+ end
124
202
 
125
- def assertion_id
126
- node = REXML::XPath.first(document, "/p:Response/a:Assertion/", { "p" => PROTOCOL, "a" => ASSERTION })
127
- return node.attributes["ID"]
128
- end
203
+ def assertion_issue_instant
204
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/", { "p" => PROTOCOL, "a" => ASSERTION })
205
+ return node.attributes["IssueInstant"] unless node.blank?
206
+ end
129
207
 
130
- def assertion_subject
131
- node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
132
- return node.text
133
- end
208
+ def assertion_id
209
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/", { "p" => PROTOCOL, "a" => ASSERTION })
210
+ return node.attributes["ID"] unless node.blank?
211
+ end
134
212
 
135
- def assertion_subject_name_qualifier
136
- node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
137
- return node.attributes["NameQualifier"]
138
- end
213
+ def assertion_subject
214
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
215
+ return node.text
216
+ end
139
217
 
140
-
218
+ def assertion_subject_name_qualifier
219
+ node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
220
+ return node.attributes["NameQualifier"] unless node.blank?
221
+ end
141
222
 
223
+ def assertion_subject_confirmation_data_not_on_or_after
224
+ node_subj_conf_data = xpath_first_from_signed_assertion('/a:Subject/a:SubjectConfirmation/a:SubjectConfirmationData')
225
+ return node_subj_conf_data.attributes["NotOnOrAfter"] unless node_subj_conf_data.blank?
226
+ end
142
227
 
228
+ def assertion_conditions_not_before
229
+ node_cond_not_before = xpath_first_from_signed_assertion('/a:Conditions')
230
+ return node_cond_not_before.attributes["NotBefore"] unless node_cond_not_before.blank?
231
+ end
143
232
 
144
- private
233
+ def assertion_conditions_not_on_or_after
234
+ node_cond_not_on_or_after = xpath_first_from_signed_assertion('/a:Conditions')
235
+ return node_cond_not_on_or_after.attributes["NotOnOrAfter"] unless node_cond_not_on_or_after.blank?
236
+ end
145
237
 
146
- def validation_error(message)
147
- raise ValidationError.new(message)
148
- end
238
+ private
149
239
 
150
- def validate(soft = true)
151
- # prime the IdP metadata before the document validation.
152
- # The idp_cert needs to be populated before the validate_response_state method
153
-
154
- if settings
155
- Spid::Saml::Metadata.new(settings).get_idp_metadata
240
+ def validation_error(message)
241
+ raise ValidationError.new(message)
242
+ end
243
+
244
+ def validate(soft = true)
245
+ # prime the IdP metadata before the document validation.
246
+ # The idp_cert needs to be populated before the validate_response_state method
247
+
248
+ if settings
249
+ idp_metadata = Spid::Saml::Metadata.new(settings).get_idp_metadata
250
+ end
251
+
252
+ #carico nei setting l'idp_entity_id
253
+ entity_descriptor_element = REXML::XPath.first(idp_metadata,"/EntityDescriptor")
254
+ if !entity_descriptor_element.nil?
255
+ settings.idp_entity_id = entity_descriptor_element.attributes["entityID"]
256
+ end
257
+
258
+ return false if validate_structure(soft) == false
259
+ return false if validate_response_state(soft) == false
260
+ return false if validate_conditions(soft) == false
261
+ #validazione assertion firmata
262
+ return false if validate_signed_elements(soft) == false
263
+ #validazione version che sia 2.0
264
+ return false if validate_version(soft) == false
265
+ #validazione version delle asserzioni che sia 2.0
266
+ return false if validate_version_assertion(soft) == false
267
+ #validazione destination
268
+ return false if validate_destination(soft) == false
269
+ #validazione status
270
+ return false if validate_status(soft) == false
271
+ #validazione inresponseto
272
+ return false if validate_presence_inresponseto(soft) == false
273
+ #validazione issuer
274
+ return false if validate_issuer(soft) == false
275
+ #validazioni varie su asserzioni
276
+ return false if validate_assertion(soft) == false
277
+ #validazione presenza format su attributes
278
+ return false if validate_name_format_attributes(soft) == false
279
+
280
+
281
+ # Just in case a user needs to toss out the signature validation,
282
+ # I'm adding in an option for it. (Sometimes canonicalization is a bitch!)
283
+ return true if settings.skip_validation == true
284
+
285
+ # document.validte populates the idp_cert
286
+ return false if document.validate_document(get_fingerprint, soft) == false
287
+
288
+ # validate response code
289
+ return false if success? == false
290
+
291
+ return true
292
+ end
293
+
294
+ # Validates the Issuer (Of the SAML Response and the SAML Assertion)
295
+ # @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the response is invalid or not)
296
+ # @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True
297
+ # @raise [ValidationError] if soft == false and validation fails
298
+ #
299
+ def validate_issuer(soft=true)
300
+ obtained_issuers = issuers(soft)
301
+ if obtained_issuers == false
302
+ return false #errori all'interno del metodo issuers
303
+ else
304
+ obtained_issuers.each do |iss|
305
+
306
+ unless Spid::Saml::Utils.uri_match?(iss, settings.idp_entity_id)
307
+ # error_msg = "Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>"
308
+ # return append_error(error_msg)
309
+ return (soft ? false : validation_error("Elemento Issuer diverso da EntityID IdP, expected: <#{settings.idp_entity_id}>, but was: <#{iss}>"))
310
+ end
311
+ end
312
+
313
+ true
314
+ end
315
+ end
316
+
317
+ def validate_presence_inresponseto(soft=true)
318
+ response_to_id_value = response_to_id
319
+ return (soft ? false : validation_error("InResponseTo non specificato o mancante")) if response_to_id_value.blank?
156
320
  end
157
- return false if validate_structure(soft) == false
158
- return false if validate_response_state(soft) == false
159
- return false if validate_conditions(soft) == false
160
-
161
- # Just in case a user needs to toss out the signature validation,
162
- # I'm adding in an option for it. (Sometimes canonicalization is a bitch!)
163
- return true if settings.skip_validation == true
164
-
165
- # document.validte populates the idp_cert
166
- return false if document.validate_document(get_fingerprint, soft) == false
167
-
168
- # validate response code
169
- return false if success? == false
170
321
 
171
- return true
172
- end
173
322
 
174
323
 
175
- def validate_structure(soft = true)
176
- Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
177
- @schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
178
- @xml = Nokogiri::XML(self.document.to_s)
324
+ #validate status e status code
325
+ def validate_status(soft=true)
326
+ #controlli su status
327
+ node_status = REXML::XPath.first(document, "/p:Response/p:Status", { "p" => PROTOCOL, "a" => ASSERTION })
328
+ return (soft ? false : validation_error("Status non presente")) if node_status.blank?
329
+ #controlli su status code
330
+ node_status_code = REXML::XPath.first(document, "/p:Response/p:Status/p:StatusCode", { "p" => PROTOCOL, "a" => ASSERTION })
331
+ return (soft ? false : validation_error("Status code non presente")) if node_status_code.blank?
332
+ return (soft ? false : validation_error("Status non presente")) unless node_status_code.attributes["Value"] == "urn:oasis:names:tc:SAML:2.0:status:Success"
333
+ true
179
334
  end
180
- if soft
181
- @schema.validate(@xml).map{ return false }
182
- else
183
- @schema.validate(@xml).map{ |error| raise(Exception.new("#{error.message}\n\n#{@xml.to_s}")) }
335
+
336
+
337
+
338
+ # Validates the SAML version (2.0)
339
+ # If fails, the error is added to the errors array.
340
+ # @return [Boolean] True if the SAML Response is 2.0, otherwise returns False
341
+ #
342
+ def version(document)
343
+ @version ||= begin
344
+ node = REXML::XPath.first(
345
+ document,
346
+ "/p:AuthnRequest | /p:Response | /p:LogoutResponse | /p:LogoutRequest",
347
+ { "p" => PROTOCOL }
348
+ )
349
+ node.nil? ? nil : node.attributes['Version']
350
+ end
184
351
  end
185
- end
186
352
 
187
- def validate_response_state(soft = true)
188
- if response.empty?
189
- return soft ? false : validation_error("Blank response")
353
+ def version_assertion(document)
354
+ assertion_nodes = xpath_from_signed_assertion()
355
+ @version_assertion = "2.0"
356
+ #ciclo sui nodi delle asserzioni, se uno ha una versione diversa da 2.0 ritorno nil
357
+ unless assertion_nodes.blank?
358
+ assertion_nodes.each{ |ass_node|
359
+ return nil if ass_node.attributes['Version'] != "2.0"
360
+ }
361
+ end
362
+ @version_assertion
190
363
  end
191
364
 
192
- if settings.nil?
193
- return soft ? false : validation_error("No settings on response")
365
+ def validate_version(soft = true)
366
+ unless version(self.document) == "2.0"
367
+ #return append_error("Unsupported SAML version")
368
+ return soft ? false : validation_error("Unsupported SAML version")
369
+ end
370
+ true
194
371
  end
195
372
 
196
- if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
197
- return soft ? false : validation_error("No fingerprint or certificate on settings")
373
+ def validate_version_assertion(soft = true)
374
+ unless version_assertion(self.document) == "2.0"
375
+ #return append_error("Unsupported SAML version")
376
+ return soft ? false : validation_error("Unsupported SAML Assertion version")
377
+ end
378
+ true
198
379
  end
199
380
 
200
- true
201
- end
381
+ def validate_signed_elements(soft = true)
382
+ signature_nodes = REXML::XPath.match(decrypted_document.nil? ? document : decrypted_document,"//ds:Signature",{"ds"=>DSIG})
383
+ signed_elements = []
384
+ verified_seis = []
385
+ verified_ids = []
386
+ signature_nodes.each do |signature_node|
387
+ signed_element = signature_node.parent.name
388
+ if signed_element != 'Response' && signed_element != 'Assertion'
389
+ return soft ? false : validation_error("Invalid Signature Element '#{signed_element}'. SAML Response rejected")
390
+ #return append_error("Invalid Signature Element '#{signed_element}'. SAML Response rejected")
391
+ end
392
+
393
+ if signature_node.parent.attributes['ID'].nil?
394
+ return soft ? false : validation_error("Signed Element must contain an ID. SAML Response rejected")
395
+ #return append_error("Signed Element must contain an ID. SAML Response rejected")
396
+ end
397
+
398
+ id = signature_node.parent.attributes.get_attribute("ID").value
399
+ if verified_ids.include?(id)
400
+ return soft ? false : validation_error("Duplicated ID. SAML Response rejected")
401
+ #return append_error("Duplicated ID. SAML Response rejected")
402
+ end
403
+ verified_ids.push(id)
404
+
405
+ # Check that reference URI matches the parent ID and no duplicate References or IDs
406
+ ref = REXML::XPath.first(signature_node, ".//ds:Reference", {"ds"=>DSIG})
407
+ if ref
408
+ uri = ref.attributes.get_attribute("URI")
409
+ if uri && !uri.value.empty?
410
+ sei = uri.value[1..-1]
411
+
412
+ unless sei == id
413
+ #return append_error("Found an invalid Signed Element. SAML Response rejected")
414
+ return soft ? false : validation_error("Found an invalid Signed Element. SAML Response rejected")
415
+ end
416
+
417
+ if verified_seis.include?(sei)
418
+ #return append_error("Duplicated Reference URI. SAML Response rejected")
419
+ return soft ? false : validation_error("Duplicated Reference URI. SAML Response rejected")
420
+ end
421
+
422
+ verified_seis.push(sei)
423
+ end
424
+ end
425
+
426
+ signed_elements << signed_element
427
+ end
428
+
429
+ unless signature_nodes.length < 3 && !signed_elements.empty?
430
+ #return append_error("Found an unexpected number of Signature Element. SAML Response rejected")
431
+ return soft ? false : validation_error("Found an unexpected number of Signature Element. SAML Response rejected")
432
+ end
433
+
434
+ #if settings.security[:want_assertions_signed] && !(signed_elements.include? "Assertion")
435
+ if !(signed_elements.include? "Assertion")
436
+ #return append_error("The Assertion of the Response is not signed and the SP requires it")
437
+ return soft ? false : validation_error("L'asserzione non è firmata.")
438
+ end
439
+
440
+ true
441
+ end
202
442
 
203
- def get_fingerprint
204
- idp_metadata = Spid::Saml::Metadata.new(settings).get_idp_metadata
205
-
206
- if settings.idp_cert
207
- cert_text = Base64.decode64(settings.idp_cert)
208
- cert = OpenSSL::X509::Certificate.new(cert_text)
209
- Digest::SHA2.hexdigest(cert.to_der).upcase.scan(/../).join(":")
210
- else
211
- settings.idp_cert_fingerprint
443
+ def validate_structure(soft = true)
444
+ Dir.chdir(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'schemas'))) do
445
+ @schema = Nokogiri::XML::Schema(IO.read('saml20protocol_schema.xsd'))
446
+ @xml = Nokogiri::XML(self.document.to_s)
447
+ end
448
+ if soft
449
+ @schema.validate(@xml).map{ return false }
450
+ else
451
+ @schema.validate(@xml).map{ |error| raise(Exception.new("#{error.message}\n\n#{@xml.to_s}")) }
452
+ end
453
+ end
454
+
455
+ def validate_response_state(soft = true)
456
+ if response.empty?
457
+ return soft ? false : validation_error("Blank response")
458
+ end
459
+
460
+ if settings.nil?
461
+ return soft ? false : validation_error("No settings on response")
462
+ end
463
+
464
+ if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil?
465
+ return soft ? false : validation_error("No fingerprint or certificate on settings")
466
+ end
467
+
468
+ true
212
469
  end
470
+
471
+ # Validates the Destination, (If the SAML Response is received where expected).
472
+ # If the response was initialized with the :skip_destination option, this validation is skipped,
473
+ # If fails, the error is added to the errors array
474
+ # @return [Boolean] True if there is a Destination element that matches the Consumer Service URL, otherwise False
213
475
 
214
- end
476
+ # @return [String|nil] Destination attribute from the SAML Response.
477
+ #
478
+ def destination
479
+ @destination ||= begin
480
+ node = REXML::XPath.first(
481
+ document,
482
+ "/p:Response",
483
+ { "p" => PROTOCOL }
484
+ )
485
+ node.nil? ? nil : node.attributes['Destination']
486
+ end
487
+ end
215
488
 
216
- def validate_conditions(soft = true)
217
- return true if conditions.nil?
218
- return true if options[:skip_conditions]
489
+ def validate_destination(soft = true)
490
+ return (soft ? false : validation_error("La response non ha destination")) if destination.nil?
491
+ #return true if options[:skip_destination]
219
492
 
220
- if not_before = parse_time(conditions, "NotBefore")
221
- if Time.now.utc < not_before
222
- return soft ? false : validation_error("Current time is earlier than NotBefore condition")
223
- end
493
+ if destination.empty?
494
+ # error_msg = "The response has an empty Destination value"
495
+ # return append_error(error_msg)
496
+ return soft ? false : validation_error("The response has an empty Destination value")
497
+ end
498
+
499
+ return true if settings.assertion_consumer_service_url.nil? || settings.assertion_consumer_service_url.empty?
500
+
501
+ unless Spid::Saml::Utils.uri_match?(destination, settings.assertion_consumer_service_url)
502
+ # error_msg = "The response was received at #{destination} instead of #{settings.assertion_consumer_service_url}"
503
+ # return append_error(error_msg)
504
+ return soft ? false : validation_error("The response was received at #{destination} instead of #{settings.assertion_consumer_service_url}")
505
+ end
506
+
507
+ true
224
508
  end
225
509
 
226
- if not_on_or_after = parse_time(conditions, "NotOnOrAfter")
227
- if Time.now.utc >= not_on_or_after
228
- return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
229
- end
510
+ def validate_assertion(soft = true)
511
+ #posso avere n nodi asserzione..forse
512
+ nodes_assertion = xpath_from_signed_assertion
513
+ unless nodes_assertion.blank?
514
+ #Elemento NameID non specificato
515
+ node_name_id = xpath_first_from_signed_assertion('/a:Subject/a:NameID')
516
+ unless node_name_id.blank?
517
+ return soft ? false : validation_error("Errore su Asserzione: NameID vuoto") if node_name_id.text.blank?
518
+ #controlli su attributo format
519
+ attr_format = node_name_id.attribute("Format")
520
+ return soft ? false : validation_error("Errore su Asserzione: Format su NameID vuoto") if attr_format.blank? || attr_format.to_s != "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" #45
521
+ #controlli su attributo NameQualifier
522
+ attr_name_qual = node_name_id.attribute("NameQualifier")
523
+ return soft ? false : validation_error("Errore su Asserzione: NameQualifier su NameID vuoto") if attr_name_qual.blank? || ( !attr_name_qual.blank? && attr_name_qual.value.blank?)#48 e 49
524
+ else
525
+ return soft ? false : validation_error("Errore su Asserzione: NameID non presente")
526
+
527
+ end
528
+ #Controlli su SubjectConfirmation
529
+ node_subj_conf = xpath_first_from_signed_assertion('/a:Subject/a:SubjectConfirmation')
530
+ unless node_subj_conf.blank?
531
+ #controlli su attributo Method
532
+ attr_method = node_subj_conf.attribute("Method")
533
+ return soft ? false : validation_error("Errore su Asserzione: Method su SubjectConfirmation vuoto") if attr_method.blank? || attr_method.to_s != "urn:oasis:names:tc:SAML:2.0:cm:bearer" #53 54 e 55
534
+ #Controlli su SubjectConfirmationData
535
+ node_subj_conf_data = xpath_first_from_signed_assertion('/a:Subject/a:SubjectConfirmation/a:SubjectConfirmationData')
536
+ unless node_subj_conf_data.blank?
537
+ #controllo attr Recipient, vuoto o diverso da AssertionConsumerServiceURL
538
+ attr_recipient = node_subj_conf_data.attribute("Recipient")
539
+ return soft ? false : validation_error("Errore su Asserzione: Recipient su SubjectConfirmationData vuoto o diverso da AssertionConsumerServiceURL") if attr_recipient.blank? || attr_recipient.to_s != settings.assertion_consumer_service_url #57 58 e 59
540
+ #controllo attr InResponseTo, vuoto o diverso da ID request
541
+ node_response = REXML::XPath.first(document, "/p:Response", { "p" => PROTOCOL})
542
+ id_request = node_response.attribute("InResponseTo")
543
+ attr_in_resp_to = node_subj_conf_data.attribute("InResponseTo")
544
+ return soft ? false : validation_error("Errore su Asserzione: InResponseTo su SubjectConfirmationData vuoto o diverso da ID request") if attr_in_resp_to.blank? || attr_in_resp_to.to_s != id_request.to_s #57 58 e 59
545
+
546
+ #controllo attr NotOnOrAfter se vuoto o non presente #63 64
547
+ attr_not_on_or_after = node_subj_conf_data.attribute("NotOnOrAfter")
548
+ return soft ? false : validation_error("Errore su Asserzione: NotOnOrAfter su SubjectConfirmationData mancante") if attr_not_on_or_after.blank?
549
+
550
+
551
+ else
552
+ return soft ? false : validation_error("Errore su Asserzione: SubjectConfirmationData non presente")
553
+ end
554
+
555
+ else
556
+ return soft ? false : validation_error("Errore su Asserzione: SubjectConfirmation non presente")
557
+ end
558
+
559
+ #Controlli su Conditions
560
+ node_conditions = xpath_first_from_signed_assertion('/a:Conditions')
561
+ unless node_conditions.blank?
562
+ attr_not_before = node_conditions.attribute("NotBefore")
563
+ return soft ? false : validation_error("Errore su Asserzione: Recipient su SubjectConfirmationData vuoto") if attr_not_before.blank? #75 76
564
+ #83 84. Assertion - Elemento AudienceRestriction di Condition mancante
565
+ node_conditions_audience_restrictions = xpath_first_from_signed_assertion('/a:Conditions/a:AudienceRestriction')
566
+ return soft ? false : validation_error("Errore su Asserzione: AudienceRestriction su Conditions vuoto") if node_conditions_audience_restrictions.blank? #83 84
567
+ #85 86 87. Assertion - Elemento Audience di AudienceRestriction mancante
568
+ node_conditions_audience_restrictions_audience = xpath_first_from_signed_assertion('/a:Conditions/a:AudienceRestriction/a:Audience')
569
+ #Spider.logger.error "\n\n node_conditions_audience_restrictions_audience #{node_conditions_audience_restrictions_audience}"
570
+ #Spider.logger.error "\n\n settings.issuer #{settings.issuer}"
571
+ return soft ? false : validation_error("Errore su Asserzione: Audience su AudienceRestriction vuoto") if node_conditions_audience_restrictions_audience.blank? || node_conditions_audience_restrictions_audience.text != settings.issuer #83 84
572
+ else
573
+ return soft ? false : validation_error("Errore su Asserzione: Conditions non presente")
574
+ end
575
+
576
+
577
+ node_auth_stat_context_class_ref = xpath_first_from_signed_assertion('/a:AuthnStatement/a:AuthnContext/a:AuthnContextClassRef')
578
+ #Spider.logger.error "\n\n node_auth_stat_context_class_ref #{node_auth_stat_context_class_ref.text}"
579
+ return soft ? false : validation_error("Errore su Asserzione: AuthnContextClassRef di AuthnContext su AuthnStatement vuoto o non L2") if node_auth_stat_context_class_ref.blank? || ( (node_auth_stat_context_class_ref.text != 'https://www.spid.gov.it/SpidL2') && (node_auth_stat_context_class_ref.text != 'https://www.spid.gov.it/SpidL3'))
580
+
581
+ node_attr_stmt_attribute_value = xpath_first_from_signed_assertion("/a:AttributeStatement/a:Attribute/a:AttributeValue")
582
+ #Elemento AttributeStatement presente, ma sottoelemento Attribute non specificato, caso 99
583
+ return soft ? false : validation_error("Errore su Asserzione: AttributeValue di Attribute su AttributeStatement vuoto") if node_attr_stmt_attribute_value.blank?
584
+
585
+
586
+ else
587
+ return soft ? false : validation_error("Errore su Asserzione: non presente")
588
+ end
589
+ true
590
+ end
591
+
592
+
593
+ def validate_name_format_attributes(soft=true)
594
+ unless attributes.blank?
595
+ return false if @attr_name_format.blank? || (@attr_name_format.length != (attributes.length / 2))
596
+ end
597
+ true
598
+ end
599
+
600
+ def get_fingerprint
601
+ idp_metadata = Spid::Saml::Metadata.new(settings).get_idp_metadata
602
+
603
+ if settings.idp_cert
604
+ cert_text = Base64.decode64(settings.idp_cert)
605
+ cert = OpenSSL::X509::Certificate.new(cert_text)
606
+ Digest::SHA2.hexdigest(cert.to_der).upcase.scan(/../).join(":")
607
+ else
608
+ settings.idp_cert_fingerprint
609
+ end
610
+
611
+ end
612
+
613
+ def validate_conditions(soft = true)
614
+ return true if conditions.nil?
615
+ return true if options[:skip_conditions]
616
+
617
+ if not_before = parse_time(conditions, "NotBefore")
618
+ if Time.now.utc < not_before
619
+ return soft ? false : validation_error("Current time is earlier than NotBefore condition")
620
+ end
621
+ end
622
+
623
+ if not_on_or_after = parse_time(conditions, "NotOnOrAfter")
624
+ if Time.now.utc >= not_on_or_after
625
+ return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition")
626
+ end
627
+ end
628
+
629
+ true
630
+ end
631
+
632
+ def parse_time(node, attribute)
633
+ if node && node.attributes[attribute]
634
+ Time.parse(node.attributes[attribute])
635
+ end
230
636
  end
231
637
 
232
- true
233
- end
638
+ # Extracts all the appearances that matchs the subelt (pattern)
639
+ # Search on any Assertion that is signed, or has a Response parent signed
640
+ # @param subelt [String] The XPath pattern
641
+ # @return [Array of REXML::Element] Return all matches
642
+ #
643
+ def xpath_from_signed_assertion(subelt=nil)
644
+ doc = decrypted_document.nil? ? document : decrypted_document
645
+ node = REXML::XPath.match(
646
+ doc,
647
+ "/p:Response/a:Assertion[@ID=$id]#{subelt}",
648
+ { "p" => PROTOCOL, "a" => ASSERTION },
649
+ { 'id' => doc.signed_element_id }
650
+ )
651
+ node.concat( REXML::XPath.match(
652
+ doc,
653
+ "/p:Response[@ID=$id]/a:Assertion#{subelt}",
654
+ { "p" => PROTOCOL, "a" => ASSERTION },
655
+ { 'id' => doc.signed_element_id }
656
+ ))
657
+ end
234
658
 
235
- def parse_time(node, attribute)
236
- if node && node.attributes[attribute]
237
- Time.parse(node.attributes[attribute])
659
+ # Extracts the first appearance that matchs the subelt (pattern)
660
+ # Search on any Assertion that is signed, or has a Response parent signed
661
+ # @param subelt [String] The XPath pattern
662
+ # @return [REXML::Element | nil] If any matches, return the Element
663
+ #
664
+ def xpath_first_from_signed_assertion(subelt=nil)
665
+ doc = decrypted_document.nil? ? document : decrypted_document
666
+ node = REXML::XPath.first(
667
+ doc,
668
+ "/p:Response/a:Assertion[@ID=$id]#{subelt}",
669
+ { "p" => PROTOCOL, "a" => ASSERTION },
670
+ { 'id' => doc.signed_element_id }
671
+ )
672
+ node ||= REXML::XPath.first(
673
+ doc,
674
+ "/p:Response[@ID=$id]/a:Assertion#{subelt}",
675
+ { "p" => PROTOCOL, "a" => ASSERTION },
676
+ { 'id' => doc.signed_element_id }
677
+ )
678
+ node
238
679
  end
239
- end
240
- end
680
+
681
+
682
+ end #chiudo classe
683
+
241
684
  end
242
685
  end
@@ -5,12 +5,12 @@ module Spid
5
5
  class Settings
6
6
 
7
7
  attr_accessor :sp_name_qualifier, :sp_name_identifier, :sp_cert, :sp_external_consumer_cert, :sp_private_key, :metadata_signed, :requested_attribute,:requested_attribute_eidas_min, :requested_attribute_eidas_full, :organization
8
- attr_accessor :idp_sso_target_url, :idp_cert_fingerprint, :idp_cert, :idp_slo_target_url, :idp_metadata, :idp_metadata_ttl, :idp_name_qualifier
8
+ attr_accessor :idp_entity_id, :idp_sso_target_url, :idp_cert_fingerprint, :idp_cert, :idp_slo_target_url, :idp_metadata, :idp_metadata_ttl, :idp_name_qualifier
9
9
  attr_accessor :assertion_consumer_service_binding, :assertion_consumer_service_url, :assertion_consumer_service_index, :attribute_consuming_service_index, :hash_assertion_consumer
10
10
  attr_accessor :name_identifier_value, :name_identifier_format
11
11
  attr_accessor :sessionindex, :issuer, :destination_service_url, :authn_context, :requester_identificator
12
12
  attr_accessor :single_logout_service_url, :single_logout_service_binding, :single_logout_destination
13
- attr_accessor :skip_validation
13
+ attr_accessor :skip_validation, :aggregato, :hash_aggregatore
14
14
 
15
15
  def initialize(config = {})
16
16
  config.each do |k,v|
@@ -183,6 +183,43 @@ module Spid
183
183
  def self.uuid
184
184
  RUBY_VERSION < '1.9' ? "_#{@@uuid_generator.generate}" : "_#{SecureRandom.uuid}"
185
185
  end
186
+
187
+
188
+ # Given two strings, attempt to match them as URIs using Rails' parse method. If they can be parsed,
189
+ # then the fully-qualified domain name and the host should performa a case-insensitive match, per the
190
+ # RFC for URIs. If Rails can not parse the string in to URL pieces, return a boolean match of the
191
+ # two strings. This maintains the previous functionality.
192
+ # @return [Boolean]
193
+ def self.uri_match?(destination_url, settings_url)
194
+ dest_uri = URI.parse(destination_url)
195
+ acs_uri = URI.parse(settings_url)
196
+
197
+ if dest_uri.scheme.nil? || acs_uri.scheme.nil? || dest_uri.host.nil? || acs_uri.host.nil?
198
+ raise URI::InvalidURIError
199
+ else
200
+ dest_uri.scheme.downcase == acs_uri.scheme.downcase &&
201
+ dest_uri.host.downcase == acs_uri.host.downcase &&
202
+ dest_uri.path == acs_uri.path &&
203
+ dest_uri.query == acs_uri.query
204
+ end
205
+ rescue URI::InvalidURIError
206
+ original_uri_match?(destination_url, settings_url)
207
+ end
208
+
209
+ # If Rails' URI.parse can't match to valid URL, default back to the original matching service.
210
+ # @return [Boolean]
211
+ def self.original_uri_match?(destination_url, settings_url)
212
+ destination_url == settings_url
213
+ end
214
+
215
+ # Given a REXML::Element instance, return the concatenation of all child text nodes. Assumes
216
+ # that there all children other than text nodes can be ignored (e.g. comments). If nil is
217
+ # passed, nil will be returned.
218
+ def self.element_text(element)
219
+ element.texts.map(&:value).join if element
220
+ end
221
+
222
+
186
223
  end
187
224
  end
188
225
  end
@@ -2,7 +2,7 @@ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'spid-es'
5
- s.version = '0.0.19'
5
+ s.version = '0.0.24'
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Fabiano Pavan"]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spid-es
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.19
4
+ version: 0.0.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fabiano Pavan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-07 00:00:00.000000000 Z
11
+ date: 2020-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: canonix
@@ -73,7 +73,6 @@ extensions: []
73
73
  extra_rdoc_files: []
74
74
  files:
75
75
  - ".document"
76
- - ".travis.yml"
77
76
  - Gemfile
78
77
  - LICENSE
79
78
  - README.md
@@ -141,8 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
140
  - !ruby/object:Gem::Version
142
141
  version: '0'
143
142
  requirements: []
144
- rubyforge_project:
145
- rubygems_version: 2.7.8
143
+ rubygems_version: 3.0.6
146
144
  signing_key:
147
145
  specification_version: 4
148
146
  summary: SAML Ruby Tookit Spid
@@ -1,5 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 1.8.7
4
- - 1.9.3
5
- - ree