ciam-es 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/Gemfile +4 -0
  4. data/README.md +127 -0
  5. data/ciam-es.gemspec +23 -0
  6. data/lib/ciam-es.rb +14 -0
  7. data/lib/ciam/ruby-saml/authrequest.rb +206 -0
  8. data/lib/ciam/ruby-saml/coding.rb +34 -0
  9. data/lib/ciam/ruby-saml/error_handling.rb +27 -0
  10. data/lib/ciam/ruby-saml/logging.rb +26 -0
  11. data/lib/ciam/ruby-saml/logout_request.rb +126 -0
  12. data/lib/ciam/ruby-saml/logout_response.rb +132 -0
  13. data/lib/ciam/ruby-saml/metadata.rb +509 -0
  14. data/lib/ciam/ruby-saml/request.rb +81 -0
  15. data/lib/ciam/ruby-saml/response.rb +683 -0
  16. data/lib/ciam/ruby-saml/settings.rb +89 -0
  17. data/lib/ciam/ruby-saml/utils.rb +225 -0
  18. data/lib/ciam/ruby-saml/validation_error.rb +7 -0
  19. data/lib/ciam/ruby-saml/version.rb +5 -0
  20. data/lib/ciam/xml_security.rb +166 -0
  21. data/lib/ciam/xml_security_new.rb +373 -0
  22. data/lib/schemas/saml20assertion_schema.xsd +283 -0
  23. data/lib/schemas/saml20protocol_schema.xsd +302 -0
  24. data/lib/schemas/xenc_schema.xsd +146 -0
  25. data/lib/schemas/xmldsig_schema.xsd +318 -0
  26. data/test/certificates/certificate1 +12 -0
  27. data/test/logoutrequest_test.rb +98 -0
  28. data/test/request_test.rb +53 -0
  29. data/test/response_test.rb +219 -0
  30. data/test/responses/adfs_response_sha1.xml +46 -0
  31. data/test/responses/adfs_response_sha256.xml +46 -0
  32. data/test/responses/adfs_response_sha384.xml +46 -0
  33. data/test/responses/adfs_response_sha512.xml +46 -0
  34. data/test/responses/no_signature_ns.xml +48 -0
  35. data/test/responses/open_saml_response.xml +56 -0
  36. data/test/responses/response1.xml.base64 +1 -0
  37. data/test/responses/response2.xml.base64 +79 -0
  38. data/test/responses/response3.xml.base64 +66 -0
  39. data/test/responses/response4.xml.base64 +93 -0
  40. data/test/responses/response5.xml.base64 +102 -0
  41. data/test/responses/response_with_ampersands.xml +139 -0
  42. data/test/responses/response_with_ampersands.xml.base64 +93 -0
  43. data/test/responses/simple_saml_php.xml +71 -0
  44. data/test/responses/wrapped_response_2.xml.base64 +150 -0
  45. data/test/settings_test.rb +43 -0
  46. data/test/test_helper.rb +65 -0
  47. data/test/xml_security_test.rb +123 -0
  48. metadata +145 -0
