spid-es 0.0.19 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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