kitchen-vcenter 1.2.1 → 1.3.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,257 @@ 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
120
145
 
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
146
+ attr_accessor :request_type, :delegatable
126
147
 
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
148
+ # Constructs a new instance.
149
+ def initialize(client, username, password, hours = 2)
150
+ super(:issue, client)
132
151
 
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
152
+ @username = username
153
+ @password = password
154
+ @hours = hours
138
155
 
139
- def response_hash
140
- @response_hash ||= response.to_hash
141
- end
156
+ # TODO: these things should be configurable, so we can get
157
+ # non-delegatable tokens, HoK tokens, etc.
158
+ @request_type = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue"
159
+ @delegatable = true
142
160
  end
143
161
 
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)
162
+ def now
163
+ @now ||= Time.now.utc.to_datetime
164
+ end
153
165
 
154
- @username = username
155
- @password = password
156
- @hours = hours
166
+ def created
167
+ @created ||= now.strftime(DATE_FORMAT)
168
+ end
157
169
 
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
170
+ def future
171
+ @future ||= now + (2 / 24.0) # days (for DateTime math)
172
+ end
163
173
 
164
- def now
165
- @now ||= Time.now.utc.to_datetime
166
- end
174
+ def expires
175
+ @expires ||= future.strftime(DATE_FORMAT)
176
+ end
167
177
 
168
- def created
169
- @created ||= now.strftime(DATE_FORMAT)
178
+ # Builds the header XML for the SOAP request.
179
+ def header_xml(header)
180
+ id = "uuid-" + SecureRandom.uuid
181
+
182
+ # header.tag!("x:Security", "x:mustUnderstand" => "1") do |security|
183
+ header.tag!("x:Security") do |security|
184
+ security.tag!("u:Timestamp", "u:Id" => "_0") do |timestamp|
185
+ timestamp.tag!("u:Created") do |element|
186
+ element << created
187
+ end
188
+ timestamp.tag!("u:Expires") do |element|
189
+ element << expires
190
+ end
170
191
  end
171
192
 
172
- def future
173
- @future ||= now + (2/24.0) #days (for DateTime math)
193
+ security.tag!("x:UsernameToken", "u:Id" => id) do |utoken|
194
+ utoken.tag!("x:Username") do |element|
195
+ element << @username
196
+ end
197
+ utoken.tag!("x:Password") do |element|
198
+ element << @password
199
+ end
174
200
  end
201
+ end
202
+ end
175
203
 
176
- def expires
177
- @expires ||= future.strftime(DATE_FORMAT)
204
+ # Builds the body XML for the SOAP request.
205
+ def body_xml(body)
206
+ body.tag!("wst:RequestSecurityToken") do |rst|
207
+ rst.tag!("wst:RequestType") do |element|
208
+ element << request_type
178
209
  end
179
-
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
210
+ rst.tag!("wst:Delegatable") do |element|
211
+ element << delegatable.to_s
203
212
  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
213
  =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
214
+ #TODO: we don't seem to need this, but I'm leaving this
215
+ #here for now as a reminder.
216
+ rst.tag!("wst:Lifetime") do |lifetime|
217
+ lifetime.tag!("u:Created") do |element|
218
+ element << created
219
+ end
220
+ lifetime.tag!("u:Expires") do |element|
221
+ element << expires
222
+ end
223
+ end
225
224
  =end
226
- end
227
- end
225
+ end
226
+ end
228
227
 
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
228
+ # Gets the saml_token from the SOAP response body.
229
+ # @return [SamlToken] the requested SAML token
230
+ def saml_token
231
+ assertion = response_xml.at_xpath("//saml2:Assertion",
232
+ "saml2" => "urn:oasis:names:tc:SAML:2.0:assertion")
233
+ SamlToken.new(assertion)
236
234
  end
235
+ end
237
236
 
238
237
  # Holds a SAML token.
239
- class SamlToken
240
- attr_reader :xml
238
+ class SamlToken
239
+ attr_reader :xml
241
240
 
242
- # Creates a new instance.
243
- def initialize(xml)
244
- @xml = xml
245
- end
241
+ # Creates a new instance.
242
+ def initialize(xml)
243
+ @xml = xml
244
+ end
246
245
 
247
- #TODO: add some getters for interesting content
246
+ # TODO: add some getters for interesting content
248
247
 
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
248
+ def to_s
249
+ esc_token = xml.to_xml(indent: 0, encoding: "UTF-8")
250
+ esc_token = esc_token.delete("\n")
251
+ esc_token
254
252
  end
253
+ end
255
254
  end
256
255
 
257
256
  # main: quick self tester
258
257
  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
258
+ cloudvm_ip = ARGV[0]
259
+ cloudvm_ip ||= "10.20.17.0"
260
+ # cloudvm_ip ||= "10.67.245.207"
261
+ sso_url = "https://#{cloudvm_ip}/sts/STSService/vsphere.local"
262
+ wsdl_url = "#{sso_url}?wsdl"
263
+ sso = SSO::Connection.new(sso_url, wsdl_url)
264
+ # sso.login("administrator@vsphere.local", "Admin!23")
265
+ sso.login("root", "vmware")
266
+ token = sso.request_bearer_token
267
+ puts token.to_s
269
268
  end