ovirt-engine-sdk 4.0.0.alpha11 → 4.0.0.alpha12

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.
data/lib/ovirtsdk4.rb CHANGED
@@ -18,10 +18,10 @@
18
18
  # Library requirements.
19
19
  #
20
20
  require 'date'
21
- require 'stringio'
22
21
 
23
22
  ##
24
23
  # Load the extension:
24
+ #
25
25
  require 'ovirtsdk4c'
26
26
 
27
27
  ##
@@ -15,6 +15,7 @@
15
15
  #++
16
16
 
17
17
  require 'curb'
18
+ require 'json'
18
19
  require 'uri'
19
20
 
20
21
  module OvirtSDK4
@@ -27,7 +28,6 @@ module OvirtSDK4
27
28
  class Request
28
29
  attr_accessor :method
29
30
  attr_accessor :path
30
- attr_accessor :matrix
31
31
  attr_accessor :query
32
32
  attr_accessor :headers
33
33
  attr_accessor :body
@@ -39,7 +39,6 @@ module OvirtSDK4
39
39
  self.method = opts[:method] || :GET
40
40
  self.path = opts[:path] || ''
41
41
  self.headers = opts[:headers] || {}
42
- self.matrix = opts[:matrix] || {}
43
42
  self.query = opts[:query] || {}
44
43
  self.body = opts[:body]
45
44
  end
@@ -78,6 +77,21 @@ module OvirtSDK4
78
77
  ##
79
78
  # Creates a new connection to the API server.
80
79
  #
80
+ # Note that all the parameters with names starting with `sso` are intended for use with external authentication
81
+ # services, using the http://oauth.net/2/[OAuth2] protocol. But the typical usage doesn't require them, as they
82
+ # are automatically calculated to use the authentication service that is part of the engine. A typical connection
83
+ # can be created specifying just the `url`, `username`, `password` and `ca_file` parameters:
84
+ #
85
+ # [source,ruby]
86
+ # ----
87
+ # connection = OvirtSDK4::Connection.new(
88
+ # :url => 'https://engine.example.com/ovirt-engine/api',
89
+ # :username => 'admin@internal',
90
+ # :password => '...',
91
+ # :ca_file => '/etc/pki/ovirt-engine/ca.pem',
92
+ # )
93
+ # ----
94
+ #
81
95
  # @param opts [Hash] The options used to create the connection.
82
96
  #
83
97
  # @option opts [String] :url A string containing the base URL of the server, usually something like
@@ -87,10 +101,10 @@ module OvirtSDK4
87
101
  #
88
102
  # @option opts [String] :password The password of the user.
89
103
  #
90
- # @option opts [Boolean] :insecure (true) A boolean flag that indicates if the server TLS certificate and host
104
+ # @option opts [Boolean] :insecure (false) A boolean flag that indicates if the server TLS certificate and host
91
105
  # name should be checked.
92
106
  #
93
- # @option opts [String] :ca_file The name of a a PEM file containing the trusted CA certificates. The certificate
107
+ # @option opts [String] :ca_file The name of a PEM file containing the trusted CA certificates. The certificate
94
108
  # presented by the server will be verified using these CA certificates.
95
109
  #
96
110
  # @option opts [Boolean] :debug (false) A boolean flag indicating if debug output should be generated. If the
@@ -105,11 +119,49 @@ module OvirtSDK4
105
119
  # @option opts [Boolean] :kerberos (false) A boolean flag indicating if Kerberos uthentication should be used
106
120
  # instead of the default basic authentication.
107
121
  #
108
- # @option opts [Boolean] :timeout (0) The maximun total time to wait for the response, in seconds. A value of zero
122
+ # @option opts [Integer] :timeout (0) The maximun total time to wait for the response, in seconds. A value of zero
109
123
  # (the default) means wait for ever. If the timeout expires before the response is received an exception will be
110
124
  # raised.
111
125
  #
