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

Sign up to get free protection for your applications and to get access to all the features.
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