knife-vcenter 2.0.0 → 2.0.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.
data/lib/sso.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  # Copyright 2014-2017 VMware, Inc. All Rights Reserved.
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
- require 'savon'
5
- require 'nokogiri'
6
- require 'date'
7
- require 'securerandom'
4
+ require "savon"
5
+ require "nokogiri"
6
+ require "date"
7
+ require "securerandom"
8
8
 
9
9
  # A little utility library for VMware SSO.
10
10
  # For now, this is not a general purpose library that covers all
@@ -12,258 +12,253 @@ require 'securerandom'
12
12
  # Specifically, the support is limited to the following:
13
13
  # * request bearer token.
14
14
  module SSO
15
+ # The XML date format.
16
+ DATE_FORMAT = "%FT%T.%LZ".freeze
17
+
18
+ # The XML namespaces that are required: SOAP, WSDL, et al.
19
+ NAMESPACES = {
20
+ "xmlns:S" => "http://schemas.xmlsoap.org/soap/envelope/",
21
+ "xmlns:wst" => "http://docs.oasis-open.org/ws-sx/ws-trust/200512",
22
+ "xmlns:u" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
23
+ "xmlns:x" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
24
+ }.freeze
25
+
26
+ # Provides the connection details for the SSO service.
27
+ class Connection
28
+ attr_accessor :sso_url, :wsdl_url, :username, :password
29
+
30
+ # Creates a new instance.
31
+ def initialize(sso_url, wsdl_url = nil)
32
+ self.sso_url = sso_url
33
+ self.wsdl_url = wsdl_url || "#{sso_url}?wsdl"
34
+ end
15
35
 
16
- # The XML date format.
17
- DATE_FORMAT = "%FT%T.%LZ"
36
+ # Login with the given credentials.
37
+ # Note: this does not invoke a login action, but rather stores the
38
+ # credentials for use later.
39
+ def login(username, password)
40
+ self.username = username
41
+ self.password = password
42
+ self # enable builder pattern
43
+ end
18
44
 
19
- # The XML namespaces that are required: SOAP, WSDL, et al.
20
- NAMESPACES = {
21
- "xmlns:S" => "http://schemas.xmlsoap.org/soap/envelope/",
22
- "xmlns:wst" => "http://docs.oasis-open.org/ws-sx/ws-trust/200512",
23
- "xmlns:u" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
24
- "xmlns:x" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
25
- }
45
+ # Gets (or creates) the Savon client instance.
46
+ def client
47
+ # construct and init the client proxy
48
+ @client ||= Savon.client do |globals|
49
+ # see: http://savonrb.com/version2/globals.html
50
+ globals.wsdl wsdl_url
51
+ globals.endpoint sso_url
52
+
53
+ globals.strip_namespaces false
54
+ globals.env_namespace :S
55
+
56
+ # set like this so https connection does not fail
57
+ # TODO: find an acceptable solution for production
58
+ globals.ssl_verify_mode :none
59
+
60
+ # dev/debug settings
61
+ # globals.pretty_print_xml ENV['DEBUG_SOAP']
62
+ # globals.log ENV['DEBUG_SOAP']
63
+ end
64
+ end
26
65
 
27
- # Provides the connection details for the SSO service.
28
- class Connection
66
+ # Invokes the request bearer token operation.
67
+ # @return [SamlToken]
68
+ def request_bearer_token
69
+ rst = RequestSecurityToken.new(client, username, password)
70
+ rst.invoke
71
+ rst.saml_token
72
+ end
73
+ end
74
+
75
+ # @abstract Base class for invocable service calls.
76
+ class SoapInvocable
77
+ attr_reader :operation, :client, :response
78
+
79
+ # Constructs a new instance.
80
+ # @param operation [Symbol] the SOAP operation name (in Symbol form)
81
+ # @param client [Savon::Client] the client
82
+ def initialize(operation, client)
83
+ @operation = operation
84
+ @client = client
85
+ end
29
86
 
30
- attr_accessor :sso_url, :wsdl_url, :username, :password
87
+ # Invokes the service call represented by this type.
88
+ def invoke
89
+ request = request_xml.to_s
90
+ puts "request = #{request}" if ENV["DEBUG"]
91
+ @response = client.call(operation, xml: request)
92
+ puts "response = #{response}" if ENV["DEBUG"]
93
+ self # for chaining with new
94
+ end
31
95
 