112
- def initialize(opts = {})
126
+ # @option opts [Boolean] :compress (false) A boolean flag indicating if the SDK should ask the server to send
127
+ # compressed responses. Note that this is a hint for the server, and that it may return uncompressed data even
128
+ # when this parameter is set to `true`.
129
+ #
130
+ # @option opts [String] :sso_url A string containing the base URL of the authentication service. This needs to be
131
+ # specified only when using an external authentication service. By default this URL is automatically calculated
132
+ # from the value of the `url` parameter, so that authentication will be performed using the authentication
133
+ # service that is part of the engine.
134
+ #
135
+ # @option opts [String] :sso_revoke_url A string containing the base URL of the SSO revoke service. This needs to be
136
+ # specified only when using an external authentication service. By default this URL is automatically calculated
137
+ # from the value of the `url` parameter, so that SSO token revoke will be performed using the SSO service that
138
+ # is part of the engine.
139
+ #
140
+ # @option opts [Boolean] :sso_insecure A boolean flag that indicates if the SSO server TLS certificate and
141
+ # host name should be checked. Default is value of `insecure`.
142
+ #
143
+ # @option opts [String] :sso_ca_file The name of a PEM file containing the trusted CA certificates. The
144
+ # certificate presented by the SSO server will be verified using these CA certificates. Default is value of
145
+ # `ca_file`.
146
+ #
147
+ # @option opts [Boolean] :sso_debug A boolean flag indicating if SSO debug output should be generated. If the
148
+ # values is `true` all the data sent to and received from the SSO server will be written to `$stdout`. Be aware
149
+ # that user names and passwords will also be written, so handle it with care. Default is value of `debug`.
150
+ #
151
+ # @option opts [String, IO] :sso_log The log file where the SSO debug output will be written. The value can be a
152
+ # string containing a file name or an IO object. If it is a file name then the file will be created if it doesn't
153
+ # exist, and the SSO debug output will be added to the end. The file will be closed when the connection is closed.
154
+ # If it is an IO object then the SSO debug output will be written directly, and it won't be closed. Default is
155
+ # value of `log`.
156
+ #
157
+ # @option opts [Boolean] :sso_timeout The maximun total time to wait for the SSO response, in seconds. A value
158
+ # of zero means wait for ever. If the timeout expires before the SSO response is received an exception will be
159
+ # raised. Default is value of `timeout`.
160
+ #
161
+ # @option opts [String] :sso_token_name (access_token) The token name in the JSON SSO response returned from the SSO
162
+ # server. Default value is `access_token`
163
+ #
164
+ def initialize(opts = {})
113
165
  # Get the values of the parameters and assign default values:
114
166
  url = opts[:url]
115
167
  username = opts[:username]
@@ -120,6 +172,15 @@ module OvirtSDK4
120
172
  log = opts[:log]
121
173
  kerberos = opts[:kerberos] || false
122
174
  timeout = opts[:timeout] || 0
175
+ compress = opts[:compress] || false
176
+ sso_url = opts[:sso_url]
177
+ sso_revoke_url = opts[:sso_revoke_url]
178
+ sso_insecure = opts[:sso_insecure] || insecure
179
+ sso_ca_file = opts[:sso_ca_file] || ca_file
180
+ sso_debug = opts[:sso_debug] || debug
181
+ sso_log = opts[:sso_log] || log
182
+ sso_timeout = opts[:sso_timeout] || timeout
183
+ sso_token_name = opts[:sso_token_name] || 'access_token'
123
184
 
124
185
  # Check mandatory parameters:
125
186
  if url.nil?
@@ -129,25 +190,23 @@ module OvirtSDK4
129
190
  # Save the URL:
130
191
  @url = URI(url)
131
192
 
193
+ # Save SSO parameters:
194
+ @sso_url = sso_url
195
+ @sso_revoke_url = sso_revoke_url
196
+ @username = username
197
+ @password = password
198
+ @kerberos = kerberos
199
+ @sso_insecure = sso_insecure
200
+ @sso_ca_file = sso_ca_file
201
+ @sso_log_file = sso_log
202
+ @sso_debug = sso_debug
203
+ @sso_timeout = sso_timeout
204
+ @log_file = log
205
+ @sso_token_name = sso_token_name
206
+
132
207
  # Create the cURL handle:
