spid-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 +15 -0
  2. data/.document +5 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +12 -0
  5. data/LICENSE +19 -0
  6. data/README.md +124 -0
  7. data/Rakefile +41 -0
  8. data/lib/schemas/saml20assertion_schema.xsd +283 -0
  9. data/lib/schemas/saml20protocol_schema.xsd +302 -0
  10. data/lib/schemas/xenc_schema.xsd +146 -0
  11. data/lib/schemas/xmldsig_schema.xsd +318 -0
  12. data/lib/spid/ruby-saml/authrequest.rb +196 -0
  13. data/lib/spid/ruby-saml/coding.rb +34 -0
  14. data/lib/spid/ruby-saml/logging.rb +26 -0
  15. data/lib/spid/ruby-saml/logout_request.rb +126 -0
  16. data/lib/spid/ruby-saml/logout_response.rb +132 -0
  17. data/lib/spid/ruby-saml/metadata.rb +353 -0
  18. data/lib/spid/ruby-saml/request.rb +81 -0
  19. data/lib/spid/ruby-saml/response.rb +202 -0
  20. data/lib/spid/ruby-saml/settings.rb +72 -0
  21. data/lib/spid/ruby-saml/validation_error.rb +7 -0
  22. data/lib/spid/ruby-saml/version.rb +5 -0
  23. data/lib/spid-es.rb +14 -0
  24. data/lib/xml_security.rb +165 -0
  25. data/spid-es.gemspec +23 -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 +158 -0