32
- # Creates a new instance.
33
- def initialize(sso_url, wsdl_url=nil)
34
- self.sso_url = sso_url
35
- self.wsdl_url = wsdl_url || "#{sso_url}?wsdl"
36
- end
96
+ # Builds the request XML content.
97
+ def request_xml
98
+ builder = Builder::XmlMarkup.new
99
+ builder.instruct!(:xml, encoding: "UTF-8")
37
100
 
38
- # Login with the given credentials.
39
- # Note: this does not invoke a login action, but rather stores the
40
- # credentials for use later.
41
- def login(username, password)
42
- self.username = username
43
- self.password = password
44
- self # enable builder pattern
101
+ builder.tag!("S:Envelope", NAMESPACES) do |envelope|
102
+ if has_header?
103
+ envelope.tag!("S:Header") do |header|
104
+ header_xml(header)
105
+ end
45
106
  end
46
-
47
- # Gets (or creates) the Savon client instance.
48
- def client
49
- # construct and init the client proxy
50
- @client ||= Savon.client do |globals|
51
- # see: http://savonrb.com/version2/globals.html
52
- globals.wsdl wsdl_url
53
- globals.endpoint sso_url
54
-
55
- globals.strip_namespaces false
56
- globals.env_namespace :S
57
-
58
- # set like this so https connection does not fail
59
- # TODO: find an acceptable solution for production
60
- globals.ssl_verify_mode :none
61
-
62
- # dev/debug settings
63
- # globals.pretty_print_xml ENV['DEBUG_SOAP']
64
- # globals.log ENV['DEBUG_SOAP']
65
- end
107
+ envelope.tag!("S:Body") do |body|
108
+ body_xml(body)
66
109
  end
110
+ end
111
+ builder.target!
112
+ end
67
113
 
68
- # Invokes the request bearer token operation.
69
- # @return [SamlToken]
70
- def request_bearer_token()
71
- rst = RequestSecurityToken.new(client, username, password)
72
- rst.invoke()
73
- rst.saml_token
74
- end
114
+ def has_header?
115
+ true
75
116
  end
76
117
 
77
- # @abstract Base class for invocable service calls.
78
- class SoapInvocable
118
+ # Builds the header portion of the SOAP request.
119
+ # Specific service operations must override this method.
120
+ def header_xml(_header)
121
+ raise "abstract method not implemented!"
122
+ end
79
123
 
80
- attr_reader :operation, :client, :response
124
+ # Builds the body portion of the SOAP request.
125
+ # Specific service operations must override this method.
126
+ def body_xml(_body)
127
+ raise "abstract method not implemented!"
128
+ end
81
129
 
82
- # Constructs a new instance.
83
- # @param operation [Symbol] the SOAP operation name (in Symbol form)
84
- # @param client [Savon::Client] the client
85
- def initialize(operation, client)
86
- @operation = operation
87
- @client = client
88
- end
130
+ # Gets the response XML content.
131
+ def response_xml
132
+ raise "illegal state: response not set yet" if response.nil?
89
133
 
90
- # Invokes the service call represented by this type.
91
- def invoke
92
- request = request_xml.to_s
93
- puts "request = #{request}" if ENV['DEBUG']
94
- @response = client.call(operation, xml:request)
95
- puts "response = #{response}" if ENV['DEBUG']
96
- self # for chaining with new
97
- end
134
+ @response_xml ||= Nokogiri::XML(response.to_xml)
135
+ end
98
136
 
99
- # Builds the request XML content.
100
- def request_xml
101
- builder = Builder::XmlMarkup.new()
102
- builder.instruct!(:xml, encoding: "UTF-8")
103
-
104
- builder.tag!("S:Envelope", NAMESPACES) do |envelope|
105
- if has_header?
106
- envelope.tag!("S:Header") do |header|
107
- header_xml(header)
108
- end
109
- end
110
- envelope.tag!("S:Body") do |body|
111
- body_xml(body)
112
- end
113
- end
114
- builder.target!
115
- end
137
+ def response_hash
138
+ @response_hash ||= response.to_hash
139
+ end
140
+ end
116
141
 
117
- def has_header?
118
- true
119
- end
142
+ # Encapsulates an issue operation that requests a security token
143
+ # from the SSO service.
144
+ class RequestSecurityToken < SoapInvocable
145
+ attr_accessor :request_type, :delegatable
120
146
 
