ruby-saml-federa 0.0.2

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.
Files changed (48) hide show
  1. data/.document +5 -0
  2. data/.gitignore +10 -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/federa/ruby-saml/authrequest.rb +181 -0
  9. data/lib/federa/ruby-saml/coding.rb +34 -0
  10. data/lib/federa/ruby-saml/logging.rb +26 -0
  11. data/lib/federa/ruby-saml/logout_request.rb +126 -0
  12. data/lib/federa/ruby-saml/logout_response.rb +132 -0
  13. data/lib/federa/ruby-saml/metadata.rb +266 -0
  14. data/lib/federa/ruby-saml/request.rb +81 -0
  15. data/lib/federa/ruby-saml/response.rb +203 -0
  16. data/lib/federa/ruby-saml/settings.rb +28 -0
  17. data/lib/federa/ruby-saml/validation_error.rb +7 -0
  18. data/lib/federa/ruby-saml/version.rb +5 -0
  19. data/lib/ruby-saml-federa.rb +11 -0
  20. data/lib/schemas/saml20assertion_schema.xsd +283 -0
  21. data/lib/schemas/saml20protocol_schema.xsd +302 -0
  22. data/lib/schemas/xenc_schema.xsd +146 -0
  23. data/lib/schemas/xmldsig_schema.xsd +318 -0
  24. data/lib/xml_security.rb +165 -0
  25. data/ruby-saml-federa.gemspec +21 -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 +66 -0
  47. data/test/xml_security_test.rb +123 -0
  48. metadata +155 -0
