rbvmomi 1.8.2 → 2.4.1

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 (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