121
- # Builds the header portion of the SOAP request.
122
- # Specific service operations must override this method.
123
- def header_xml(header)
124
- raise 'abstract method not implemented!'
125
- end
147
+ # Constructs a new instance.
148
+ def initialize(client, username, password, hours = 2)
149
+ super(:issue, client)
126
150
 
127
- # Builds the body portion of the SOAP request.
128
- # Specific service operations must override this method.
129
- def body_xml(body)
130
- raise 'abstract method not implemented!'
131
- end
151
+ @username = username
152
+ @password = password
153
+ @hours = hours
132
154
 
133
- # Gets the response XML content.
134
- def response_xml
135
- raise 'illegal state: response not set yet' if response.nil?
136
- @response_xml ||= Nokogiri::XML(response.to_xml)
137
- end
138
-
139
- def response_hash
140
- @response_hash ||= response.to_hash
141
- end
155
+ # TODO: these things should be configurable, so we can get
156
+ # non-delegatable tokens, HoK tokens, etc.
157
+ @request_type = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue"
158
+ @delegatable = true
142
159
  end
143
160
 
144
- # Encapsulates an issue operation that requests a security token
145
- # from the SSO service.
146
- class RequestSecurityToken < SoapInvocable
147
-
148
- attr_accessor :request_type, :delegatable
149
-
150
- # Constructs a new instance.
151
- def initialize(client, username, password, hours=2)
152
- super(:issue, client)
153
-
154
- @username = username
155
- @password = password
156
- @hours = hours
161
+ def now
162
+ @now ||= Time.now.utc.to_datetime
163
+ end
157
164
 
158
- #TODO: these things should be configurable, so we can get
159
- #non-delegatable tokens, HoK tokens, etc.
160
- @request_type = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue"
161
- @delegatable = true
162
- end
165
+ def created
166
+ @created ||= now.strftime(DATE_FORMAT)
167
+ end
163
168
 
164
- def now
165
- @now ||= Time.now.utc.to_datetime
166
- end
169
+ def future
170
+ @future ||= now + (2 / 24.0) # days (for DateTime math)
171
+ end
167
172
 
168
- def created
169
- @created ||= now.strftime(DATE_FORMAT)
170
- end
173
+ def expires
174
+ @expires ||= future.strftime(DATE_FORMAT)
175
+ end
171
176
 
172
- def future
173
- @future ||= now + (2/24.0) #days (for DateTime math)
177
+ # Builds the header XML for the SOAP request.
178
+ def header_xml(header)
179
+ id = "uuid-" + SecureRandom.uuid
180
+
181
+ # header.tag!("x:Security", "x:mustUnderstand" => "1") do |security|
182
+ header.tag!("x:Security") do |security|
183
+ security.tag!("u:Timestamp", "u:Id" => "_0") do |timestamp|
184
+ timestamp.tag!("u:Created") do |element|
185
+ element << created
186
+ end
187
+ timestamp.tag!("u:Expires") do |element|
188
+ element << expires
189
+ end
174
190
  end
175
-
176
- def expires
177
- @expires ||= future.strftime(DATE_FORMAT)
191
+ security.tag!("x:UsernameToken", "u:Id" => id) do |utoken|
192
+ utoken.tag!("x:Username") do |element|
193
+ element << @username
194
+ end
195
+ utoken.tag!("x:Password") do |element|
196
+ element << @password
197
+ end
178
198
  end
199
+ end
200
+ end
179
201
 
180
- # Builds the header XML for the SOAP request.
181
- def header_xml(header)
182
- id = "uuid-" + SecureRandom.uuid
183
-
184
- #header.tag!("x:Security", "x:mustUnderstand" => "1") do |security|
185
- header.tag!("x:Security") do |security|
186
- security.tag!("u:Timestamp", "u:Id" => "_0") do |timestamp|
187
- timestamp.tag!("u:Created") do |element|
188
- element << created
189
- end
190
- timestamp.tag!("u:Expires") do |element|
191
- element << expires
192
- end
193
- end
194
- security.tag!("x:UsernameToken", "u:Id" => id) do |utoken|
195
- utoken.tag!("x:Username") do |element|
196
- element << @username
197
- end
198
- utoken.tag!("x:Password") do |element|
199
- element << @password
200
- end
201
- end
202
- end
202
+ # Builds the body XML for the SOAP request.
203
+ def body_xml(body)
204
+ body.tag!("wst:RequestSecurityToken") do |rst|
205
+ rst.tag!("wst:RequestType") do |element|
206
+ element << request_type
203
207
  end