@@ -0,0 +1,126 @@
1
+ require 'uuid'
2
+
3
+ module Federa::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 = Federa::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 Federa
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,266 @@
1
+ require "rexml/document"
2
+ require "rexml/xpath"
3
+ require "net/https"
4
+ require "uri"
5
+ require "digest/md5"
6
+
7
+ # Class to return SP metadata based on the settings requested.
8
+ # Return this XML in a controller, then give that URL to the the
9
+ # IdP administrator. The IdP will poll the URL and your settings
10
+ # will be updated automatically
11
+ module Federa
12
+ module Saml
13
+ class Metadata
14
+ include REXML
15
+ include Coding
16
+ # a few symbols for SAML class names
17
+ HTTP_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
18
+ HTTP_GET = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
19
+
20
+ def initialize(settings=nil)
21
+ if settings
22
+ @settings = settings
23
+ end
24
+ end
25
+
26
+ def generate(settings)
27
+ meta_doc = REXML::Document.new
28
+ root = meta_doc.add_element "md:EntityDescriptor", {
29
+ "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata",
30
+ "xmlns:xml" => "http://www.w3.org/XML/1998/namespace",
31
+ "cacheDuration" => "P1M"
32
+ }
33
+ if settings.issuer != nil
34
+ root.attributes["entityID"] = settings.issuer
35
+ end
36
+ sp_sso = root.add_element "md:SPSSODescriptor", {
37
+ "protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
38
+ "WantAssertionsSigned" => "true"
39
+
40
+ }
41
+ name_identifier_formats = settings.name_identifier_format
42
+ if name_identifier_formats != nil
43
+ name_id = []
44
+ name_identifier_formats.each_with_index{ |format, index|
45
+ name_id[index] = sp_sso.add_element "md:NameIDFormat"
46
+ name_id[index].text = format
47
+ }
48
+
49
+ end
50
+ if settings.sp_cert != nil
51
+ keyDescriptor = sp_sso.add_element "md:KeyDescriptor", {
52
+ "use" => "signing"
53
+ }
54
+ keyInfo = keyDescriptor.add_element "ds:KeyInfo", {
55
+ "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"
56
+ }
57
+ x509Data = keyInfo.add_element "ds:X509Data"
58
+ x509Certificate = x509Data.add_element "ds:X509Certificate"
59
+ file = ""
60
+ File.foreach(settings.sp_cert){ |line|
61
+ file += line unless (line.include?("RSA PUBLIC KEY") || line.include?("CERTIFICATE"))
62
+ }
63
+ x509Certificate.text = file
64
+ end
65
+ if settings.assertion_consumer_service_url != nil
66
+ sp_sso.add_element "md:AssertionConsumerService", {
67
+ # Add this as a setting to create different bindings?
68
+ "Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
69
+ "Location" => settings.assertion_consumer_service_url,
70
+ "index" => "1"
71
+ }
72
+ end
73
+ if settings.single_logout_service_url != nil
74
+ sp_sso.add_element "md:SingleLogoutService", {
75
+ "Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
76
+ "Location" => settings.single_logout_service_url
77
+ }
78
+ sp_sso.add_element "md:SingleLogoutService", {
79
+ "Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
80
+ "Location" => settings.single_logout_service_url
81
+ }
82
+ end
83
+ meta_doc << REXML::XMLDecl.new(version='1.0', encoding='UTF-8')
84
+ ret = ""
85
+ # pretty print the XML so IdP administrators can easily see what the SP supports
86
+ meta_doc.write(ret, 1)
87
+
88
+ #Logging.debug "Generated metadata:\n#{ret}"
89
+
90
+ return ret
91
+
92
+ end
93
+
94
+ def create_sso_request(message, extra_parameters = {} )
95
+ build_message( :type => "SAMLRequest",
96
+ :service => "SingleSignOnService",
97
+ :message => message, :extra_parameters => extra_parameters)
98
+ end
99
+ def create_sso_response(message, extra_parameters = {} )
100
+ build_message( :type => "SAMLResponse",
101
+ :service => "SingleSignOnService",
102
+ :message => message, :extra_parameters => extra_parameters)
103
+ end
104
+ def create_slo_request(message, extra_parameters = {} )
105
+ build_message( :type => "SAMLRequest",
106
+ :service => "SingleLogoutService",
107
+ :message => message, :extra_parameters => extra_parameters)
108
+ end
109
+ def create_slo_response(message, extra_parameters = {} )
110
+ build_message( :type => "SAMLResponse",
111
+ :service => "SingleLogoutService",
112
+ :message => message, :extra_parameters => extra_parameters)
113
+ end
114
+
115
+ # Construct a SAML message using information in the IdP metadata.
116
+ # :type can be either "SAMLRequest" or "SAMLResponse"
117
+ # :service refers to the Binding method,
118
+ # either "SingleLogoutService" or "SingleSignOnService"
119
+ # :message is the SAML message itself (XML)
120
+ # I've provided easy to use wrapper functions above
121
+ def build_message( options = {} )
122
+ opt = { :type => nil, :service => nil, :message => nil, :extra_parameters => nil }.merge(options)
123
+ url = binding_select( opt[:service] )
124
+ return message_get( opt[:type], url, opt[:message], opt[:extra_parameters] )
125
+ end
126
+
127
+ # get the IdP metadata, and select the appropriate SSO binding
128
+ # that we can support. Currently this is HTTP-Redirect and HTTP-POST
129
+ # but more could be added in the future
130
+ def binding_select(service)
131
+ # first check if we're still using the old hard coded method for
132
+ # backwards compatability
133
+ if service == "SingleSignOnService" && @settings.idp_metadata == nil && @settings.idp_sso_target_url != nil
134
+ return @settings.idp_sso_target_url
135
+ end
136
+ if service == "SingleLogoutService" && @settings.idp_metadata == nil && @settings.idp_slo_target_url != nil
137
+ return @settings.idp_slo_target_url
138
+ end
139
+
140
+ meta_doc = get_idp_metadata
141
+
142
+ return nil unless meta_doc
143
+ # first try POST
144
+ sso_element = REXML::XPath.first(meta_doc, "/md:EntityDescriptor/md:IDPSSODescriptor/md:#{service}[@Binding='#{HTTP_POST}']")
145
+ if !sso_element.nil?
146
+ @URL = sso_element.attributes["Location"]
147
+ #Logging.debug "binding_select: POST to #{@URL}"
148
+ return @URL
149
+ end
150
+
151
+ # next try GET
152
+ sso_element = REXML::XPath.first(meta_doc, "/md:EntityDescriptor/md:IDPSSODescriptor/md:#{service}[@Binding='#{HTTP_GET}']")
153
+ if !sso_element.nil?
154
+ @URL = sso_element.attributes["Location"]
155
+ Logging.debug "binding_select: GET from #{@URL}"
156
+ return @URL
157
+ end
158
+ # other types we might want to add in the future: SOAP, Artifact
159
+ end
160
+
161
+ # Retrieve the remote IdP metadata from the URL or a cached copy
162
+ # returns a REXML document of the metadata
163
+ def get_idp_metadata
164
+ return false if @settings.idp_metadata.nil?
165
+
166
+ # Look up the metdata in cache first
167
+ id = Digest::MD5.hexdigest(@settings.idp_metadata)
168
+
169
+ uri = URI.parse(@settings.idp_metadata)
170
+ if uri.scheme == "http"
171
+ response = Net::HTTP.get_response(uri)
172
+ meta_text = response.body
173
+ elsif uri.scheme == "https"
174
+ http = Net::HTTP.new(uri.host, uri.port)
175
+ http.use_ssl = true
176
+ # Most IdPs will probably use self signed certs
177
+ #http.verify_mode = OpenSSL::SSL::VERIFY_PEER
178
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
179
+ get = Net::HTTP::Get.new(uri.request_uri)
180
+ response = http.request(get)
181
+ meta_text = response.body
182
+ end
183
+ testo_response = meta_text.sub!(' xmlns:xml="http://www.w3.org/XML/1998/namespace"', '')
184
+ doc = REXML::Document.new(testo_response)
185
+ extract_certificate(doc)
186
+ return doc
187
+
188
+ # USE OF CACHE WITH CERTIFICATE
189
+ # lookup = @cache.read(id)
190
+ # if lookup != nil
191
+ # Logging.debug "IdP metadata cached lookup for #{@settings.idp_metadata}"
192
+ # doc = REXML::Document.new( lookup )
193
+ # extract_certificate( doc )
194
+ # return doc
195
+ # end
196
+
197
+ # Logging.debug "IdP metadata cache miss on #{@settings.idp_metadata}"
198
+ # # cache miss
199
+ # if File.exists?(@settings.idp_metadata)
200
+ # fp = File.open( @settings.idp_metadata, "r")
201
+ # meta_text = fp.read
202
+ # else
203
+ # uri = URI.parse(@settings.idp_metadata)
204
+ # if uri.scheme == "http"
205
+ # response = Net::HTTP.get_response(uri)
206
+ # meta_text = response.body
207
+ # elsif uri.scheme == "https"
208
+ # http = Net::HTTP.new(uri.host, uri.port)
209
+ # http.use_ssl = true
210
+ # # Most IdPs will probably use self signed certs
211
+ # #http.verify_mode = OpenSSL::SSL::VERIFY_PEER
212
+ # http.verify_mode = OpenSSL::SSL::VERIFY_NONE
213
+ # get = Net::HTTP::Get.new(uri.request_uri)
214
+ # response = http.request(get)
215
+ # meta_text = response.body
216
+ # end
217
+ # end
218
+ # # Add it to the cache
219
+ # @cache.write(id, meta_text, @settings.idp_metadata_ttl )
220
+ # doc = REXML::Document.new( meta_text )
221
+ # extract_certificate(doc)
222
+ # return doc
223
+ end
224
+
225
+ def extract_certificate(meta_doc)
226
+
227
+ # pull out the x509 tag
228
+ x509 = REXML::XPath.first(meta_doc, "/md:EntityDescriptor/md:IDPSSODescriptor"+"/md:KeyDescriptor"+"/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
229
+ )
230
+ # If the IdP didn't specify the use attribute
231
+ if x509.nil?
232
+ x509 = REXML::XPath.first(meta_doc,
233
+ "/EntityDescriptor/IDPSSODescriptor" +
234
+ "/KeyDescriptor" +
235
+ "/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
236
+ )
237
+ end
238
+ @settings.idp_cert = x509.text.gsub(/\n/, "").gsub(/\t/, "")
239
+ end
240
+
241
+ # construct the parameter list on the URL and return
242
+ def message_get( type, url, message, extra_parameters = {} )
243
+ params = Hash.new
244
+ if extra_parameters
245
+ params.merge!(extra_parameters)
246
+ end
247
+ # compress GET requests to try and stay under that 8KB request limit
248
+ #deflate of samlrequest
249
+ params[type] = encode( deflate( message ) )
250
+ #Logging.debug "#{type}=#{params[type]}"
251
+
252
+ uri = Addressable::URI.parse(url)
253
+ if uri.query_values == nil
254
+ uri.query_values = params
255
+ else
256
+ # solution to stevenwilkin's parameter merge
257
+ uri.query_values = params.merge(uri.query_values)
258
+ end
259
+ url = uri.to_s
260
+ #Logging.debug "Sending to URL #{url}"
261
+ return url
262
+ end
263
+
264
+ end
265
+ end
266
+ end