133
208
  @curl = Curl::Easy.new
134
209
 
135
- # Configure cookies so that they are enabled but stored only in memory:
136
- @curl.enable_cookies = true
137
- @curl.cookiefile = '/dev/null'
138
- @curl.cookiejar = '/dev/null'
139
-
140
- # Configure authentication:
141
- if kerberos
142
- @curl.http_auth_types = :gssnegotiate
143
- @curl.username = ''
144
- @curl.password = ''
145
- else
146
- @curl.http_auth_types = :basic
147
- @curl.username = username
148
- @curl.password = password
149
- end
150
-
151
210
  # Configure TLS parameters:
152
211
  if @url.scheme == 'https'
153
212
  if insecure
@@ -165,6 +224,12 @@ module OvirtSDK4
165
224
  # Configure the timeout:
166
225
  @curl.timeout = timeout
167
226
 
227
+ # Configure compression of responses (setting the value to a zero length string means accepting all the
228
+ # compression types that libcurl supports):
229
+ if compress
230
+ @curl.encoding = ''
231
+ end
232
+
168
233
  # Configure debug mode:
169
234
  @close_log = false
170
235
  if debug
@@ -241,12 +306,17 @@ module OvirtSDK4
241
306
  #
242
307
  # @api private
243
308
  #
244
- def send(request, last = false)
309
+ def send(request)
310
+
311
+ # Check if we already have an SSO access token:
312
+ if @sso_token.nil?
313
+ @sso_token = get_access_token
314
+ end
315
+
245
316
  # Build the URL:
246
317
  @curl.url = build_url({
247
318
  :path => request.path,
248
319
  :query => request.query,
249
- :matrix => request.matrix,
250
320
  })
251
321
 
252
322
  # Add headers, avoiding those that have no value:
@@ -256,11 +326,7 @@ module OvirtSDK4
256
326
  @curl.headers['Version'] = '4'
257
327
  @curl.headers['Content-Type'] = 'application/xml'
258
328
  @curl.headers['Accept'] = 'application/xml'
259
-
260
- # All requests except the last one should indicate that we want to use persistent authentication:
261
- if !last
262
- @curl.headers['Prefer'] = 'persistent-auth'
263
- end
329
+ @curl.headers['Authorization'] = 'Bearer ' + @sso_token
264
330
 
265
331
  # Send the request and wait for the response:
266
332
  case request.method
@@ -283,6 +349,206 @@ module OvirtSDK4
283
349
  return response
284
350
  end
285
351
 
