rbvmomi 1.8.2 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +5 -13
  2. data/LICENSE +1 -1
  3. data/README.md +114 -0
  4. data/{bin → exe}/rbvmomish +5 -5
  5. data/lib/rbvmomi.rb +11 -7
  6. data/lib/rbvmomi/basic_types.rb +9 -1
  7. data/lib/rbvmomi/connection.rb +11 -9
  8. data/lib/rbvmomi/deserialization.rb +4 -3
  9. data/lib/rbvmomi/fault.rb +3 -1
  10. data/lib/rbvmomi/{trollop.rb → optimist.rb} +10 -8
  11. data/lib/rbvmomi/pbm.rb +3 -1
  12. data/lib/rbvmomi/sms.rb +3 -1
  13. data/lib/rbvmomi/sms/SmsStorageManager.rb +3 -0
  14. data/lib/rbvmomi/sso.rb +313 -0
  15. data/lib/rbvmomi/trivial_soap.rb +19 -11
  16. data/lib/rbvmomi/type_loader.rb +4 -2
  17. data/lib/rbvmomi/utils/admission_control.rb +22 -19
  18. data/lib/rbvmomi/utils/deploy.rb +18 -14
  19. data/lib/rbvmomi/utils/leases.rb +4 -1
  20. data/lib/rbvmomi/utils/perfdump.rb +4 -1
  21. data/lib/rbvmomi/version.rb +6 -0
  22. data/lib/rbvmomi/vim.rb +41 -12
  23. data/lib/rbvmomi/vim/ComputeResource.rb +3 -0
  24. data/lib/rbvmomi/vim/Datacenter.rb +8 -0
  25. data/lib/rbvmomi/vim/Datastore.rb +5 -1
  26. data/lib/rbvmomi/vim/DynamicTypeMgrAllTypeInfo.rb +3 -0
  27. data/lib/rbvmomi/vim/DynamicTypeMgrDataTypeInfo.rb +4 -1
  28. data/lib/rbvmomi/vim/DynamicTypeMgrManagedTypeInfo.rb +15 -7
  29. data/lib/rbvmomi/vim/Folder.rb +19 -12
  30. data/lib/rbvmomi/vim/HostSystem.rb +5 -2
  31. data/lib/rbvmomi/vim/ManagedEntity.rb +3 -0
  32. data/lib/rbvmomi/vim/ManagedObject.rb +4 -1
  33. data/lib/rbvmomi/vim/ObjectContent.rb +3 -0
  34. data/lib/rbvmomi/vim/ObjectUpdate.rb +3 -0
  35. data/lib/rbvmomi/vim/OvfManager.rb +6 -2
  36. data/lib/rbvmomi/vim/PerfCounterInfo.rb +3 -1
  37. data/lib/rbvmomi/vim/PerformanceManager.rb +3 -0
  38. data/lib/rbvmomi/vim/PropertyCollector.rb +3 -0
  39. data/lib/rbvmomi/vim/ReflectManagedMethodExecuter.rb +5 -2
  40. data/lib/rbvmomi/vim/ResourcePool.rb +3 -0
  41. data/lib/rbvmomi/vim/ServiceInstance.rb +3 -0
  42. data/lib/rbvmomi/vim/Task.rb +3 -0
  43. data/lib/rbvmomi/vim/VirtualMachine.rb +13 -12
  44. data/vmodl.db +0 -0
  45. metadata +100 -63
  46. data/.yardopts +0 -6
  47. data/README.rdoc +0 -78
  48. data/Rakefile +0 -45
  49. data/VERSION +0 -1
  50. data/devel/analyze-vim-declarations.rb +0 -213
  51. data/devel/analyze-xml.rb +0 -46
  52. data/devel/benchmark.rb +0 -117
  53. data/devel/collisions.rb +0 -18
  54. data/devel/merge-internal-vmodl.rb +0 -59
  55. data/devel/merge-manual-vmodl.rb +0 -32
  56. data/examples/annotate.rb +0 -54
  57. data/examples/cached_ovf_deploy.rb +0 -120
  58. data/examples/clone_vm.rb +0 -84
  59. data/examples/create_vm-1.9.rb +0 -93
  60. data/examples/create_vm.rb +0 -93
  61. data/examples/extraConfig.rb +0 -54
  62. data/examples/lease_tool.rb +0 -102
  63. data/examples/logbundle.rb +0 -63
  64. data/examples/logtail.rb +0 -60
  65. data/examples/nfs_datastore.rb +0 -95
  66. data/examples/power.rb +0 -59
  67. data/examples/readme-1.rb +0 -35
  68. data/examples/readme-2.rb +0 -51
  69. data/examples/run.sh +0 -41
  70. data/examples/screenshot.rb +0 -48
  71. data/examples/vdf.rb +0 -81
  72. data/examples/vm_drs_behavior.rb +0 -76
  73. data/test/test_deserialization.rb +0 -383
  74. data/test/test_emit_request.rb +0 -128
  75. data/test/test_exceptions.rb +0 -14
  76. data/test/test_helper.rb +0 -14
  77. data/test/test_misc.rb +0 -24
  78. data/test/test_parse_response.rb +0 -69
  79. data/test/test_serialization.rb +0 -311