204
-
205
- # Builds the body XML for the SOAP request.
206
- def body_xml(body)
207
- body.tag!("wst:RequestSecurityToken") do |rst|
208
- rst.tag!("wst:RequestType") do |element|
209
- element << request_type
210
- end
211
- rst.tag!("wst:Delegatable") do |element|
212
- element << delegatable.to_s
213
- end
214
- =begin
215
- #TODO: we don't seem to need this, but I'm leaving this
216
- #here for now as a reminder.
217
- rst.tag!("wst:Lifetime") do |lifetime|
218
- lifetime.tag!("u:Created") do |element|
219
- element << created
220
- end
221
- lifetime.tag!("u:Expires") do |element|
222
- element << expires
223
- end
224
- end
225
- =end
226
- end
208
+ rst.tag!("wst:Delegatable") do |element|
209
+ element << delegatable.to_s
227
210
  end
211
+ # #TODO: we don't seem to need this, but I'm leaving this
212
+ # #here for now as a reminder.
213
+ # rst.tag!("wst:Lifetime") do |lifetime|
214
+ # lifetime.tag!("u:Created") do |element|
215
+ # element << created
216
+ # end
217
+ # lifetime.tag!("u:Expires") do |element|
218
+ # element << expires
219
+ # end
220
+ # end
221
+ end
222
+ end
228
223
 
229
- # Gets the saml_token from the SOAP response body.
230
- # @return [SamlToken] the requested SAML token
231
- def saml_token
232
- assertion = response_xml.at_xpath('//saml2:Assertion',
233
- 'saml2' => 'urn:oasis:names:tc:SAML:2.0:assertion')
234
- SamlToken.new(assertion)
235
- end
224
+ # Gets the saml_token from the SOAP response body.
225
+ # @return [SamlToken] the requested SAML token
226
+ def saml_token
227
+ assertion = response_xml.at_xpath("//saml2:Assertion",
228
+ "saml2" => "urn:oasis:names:tc:SAML:2.0:assertion")
229
+ SamlToken.new(assertion)
236
230
  end
231
+ end
237
232
 
238
- # Holds a SAML token.
239
- class SamlToken
240
- attr_reader :xml
233
+ # Holds a SAML token.
234
+ class SamlToken
235
+ attr_reader :xml
241
236
 
242
- # Creates a new instance.
243
- def initialize(xml)
244
- @xml = xml
245
- end
237
+ # Creates a new instance.
238
+ def initialize(xml)
239
+ @xml = xml
240
+ end
246
241
 
247
- #TODO: add some getters for interesting content
242
+ # TODO: add some getters for interesting content
248
243
 
249
- def to_s
250
- esc_token = xml.to_xml(:indent => 0, :encoding => 'UTF-8')
251
- esc_token = esc_token.gsub(/\n/, '')
252
- esc_token
253
- end
244
+ def to_s
245
+ esc_token = xml.to_xml(indent: 0, encoding: "UTF-8")
246
+ esc_token = esc_token.delete("\n")
247
+ esc_token
254
248
  end
249
+ end
255
250
  end
256
251
 
257
252
  # main: quick self tester
258
- if __FILE__ == $0
259
- cloudvm_ip = ARGV[0]
260
- cloudvm_ip ||= "10.20.17.0"
261
- #cloudvm_ip ||= "10.67.245.207"
262
- sso_url = "https://#{cloudvm_ip}/sts/STSService/vsphere.local"
263
- wsdl_url = "#{sso_url}?wsdl"
264
- sso = SSO::Connection.new(sso_url, wsdl_url)
265
- #sso.login("administrator@vsphere.local", "Admin!23")
266
- sso.login("root", "vmware")
267
- token = sso.request_bearer_token
268
- puts token.to_s
253
+ if $PROGRAM_NAME == __FILE__
254
+ cloudvm_ip = ARGV[0]
255
+ cloudvm_ip ||= "10.20.17.0"
256
+ # cloudvm_ip ||= "10.67.245.207"
257
+ sso_url = "https://#{cloudvm_ip}/sts/STSService/vsphere.local"
258
+ wsdl_url = "#{sso_url}?wsdl"
259
+ sso = SSO::Connection.new(sso_url, wsdl_url)
260
+ # sso.login("administrator@vsphere.local", "Admin!23")
261
+ sso.login("root", "vmware")
262
+ token = sso.request_bearer_token
263
+ puts token.to_s
269
264
  end