352
+ ##
353
+ # Obtains the access token from SSO to be used for Bearer authentication.
354
+ #
355
+ # @return [String] The URL.
356
+ #
357
+ # @api private
358
+ #
359
+ def get_access_token
360
+ # If SSO url is not supplied build default one:
361
+ if @sso_url.nil?
362
+ @sso_url = URI(build_sso_auth_url)
363
+ else
364
+ @sso_url = URI(@sso_url)
365
+ end
366
+
367
+ sso_response = get_sso_response(@sso_url)
368
+
369
+ if sso_response.is_a?(Array)
370
+ sso_response = sso_response[0]
371
+ end
372
+
373
+ if !sso_response["error"].nil?
374
+ raise Error.new("Error during SSO authentication #{sso_response['error_code']} : #{sso_response['error']}")
375
+ end
376
+
377
+ return sso_response[@sso_token_name]
378
+ end
379
+
380
+ ##
381
+ # Revoke the SSO access token.
382
+ #
383
+ # @api private
384
+ #
385
+ def revoke_access_token
386
+ # If SSO revoke url is not supplied build default one:
387
+ if @sso_revoke_url.nil?
388
+ @sso_revoke_url = URI(build_sso_revoke_url)
389
+ else
390
+ @sso_revoke_url = URI(@sso_revoke_url)
391
+ end
392
+
393
+ sso_response = get_sso_response(@sso_revoke_url)
394
+
395
+ if sso_response.is_a?(Array)
396
+ sso_response = sso_response[0]
397
+ end
398
+
399
+ if !sso_response["error"].nil?
400
+ raise Error.new("Error during SSO revoke #{sso_response['error_code']} : #{sso_response['error']}")
401
+ end
402
+ end
403
+
404
+ ##
405
+ # Execute a get request to the SSO server and return the response.
406
+ #
407
+ # @return [Hash] The JSON response.
408
+ #
409
+ # @api private
410
+ #
411
+ def get_sso_response(sso_base_url)
412
+ # Create the cURL handle for SSO:
413
+ sso_curl = Curl::Easy.new
414
+
415
+ # Configure the timeout:
416
+ sso_curl.timeout = @sso_timeout
417
+
418
+ # Configure debug mode:
419
+ sso_close_log = false
420
+ if @sso_debug
421
+ if @sso_log_file.nil?
422
+ sso_log = STDOUT
423
+ elsif @sso_log_file == @log_file
424
+ sso_log = @log
425
+ elsif @sso_log_file.is_a?(String)
426
+ sso_log = ::File.open(@sso_log_file, 'a')
427
+ sso_close_log = true
428
+ else
429
+ sso_log = @sso_log_file
430
+ end
431
+ sso_curl.verbose = true
432
+ sso_curl.on_debug do |type, data|
433
+ case type
434
+ when Curl::CURLINFO_DATA_IN
435
+ prefix = '< '
436
+ when Curl::CURLINFO_DATA_OUT
437
+ prefix = '> '
438
+ when Curl::CURLINFO_HEADER_IN
439
+ prefix = '< '
440
+ when Curl::CURLINFO_HEADER_OUT
441
+ prefix = '> '
442
+ else
443
+ prefix = '* '
444
+ end
445
+ lines = data.gsub("\r\n", "\n").strip.split("\n")
446
+ lines.each do |line|
447
+ sso_log.puts(prefix + line)
448
+ end
449
+ sso_log.flush
450
+ end
451
+ end
452
+
453
+ begin
454
+ # Configure TLS parameters:
455
+ if sso_base_url.scheme == 'https'
456
+ if @sso_insecure
457
+ sso_curl.ssl_verify_peer = false
458
+ sso_curl.ssl_verify_host = false
459
+ elsif @sso_ca_file.nil?
460
+ raise ArgumentError.new("The \"sso_ca_file\" argument is mandatory when using TLS.")
461
+ elsif not ::File.file?(@sso_ca_file)
462
+ raise ArgumentError.new("The CA file \"#{@sso_ca_file}\" doesn't exist.")
463
+ else
464
+ sso_curl.cacert = @sso_ca_file
465
+ end
466
+ end
467
+
468
+ # The username and password parameters:
469
+ params = {}
470
+
471
+ # The base SSO URL:
472
+ sso_url = sso_base_url.to_s
473
+
474
+ # Configure authentication:
475
+ if @kerberos
476
+ sso_curl.http_auth_types = :gssnegotiate
477
+ sso_curl.username = ''
478
+ sso_curl.password = ''
479
+ else
480
+ sso_curl.http_auth_types = :basic
481
+ sso_curl.username = @username
482
+ sso_curl.password = @password
483
+ if sso_url.index('?').nil?
484
+ sso_url += '?'
485
+ end
486
+ params['username'] = @username
487
+ params['password'] = @password
488
+ sso_url = sso_url + '&' + URI.encode_www_form(params)
489
+ end
490
+
491
+ # Build the SSO access_token request url:
492
+ sso_curl.url = sso_url
493
+
494
+ # Add headers:
495
+ sso_curl.headers['User-Agent'] = "RubySDK/#{VERSION}"
496
+ sso_curl.headers['Accept'] = 'application/json'
497
+
498
+ # Request access token:
499
+ sso_curl.http_get
500
+
501
+ # Parse and return the JSON response:
502
+ return JSON.parse(sso_curl.body_str)
503
+ ensure
504
+ sso_curl.close
505
+ # Close the log file, if we did open it:
506
+ if sso_close_log
507
+ sso_log.close
508
+ end
509
+ end
510
+ end
511
+
512
+ ##
513
+ # Builds a request URL to acquire the access token from SSO. The URLS are different for basic auth and Kerberos,
514
+ # @return [String] The URL.
515
+ #
516
+ # @api private
517
+ #
518
+ def build_sso_auth_url
519
+ # Get the base URL:
520
+ sso_url = @url.to_s[0..@url.to_s.rindex('/')]
521
+
522
+ # The SSO access scope:
523
+ scope = 'ovirt-app-api'
524
+
525
+ # Set the grant type and entry point to request from SSO:
526
+ if @kerberos
527
+ grant_type = 'urn:ovirt:params:oauth:grant-type:http'
528
+ entry_point = 'token-http-auth'
529
+ else
530
+ grant_type = 'password'
531
+ entry_point = 'token'
532
+ end
533
+
534
+ # Build and return the SSO URL:
535
+ return "#{sso_url}sso/oauth/#{entry_point}?grant_type=#{grant_type}&scope=#{scope}"
536
+ end
537
+
538
+ ##
539
+ # Builds a request URL to revoke the SSO access token.
540
+ # @return [String] The URL.
541
+ #
542
+ # @api private
543
+ #
544
+ def build_sso_revoke_url
545
+ # Get the base URL:
546
+ sso_url = @url.to_s[0..@url.to_s.rindex('/')]
547
+
548
+ # Build and return the SSO revoke URL:
549
+ return "#{sso_url}services/sso-logout?scope=&token=#{@sso_token}"
550
+ end
551
+
286
552
  ##