@@ -0,0 +1,126 @@
1
+ require 'uuid'
2
+
3
+ module Spid::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 = Spid::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 = 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 = 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 Spid
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 = 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 = 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,353 @@
1
+ require "rexml/document"
2
+ require "rexml/xpath"
3
+ require "net/https"
4
+ require "uri"
5
+ require "digest/md5"
6
+ require "nokogiri"
7
+ require "xml_security_new" #fa il require della nokogiri
8
+
9
+
10
+ # Class to return SP metadata based on the settings requested.
11
+ # Return this XML in a controller, then give that URL to the the
12
+ # IdP administrator. The IdP will poll the URL and your settings
13
+ # will be updated automatically
14
+ module Spid
15
+ module Saml
16
+ class Metadata
17
+ include REXML
18
+ include Coding
19
+ # a few symbols for SAML class names
20
+ HTTP_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
21
+ HTTP_GET = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
22
+
23
+ def initialize(settings=nil)
24
+ if settings
25
+ @settings = settings
26
+ end
27
+ end
28
+
29
+ def generate(settings)
30
+ #meta_doc = REXML::Document.new
31
+ meta_doc = ::XMLSecurityNew::Document.new
32
+ root = meta_doc.add_element "md:EntityDescriptor", {
33
+ "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata",
34
+ "xmlns:xml" => "http://www.w3.org/XML/1998/namespace",
35
+ "cacheDuration" => "P1M"
36
+ }
37
+ if settings.issuer != nil
38
+ root.attributes["entityID"] = settings.issuer
39
+ end
40
+ sp_sso = root.add_element "md:SPSSODescriptor", {
41
+ "protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
42
+ #"WantAssertionsSigned" => "true",
43
+ "AuthnRequestSigned" => "true"
44
+
45
+ }
46
+ name_identifier_formats = settings.name_identifier_format
47
+ if name_identifier_formats != nil
48
+ name_id = []
49
+ name_identifier_formats.each_with_index{ |format, index|
50
+ name_id[index] = sp_sso.add_element "md:NameIDFormat"
51
+ name_id[index].text = format
52
+ }
53
+
54
+ end
55
+ if settings.sp_cert != nil
56
+ keyDescriptor = sp_sso.add_element "md:KeyDescriptor", {
57
+ "use" => "signing"
58
+ }
59
+ keyInfo = keyDescriptor.add_element "ds:KeyInfo", {
60
+ "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"
61
+ }
62
+ x509Data = keyInfo.add_element "ds:X509Data"
63
+ x509Certificate = x509Data.add_element "ds:X509Certificate"
64
+ file = ""
65
+ File.foreach(settings.sp_cert){ |line|
66
+ file += line unless (line.include?("RSA PUBLIC KEY") || line.include?("CERTIFICATE"))
67
+ }
68
+ x509Certificate.text = file
69
+ end
70
+ if settings.assertion_consumer_service_url != nil
71
+ sp_sso.add_element "md:AssertionConsumerService", {
72
+ # Add this as a setting to create different bindings?
73
+ "Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
74
+ "Location" => settings.assertion_consumer_service_url,
75
+ "index" => "0",
76
+ "isDefault" => "true"
77
+ }
78
+ end
79
+ if settings.single_logout_service_url != nil
80
+ sp_sso.add_element "md:SingleLogoutService", {
81
+ "Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
82
+ "Location" => settings.single_logout_service_url
83
+ }
84
+ sp_sso.add_element "md:SingleLogoutService", {
85
+ "Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
86
+ "Location" => settings.single_logout_service_url
87
+ }
88
+ end
89
+ #AttributeConsumingService
90
+ attr_cons_service = sp_sso.add_element "md:AttributeConsumingService", {
91
+ "index" => "0",
92
+ "ServiceName" => "user_data"
93
+ }
94
+ settings.requested_attribute.each_with_index{ |attribute, index|
95
+ attr_cons_service.add_element "md:RequestedAttribute", {
96
+ "Name" => attribute
97
+ }
98
+ }
99
+
100
+ #organization
101
+ organization = root.add_element "md:Organization"
102
+ org_name = organization.add_element "md:OrganizationName", {
103
+ "xml:lang" => "it"
104
+ }
105
+ org_name.text = settings.organization['org_name']
106
+ org_display_name = organization.add_element "md:OrganizationDisplayName", {
107
+ "xml:lang" => "it"
108
+ }
109
+ org_display_name.text = settings.organization['org_display_name']
110
+ org_url = organization.add_element "md:OrganizationURL", {
111
+ "xml:lang" => "it"
112
+ }
113
+ org_url.text = settings.organization['org_url']
114
+
115
+ #meta_doc << REXML::XMLDecl.new(version='1.0', encoding='UTF-8')
116
+ meta_doc << REXML::XMLDecl.new("1.0", "UTF-8")
117
+
118
+ cert = settings.get_sp_cert
119
+ #SE SERVE ANCHE ENCRYPTION
120
+ # # Add KeyDescriptor if messages will be signed / encrypted
121
+ #
122
+ # if cert
123
+ # cert_text = Base64.encode64(cert.to_der).gsub("\n", '')
124
+ # kd = sp_sso.add_element "md:KeyDescriptor", { "use" => "signing" }
125
+ # ki = kd.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
126
+ # xd = ki.add_element "ds:X509Data"
127
+ # xc = xd.add_element "ds:X509Certificate"
128
+ # xc.text = cert_text
129
+
130
+ # kd2 = sp_sso.add_element "md:KeyDescriptor", { "use" => "encryption" }
131
+ # ki2 = kd2.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
132
+ # xd2 = ki2.add_element "ds:X509Data"
133
+ # xc2 = xd2.add_element "ds:X509Certificate"
134
+ # xc2.text = cert_text
135
+ # end
136
+
137
+ # embed signature
138
+ if settings.metadata_signed && settings.sp_private_key && settings.sp_cert
139
+ private_key = settings.get_sp_key
140
+
141
+ meta_doc.sign_document(private_key, cert)
142
+ end
143
+
144
+
145
+
146
+ ret = ""
147
+ # pretty print the XML so IdP administrators can easily see what the SP supports
148
+ meta_doc.write(ret, 1)
149
+
150
+ #Logging.debug "Generated metadata:\n#{ret}"
151
+
152
+ return ret
153
+
154
+ end
155
+
156
+ def create_sso_request(message, extra_parameters = {} )
157
+ build_message( :type => "SAMLRequest",
158
+ :service => "SingleSignOnService",
159
+ :message => message, :extra_parameters => extra_parameters)
160
+ end
161
+ def create_sso_response(message, extra_parameters = {} )
162
+ build_message( :type => "SAMLResponse",
163
+ :service => "SingleSignOnService",
164
+ :message => message, :extra_parameters => extra_parameters)
165
+ end
166
+ def create_slo_request(message, extra_parameters = {} )
167
+ build_message( :type => "SAMLRequest",
168
+ :service => "SingleLogoutService",
169
+ :message => message, :extra_parameters => extra_parameters)
170
+ end
171
+ def create_slo_response(message, extra_parameters = {} )
172
+ build_message( :type => "SAMLResponse",
173
+ :service => "SingleLogoutService",
174
+ :message => message, :extra_parameters => extra_parameters)
175
+ end
176
+
177
+ # Construct a SAML message using information in the IdP metadata.
178
+ # :type can be either "SAMLRequest" or "SAMLResponse"
179
+ # :service refers to the Binding method,
180
+ # either "SingleLogoutService" or "SingleSignOnService"
181
+ # :message is the SAML message itself (XML)
182
+ # I've provided easy to use wrapper functions above
183
+ def build_message( options = {} )
184
+ opt = { :type => nil, :service => nil, :message => nil, :extra_parameters => nil }.merge(options)
185
+ url = binding_select( opt[:service] )
186
+ return message_get( opt[:type], url, opt[:message], opt[:extra_parameters] )
187
+ end
188
+
189
+ # get the IdP metadata, and select the appropriate SSO binding
190
+ # that we can support. Currently this is HTTP-Redirect and HTTP-POST
191
+ # but more could be added in the future
192
+ def binding_select(service)
193
+ # first check if we're still using the old hard coded method for
194
+ # backwards compatability
195
+ if service == "SingleSignOnService" && @settings.idp_metadata == nil && @settings.idp_sso_target_url != nil
196
+ return @settings.idp_sso_target_url
197
+ end
198
+ if service == "SingleLogoutService" && @settings.idp_metadata == nil && @settings.idp_slo_target_url != nil
199
+ return @settings.idp_slo_target_url
200
+ end
201
+
202
+ meta_doc = get_idp_metadata
203
+
204
+ return nil unless meta_doc
205
+ # first try POST
206
+ sso_element = REXML::XPath.first(meta_doc, "/EntityDescriptor/IDPSSODescriptor/#{service}[@Binding='#{HTTP_POST}']")
207
+ if !sso_element.nil?
208
+ @URL = sso_element.attributes["Location"]
209
+ #Logging.debug "binding_select: POST to #{@URL}"
210
+ return @URL
211
+ end
212
+
213
+ # next try GET
214
+ sso_element = REXML::XPath.first(meta_doc, "/EntityDescriptor/IDPSSODescriptor/#{service}[@Binding='#{HTTP_GET}']")
215
+ if !sso_element.nil?
216
+ @URL = sso_element.attributes["Location"]
217
+ Logging.debug "binding_select: GET from #{@URL}"
218
+ return @URL
219
+ end
220
+ # other types we might want to add in the future: SOAP, Artifact
221
+ end
222
+
223
+
224
+ def fetch(uri_str, limit = 10)
225
+ # You should choose a better exception.
226
+ raise ArgumentError, 'too many HTTP redirects' if limit == 0
227
+
228
+ uri = URI.parse(uri_str)
229
+ if uri.scheme == "http"
230
+ response = Net::HTTP.get_response(uri)
231
+ elsif uri.scheme == "https"
232
+ http = Net::HTTP.new(uri.host, uri.port)
233
+ http.use_ssl = true
234
+ # Most IdPs will probably use self signed certs
235
+ #http.verify_mode = OpenSSL::SSL::VERIFY_PEER
236
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
237
+ get = Net::HTTP::Get.new(uri.request_uri)
238
+ response = http.request(get)
239
+ end
240
+
241
+ case response
242
+ when Net::HTTPSuccess then
243
+ response
244
+ when Net::HTTPRedirection then
245
+ location = response['location']
246
+ warn "redirected to #{location}"
247
+ fetch(location, limit - 1)
248
+ else
249
+ response.value
250
+ end
251
+ end
252
+
253
+
254
+
255
+ # Retrieve the remote IdP metadata from the URL or a cached copy
256
+ # returns a REXML document of the metadata
257
+ def get_idp_metadata
258
+ return false if @settings.idp_metadata.nil?
259
+
260
+ # Look up the metdata in cache first
261
+ id = Digest::MD5.hexdigest(@settings.idp_metadata)
262
+ response = fetch(@settings.idp_metadata)
263
+ #meta_text = response.body
264
+ #testo_response = meta_text.sub!(' xmlns:xml="http://www.w3.org/XML/1998/namespace"', '') da errori
265
+ #uso nokogiri per cercare il certificato, uso la funzione che rimuove tutti i namespace
266
+ doc_noko = Nokogiri::XML(response.body)
267
+ doc_noko.remove_namespaces!
268
+ extract_certificate(doc_noko)
269
+ doc_rexml = REXML::Document.new(doc_noko.to_xml)
270
+
271
+ return doc_rexml
272
+
273
+ # USE OF CACHE WITH CERTIFICATE
274
+ # lookup = @cache.read(id)
275
+ # if lookup != nil
276
+ # Logging.debug "IdP metadata cached lookup for #{@settings.idp_metadata}"
277
+ # doc = REXML::Document.new( lookup )
278
+ # extract_certificate( doc )
279
+ # return doc
280
+ # end
281
+
282
+ # Logging.debug "IdP metadata cache miss on #{@settings.idp_metadata}"
283
+ # # cache miss
284
+ # if File.exists?(@settings.idp_metadata)
285
+ # fp = File.open( @settings.idp_metadata, "r")
286
+ # meta_text = fp.read
287
+ # else
288
+ # uri = URI.parse(@settings.idp_metadata)
289
+ # if uri.scheme == "http"
290
+ # response = Net::HTTP.get_response(uri)
291
+ # meta_text = response.body
292
+ # elsif uri.scheme == "https"
293
+ # http = Net::HTTP.new(uri.host, uri.port)
294
+ # http.use_ssl = true
295
+ # # Most IdPs will probably use self signed certs
296
+ # #http.verify_mode = OpenSSL::SSL::VERIFY_PEER
297
+ # http.verify_mode = OpenSSL::SSL::VERIFY_NONE
298
+ # get = Net::HTTP::Get.new(uri.request_uri)
299
+ # response = http.request(get)
300
+ # meta_text = response.body
301
+ # end
302
+ # end
303
+ # # Add it to the cache
304
+ # @cache.write(id, meta_text, @settings.idp_metadata_ttl )
305
+ # doc = REXML::Document.new( meta_text )
306
+ # extract_certificate(doc)
307
+ # return doc
308
+ end
309
+
310
+ def extract_certificate(meta_doc)
311
+ #ricerco il certificato con nokogiri
312
+ # pull out the x509 tag
313
+ x509 = meta_doc.xpath("//EntityDescriptor//IDPSSODescriptor//KeyDescriptor//KeyInfo//X509Data//X509Certificate")
314
+
315
+ #x509 = REXML::XPath.first(meta_doc, "/md:EntityDescriptor/md:IDPSSODescriptor"+"/md:KeyDescriptor"+"/ds:KeyInfo/ds:X509Data/ds:X509Certificate")
316
+ # If the IdP didn't specify the use attribute
317
+ if x509.nil?
318
+ x509 = meta_doc.xpath("//EntityDescriptor//IDPSSODescriptor//KeyDescriptor//KeyInfo//X509Data//X509Certificate")
319
+ # x509 = REXML::XPath.first(meta_doc,
320
+ # "/EntityDescriptor/IDPSSODescriptor" +
321
+ # "/KeyDescriptor" +
322
+ # "/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
323
+ # )
324
+ end
325
+ @settings.idp_cert = x509.children.to_s.gsub(/\n/, "").gsub(/\t/, "")
326
+ end
327
+
328
+ # construct the parameter list on the URL and return
329
+ def message_get( type, url, message, extra_parameters = {} )
330
+ params = Hash.new
331
+ if extra_parameters
332
+ params.merge!(extra_parameters)
333
+ end
334
+ # compress GET requests to try and stay under that 8KB request limit
335
+ #deflate of samlrequest
336
+ params[type] = encode( deflate( message ) )
337
+ #Logging.debug "#{type}=#{params[type]}"
338
+
339
+ uri = Addressable::URI.parse(url)
340
+ if uri.query_values == nil
341
+ uri.query_values = params
342
+ else
343
+ # solution to stevenwilkin's parameter merge
344
+ uri.query_values = params.merge(uri.query_values)
345
+ end
346
+ url = uri.to_s
347
+ #Logging.debug "Sending to URL #{url}"
348
+ return url
349
+ end
350
+
351
+ end
352
+ end
353
+ end
@@ -0,0 +1,81 @@
1
+
2
+ # A few helper functions for assembling a SAMLRequest and
3
+ # sending it to the IdP
4
+ module Spid::Saml
5
+ include Coding
6
+ module Request
7
+
8
+ # a few symbols for SAML class names
9
+ HTTP_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
10
+ HTTP_GET = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
11
+ # get the IdP metadata, and select the appropriate SSO binding
12
+ # that we can support. Currently this is HTTP-Redirect and HTTP-POST
13
+ # but more could be added in the future
14
+ def binding_select(service)
15
+ # first check if we're still using the old hard coded method for
16
+ # backwards compatability
17
+ if @settings.idp_metadata == nil && @settings.idp_sso_target_url != nil
18
+ @URL = @settings.idp_sso_target_url
19
+ return "GET", content_get
20
+ end
21
+ # grab the metadata
22
+ metadata = Metadata::new
23
+ meta_doc = metadata.get_idp_metadata(@settings)
24
+
25
+ # first try POST
26
+ sso_element = REXML::XPath.first(meta_doc,
27
+ "/EntityDescriptor/IDPSSODescriptor/#{service}[@Binding='#{HTTP_POST}']")
28
+ if sso_element
29
+ @URL = sso_element.attributes["Location"]
30
+ #Logging.debug "binding_select: POST to #{@URL}"
31
+ return "POST", content_post
32
+ end
33
+
34
+ # next try GET
35
+ sso_element = REXML::XPath.first(meta_doc,
36
+ "/EntityDescriptor/IDPSSODescriptor/#{service}[@Binding='#{HTTP_GET}']")
37
+ if sso_element
38
+ @URL = sso_element.attributes["Location"]
39
+ Logging.debug "binding_select: GET from #{@URL}"
40
+ return "GET", content_get
41
+ end
42
+ # other types we might want to add in the future: SOAP, Artifact
43
+ end
44
+
45
+ # construct the the parameter list on the URL and return
46
+ def content_get
47
+ # compress GET requests to try and stay under that 8KB request limit
48
+ deflated_request = Zlib::Deflate.deflate(@request, 9)[2..-5]
49
+ # strict_encode64() isn't available? sub out the newlines
50
+ @request_params["SAMLRequest"] = Base64.encode64(deflated_request).gsub(/\n/, "")
51
+
52
+ Logging.debug "SAMLRequest=#{@request_params["SAMLRequest"]}"
53
+ uri = Addressable::URI.parse(@URL)
54
+ uri.query_values = @request_params
55
+ url = uri.to_s
56
+ #url = @URL + "?SAMLRequest=" + @request_params["SAMLRequest"]
57
+ Logging.debug "Sending to URL #{url}"
58
+ return url
59
+ end
60
+ # construct an HTML form (POST) and return the content
61
+ def content_post
62
+ # POST requests seem to bomb out when they're deflated
63
+ # and they probably don't need to be compressed anyway
64
+ @request_params["SAMLRequest"] = Base64.encode64(@request).gsub(/\n/, "")
65
+
66
+ #Logging.debug "SAMLRequest=#{@request_params["SAMLRequest"]}"
67
+ # kind of a cheesy method of building an HTML, form since we can't rely on Rails too much,
68
+ # and REXML doesn't work well with quote characters
69
+ str = "<html><body onLoad=\"document.getElementById('form').submit();\">\n"
70
+ str += "<form id='form' name='form' method='POST' action=\"#{@URL}\">\n"
71
+ # we could change this in the future to associate a temp auth session ID
72
+ str += "<input name='RelayState' value='ruby-saml' type='hidden' />\n"
73
+ @request_params.each_pair do |key, value|
74
+ str += "<input name=\"#{key}\" value=\"#{value}\" type='hidden' />\n"
75
+ end
76
+ str += "</form></body></html>\n"
77
+ #Logging.debug "Created form:\n#{str}"
78
+ return str
79
+ end
80
+ end
81
+ end