checksums.yaml CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MjIzODk3MDBhOTNjMDg5ODAyZjgyNWZjNDMwYzk2OTQ2NTQzYmEyYw==
5
- data.tar.gz: !binary |-
6
- Y2U1MmQ2YTM2NzNiOTI1NzYyMzhkYmZmODdkMGVhNWE5OTBlMzY5Nw==
2
+ SHA256:
3
+ metadata.gz: fa94b53805d6066da9dab0c887099c1fbda5f3bbf053b828d24fa3532761276f
4
+ data.tar.gz: a1061d9d03c38eda8a17061e6fb6ffc8ce7053f4d14c237201e5690cd8e77ee6
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- NjA5YmM1ZTExODA5ODM4NjllNTU0OGEyOGIxNTQ2OTIzNzRjODEyOWE1MDkz
10
- MjM5MmZjNTMwZTg1MDUwZjE0MWI2ODVhYTdmN2IyOWUyNTYzODY0MDMzMzg5
11
- ODU3YzY0Y2MzMjg5MzM1MTdiZWI0MGJlMjM1ODIzNWMxYjFiZTI=
12
- data.tar.gz: !binary |-
13
- OTZiMDI4MzA5ODcwOTAwN2I1MDNiOTBlNWNjYWI2OTdhNWQzYWI0M2M1YzI5
14
- ZWZiMDVjNTRhNTg5MTU0ZmE5Yzg5YTY5NDcwNWZmMDIxMTY1ZGUwZGZkNzUw
15
- NTBhOGJjN2ZmYzI0MjVlZDBkM2UxZjkyNDU3YzY2NGZjNTkzOWU=
6
+ metadata.gz: '082a81fbb750449c92511db26dd03e4df2de5602f9091c1b035c70a7e4389ce40d4b8a2df9600f49f6feb9f96239811165f2b0b0df5c6b9ef49f0a3b98a95d26'
7
+ data.tar.gz: 0a1cceb5a1c08fb369ef0629ca9aa824c42192f9625d1f0ebc2baf0b912cf76b7d47f4088450dd1b4520f0f58a8ec815070912733fff6d4fbb8b4739580cd44c
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 VMware, Inc. All Rights Reserved.
1
+ Copyright (c) 2010-2017 VMware, Inc. All Rights Reserved.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
data/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # RbVmomi
2
+
3
+ [<img src="https://badge.fury.io/rb/rbvmomi.svg" alt="gem-version">](https://rubygems.org/gems/rbvmomi)
4
+ [<img src="https://travis-ci.org/vmware/rbvmomi.svg?branch=master" alt="travis-ci">](http://travis-ci.org/vmware/rbvmomi)
5
+ [<img src="https://badges.gitter.im/vmware/rbvmomi.svg">](https://gitter.im/vmware/rbvmomi)
6
+
7
+ This is a community-supported, open source project at VMware. It is built and
8
+ maintained by programmers like you!
9
+
10
+ ## Introduction
11
+
12
+ RbVmomi is a Ruby interface to the vSphere API. Like the Perl and Java SDKs,
13
+ you can use it to manage ESX and vCenter servers. The current release
14
+ supports the vSphere 6.5 API. RbVmomi specific documentation is
15
+ [online](http://rdoc.info/github/vmware/rbvmomi/master/frames) and is meant to
16
+ be used alongside the official [documentation](http://pubs.vmware.com/vsphere-65/index.jsp#com.vmware.wssdk.apiref.doc/right-pane.html).
17
+
18
+ ## Installation
19
+
20
+ gem install rbvmomi
21
+
22
+ ### Support for older Ruby versions
23
+
24
+ RbVmomi supports Ruby 1.8.7 and higher, but certain dependencies may need
25
+ pinning to older versions to get a compatible set of gems.
26
+
27
+ On Ruby 1.8.7:
28
+
29
+ * use `nokogiri` 1.5.x (Gemfile: `gem 'nokogiri', '< 1.6'`)
30
+
31
+
32
+ On both Ruby 1.9 and 1.8.7:
33
+
34
+ * use `json` 1.x (Gemfile: `gem 'json', '< 2'`)
35
+
36
+
37
+ ## Usage
38
+
39
+ A simple example of turning on a VM:
40
+
41
+ ```ruby
42
+ require 'rbvmomi'
43
+
44
+ vim = RbVmomi::VIM.connect(host: 'foo', user: 'bar', password: 'baz')
45
+ dc = vim.serviceInstance.find_datacenter('my_datacenter') || fail('datacenter not found')
46
+ vm = dc.find_vm('my_vm') || fail('VM not found')
47
+ vm.PowerOnVM_Task.wait_for_completion
48
+ ```
49
+
50
+ This code uses several RbVmomi extensions to the vSphere API for concision.
51
+ The expanded snippet below uses only standard API calls and should be familiar
52
+ to users of the Java SDK:
53
+
54
+ ```ruby
55
+ require 'rbvmomi'
56
+
57
+ vim = RbVmomi::VIM.connect(host: 'foo', user: 'bar', password: 'baz')
58
+ root_folder = vim.serviceInstance.content.rootFolder
59
+ dc = root_folder.childEntity.grep(RbVmomi::VIM::Datacenter).find { |x| x.name == 'mydatacenter' } || fail('datacenter not found')
60
+ vm = dc.vmFolder.childEntity.grep(RbVmomi::VIM::VirtualMachine).find { |x| x.name == 'my_vm' } || fail('VM not found')
61
+ task = vm.PowerOnVM_Task
62
+ filter = vim.propertyCollector.CreateFilter(
63
+ spec: {
64
+ propSet: [{ type: 'Task', all: false, pathSet: ['info.state']}],
65
+ objectSet: [{ obj: task }]
66
+ },
67
+ partialUpdates: false
68
+ )
69
+ ver = ''
70
+ loop do
71
+ result = vim.propertyCollector.WaitForUpdates(version: ver)
72
+ ver = result.version
73
+ break if ['success', 'error'].member?(task.info.state)
74
+ end
75
+ filter.DestroyPropertyFilter
76
+ raise(task.info.error) if task.info.state == 'error'
77
+ ```
78
+
79
+ As you can see, the extensions RbVmomi adds can dramatically decrease the code
80
+ needed to perform simple tasks while still letting you use the full power of
81
+ the API when necessary. RbVmomi extensions are often more efficient than a
82
+ naive implementation; for example, the find_vm method on VIM::Datacenter used
83
+ in the first example uses the SearchIndex for fast lookups.
84
+
85
+ A few important points:
86
+
87
+ * All class, method, parameter, and property names match the official [documentation](http://pubs.vmware.com/vsphere-65/index.jsp#com.vmware.wssdk.apiref.doc/right-pane.html).
88
+ * Properties are exposed as accessor methods.
89
+ * Data object types can usually be inferred from context, so you may use a hash instead.
90
+ * Enumeration values are simply strings.
91
+ * Example code is included in the examples/ directory.
92
+ * A set of helper methods for Optimist is included to speed up development of
93
+ command line apps. See the included examples for usage.
94
+ * If you don't have trusted SSL certificates installed on the host you're
95
+ connecting to, you'll get an `OpenSSL::SSL::SSLError` "certificate verify
96
+ failed". You can work around this by using the `:insecure` option to
97
+ `RbVmomi::VIM.connect`.
98
+ * This is a side project of a VMware employee and is entirely unsupported by
99
+ VMware.
100
+
101
+
102
+ Built-in extensions are under `lib/rbvmomi/vim/`. You are encouraged to reopen
103
+ VIM classes in your applications and add extensions of your own. If you write
104
+ something generally useful please open a [pull request](https://github.com/vmware/rbvmomi/pulls) so it can be merged back in
105
+
106
+ ## Development
107
+
108
+ Open an issue on the [issues page](https://github.com/vmware/rbvmomi/issues)
109
+ or fork the project on [GitHub](https://github.com/vmware/rbvmomi) and send a
110
+ [pull request](https://github.com/vmware/rbvmomi/pulls).
111
+
112
+ ## Support
113
+
114
+ You can chat on [Gitter](https://gitter.im/vmware/rbvmomi) or join the [VMware {code} Slack team](https://vmwarecode.slack.com/) and join the [#rbvmomi channel](https://vmwarecode.slack.com/messages/rbvmomi).
@@ -2,14 +2,14 @@
2
2
  # TODO keepalive
3
3
  # TODO rc file
4
4
  # TODO proxy support?
5
- require 'trollop'
5
+ require 'optimist'
6
6
  require 'readline'
7
7
  require 'rbvmomi'
8
- require 'rbvmomi/trollop'
8
+ require 'rbvmomi/optimist'
9
9
 
10
10
  VIM = RbVmomi::VIM
11
11
 
12
- opts = Trollop.options do
12
+ opts = Optimist.options do
13
13
  banner <<-EOS
14
14
  vSphere API console.
15
15
 
@@ -35,7 +35,7 @@ VIM connection options:
35
35
  Other options:
36
36
  EOS
37
37
 
38
- $trollop = self
38
+ $optimist = self
39
39
  end
40
40
 
41
41
  begin
@@ -102,7 +102,7 @@ def si
102
102
  end
103
103
 
104
104
  def help
105
- $trollop.educate
105
+ $optimist.educate
106
106
  :no_result
107
107
  end
108
108
 
data/lib/rbvmomi.rb CHANGED
@@ -1,12 +1,16 @@
1
- # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
2
- module RbVmomi
1
+ # Copyright (c) 2010-2019 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
3
 
4
- # @private
5
- # @deprecated Use +RbVmomi::VIM.connect+.
6
- def self.connect opts
7
- VIM.connect opts
4
+ # RbVmomi is a Ruby interface to the vSphere management interface
5
+ module RbVmomi
6
+ # @private
7
+ # @deprecated Use +RbVmomi::VIM.connect+.
8
+ def self.connect(opts)
9
+ VIM.connect opts
10
+ end
8
11
  end
9
12
 
10
- end
11
13
  require 'rbvmomi/connection'
14
+ require 'rbvmomi/sso'
15
+ require 'rbvmomi/version'
12
16
  require 'rbvmomi/vim'
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
1
+ # Copyright (c) 2011-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
2
4
  require 'pp'
3
5
  require 'set'
4
6
 
@@ -158,6 +160,12 @@ class DataObject < ObjectWithProperties
158
160
  q.text ')'
159
161
  end
160
162
 
163
+ def to_json(*args)
164
+ h = self.props
165
+ m = h.merge({ JSON.create_id => self.class.name })
166
+ m.to_json(*args)
167
+ end
168
+
161
169
  init
162
170
  end
163
171
 
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
1
+ # Copyright (c) 2011-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
2
4
  require 'time'
3
5
  require 'date'
4
6
  require 'rbvmomi/trivial_soap'
@@ -21,7 +23,7 @@ class Connection < TrivialSoap
21
23
  attr_reader :profile_summary
22
24
  attr_accessor :profiling
23
25
  attr_reader :deserializer
24
-
26
+
25
27
  def initialize opts
26
28
  @ns = opts[:ns] or fail "no namespace specified"
27
29
  @rev = opts[:rev] or fail "no revision specified"
@@ -30,7 +32,7 @@ class Connection < TrivialSoap
30
32
  @profiling = false
31
33
  super opts
32
34
  end
33
-
35
+
34
36
  def reset_profiling
35
37
  @profile = {}
36
38
  @profile_summary = {:network_latency => 0, :request_emit => 0, :response_parse => 0, :num_calls => 0}
@@ -88,7 +90,7 @@ class Connection < TrivialSoap
88
90
 
89
91
  t3 = Time.now
90
92
  out = parse_response resp, desc['result']
91
-
93
+
92
94
  if @profiling
93
95
  t4 = Time.now
94
96
  @profile[method] ||= []
@@ -96,8 +98,8 @@ class Connection < TrivialSoap
96
98
  :network_latency => (t3 - t2),
97
99
  :request_emit => t2 - t1,
98
100
  :response_parse => t4 - t3,
99
- :params => params,
100
- :obj => this,
101
+ :params => params,
102
+ :obj => this,
101
103
  :backtrace => caller,
102
104
  :request_size => body.length,
103
105
  :response_size => resp_size,
@@ -108,7 +110,7 @@ class Connection < TrivialSoap
108
110
  @profile_summary[:request_emit] += profile_info[:request_emit]
109
111
  @profile_summary[:num_calls] += 1
110
112
  end
111
-
113
+
112
114
  out
113
115
  end
114
116
 
@@ -137,7 +139,7 @@ class Connection < TrivialSoap
137
139
  when BasicTypes::DataObject
138
140
  if expected and not expected >= o.class and not expected == BasicTypes::AnyType
139
141
  fail "expected #{expected.wsdl_name} for '#{name}', got #{o.class.wsdl_name} for field #{name.inspect}"
140
- end
142
+ end
141
143
  xml.tag! name, attrs.merge("xsi:type" => o.class.wsdl_name) do
142
144
  o.class.full_props_desc.each do |desc|
143
145
  if o.props.member? desc['name'].to_sym
@@ -221,7 +223,7 @@ class Connection < TrivialSoap
221
223
  def type name
222
224
  self.class.type name
223
225
  end
224
-
226
+
225
227
  def instanceUuid
226
228
  nil
227
229
  end
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
1
+ # Copyright (c) 2011-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
2
4
  require 'time'
3
5
 
4
6
  module RbVmomi
@@ -99,7 +101,6 @@ class NewDeserializer
99
101
  obj = klass.new nil
100
102
  props = obj.props
101
103
  children = node.children.select(&:element?)
102
- n = children.size
103
104
  i = 0
104
105
 
105
106
  klass.full_props_desc.each do |desc|
@@ -150,7 +151,7 @@ class NewDeserializer
150
151
  h[child.name] = child.content
151
152
  end
152
153
  [h['key'], h['value']]
153
- end
154
+ end
154
155
  end
155
156
 
156
157
  class OldDeserializer
data/lib/rbvmomi/fault.rb CHANGED
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2010 VMware, Inc. All Rights Reserved.
1
+ # Copyright (c) 2011-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
2
4
  module RbVmomi
3
5
 
4
6
  class Fault < StandardError
@@ -1,17 +1,19 @@
1
- # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
2
- require 'trollop'
1
+ # Copyright (c) 2010-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
3
 
4
- # Convenience methods for Trollop, Ruby's premier option parser.
5
- # @see http://trollop.rubyforge.org/
6
- # @see Trollop::Parser
7
- module Trollop
4
+ require 'optimist'
8
5
 
9
- # Convenience methods for Trollop, Ruby's premier option parser.
6
+ # Convenience methods for Optimist, Ruby's premier option parser.
7
+ # @see http://optimist.rubyforge.org/
8
+ # @see Optimist::Parser
9
+ module Optimist
10
+
11
+ # Convenience methods for Optimist, Ruby's premier option parser.
10
12
  #
11
13
  # See the examples directory for sample code.
12
14
  # Descriptions are of the form:
13
15
  # <key>: <options> <environment variable> (<default>)
14
- # @see http://trollop.rubyforge.org/
16
+ # @see http://optimist.rubyforge.org/
15
17
  class Parser
16
18
  # Options used by VIM.connect
17
19
  #
data/lib/rbvmomi/pbm.rb CHANGED
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2012 VMware, Inc. All Rights Reserved.
1
+ # Copyright (c) 2012-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
2
4
  require 'rbvmomi'
3
5
 
4
6
  module RbVmomi
data/lib/rbvmomi/sms.rb CHANGED
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2013 VMware, Inc. All Rights Reserved.
1
+ # Copyright (c) 2013-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
2
4
  require 'rbvmomi'
3
5
  module RbVmomi
4
6
 
@@ -1,3 +1,6 @@
1
+ # Copyright (c) 2013-2017 VMware, Inc. All Rights Reserved.
2
+ # SPDX-License-Identifier: MIT
3
+
1
4
  class RbVmomi::SMS::SmsStorageManager
2
5
 
3
6
  def RegisterProvider_Task2 providerSpec
@@ -0,0 +1,313 @@
1
+ require 'base64'
2
+ require 'net/https'
3
+ require 'nokogiri'
4
+ require 'openssl'
5
+ require 'securerandom'
6
+ require 'time'
7
+
8
+ module RbVmomi
9
+ # Provides access to vCenter Single Sign-On
10
+ class SSO
11
+ BST_PROFILE = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3'.freeze
12
+ C14N_CLASS = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
13
+ C14N_METHOD = 'http://www.w3.org/2001/10/xml-exc-c14n#'.freeze
14
+ DIGEST_METHOD = 'http://www.w3.org/2001/04/xmlenc#sha512'.freeze
15
+ ENCODING_METHOD = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary'.freeze
16
+ SIGNATURE_METHOD = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'.freeze
17
+ STS_PATH = '/sts/STSService'.freeze
18
+ TOKEN_TYPE = 'urn:oasis:names:tc:SAML:2.0:assertion'.freeze
19
+ TOKEN_PROFILE = 'http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0'.freeze
20
+ NAMESPACES = {
21
+ :ds => 'http://www.w3.org/2000/09/xmldsig#',
22
+ :soap => 'http://schemas.xmlsoap.org/soap/envelope/',
23
+ :wsse => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd',
24
+ :wsse11 => 'http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd',
25
+ :wst => 'http://docs.oasis-open.org/ws-sx/ws-trust/200512',
26
+ :wsu => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
27
+ }.freeze
28
+
29
+ attr_reader :assertion,
30
+ :assertion_id,
31
+ :certificate,
32
+ :host,
33
+ :user,
34
+ :password,
35
+ :path,
36
+ :port,
37
+ :private_key
38
+
39
+ # Creates an instance of an SSO object
40
+ #
41
+ # @param [Hash] opts the options to create the object with
42
+ # @option opts [String] :host the host to connect to
43
+ # @option opts [Fixnum] :port (443) the port to connect to
44
+ # @option opts [String] :path the path to call
45
+ # @option opts [String] :user the user to authenticate with
46
+ # @option opts [String] :password the password to authenticate with
47
+ # @option opts [String] :private_key the private key to use
48
+ # @option opts [String] :certificate the certificate to use
49
+ # @option opts [Boolean] :insecure (false) whether to connect insecurely
50
+ def initialize(opts = {})
51
+ @host = opts[:host]
52
+ @insecure = opts.fetch(:insecure, false)
53
+ @password = opts[:password]
54
+ @path = opts.fetch(:path, STS_PATH)
55
+ @port = opts.fetch(:port, 443)
56
+ @user = opts[:user]
57
+
58
+ load_x509(opts[:private_key], opts[:certificate])
59
+ end
60
+
61
+ def request_token
62
+ req = sso_call(hok_token_request)
63
+
64
+ unless req.is_a?(Net::HTTPSuccess)
65
+ resp = Nokogiri::XML(req.body)
66
+ resp.remove_namespaces!
67
+ raise(resp.at_xpath('//Envelope/Body/Fault/faultstring/text()'))
68
+ end
69
+
70
+ extract_assertion(req.body)
71
+ end
72
+
73
+ def sign_request(request)
74
+ raise('Need SAML2 assertion') unless @assertion
75
+ raise('No SAML2 assertion ID') unless @assertion_id
76
+
77
+ request_id = generate_id
78
+ timestamp_id = generate_id
79
+
80
+ request = request.is_a?(String) ? Nokogiri::XML(request) : request
81
+ builder = Nokogiri::XML::Builder.new do |xml|
82
+ xml[:soap].Header(Hash[NAMESPACES.map { |ns, uri| ["xmlns:#{ns}", uri] }]) do
83
+ xml[:wsse].Security do
84
+ wsu_timestamp(xml, timestamp_id)
85
+ ds_signature(xml, request_id, timestamp_id) do |x|
86
+ x[:wsse].SecurityTokenReference('wsse11:TokenType' => TOKEN_PROFILE) do
87
+ x[:wsse].KeyIdentifier(
88
+ @assertion_id,
89
+ 'ValueType' => 'http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID'
90
+ )
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ # To avoid Nokogiri mangling the token, we replace it as a string
98
+ # later on. Figure out a way around this.
99
+ builder.doc.at_xpath('//soap:Header/wsse:Security/wsu:Timestamp').add_previous_sibling(Nokogiri::XML::Text.new('SAML_ASSERTION_PLACEHOLDER', builder.doc))
100
+
101
+ request.at_xpath('//soap:Envelope', NAMESPACES).tap do |e|
102
+ NAMESPACES.each do |ns, uri|
103
+ e.add_namespace(ns.to_s, uri)
104
+ end
105
+ end
106
+ request.xpath('//soap:Envelope/soap:Body').each do |body|
107
+ body.add_previous_sibling(builder.doc.root)
108
+ body.add_namespace('wsu', NAMESPACES[:wsu])
109
+ body['wsu:Id'] = request_id
110
+ end
111
+
112
+ signed = sign(request)
113
+ signed.gsub!('SAML_ASSERTION_PLACEHOLDER', @assertion.to_xml(:indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML).strip)
114
+
115
+ signed
116
+ end
117
+
118
+ # We default to Issue, since that's all we currently need.
119
+ def sso_call(body)
120
+ sso_url = URI::HTTPS.build(:host => @host, :port => @port, :path => @path)
121
+ http = Net::HTTP.new(sso_url.host, sso_url.port)
122
+ http.use_ssl = true
123
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @insecure
124
+
125
+ req = Net::HTTP::Post.new(sso_url.request_uri)
126
+ req.add_field('Accept', 'text/xml, multipart/related')
127
+ req.add_field('User-Agent', "VMware/RbVmomi #{RbVmomi::VERSION}")
128
+ req.add_field('SOAPAction', 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue')
129
+ req.content_type = 'text/xml; charset="UTF-8"'
130
+ req.body = body
131
+
132
+ http.request(req)
133
+ end
134
+
135
+ private
136
+
137
+ def hok_token_request
138
+ request_id = generate_id
139
+ security_token_id = generate_id
140
+ signature_id = generate_id
141
+ timestamp_id = generate_id
142
+
143
+ datum = Time.now.utc
144
+ created_at = datum.iso8601
145
+ token_expires_at = (datum + 1800).iso8601
146
+
147
+ builder = Nokogiri::XML::Builder.new do |xml|
148
+ xml[:soap].Envelope(Hash[NAMESPACES.map { |ns, uri| ["xmlns:#{ns}", uri] }]) do
149
+ xml[:soap].Header do
150
+ xml[:wsse].Security do
151
+ wsu_timestamp(xml, timestamp_id, datum)
152
+ wsse_username_token(xml)
153
+ wsse_binary_security_token(xml, security_token_id)
154
+ ds_signature(xml, request_id, timestamp_id, signature_id) do |x|
155
+ x[:wsse].SecurityTokenReference do
156
+ x[:wsse].Reference(
157
+ 'URI' => "##{security_token_id}",
158
+ 'ValueType' => BST_PROFILE
159
+ )
160
+ end
161
+ end
162
+ end
163
+ end
164
+ xml[:soap].Body('wsu:Id' => request_id) do
165
+ xml[:wst].RequestSecurityToken do
166
+ xml[:wst].TokenType(TOKEN_TYPE)
167
+ xml[:wst].RequestType('http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue')
168
+ xml[:wst].Lifetime do
169
+ xml[:wsu].Created(created_at)
170
+ xml[:wsu].Expires(token_expires_at)
171
+ end
172
+ xml[:wst].Renewing('Allow' => 'false', 'OK' => 'false')
173
+ xml[:wst].KeyType('http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey')
174
+ xml[:wst].SignatureAlgorithm(SIGNATURE_METHOD)
175
+ xml[:wst].Delegatable('false')
176
+ end
177
+ xml[:wst].UseKey('Sig' => signature_id)
178
+ end
179
+ end
180
+ end
181
+
182
+ sign(builder.doc)
183
+ end
184
+
185
+ def extract_assertion(sso_response)
186
+ sso_response = Nokogiri::XML(sso_response) if sso_response.is_a?(String)
187
+ namespaces = sso_response.collect_namespaces
188
+
189
+ # Doesn't matter that usually there's more than one NS with the same
190
+ # URI - either will work for XPath. We just don't want to hardcode
191
+ # xmlns:saml2.
192
+ token_ns = namespaces.find { |_, uri| uri == TOKEN_TYPE }.first.gsub(/^xmlns:/, '')
193
+
194
+ @assertion = sso_response.at_xpath("//#{token_ns}:Assertion", namespaces)
195
+ @assertion_id = @assertion.at_xpath("//#{token_ns}:Assertion/@ID", namespaces).value
196
+ end
197
+
198
+ def sign(doc)
199
+ signature_digest_references = doc.xpath('/soap:Envelope/soap:Header/wsse:Security/ds:Signature/ds:SignedInfo/ds:Reference/@URI', doc.collect_namespaces).map { |a| a.value.sub(/^#/, '') }
200
+ signature_digest_references.each do |ref|
201
+ data = doc.at_xpath("//*[@wsu:Id='#{ref}']", doc.collect_namespaces)
202
+ digest = Base64.strict_encode64(Digest::SHA2.new(512).digest(data.canonicalize(C14N_CLASS)))
203
+ digest_tag = doc.at_xpath("/soap:Envelope/soap:Header/wsse:Security/ds:Signature/ds:SignedInfo/ds:Reference[@URI='##{ref}']/ds:DigestValue", doc.collect_namespaces)
204
+ digest_tag.add_child(Nokogiri::XML::Text.new(digest, doc))
205
+ end
206
+
207
+ signed_info = doc.at_xpath('/soap:Envelope/soap:Header/wsse:Security/ds:Signature/ds:SignedInfo', doc.collect_namespaces)
208
+ signature = Base64.strict_encode64(@private_key.sign(OpenSSL::Digest::SHA512.new, signed_info.canonicalize(C14N_CLASS)))
209
+ signature_value_tag = doc.at_xpath('/soap:Envelope/soap:Header/wsse:Security/ds:Signature/ds:SignatureValue', doc.collect_namespaces)
210
+ signature_value_tag.add_child(Nokogiri::XML::Text.new(signature, doc))
211
+
212
+ doc.to_xml(:indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML).strip
213
+ end
214
+
215
+ def load_x509(private_key, certificate)
216
+ @private_key = private_key ? private_key : OpenSSL::PKey::RSA.new(2048)
217
+ if @private_key.is_a? String
218
+ @private_key = OpenSSL::PKey::RSA.new(@private_key)
219
+ end
220
+
221
+ @certificate = certificate
222
+ if @certificate && !private_key
223
+ raise(ArgumentError, "Can't generate private key from a certificate")
224
+ end
225
+
226
+ if @certificate.is_a? String
227
+ @certificate = OpenSSL::X509::Certificate.new(@certificate)
228
+ end
229
+ # If only a private key is specified, we will generate a certificate.
230
+ unless @certificate
231
+ timestamp = Time.now.utc
232
+ @certificate = OpenSSL::X509::Certificate.new
233
+ @certificate.not_before = timestamp
234
+ @certificate.not_after = timestamp + 3600 # 3600 is 1 hour
235
+ @certificate.subject = OpenSSL::X509::Name.new([
236
+ %w[O VMware],
237
+ %w[OU RbVmomi],
238
+ %W[CN #{@user}]
239
+ ])
240
+ @certificate.issuer = @certificate.subject
241
+ @certificate.serial = rand(2**160)
242
+ @certificate.public_key = @private_key.public_key
243
+ @certificate.sign(@private_key, OpenSSL::Digest::SHA512.new)
244
+ end
245
+
246
+ true
247
+ end
248
+
249
+ def ds_signature(xml, request_id, timestamp_id, id = nil)
250
+ signature_id = {}
251
+ signature_id['Id'] = id if id
252
+ xml[:ds].Signature(signature_id) do
253
+ ds_signed_info(xml, request_id, timestamp_id)
254
+ xml[:ds].SignatureValue
255
+ xml[:ds].KeyInfo do
256
+ yield xml
257
+ end
258
+ end
259
+ end
260
+
261
+ def ds_signed_info(xml, request_id, timestamp_id)
262
+ xml[:ds].SignedInfo do
263
+ xml[:ds].CanonicalizationMethod('Algorithm' => C14N_METHOD)
264
+ xml[:ds].SignatureMethod('Algorithm' => SIGNATURE_METHOD)
265
+ xml[:ds].Reference('URI' => "##{request_id}") do
266
+ xml[:ds].Transforms do
267
+ xml[:ds].Transform('Algorithm' => C14N_METHOD)
268
+ end
269
+ xml[:ds].DigestMethod('Algorithm' => DIGEST_METHOD)
270
+ xml[:ds].DigestValue
271
+ end
272
+ xml[:ds].Reference('URI' => "##{timestamp_id}") do
273
+ xml[:ds].Transforms do
274
+ xml[:ds].Transform('Algorithm' => C14N_METHOD)
275
+ end
276
+ xml[:ds].DigestMethod('Algorithm' => DIGEST_METHOD)
277
+ xml[:ds].DigestValue
278
+ end
279
+ end
280
+ end
281
+
282
+ def wsu_timestamp(xml, id, datum = nil)
283
+ datum ||= Time.now.utc
284
+ created_at = datum.iso8601
285
+ expires_at = (datum + 600).iso8601
286
+
287
+ xml[:wsu].Timestamp('wsu:Id' => id) do
288
+ xml[:wsu].Created(created_at)
289
+ xml[:wsu].Expires(expires_at)
290
+ end
291
+ end
292
+
293
+ def wsse_username_token(xml)
294
+ xml[:wsse].UsernameToken do
295
+ xml[:wsse].Username(@user)
296
+ xml[:wsse].Password(@password)
297
+ end
298
+ end
299
+
300
+ def wsse_binary_security_token(xml, id)
301
+ xml[:wsse].BinarySecurityToken(
302
+ Base64.strict_encode64(@certificate.to_der),
303
+ 'EncodingType' => ENCODING_METHOD,
304
+ 'ValueType' => BST_PROFILE,
305
+ 'wsu:Id' => id
306
+ )
307
+ end
308
+
309
+ def generate_id
310
+ "_#{SecureRandom.uuid}"
311
+ end
312
+ end
313
+ end