ovirt-engine-sdk 4.0.1 → 4.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,695 @@
1
+ #
2
+ # Copyright (c) 2015-2017 Red Hat, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require 'json'
18
+ require 'tempfile'
19
+ require 'uri'
20
+
21
+ module OvirtSDK4
22
+ #
23
+ # This class is responsible for managing an HTTP connection to the engine server. It is intended as the entry
24
+ # point for the SDK, and it provides access to the `system` service and, from there, to the rest of the services
25
+ # provided by the API.
26
+ #
27
+ class Connection
28
+ #
29
+ # Creates a new connection to the API server.
30
+ #
31
+ # [source,ruby]
32
+ # ----
33
+ # connection = OvirtSDK4::Connection.new(
34
+ # url: 'https://engine.example.com/ovirt-engine/api',
35
+ # username: 'admin@internal',
36
+ # password: '...',
37
+ # ca_file:'/etc/pki/ovirt-engine/ca.pem'
38
+ # )
39
+ # ----
40
+ #
41
+ # @param opts [Hash] The options used to create the connection.
42
+ #
43
+ # @option opts [String] :url A string containing the base URL of the server, usually something like
44
+ # `\https://server.example.com/ovirt-engine/api`.
45
+ #
46
+ # @option opts [String] :username The name of the user, something like `admin@internal`.
47
+ #
48
+ # @option opts [String] :password The password of the user.
49
+ #
50
+ # @option opts [String] :token The token used to authenticate. Optionally the caller can explicitly provide
51
+ # the token, instead of the user name and password. If the token isn't provided then it will be automatically
52
+ # created.
53
+ #
54
+ # @option opts [Boolean] :insecure (false) A boolean flag that indicates if the server TLS certificate and host
55
+ # name should be checked.
56
+ #
57
+ # @option opts [String] :ca_file The name of a PEM file containing the trusted CA certificates. The certificate
58
+ # presented by the server will be verified using these CA certificates. If neither this nor the `ca_certs`
59
+ # options are provided, then the system wide CA certificates store is used. If both options are provided,
60
+ # then the certificates from both options will be trusted.
61
+ #
62
+ # @option opts [Array<String>] :ca_certs An array of strings containing the trusted CA certificates, in PEM
63
+ # format. The certificate presented by the server will be verified using these CA certificates. If neither this
64
+ # nor the `ca_file` options are provided, then the system wide CA certificates store is used. If both options
65
+ # are provided, then the certificates from both options will be trusted.
66
+ #
67
+ # @option opts [Boolean] :debug (false) A boolean flag indicating if debug output should be generated. If the
68
+ # values is `true` and the `log` parameter isn't `nil` then the data sent to and received from the server will be
69
+ # written to the log. Be aware that user names and passwords will also be written, so handle with care.
70
+ #
71
+ # @option opts [Logger] :log The logger where the log messages will be written.
72
+ #
73
+ # @option opts [Boolean] :kerberos (false) A boolean flag indicating if Kerberos authentication should be used
74
+ # instead of user name and password to obtain the OAuth token.
75
+ #
76
+ # @option opts [Integer] :timeout (0) The maximun total time to wait for the response, in seconds. A value of zero
77
+ # (the default) means wait for ever. If the timeout expires before the response is received a `TimeoutError`
78
+ # exception will be raised.
79
+ #
80
+ # @option opts [Integer] :connect_timeout (300) The maximun time to wait for connection establishment, in seconds.
81
+ # If the timeout expires before the connection is established a `TimeoutError` exception will be raised.
82
+ #
83
+ # @option opts [Boolean] :compress (true) A boolean flag indicating if the SDK should ask the server to send
84
+ # compressed responses. Note that this is a hint for the server, and that it may return uncompressed data even
85
+ # when this parameter is set to `true`. Also, compression will be automatically disabled when the `debug`
86
+ # parameter is set to `true`, as otherwise the debug output will be compressed as well, and then it isn't
87
+ # useful.
88
+ #
89
+ # @option opts [String] :proxy_url A string containing the protocol, address and port number of the proxy server
90
+ # to use to connect to the server. For example, in order to use the HTTP proxy `proxy.example.com` that is
91
+ # listening on port `3128` the value should be `http://proxy.example.com:3128`. This is optional, and if not
92
+ # given the connection will go directly to the server specified in the `url` parameter.
93
+ #
94
+ # @option opts [String] :proxy_username The name of the user to authenticate to the proxy server.
95
+ #
96
+ # @option opts [String] :proxy_password The password of the user to authenticate to the proxy server.
97
+ #
98
+ # @option opts [Hash] :headers Custom HTTP headers to send with all requests. The keys of the hash can be
99
+ # strings of symbols, and they will be used as the names of the headers. The values of the hash will be used
100
+ # as the names of the headers. If the same header is provided here and in the `headers` parameter of a specific
101
+ # method call, then the `headers` parameter of the specific method call will have precedence.
102
+ #
103
+ # @option opts [Integer] :connections (1) The maximum number of connections to open to the host. The value must
104
+ # be greater than 0.
105
+ #
106
+ # @option opts [Integer] :pipeline (0) The maximum number of request to put in an HTTP pipeline without waiting for
107
+ # the response. If the value is `0` (the default) then pipelining is disabled.
108
+ #
109
+ def initialize(opts = {})
110
+ # Get the values of the parameters and assign default values:
111
+ @url = opts[:url]
112
+ @username = opts[:username]
113
+ @password = opts[:password]
114
+ @token = opts[:token]
115
+ @insecure = opts[:insecure] || false
116
+ @ca_file = opts[:ca_file]
117
+ @ca_certs = opts[:ca_certs]
118
+ @debug = opts[:debug] || false
119
+ @log = opts[:log]
120
+ @kerberos = opts[:kerberos] || false
121
+ @timeout = opts[:timeout] || 0
122
+ @connect_timeout = opts[:connect_timeout] || 0
123
+ @compress = opts[:compress] || true
124
+ @proxy_url = opts[:proxy_url]
125
+ @proxy_username = opts[:proxy_username]
126
+ @proxy_password = opts[:proxy_password]
127
+ @headers = opts[:headers]
128
+ @connections = opts[:connections] || 1
129
+ @pipeline = opts[:pipeline] || 0
130
+
131
+ # Check that the URL has been provided:
132
+ raise ArgumentError, "The 'url' option is mandatory" unless @url
133
+
134
+ # Automatically disable compression when debug is enabled, as otherwise the debug output generated by
135
+ # libcurl is also compressed, and that isn't useful for debugging:
136
+ @compress = false if @debug
137
+
138
+ # Create a temporary file to store the CA certificates, and populate it with the contents of the 'ca_file' and
139
+ # 'ca_certs' options. The file will be removed when the connection is closed.
140
+ @ca_store = nil
141
+ if @ca_file || @ca_certs
142
+ @ca_store = Tempfile.new('ca_store')
143
+ @ca_store.write(::File.read(@ca_file)) if @ca_file
144
+ if @ca_certs
145
+ @ca_certs.each do |ca_cert|
146
+ @ca_store.write(ca_cert)
147
+ end
148
+ end
149
+ @ca_store.close
150
+ end
151
+
152
+ # Create the mutex that will be used to prevents simultaneous access to the same HTTP client by multiple threads:
153
+ @mutex = Mutex.new
154
+
155
+ # Create the HTTP client:
156
+ @client = HttpClient.new(
157
+ insecure: @insecure,
158
+ ca_file: @ca_store ? @ca_store.path : nil,
159
+ debug: @debug,
160
+ log: @log,
161
+ timeout: @timeout,
162
+ connect_timeout: @connect_timeout,
163
+ compress: @compress,
164
+ proxy_url: @proxy_url,
165
+ proxy_username: @proxy_username,
166
+ proxy_password: @proxy_password,
167
+ connections: @connections,
168
+ pipeline: @pipeline
169
+ )
170
+ end
171
+
172
+ #
173
+ # Returns a reference to the root of the services tree.
174
+ #
175
+ # @return [SystemService]
176
+ #
177
+ def system_service
178
+ @system_service ||= SystemService.new(self, '')
179
+ end
180
+
181
+ #
182
+ # Returns a reference to the service corresponding to the given path. For example, if the `path` parameter
183
+ # is `vms/123/diskattachments` then it will return a reference to the service that manages the disk
184
+ # attachments for the virtual machine with identifier `123`.
185
+ #
186
+ # @param path [String] The path of the service, for example `vms/123/diskattachments`.
187
+ # @return [Service]
188
+ # @raise [Error] If there is no service corresponding to the given path.
189
+ #
190
+ def service(path)
191
+ system_service.service(path)
192
+ end
193
+
194
+ #
195
+ # Sends an HTTP request, making sure that multiple threads are coordinated correctly.
196
+ #
197
+ # @param request [HttpRequest] The request object containing the details of the HTTP request to send.
198
+ #
199
+ # @api private
200
+ #
201
+ def send(request)
202
+ @mutex.synchronize { internal_send(request) }
203
+ end
204
+
205
+ #
206
+ # Waits for the response to the given request, making sure that multiple threads are coordinated correctly.
207
+ #
208
+ # @param request [HttpRequest] The request object whose corresponding response you want to wait for.
209
+ # @return [HttpResponse] A request object containing the details of the HTTP response received.
210
+ #
211
+ # @api private
212
+ #
213
+ def wait(request)
214
+ @mutex.synchronize { internal_wait(request) }
215
+ end
216
+
217
+ #
218
+ # Tests the connectivity with the server. If connectivity works correctly it returns `true`. If there is any
219
+ # connectivity problem it will either return `false` or raise an exception if the `raise_exception` parameter is
220
+ # `true`.
221
+ #
222
+ # @param raise_exception [Boolean]
223
+ #
224
+ # @param timeout [Integer] (nil) The maximun total time to wait for the test to complete, in seconds. If the value
225
+ # is `nil` (the default) then the timeout set globally for the connection will be used.
226
+ #
227
+ # @return [Boolean]
228
+ #
229
+ def test(raise_exception = false, timeout = nil)
230
+ system_service.get(timeout: timeout)
231
+ true
232
+ rescue StandardError
233
+ raise if raise_exception
234
+
235
+ false
236
+ end
237
+
238
+ #
239
+ # Performs the authentication process and returns the authentication token. Usually there is no need to
240
+ # call this method, as authentication is performed automatically when needed. But in some situations it
241
+ # may be useful to perform authentication explicitly, and then use the obtained token to create other
242
+ # connections, using the `token` parameter of the constructor instead of the user name and password.
243
+ #
244
+ # @return [String]
245
+ #
246
+ def authenticate
247
+ # rubocop:disable Naming/MemoizedInstanceVariableName
248
+ @token ||= create_access_token
249
+ # rubocop:enable Naming/MemoizedInstanceVariableName
250
+ end
251
+
252
+ #
253
+ # Indicates if the given object is a link. An object is a link if it has an `href` attribute.
254
+ #
255
+ # @return [Boolean]
256
+ #
257
+ def link?(object)
258
+ !object.href.nil?
259
+ end
260
+
261
+ #
262
+ # The `link?` method used to be named `is_link?`, and we need to preserve it for backwards compatibility, but try to
263
+ # avoid using it.
264
+ #
265
+ # @return [Boolean]
266
+ #
267
+ # @deprecated Please use `link?` instead.
268
+ #
269
+ alias is_link? link?
270
+
271
+ #
272
+ # Follows the `href` attribute of the given object, retrieves the target object and returns it.
273
+ #
274
+ # @param object [Type] The object containing the `href` attribute.
275
+ # @raise [Error] If the `href` attribute has no value, or the link can't be followed.
276
+ #
277
+ def follow_link(object)
278
+ # Check that the "href" has a value, as it is needed in order to retrieve the representation of the object:
279
+ href = object.href
280
+ raise Error, "Can't follow link because the 'href' attribute doesn't have a value" if href.nil?
281
+
282
+ # Check that the value of the "href" attribute is compatible with the base URL of the connection:
283
+ prefix = URI(@url).path
284
+ prefix += '/' unless prefix.end_with?('/')
285
+ unless href.start_with?(prefix)
286
+ raise Error, "The URL '#{href}' isn't compatible with the base URL of the connection"
287
+ end
288
+
289
+ # Remove the prefix from the URL, follow the path to the relevant service and invoke the "get" or "list" method
290
+ # to retrieve its representation:
291
+ path = href[prefix.length..-1]
292
+ service = service(path)
293
+ if object.is_a?(Array)
294
+ service.list
295
+ else
296
+ service.get
297
+ end
298
+ end
299
+
300
+ #
301
+ # Releases the resources used by this connection, making sure that multiple threads are coordinated correctly.
302
+ #
303
+ def close
304
+ @mutex.synchronize { internal_close }
305
+ end
306
+
307
+ #
308
+ # Checks that the content type of the given response is JSON. If it is JSON then it does nothing. If it isn't
309
+ # JSON then it raises an exception.
310
+ #
311
+ # @param response [HttpResponse] The HTTP response to check.
312
+ #
313
+ # @api private
314
+ #
315
+ def check_json_content_type(response)
316
+ check_content_type(JSON_CONTENT_TYPE_RE, 'JSON', response)
317
+ end
318
+
319
+ #
320
+ # Checks that the content type of the given response is XML. If it is XML then it does nothing. If it isn't
321
+ # XML then it raises an exception.
322
+ #
323
+ # @param response [HttpResponse] The HTTP response to check.
324
+ #
325
+ # @api private
326
+ #
327
+ def check_xml_content_type(response)
328
+ check_content_type(XML_CONTENT_TYPE_RE, 'XML', response)
329
+ end
330
+
331
+ #
332
+ # Creates and raises an error containing the details of the given HTTP response.
333
+ #
334
+ # @param response [HttpResponse] The HTTP response where the details of the raised error will be taken from.
335
+ # @param detail [String, Fault] (nil) The detail of the error. It can be a string or a `Fault` object.
336
+ #
337
+ # @api private
338
+ #
339
+ def raise_error(response, detail = nil)
340
+ # Check if the detail is a fault:
341
+ fault = detail.is_a?(Fault) ? detail : nil
342
+
343
+ # Build the error message from the response and the fault:
344
+ message = ''
345
+ unless fault.nil?
346
+ unless fault.reason.nil?
347
+ message << ' ' unless message.empty?
348
+ message << "Fault reason is \"#{fault.reason}\"."
349
+ end
350
+ unless fault.detail.nil?
351
+ message << ' ' unless message.empty?
352
+ message << "Fault detail is \"#{fault.detail}\"."
353
+ end
354
+ end
355
+ unless response.nil?
356
+ unless response.code.nil?
357
+ message << ' ' unless message.empty?
358
+ message << "HTTP response code is #{response.code}."
359
+ end
360
+ unless response.message.nil?
361
+ message << ' ' unless message.empty?
362
+ message << "HTTP response message is \"#{response.message}\"."
363
+ end
364
+ end
365
+
366
+ # If the detail is a string, append it to the message:
367
+ if detail.is_a?(String)
368
+ message << ' ' unless message.empty?
369
+ message << detail
370
+ message << '.'
371
+ end
372
+
373
+ # Create and populate the error:
374
+ klass = Error
375
+ unless response.nil?
376
+ case response.code
377
+ when 401, 403
378
+ klass = AuthError
379
+ when 404
380
+ klass = NotFoundError
381
+ end
382
+ end
383
+ error = klass.new(message)
384
+ error.code = response.code if response
385
+ error.fault = fault
386
+
387
+ raise error
388
+ end
389
+
390
+ #
391
+ # Returns a string representation of the connection.
392
+ #
393
+ # @return [String] The string representation.
394
+ #
395
+ def inspect
396
+ "#<#{self.class.name}:#{@url}>"
397
+ end
398
+
399
+ #
400
+ # Returns a string representation of the connection.
401
+ #
402
+ # @return [String] The string representation.
403
+ #
404
+ def to_s
405
+ inspect
406
+ end
407
+
408
+ #
409
+ # Returns a string representation of the connection.
410
+ #
411
+ # @return [String] The string representation.
412
+ #
413
+
414
+ private
415
+
416
+ #
417
+ # Regular expression used to check JSON content type.
418
+ #
419
+ # @api private
420
+ #
421
+ JSON_CONTENT_TYPE_RE = %r{^\s*(application|text)/json\s*(;.*)?$}i.freeze
422
+
423
+ #
424
+ # Regular expression used to check XML content type.
425
+ #
426
+ # @api private
427
+ #
428
+ XML_CONTENT_TYPE_RE = %r{^\s*(application|text)/xml\s*(;.*)?$}i.freeze
429
+
430
+ #
431
+ # The typical URL path, used just to generate informative error messages.
432
+ #
433
+ # @api private
434
+ #
435
+ TYPICAL_PATH = '/ovirt-engine/api'.freeze
436
+
437
+ #
438
+ # Checks the content type of the given HTTP response and raises an exception if it isn't the expected one.
439
+ #
440
+ # @param expected_re [Regex] The regular expression used to check the expected content type.
441
+ # @param expected_name [String] The name of the expected content type.
442
+ # @param response [HttpResponse] The HTTP response to check.
443
+ #
444
+ # @api private
445
+ #
446
+ def check_content_type(expected_re, expected_name, response)
447
+ content_type = response.headers['content-type']
448
+ return if expected_re =~ content_type
449
+
450
+ detail = "The response content type '#{content_type}' isn't #{expected_name}"
451
+ url = URI(@url)
452
+ if url.path != TYPICAL_PATH
453
+ detail << ". Is the path '#{url.path}' included in the 'url' parameter correct?"
454
+ detail << " The typical one is '#{TYPICAL_PATH}'"
455
+ end
456
+ raise_error(response, detail)
457
+ end
458
+
459
+ #
460
+ # Obtains the access token from SSO to be used for bearer authentication.
461
+ #
462
+ # @return [String] The access token.
463
+ #
464
+ # @api private
465
+ #
466
+ def create_access_token
467
+ # Build the URL and parameters required for the request:
468
+ url, parameters = build_sso_auth_request
469
+
470
+ # Send the request and wait for the request:
471
+ response = get_sso_response(url, parameters)
472
+ response = response[0] if response.is_a?(Array)
473
+
474
+ # Check the response and raise an error if it contains an error code:
475
+ error = get_sso_error_message(response)
476
+ raise AuthError, "Error during SSO authentication: #{error}" if error
477
+
478
+ response['access_token']
479
+ end
480
+
481
+ #
482
+ # Revoke the SSO access token.
483
+ #
484
+ # @api private
485
+ #
486
+ def revoke_access_token
487
+ # Build the URL and parameters required for the request:
488
+ url, parameters = build_sso_revoke_request
489
+
490
+ # Send the request and wait for the response:
491
+ response = get_sso_response(url, parameters)
492
+ response = response[0] if response.is_a?(Array)
493
+
494
+ # Check the response and raise an error if it contains an error code:
495
+ error = get_sso_error_message(response)
496
+ raise AuthError, "Error during SSO revoke: #{error}" if error
497
+ end
498
+
499
+ #
500
+ # Execute a get request to the SSO server and return the response.
501
+ #
502
+ # @param url [String] The URL of the SSO server.
503
+ #
504
+ # @param parameters [Hash] The parameters to send to the SSO server.
505
+ #
506
+ # @return [Hash] The JSON response.
507
+ #
508
+ # @api private
509
+ #
510
+ def get_sso_response(url, parameters)
511
+ # Create the request:
512
+ request = HttpRequest.new
513
+ request.method = :POST
514
+ request.url = url
515
+ request.headers = {
516
+ 'User-Agent' => "RubySDK/#{VERSION}",
517
+ 'Content-Type' => 'application/x-www-form-urlencoded',
518
+ 'Accept' => 'application/json'
519
+ }
520
+ request.body = URI.encode_www_form(parameters)
521
+
522
+ # Add the global headers:
523
+ request.headers.merge!(@headers) if @headers
524
+
525
+ # Send the request and wait for the response:
526
+ @client.send(request)
527
+ response = @client.wait(request)
528
+ raise response if response.is_a?(Exception)
529
+
530
+ # Check the returned content type:
531
+ check_json_content_type(response)
532
+
533
+ # Parse and return the JSON response:
534
+ JSON.parse(response.body)
535
+ end
536
+
537
+ #
538
+ # Builds a the URL and parameters to acquire the access token from SSO.
539
+ #
540
+ # @return [Array] An array containing two elements, the first is the URL of the SSO service and the second is a hash
541
+ # containing the parameters required to perform authentication.
542
+ #
543
+ # @api private
544
+ #
545
+ def build_sso_auth_request
546
+ # Compute the entry point and the parameters:
547
+ parameters = {
548
+ scope: 'ovirt-app-api'
549
+ }
550
+ if @kerberos
551
+ entry_point = 'token-http-auth'
552
+ parameters[:grant_type] = 'urn:ovirt:params:oauth:grant-type:http'
553
+ else
554
+ entry_point = 'token'
555
+ parameters.merge!(
556
+ grant_type: 'password',
557
+ username: @username,
558
+ password: @password
559
+ )
560
+ end
561
+
562
+ # Compute the URL:
563
+ url = URI(@url.to_s)
564
+ url.path = "/ovirt-engine/sso/oauth/#{entry_point}"
565
+ url = url.to_s
566
+
567
+ # Return the pair containing the URL and the parameters:
568
+ [url, parameters]
569
+ end
570
+
571
+ #
572
+ # Builds a the URL and parameters to revoke the SSO access token
573
+ #
574
+ # @return [Array] An array containing two elements, the first is the URL of the SSO service and the second is a hash
575
+ # containing the parameters required to perform the revoke.
576
+ #
577
+ # @api private
578
+ #
579
+ def build_sso_revoke_request
580
+ # Compute the parameters:
581
+ parameters = {
582
+ scope: '',
583
+ token: @token
584
+ }
585
+
586
+ # Compute the URL:
587
+ url = URI(@url.to_s)
588
+ url.path = '/ovirt-engine/services/sso-logout'
589
+ url = url.to_s
590
+
591
+ # Return the pair containing the URL and the parameters:
592
+ [url, parameters]
593
+ end
594
+
595
+ #
596
+ # Extrats the error message from the given SSO response.
597
+ #
598
+ # @param response [Hash] The result of parsing the JSON document returned by the SSO server.
599
+ # @return [String] The error message, or `nil` if there was no error.
600
+ #
601
+ def get_sso_error_message(response)
602
+ # OAuth uses the 'error_code' attribute for the error code, and 'error' for the error description. But OpenID uses
603
+ # 'error' for the error code and 'error_description' for the description. So we need to check if the
604
+ # 'error_description' attribute is present, and extract the code and description accordingly.
605
+ description = response['error_description']
606
+ if description.nil?
607
+ code = response['error_code']
608
+ description = response['error']
609
+ else
610
+ code = response['error']
611
+ end
612
+ "#{code}: #{description}" if code
613
+ end
614
+
615
+ #
616
+ # Sends an HTTP request.
617
+ #
618
+ # @param request [HttpRequest] The request object containing the details of the HTTP request to send.
619
+ #
620
+ # @api private
621
+ #
622
+ def internal_send(request)
623
+ # Add the base URL to the request:
624
+ request.url = request.url.nil? ? request.url = @url : "#{@url}/#{request.url}"
625
+
626
+ # Set the headers common to all requests:
627
+ request.headers.merge!(
628
+ 'User-Agent' => "RubySDK/#{VERSION}",
629
+ 'Version' => '4',
630
+ 'Content-Type' => 'application/xml',
631
+ 'Accept' => 'application/xml'
632
+ )
633
+
634
+ # Older versions of the engine (before 4.1) required the 'all_content' as an HTTP header instead of a query
635
+ # parameter. In order to better support those older versions of the engine we need to check if this parameter is
636
+ # included in the request, and add the corresponding header.
637
+ unless request.query.nil?
638
+ all_content = request.query[:all_content]
639
+ request.headers['All-Content'] = all_content unless all_content.nil?
640
+ end
641
+
642
+ # Add the global headers, but without replacing the values that may already exist:
643
+ request.headers.merge!(@headers) { |_name, local, _global| local } if @headers
644
+
645
+ # Set the authentication token:
646
+ @token ||= create_access_token
647
+ request.token = @token
648
+
649
+ # Send the request:
650
+ @client.send(request)
651
+ end
652
+
653
+ #
654
+ # Waits for the response to the given request.
655
+ #
656
+ # @param request [HttpRequest] The request object whose corresponding response you want to wait for.
657
+ # @return [Response] A request object containing the details of the HTTP response received.
658
+ #
659
+ # @api private
660
+ #
661
+ def internal_wait(request)
662
+ # Wait for the response:
663
+ response = @client.wait(request)
664
+ raise response if response.is_a?(Exception)
665
+
666
+ # If the request failed because of authentication, and it wasn't a request to the SSO service, then the
667
+ # most likely cause is an expired SSO token. In this case we need to request a new token, and try the original
668
+ # request again, but only once. It if fails again, we just return the failed response.
669
+ if response.code == 401 && request.token
670
+ @token = create_access_token
671
+ request.token = @token
672
+ @client.send(request)
673
+ response = @client.wait(request)
674
+ end
675
+
676
+ response
677
+ end
678
+
679
+ #
680
+ # Releases the resources used by this connection.
681
+ #
682
+ # @api private
683
+ #
684
+ def internal_close
685
+ # Revoke the SSO access token:
686
+ revoke_access_token if @token
687
+
688
+ # Close the HTTP client:
689
+ @client.close if @client
690
+
691
+ # Remove the temporary file that contains the trusted CA certificates:
692
+ @ca_store.unlink if @ca_store
693
+ end
694
+ end
695
+ end