287
553
  # Tests the connectivity with the server. If connectivity works correctly it returns `true`. If there is any
288
554
  # connectivity problem it will either return `false` or raise an exception if the `raise_exception` parameter is
@@ -347,7 +613,10 @@ module OvirtSDK4
347
613
  request = Request.new({
348
614
  :method => :HEAD,
349
615
  })
350
- send(request, true)
616
+ send(request)
617
+
618
+ # Revoke the SSO access token:
619
+ revoke_access_token
351
620
 
352
621
  # Close the log file, if we did open it:
353
622
  if @close_log
@@ -359,7 +628,7 @@ module OvirtSDK4
359
628
  end
360
629
 
361
630
  ##
362
- # Builds a request URL from a path, and the sets of matrix and query parameters.
631
+ # Builds a request URL from a path, and the set of query parameters.
363
632
  #
364
633
  # @params opts [Hash] The options used to build the URL.
365
634
  #
@@ -369,10 +638,6 @@ module OvirtSDK4
369
638
  # keys of the hash should be strings containing the names of the parameters, and the values should be strings
370
639
  # containing the values.
371
640
  #
372
- # @option opts [Hash<String, String>] :matrix ({}) A hash containing the matrix parameters to add to the URL. The
373
- # keys of the hash should be strings containing the names of the parameters, and the values should be strings
374
- # containing the values.
375
- #
376
641
  # @return [String] The URL.
377
642
  #
378
643
  # @api private
@@ -381,15 +646,9 @@ module OvirtSDK4
381
646
  # Get the values of the parameters and assign default values:
382
647
  path = opts[:path] || ''
383
648
  query = opts[:query] || {}
384
- matrix = opts[:matrix] || {}
385
649
 
386
650
  # Add the path and the parameters:
387
651
  url = @url.to_s + path
388
- if not matrix.empty?
389
- matrix.each do |key, value|
390
- url = url + ';' + URI.encode_www_form({key => value})
391
- end
392
- end
393
652
  if not query.empty?
394
653
  url = url + '?' + URI.encode_www_form(query)
395
654
  end