knife-vcenter 2.0.0 → 2.0.1

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