@@ -0,0 +1,26 @@
1
+ # Simplistic log class when we're running in Rails
2
+ module Ciam
3
+ module Saml
4
+ class Logging
5
+ def self.debug(message)
6
+ return if !!ENV["ruby-saml/testing"]
7
+
8
+ if defined? Rails
9
+ Rails.logger.debug message
10
+ else
11
+ puts message
12
+ end
13
+ end
14
+
15
+ def self.info(message)
16
+ return if !!ENV["ruby-saml/testing"]
17
+
18
+ if defined? Rails
19
+ Rails.logger.info message
20
+ else
21
+ puts message
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,126 @@
1
+ require 'uuid'
2
+
3
+ module Ciam::Saml
4
+ class LogoutRequest
5
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
6
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
7
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
8
+
9
+ include Coding
10
+ include Request
11
+ attr_reader :transaction_id
12
+ attr_accessor :settings
13
+
14
+ def initialize( options = {} )
15
+ opt = { :request => nil, :settings => nil }.merge(options)
16
+ @settings = opt[:settings]
17
+ @issue_instant = Ciam::Saml::LogoutRequest.timestamp
18
+ @request_params = Hash.new
19
+ # We need to generate a LogoutRequest to send to the IdP
20
+ if opt[:request].nil?
21
+ @transaction_id = UUID.new.generate
22
+ # The IdP sent us a LogoutRequest (IdP initiated SLO)
23
+ else
24
+ begin
25
+ @request = Ciam::XMLSecurity::SignedDocument.new( decode( opt[:request] ))
26
+ raise if @request.nil?
27
+ raise if @request.root.nil?
28
+ raise if @request.root.namespace != PROTOCOL
29
+ rescue
30
+ @request = Ciam::XMLSecurity::SignedDocument.new( inflate( decode( opt[:request] ) ) )
31
+ end
32
+ Logging.debug "LogoutRequest is: \n#{@request}"
33
+ end
34
+ end
35
+
36
+ def create( options = {} )
37
+ opt = { :name_id => nil, :session_index => nil, :extra_parameters => nil }.merge(options)
38
+ return nil unless opt[:name_id]
39
+
40
+ @request = REXML::Document.new
41
+ @request.context[:attribute_quote] = :quote
42
+
43
+
44
+ root = @request.add_element "saml2p:LogoutRequest", { "xmlns:saml2p" => PROTOCOL }
45
+ root.attributes['ID'] = @transaction_id
46
+ root.attributes['IssueInstant'] = @issue_instant
47
+ root.attributes['Version'] = "2.0"
48
+ root.attributes['Destination'] = @settings.single_logout_destination
49
+
50
+ issuer = root.add_element "saml2:Issuer", { "xmlns:saml2" => ASSERTION }
51
+ issuer.attributes['Format'] = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
52
+ #issuer.text = @settings.issuer
53
+ #per la federazione trentina qui ci vanno i metadati...
54
+ issuer.text = @settings.idp_metadata
55
+
56
+ name_id = root.add_element "saml2:NameID", { "xmlns:saml2" => ASSERTION }
57
+ name_id.attributes['Format'] = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
58
+ name_id.attributes['NameQualifier'] = @settings.idp_name_qualifier
59
+ name_id.text = opt[:name_id]
60
+ # I believe the rest of these are optional
61
+ if @settings && @settings.sp_name_qualifier
62
+ name_id.attributes["SPNameQualifier"] = @settings.sp_name_qualifier
63
+ end
64
+ if opt[:session_index]
65
+ session_index = root.add_element "saml2p:SessionIndex" #, { "xmlns:samlp" => PROTOCOL }
66
+ session_index.text = opt[:session_index]
67
+ end
68
+ Logging.debug "Created LogoutRequest: #{@request}"
69
+ meta = Metadata.new(@settings)
70
+ return meta.create_slo_request( to_s, opt[:extra_parameters] )
71
+ #action, content = binding_select("SingleLogoutService")
72
+ #Logging.debug "action: #{action} content: #{content}"
73
+ #return [action, content]
74
+ end
75
+
76
+ # function to return the created request as an XML document
77
+ def to_xml
78
+ text = ""
79
+ @request.write(text, 1)
80
+ return text
81
+ end
82
+ def to_s
83
+ @request.to_s
84
+ end
85
+ # Functions for pulling values out from an IdP initiated LogoutRequest
86
+ def name_id
87
+ element = REXML::XPath.first(@request, "/p:LogoutRequest/a:NameID", {
88
+ "p" => PROTOCOL, "a" => ASSERTION } )
89
+ return nil if element.nil?
90
+ # Can't seem to get this to work right...
91
+ #element.context[:compress_whitespace] = ["NameID"]
92
+ #element.context[:compress_whitespace] = :all
93
+ str = element.text.gsub(/^\s+/, "")
94
+ str.gsub!(/\s+$/, "")
95
+ return str
96
+ end
97
+
98
+ def transaction_id
99
+ return @transaction_id if @transaction_id
100
+ element = REXML::XPath.first(@request, "/p:LogoutRequest", {
101
+ "p" => PROTOCOL} )
102
+ return nil if element.nil?
103
+ return element.attributes["ID"]
104
+ end
105
+ def is_valid?
106
+ validate(soft = true)
107
+ end
108
+
109
+ def validate!
110
+ validate( soft = false )
111
+ end
112
+ def validate( soft = true )
113
+ return false if @request.nil?
114
+ return false if @request.validate(@settings, soft) == false
115
+
116
+ return true
117
+
118
+ end
119
+ private
120
+
121
+ def self.timestamp
122
+ Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,132 @@
1
+ #encoding: utf-8
2
+
3
+ require "rexml/document"
4
+
5
+ module Ciam
6
+ module Saml
7
+ class LogoutResponse
8
+ include Coding
9
+ include Request
10
+ ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
11
+ PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
12
+ DSIG = "http://www.w3.org/2000/09/xmldsig#"
13
+
14
+ def initialize( options = { } )
15
+ opt = { :response => nil, :settings => nil }.merge(options)
16
+ # We've recieved a LogoutResponse from the IdP
17
+ if opt[:response]
18
+ begin
19
+ @response = Ciam::XMLSecurity::SignedDocument.new(decode( opt[:response] ))
20
+ # Check to see if we have a root tag using the "protocol" namespace.
21
+ # If not, it means this is deflated text and we need to raise to
22
+ # the rescue below
23
+ raise if @response.nil?
24
+ raise if @response.root.nil?
25
+ raise if @response.root.namespace != PROTOCOL
26
+ document
27
+ rescue
28
+ @response = Ciam::XMLSecurity::SignedDocument.new( inflate(decode( opt[:response] ) ) )
29
+ end
30
+ end
31
+ # We plan to create() a new LogoutResponse
32
+ if opt[:settings]
33
+ @settings = opt[:settings]
34
+ end
35
+ end
36
+
37
+ # Create a LogoutResponse to to the IdP's LogoutRequest
38
+ # (For IdP initiated SLO)
39
+ def create( options )
40
+ opt = { :transaction_id => nil,
41
+ :in_response_to => nil,
42
+ :status => "urn:oasis:names:tc:SAML:2.0:status:Success",
43
+ :extra_parameters => nil }.merge(options)
44
+ return nil if opt[:transaction_id].nil?
45
+ @response = REXML::Document.new
46
+ @response.context[:attribute_quote] = :quote
47
+ uuid = "_" + UUID.new.generate
48
+ time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
49
+ root = @response.add_element "saml2p:LogoutResponse", { "xmlns:saml2p" => PROTOCOL }
50
+ root.attributes['ID'] = uuid
51
+ root.attributes['IssueInstant'] = time
52
+ root.attributes['Version'] = "2.0"
53
+ # Just convenient naming to accept both names as InResponseTo
54
+ if opt[:transaction_id]
55
+ root.attributes['InResponseTo'] = opt[:transaction_id]
56
+ elsif opt[:in_response_to]
57
+ root.attributes['InResponseTo'] = opt[:in_response_to]
58
+ end
59
+ if opt[:status]
60
+ status = root.add_element "saml2p:Status"
61
+ status_code = status.add_element "saml2p:StatusCode", {
62
+ "Value" => opt[:status]
63
+ }
64
+ end
65
+ if @settings && @settings.issuer
66
+ issuer = root.add_element "saml:Issuer", {
67
+ "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion"
68
+ }
69
+ issuer.text = @settings.issuer
70
+ end
71
+ meta = Metadata.new( @settings )
72
+ Logging.debug "Created LogoutResponse:\n#{@response}"
73
+ return meta.create_slo_response( to_s, opt[:extra_parameters] )
74
+
75
+ #root.attributes['Destination'] = action
76
+
77
+ end
78
+ # function to return the created request as an XML document
79
+ def to_xml
80
+ text = ""
81
+ @response.write(text, 1)
82
+ return text
83
+ end
84
+ def to_s
85
+ @response.to_s
86
+ end
87
+
88
+ def issuer
89
+ element = REXML::XPath.first(@response, "/p:LogoutResponse/a:Issuer", {
90
+ "p" => PROTOCOL, "a" => ASSERTION} )
91
+ return nil if element.nil?
92
+ element.text
93
+ end
94
+
95
+ def in_response_to
96
+ element = REXML::XPath.first(@response, "/p:LogoutResponse", {
97
+ "p" => PROTOCOL })
98
+ return nil if element.nil?
99
+ element.attributes["InResponseTo"]
100
+ end
101
+
102
+ def success?
103
+ element = REXML::XPath.first(@response, "/p:LogoutResponse/p:Status/p:StatusCode", {
104
+ "p" => PROTOCOL })
105
+ return false if element.nil?
106
+ element.attributes["Value"] == "urn:oasis:names:tc:SAML:2.0:status:Success"
107
+
108
+ end
109
+ def is_valid?
110
+ validate(soft = true)
111
+ end
112
+
113
+ def validate!
114
+ validate( soft = false )
115
+ end
116
+ def validate( soft = true )
117
+ return false if @response.nil?
118
+ # Skip validation with a failed response if we don't have settings
119
+ return false if @settings.nil?
120
+ return false if @response.validate(@settings, soft) == false
121
+
122
+ return true
123
+
124
+ end
125
+
126
+ protected
127
+ def document
128
+ REXML::Document.new(@response)
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,509 @@
1
+ require "rexml/document"
2
+ require "rexml/xpath"
3
+ require "net/https"
4
+ require "uri"
5
+ require "digest/md5"
6
+ require "nokogiri"
7
+ require_relative "../xml_security_new" #fa il require della nokogiri
8
+
9
+ # Class to return SP metadata based on the settings requested.
10
+ # Return this XML in a controller, then give that URL to the the
11
+ # IdP administrator. The IdP will poll the URL and your settings
12
+ # will be updated automatically
13
+ module Ciam
14
+ module Saml
15
+ class Metadata
16
+ include REXML
17
+ include Coding
18
+ # a few symbols for SAML class names
19
+ HTTP_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
20
+ HTTP_GET = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
21
+
22
+ attr_accessor :uuid
23
+
24
+ def initialize(settings=nil)
25
+ if settings
26
+ @settings = settings
27
+ end
28
+ end
29
+
30
+ def generate(settings)
31
+ #meta_doc = REXML::Document.new
32
+ meta_doc = Ciam::XMLSecurityNew::Document.new
33
+ if settings.aggregato
34
+ root = meta_doc.add_element "md:EntityDescriptor", {
35
+ "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata",
36
+ "xmlns:xml" => "http://www.w3.org/XML/1998/namespace",
37
+ "xmlns:ciam" => "https://ciam.gov.it/saml-extensions",
38
+ }
39
+ else
40
+ root = meta_doc.add_element "md:EntityDescriptor", {
41
+ "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata",
42
+ "xmlns:xml" => "http://www.w3.org/XML/1998/namespace"
43
+ }
44
+ end
45
+
46
+ if settings.issuer != nil
47
+ root.attributes["entityID"] = settings.issuer
48
+ end
49
+ #Tolto per non far cambiare sempre il metadata
50
+ #uuid = "_" + UUID.new.generate
51
+ #genero l'id come hash dell'entityID
52
+ uuid = "_"+Digest::MD5.hexdigest(settings.issuer)
53
+ self.uuid = uuid
54
+ root.attributes["ID"] = uuid
55
+
56
+ sp_sso = root.add_element "md:SPSSODescriptor", {
57
+ "protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
58
+ "WantAssertionsSigned" => "true",
59
+ "AuthnRequestsSigned" => "true"
60
+
61
+ }
62
+
63
+
64
+ # if settings.sp_cert != nil
65
+ # keyDescriptor = sp_sso.add_element "md:KeyDescriptor", {
66
+ # "use" => "signing"
67
+ # }
68
+ # keyInfo = keyDescriptor.add_element "ds:KeyInfo", {
69
+ # "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"
70
+ # }
71
+ # x509Data = keyInfo.add_element "ds:X509Data"
72
+ # x509Certificate = x509Data.add_element "ds:X509Certificate"
73
+ # file = ""
74
+ # File.foreach(settings.sp_cert){ |line|
75
+ # file += line unless (line.include?("RSA PUBLIC KEY") || line.include?("CERTIFICATE"))
76
+ # }
77
+ # x509Certificate.text = file
78
+ # end
79
+
80
+ # Add KeyDescriptor if messages will be signed / encrypted
81
+ #cert = settings.get_sp_cert
82
+ cert = settings.get_cert(settings.sp_cert)
83
+ if cert
84
+
85
+ if cert.is_a?(String)
86
+ cert = OpenSSL::X509::Certificate.new(cert)
87
+ end
88
+
89
+ cert_text = Base64.encode64(cert.to_der).to_s.gsub(/\n/, "").gsub(/\t/, "")
90
+ kd = sp_sso.add_element "md:KeyDescriptor", { "use" => "signing" }
91
+ ki = kd.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
92
+ xd = ki.add_element "ds:X509Data"
93
+ xc = xd.add_element "ds:X509Certificate"
94
+ xc.text = cert_text
95
+
96
+ # kd2 = sp_sso.add_element "md:KeyDescriptor", { "use" => "encryption" }
97
+ # ki2 = kd2.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
98
+ # xd2 = ki2.add_element "ds:X509Data"
99
+ # xc2 = xd2.add_element "ds:X509Certificate"
100
+ # xc2.text = cert_text
101
+ end
102
+
103
+ if !settings.sp_external_consumer_cert.nil? && settings.sp_external_consumer_cert.length > 0
104
+ settings.sp_external_consumer_cert.each{ |cert_cons_external|
105
+ cert_ex = settings.get_cert(cert_cons_external)
106
+ if cert_ex
107
+
108
+ if cert_ex.is_a?(String)
109
+ cert_ex = OpenSSL::X509::Certificate.new(cert_ex)
110
+ end
111
+
112
+ cert_text = Base64.encode64(cert_ex.to_der).to_s.gsub(/\n/, "").gsub(/\t/, "")
113
+ kd = sp_sso.add_element "md:KeyDescriptor", { "use" => "signing" }
114
+ ki = kd.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
115
+ xd = ki.add_element "ds:X509Data"
116
+ xc = xd.add_element "ds:X509Certificate"
117
+ xc.text = cert_text
118
+ end
119
+ }
120
+ end
121
+
122
+ if settings.single_logout_service_url != nil
123
+ sp_sso.add_element "md:SingleLogoutService", {
124
+ "Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
125
+ "Location" => settings.single_logout_service_url
126
+ }
127
+ sp_sso.add_element "md:SingleLogoutService", {
128
+ "Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
129
+ "Location" => settings.single_logout_service_url
130
+ }
131
+ end
132
+
133
+ #Logout dei servizi esterni
134
+ unless settings.hash_assertion_consumer.blank?
135
+ settings.hash_assertion_consumer.each_pair{ |index, hash_service|
136
+ unless hash_service['logout'].blank?
137
+ sp_sso.add_element "md:SingleLogoutService", {
138
+ "Binding" => hash_service['logout']['binding'] || "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
139
+ "Location" => hash_service['logout']['location']
140
+ }
141
+ end
142
+ }
143
+ end
144
+
145
+ name_identifier_formats = settings.name_identifier_format
146
+ if name_identifier_formats != nil
147
+ name_id = []
148
+ name_identifier_formats.each_with_index{ |format, index|
149
+ name_id[index] = sp_sso.add_element "md:NameIDFormat"
150
+ name_id[index].text = format
151
+ }
152
+
153
+ end
154
+
155
+ if settings.assertion_consumer_service_url
156
+
157
+ #ciclo e creo i vari tag AssertionConsumerService
158
+ settings.hash_assertion_consumer.each_pair{ |index, hash_service|
159
+
160
+ sp_sso.add_element "md:AssertionConsumerService", {
161
+ "Binding" => settings.assertion_consumer_service_binding,
162
+ "Location" => (hash_service['external'] ? hash_service['url_consumer'] : settings.assertion_consumer_service_url ),
163
+ "isDefault" => hash_service['default'],
164
+ "index" => index
165
+ }
166
+ }
167
+
168
+ # #Caso con eidas
169
+ # sp_sso.add_element "md:AssertionConsumerService", {
170
+ # "Binding" => settings.assertion_consumer_service_binding,
171
+ # "Location" => settings.assertion_consumer_service_url,
172
+ # "index" => 99
173
+ # }
174
+
175
+ # sp_sso.add_element "md:AssertionConsumerService", {
176
+ # "Binding" => settings.assertion_consumer_service_binding,
177
+ # "Location" => settings.assertion_consumer_service_url,
178
+ # "index" => 100
179
+ # }
180
+
181
+ settings.hash_assertion_consumer.each_pair{ |index, hash_service|
182
+
183
+ #AttributeConsumingService
184
+ attr_cons_service = sp_sso.add_element "md:AttributeConsumingService", {
185
+ "index" => index,
186
+ }
187
+ service_name = attr_cons_service.add_element "md:ServiceName", {
188
+ "xml:lang" => "it"
189
+ }
190
+ service_name.text = hash_service['testo']
191
+ unless hash_service['description'].blank?
192
+ service_description = attr_cons_service.add_element "md:ServiceDescription", {
193
+ "xml:lang" => "it"
194
+ }
195
+ service_description.text = hash_service['description']
196
+ end
197
+
198
+ if hash_service['array_campi'].is_a?(Array)
199
+ hash_service['array_campi'].each_with_index{ |attribute, index|
200
+ attr_cons_service.add_element "md:RequestedAttribute", {
201
+ "Name" => attribute
202
+ }
203
+ }
204
+ else #hash
205
+ hash_service['array_campi'].each_pair{ |attribute, name_format|
206
+ attr_cons_service.add_element "md:RequestedAttribute", {
207
+ "Name" => attribute,
208
+ "NameFormat" => name_format
209
+ }
210
+ }
211
+ end
212
+ }
213
+
214
+
215
+ end
216
+ #organization
217
+ organization = root.add_element "md:Organization"
218
+ org_name = organization.add_element "md:OrganizationName", {
219
+ "xml:lang" => "it"
220
+ }
221
+ org_name.text = settings.organization['org_name']
222
+ org_display_name = organization.add_element "md:OrganizationDisplayName", {
223
+ "xml:lang" => "it"
224
+ }
225
+
226
+ org_display_name.text = settings.organization['org_display_name']+(settings.aggregato ? " tramite #{settings.hash_aggregatore['soggetto_aggregatore']}" : '')
227
+ org_url = organization.add_element "md:OrganizationURL", {
228
+ "xml:lang" => "it"
229
+ }
230
+ org_url.text = settings.organization['org_url']
231
+
232
+ #ContactPerson per sp aggregato
233
+ if settings.aggregato
234
+ contact_person_aggregatore = root.add_element "md:ContactPerson", {
235
+ "contactType" => "other",
236
+ "ciam:entityType" => "ciam:aggregator"
237
+ }
238
+ company = contact_person_aggregatore.add_element "md:Company"
239
+ company.text = settings.hash_aggregatore['soggetto_aggregatore']
240
+
241
+ extensions_aggregatore = contact_person_aggregatore.add_element "md:Extensions"
242
+ vat_number_aggregatore = extensions_aggregatore.add_element "ciam:VATNumber"
243
+ vat_number_aggregatore.text = settings.hash_aggregatore['piva_aggregatore']
244
+
245
+ ipa_code_aggregatore = extensions_aggregatore.add_element "ciam:IPACode"
246
+ ipa_code_aggregatore.text = settings.hash_aggregatore['cipa_aggregatore']
247
+
248
+ fiscal_code_aggregatore = extensions_aggregatore.add_element "ciam:FiscalCode"
249
+ fiscal_code_aggregatore.text = settings.hash_aggregatore['cf_aggregatore']
250
+
251
+ contact_person_aggregato = root.add_element "md:ContactPerson", {
252
+ "contactType" => "other",
253
+ "ciam:entityType" => "ciam:aggregated"
254
+ }
255
+ company = contact_person_aggregato.add_element "md:Company"
256
+ company.text = settings.organization['org_name']
257
+
258
+ extensions_aggregato = contact_person_aggregato.add_element "md:Extensions"
259
+ unless settings.hash_aggregatore['soggetto_aggregato']['vat_number'].blank?
260
+ vat_number_aggregato = extensions_aggregato.add_element "ciam:VATNumber"
261
+ vat_number_aggregato.text = settings.hash_aggregatore['soggetto_aggregato']['vat_number']
262
+ end
263
+ unless settings.hash_aggregatore['soggetto_aggregato']['ipa_code'].blank?
264
+ ipa_code_aggregato = extensions_aggregato.add_element "ciam:IPACode"
265
+ ipa_code_aggregato.text = settings.hash_aggregatore['soggetto_aggregato']['ipa_code']
266
+ end
267
+ unless settings.hash_aggregatore['soggetto_aggregato']['fiscal_code'].blank?
268
+ fiscal_code_aggregato = extensions_aggregato.add_element "ciam:FiscalCode"
269
+ fiscal_code_aggregato.text = settings.hash_aggregatore['soggetto_aggregato']['fiscal_code']
270
+ end
271
+ end
272
+
273
+ #meta_doc << REXML::XMLDecl.new(version='1.0', encoding='UTF-8')
274
+ meta_doc << REXML::XMLDecl.new("1.0", "UTF-8")
275
+
276
+
277
+ #SE SERVE ANCHE ENCRYPTION
278
+ # # Add KeyDescriptor if messages will be signed / encrypted
279
+ #
280
+ # if cert
281
+ # cert_text = Base64.encode64(cert.to_der).gsub("\n", '')
282
+ # kd = sp_sso.add_element "md:KeyDescriptor", { "use" => "signing" }
283
+ # ki = kd.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
284
+ # xd = ki.add_element "ds:X509Data"
285
+ # xc = xd.add_element "ds:X509Certificate"
286
+ # xc.text = cert_text
287
+
288
+ # kd2 = sp_sso.add_element "md:KeyDescriptor", { "use" => "encryption" }
289
+ # ki2 = kd2.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
290
+ # xd2 = ki2.add_element "ds:X509Data"
291
+ # xc2 = xd2.add_element "ds:X509Certificate"
292
+ # xc2.text = cert_text
293
+ # end
294
+
295
+ #cert = settings.get_sp_cert
296
+ cert = settings.get_cert(settings.sp_cert) #inserisco il certificato principale
297
+ # embed signature
298
+ if settings.metadata_signed && settings.sp_private_key && settings.sp_cert
299
+ private_key = settings.get_sp_key
300
+ meta_doc.sign_document(private_key, cert)
301
+ end
302
+ ret = ""
303
+ # stampo come stringa semplice i metadata per non avere problemi con validazione firma
304
+ ret = meta_doc.to_s
305
+ #Logging.debug "Generated metadata:\n#{ret}"
306
+
307
+ return ret
308
+
309
+ end
310
+
311
+ def create_sso_request(message, extra_parameters = {} )
312
+ build_message( :type => "SAMLRequest",
313
+ :service => "SingleSignOnService",
314
+ :message => message, :extra_parameters => extra_parameters)
315
+ end
316
+ def create_sso_response(message, extra_parameters = {} )
317
+ build_message( :type => "SAMLResponse",
318
+ :service => "SingleSignOnService",
319
+ :message => message, :extra_parameters => extra_parameters)
320
+ end
321
+ def create_slo_request(message, extra_parameters = {} )
322
+ build_message( :type => "SAMLRequest",
323
+ :service => "SingleLogoutService",
324
+ :message => message, :extra_parameters => extra_parameters)
325
+ end
326
+ def create_slo_response(message, extra_parameters = {} )
327
+ build_message( :type => "SAMLResponse",
328
+ :service => "SingleLogoutService",
329
+ :message => message, :extra_parameters => extra_parameters)
330
+ end
331
+
332
+ # Construct a SAML message using information in the IdP metadata.
333
+ # :type can be either "SAMLRequest" or "SAMLResponse"
334
+ # :service refers to the Binding method,
335
+ # either "SingleLogoutService" or "SingleSignOnService"
336
+ # :message is the SAML message itself (XML)
337
+ # I've provided easy to use wrapper functions above
338
+ def build_message( options = {} )
339
+ opt = { :type => nil, :service => nil, :message => nil, :extra_parameters => nil }.merge(options)
340
+ url = binding_select( opt[:service] )
341
+ return message_get( opt[:type], url, opt[:message], opt[:extra_parameters] )
342
+ end
343
+
344
+ # get the IdP metadata, and select the appropriate SSO binding
345
+ # that we can support. Currently this is HTTP-Redirect and HTTP-POST
346
+ # but more could be added in the future
347
+ def binding_select(service)
348
+ # first check if we're still using the old hard coded method for
349
+ # backwards compatability
350
+ if service == "SingleSignOnService" && @settings.idp_sso_target_url != nil
351
+ return @settings.idp_sso_target_url
352
+ end
353
+ if service == "SingleLogoutService" && @settings.idp_slo_target_url != nil
354
+ return @settings.idp_slo_target_url
355
+ end
356
+
357
+ meta_doc = get_idp_metadata
358
+
359
+ return nil unless meta_doc
360
+ # first try GET (REDIRECT)
361
+ sso_element = REXML::XPath.first(meta_doc, "/EntityDescriptor/IDPSSODescriptor/#{service}[@Binding='#{HTTP_GET}']")
362
+ if !sso_element.nil?
363
+ @URL = sso_element.attributes["Location"]
364
+ Logging.debug "binding_select: GET from #{@URL}"
365
+ return @URL
366
+ end
367
+
368
+ # next try post
369
+ sso_element = REXML::XPath.first(meta_doc, "/EntityDescriptor/IDPSSODescriptor/#{service}[@Binding='#{HTTP_POST}']")
370
+ if !sso_element.nil?
371
+ @URL = sso_element.attributes["Location"]
372
+ #Logging.debug "binding_select: POST to #{@URL}"
373
+ return @URL
374
+ end
375
+
376
+ # other types we might want to add in the future: SOAP, Artifact
377
+ end
378
+
379
+
380
+ def fetch(uri_str, limit = 10)
381
+ # You should choose a better exception.
382
+ raise ArgumentError, 'too many HTTP redirects' if limit == 0
383
+
384
+ uri = URI.parse(uri_str)
385
+ if uri.scheme == "http"
386
+ response = Net::HTTP.get_response(uri)
387
+ elsif uri.scheme == "https"
388
+ http = Net::HTTP.new(uri.host, uri.port)
389
+ http.use_ssl = true
390
+ # Most IdPs will probably use self signed certs
391
+ #http.verify_mode = OpenSSL::SSL::VERIFY_PEER
392
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
393
+ get = Net::HTTP::Get.new(uri.request_uri)
394
+ response = http.request(get)
395
+ end
396
+
397
+ case response
398
+ when Net::HTTPSuccess then
399
+ response
400
+ when Net::HTTPRedirection then
401
+ location = response['location']
402
+ warn "redirected to #{location}"
403
+ fetch(location, limit - 1)
404
+ else
405
+ response.value
406
+ end
407
+ end
408
+
409
+
410
+
411
+ # Retrieve the remote IdP metadata from the URL or a cached copy
412
+ # returns a REXML document of the metadata
413
+ def get_idp_metadata
414
+ return false if @settings.idp_metadata.nil?
415
+
416
+ # Look up the metdata in cache first
417
+ id = Digest::MD5.hexdigest(@settings.idp_metadata)
418
+ response = fetch(@settings.idp_metadata)
419
+ #meta_text = response.body
420
+ #testo_response = meta_text.sub!(' xmlns:xml="http://www.w3.org/XML/1998/namespace"', '') da errori
421
+ #uso nokogiri per cercare il certificato, uso la funzione che rimuove tutti i namespace
422
+ doc_noko = Nokogiri::XML(response.body.gsub(/\n/, "").gsub(/\t/, "")) #modifica per poste
423
+ doc_noko.remove_namespaces!
424
+ extract_certificate(doc_noko)
425
+ doc_rexml = REXML::Document.new(doc_noko.to_xml)
426
+
427
+ return doc_rexml
428
+
429
+ # USE OF CACHE WITH CERTIFICATE
430
+ # lookup = @cache.read(id)
431
+ # if lookup != nil
432
+ # Logging.debug "IdP metadata cached lookup for #{@settings.idp_metadata}"
433
+ # doc = REXML::Document.new( lookup )
434
+ # extract_certificate( doc )
435
+ # return doc
436
+ # end
437
+
438
+ # Logging.debug "IdP metadata cache miss on #{@settings.idp_metadata}"
439
+ # # cache miss
440
+ # if File.exists?(@settings.idp_metadata)
441
+ # fp = File.open( @settings.idp_metadata, "r")
442
+ # meta_text = fp.read
443
+ # else
444
+ # uri = URI.parse(@settings.idp_metadata)
445
+ # if uri.scheme == "http"
446
+ # response = Net::HTTP.get_response(uri)
447
+ # meta_text = response.body
448
+ # elsif uri.scheme == "https"
449
+ # http = Net::HTTP.new(uri.host, uri.port)
450
+ # http.use_ssl = true
451
+ # # Most IdPs will probably use self signed certs
452
+ # #http.verify_mode = OpenSSL::SSL::VERIFY_PEER
453
+ # http.verify_mode = OpenSSL::SSL::VERIFY_NONE
454
+ # get = Net::HTTP::Get.new(uri.request_uri)
455
+ # response = http.request(get)
456
+ # meta_text = response.body
457
+ # end
458
+ # end
459
+ # # Add it to the cache
460
+ # @cache.write(id, meta_text, @settings.idp_metadata_ttl )
461
+ # doc = REXML::Document.new( meta_text )
462
+ # extract_certificate(doc)
463
+ # return doc
464
+ end
465
+
466
+ def extract_certificate(meta_doc)
467
+ #ricerco il certificato con nokogiri
468
+ # pull out the x509 tag
469
+ x509 = meta_doc.xpath("//EntityDescriptor//IDPSSODescriptor//KeyDescriptor//KeyInfo//X509Data//X509Certificate")
470
+
471
+ #x509 = REXML::XPath.first(meta_doc, "/md:EntityDescriptor/md:IDPSSODescriptor"+"/md:KeyDescriptor"+"/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
472
+ # If the IdP didn't specify the use attribute
473
+ if x509.nil?
474
+ x509 = meta_doc.xpath("//EntityDescriptor//IDPSSODescriptor//KeyDescriptor//KeyInfo//X509Data//X509Certificate")
475
+ # x509 = REXML::XPath.first(meta_doc,
476
+ # "/EntityDescriptor/IDPSSODescriptor" +
477
+ # "/KeyDescriptor" +
478
+ # "/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
479
+ # )
480
+ end
481
+ @settings.idp_cert = x509.children.to_s.gsub(/\n/, "").gsub(/\t/, "")
482
+ end
483
+
484
+ # construct the parameter list on the URL and return
485
+ def message_get( type, url, message, extra_parameters = {} )
486
+ params = Hash.new
487
+ if extra_parameters
488
+ params.merge!(extra_parameters)
489
+ end
490
+ # compress GET requests to try and stay under that 8KB request limit
491
+ #deflate of samlrequest
492
+ params[type] = encode( deflate( message ) )
493
+ #Logging.debug "#{type}=#{params[type]}"
494
+
495
+ uri = Addressable::URI.parse(url)
496
+ if uri.query_values == nil
497
+ uri.query_values = params
498
+ else
499
+ # solution to stevenwilkin's parameter merge
500
+ uri.query_values = params.merge(uri.query_values)
501
+ end
502
+ url = uri.to_s
503
+ #Logging.debug "Sending to URL #{url}"
504
+ return url
505
+ end
506
+
507
+ end
508
+ end
509
+ end