nexpose 0.6.5 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/COPYING +33 -0
- data/README.markdown +6 -2
- data/lib/nexpose.rb +6 -0
- data/lib/nexpose/ajax.rb +27 -6
- data/lib/nexpose/dag.rb +5 -13
- data/lib/nexpose/data_table.rb +1 -1
- data/lib/nexpose/discovery.rb +191 -0
- data/lib/nexpose/discovery/filter.rb +36 -0
- data/lib/nexpose/filter.rb +17 -6
- data/lib/nexpose/group.rb +21 -8
- data/lib/nexpose/multi_tenant_user.rb +219 -0
- data/lib/nexpose/report_template.rb +1 -1
- data/lib/nexpose/scan.rb +11 -0
- data/lib/nexpose/silo.rb +282 -278
- data/lib/nexpose/silo_profile.rb +239 -0
- data/lib/nexpose/site.rb +112 -5
- data/lib/nexpose/tag.rb +343 -0
- data/lib/nexpose/tag/criteria.rb +45 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 28266093c49d6743e5a662298252c67779d54659
|
4
|
+
data.tar.gz: e1525369d7ee0b2052c87c0b3d14f763085f5276
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: baa5a71852ad9259b8086ad9e0a6983cedd152f6da5efbbd3278070e4b633c4c81abd1eeb414aacb33a9010705df40ba30a46e30d85e103028a796f0c3883e10
|
7
|
+
data.tar.gz: 481d97ce05e9b5976a3f679dcc502d6f071db593e28dab6fd4be00ccfc365680405c2eda62cb5bdb897059e1de1e78e830043e05b52089cf8303efc684b51c79
|
data/COPYING
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
Copyright (C) 2014, Rapid7, Inc.
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
12
|
+
and/or other materials provided with the distribution.
|
13
|
+
|
14
|
+
* Neither the name of Rapid7, Inc. nor the names of its contributors
|
15
|
+
may be used to endorse or promote products derived from this software
|
16
|
+
without specific prior written permission.
|
17
|
+
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
19
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
20
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
22
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
23
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
24
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
25
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
26
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
27
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
28
|
+
|
29
|
+
================================================================================
|
30
|
+
|
31
|
+
The nexpose-client gem is provided under the 3-clause BSD license above.
|
32
|
+
|
33
|
+
The copyright on this package is held by Rapid7, Inc.
|
data/README.markdown
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Nexpose
|
1
|
+
# Nexpose-Client
|
2
2
|
|
3
3
|
This is the official gem package for the Ruby Nexpose API.
|
4
4
|
|
@@ -19,6 +19,10 @@ Our coding standards include:
|
|
19
19
|
* Unless otherwise noted, code should adhere to the Ruby Style Guide: https://github.com/bbatsov/ruby-style-guide
|
20
20
|
* Use YARDoc comment style to improve the API documentation of the gem.
|
21
21
|
|
22
|
+
## License
|
23
|
+
|
24
|
+
The nexpose-client gem is provided under the 3-Clause BSD License. See [COPYING](COPYING) for details.
|
25
|
+
|
22
26
|
## Credits
|
23
27
|
|
24
|
-
Rapid7
|
28
|
+
Rapid7, Inc.
|
data/lib/nexpose.rb
CHANGED
@@ -66,11 +66,14 @@ require 'nexpose/creds'
|
|
66
66
|
require 'nexpose/shared_cred'
|
67
67
|
require 'nexpose/data_table'
|
68
68
|
require 'nexpose/device'
|
69
|
+
require 'nexpose/discovery'
|
70
|
+
require 'nexpose/discovery/filter'
|
69
71
|
require 'nexpose/engine'
|
70
72
|
require 'nexpose/filter'
|
71
73
|
require 'nexpose/group'
|
72
74
|
require 'nexpose/dag'
|
73
75
|
require 'nexpose/manage'
|
76
|
+
require 'nexpose/multi_tenant_user'
|
74
77
|
require 'nexpose/pool'
|
75
78
|
require 'nexpose/report'
|
76
79
|
require 'nexpose/report_template'
|
@@ -78,7 +81,10 @@ require 'nexpose/role'
|
|
78
81
|
require 'nexpose/scan'
|
79
82
|
require 'nexpose/scan_template'
|
80
83
|
require 'nexpose/silo'
|
84
|
+
require 'nexpose/silo_profile'
|
81
85
|
require 'nexpose/site'
|
86
|
+
require 'nexpose/tag'
|
87
|
+
require 'nexpose/tag/criteria'
|
82
88
|
require 'nexpose/ticket'
|
83
89
|
require 'nexpose/user'
|
84
90
|
require 'nexpose/vuln'
|
data/lib/nexpose/ajax.rb
CHANGED
@@ -10,6 +10,12 @@ module Nexpose
|
|
10
10
|
module AJAX
|
11
11
|
module_function
|
12
12
|
|
13
|
+
module CONTENT_TYPE
|
14
|
+
XML = 'text/xml; charset=UTF-8'
|
15
|
+
JSON = 'application/json; charset-utf-8'
|
16
|
+
FORM = 'application/x-www-form-urlencoded; charset=UTF-8'
|
17
|
+
end
|
18
|
+
|
13
19
|
# GET call to a Nexpose controller.
|
14
20
|
#
|
15
21
|
# @param [Connection] nsc API connection to a Nexpose console.
|
@@ -17,7 +23,7 @@ module Nexpose
|
|
17
23
|
# @param [String] content_type Content type to use when issuing the GET.
|
18
24
|
# @return [String|REXML::Document|Hash] The response from the call.
|
19
25
|
#
|
20
|
-
def get(nsc, uri, content_type =
|
26
|
+
def get(nsc, uri, content_type = CONTENT_TYPE::XML)
|
21
27
|
get = Net::HTTP::Get.new(uri)
|
22
28
|
get.set_content_type(content_type)
|
23
29
|
_request(nsc, get)
|
@@ -31,7 +37,7 @@ module Nexpose
|
|
31
37
|
# @param [String] content_type Content type to use when issuing the PUT.
|
32
38
|
# @return [String] The response from the call.
|
33
39
|
#
|
34
|
-
def put(nsc, uri, payload = nil, content_type =
|
40
|
+
def put(nsc, uri, payload = nil, content_type = CONTENT_TYPE::XML)
|
35
41
|
put = Net::HTTP::Put.new(uri)
|
36
42
|
put.set_content_type(content_type)
|
37
43
|
put.body = payload.to_s if payload
|
@@ -46,13 +52,28 @@ module Nexpose
|
|
46
52
|
# @param [String] content_type Content type to use when issuing the POST.
|
47
53
|
# @return [String|REXML::Document|Hash] The response from the call.
|
48
54
|
#
|
49
|
-
def post(nsc, uri, payload = nil, content_type =
|
55
|
+
def post(nsc, uri, payload = nil, content_type = CONTENT_TYPE::XML)
|
50
56
|
post = Net::HTTP::Post.new(uri)
|
51
57
|
post.set_content_type(content_type)
|
52
58
|
post.body = payload.to_s if payload
|
53
59
|
_request(nsc, post)
|
54
60
|
end
|
55
61
|
|
62
|
+
# PATCH call to a Nexpose controller.
|
63
|
+
#
|
64
|
+
# @param [Connection] nsc API connection to a Nexpose console.
|
65
|
+
# @param [String] uri Controller address relative to https://host:port
|
66
|
+
# @param [String|REXML::Document] payload XML document required by the call.
|
67
|
+
# @param [String] content_type Content type to use when issuing the PATCH.
|
68
|
+
# @return [String] The response from the call.
|
69
|
+
#
|
70
|
+
def patch(nsc, uri, payload = nil, content_type = CONTENT_TYPE::XML)
|
71
|
+
patch = Net::HTTP::Patch.new(uri)
|
72
|
+
patch.set_content_type(content_type)
|
73
|
+
patch.body = payload.to_s if payload
|
74
|
+
_request(nsc, patch)
|
75
|
+
end
|
76
|
+
|
56
77
|
# POST call to a Nexpose controller that uses a form-post model.
|
57
78
|
# This is here to support legacy use of POST in old controllers.
|
58
79
|
#
|
@@ -63,7 +84,7 @@ module Nexpose
|
|
63
84
|
# @param [String] content_type Content type to use when issuing the POST.
|
64
85
|
# @return [Hash] The parsed JSON response from the call.
|
65
86
|
#
|
66
|
-
def form_post(nsc, uri, parameters, content_type =
|
87
|
+
def form_post(nsc, uri, parameters, content_type = CONTENT_TYPE::FORM)
|
67
88
|
post = Net::HTTP::Post.new(uri)
|
68
89
|
post.set_content_type(content_type)
|
69
90
|
post.set_form_data(parameters)
|
@@ -75,7 +96,7 @@ module Nexpose
|
|
75
96
|
# @param [Connection] nsc API connection to a Nexpose console.
|
76
97
|
# @param [String] uri Controller address relative to https://host:port
|
77
98
|
# @param [String] content_type Content type to use when issuing the DELETE.
|
78
|
-
def delete(nsc, uri, content_type =
|
99
|
+
def delete(nsc, uri, content_type = CONTENT_TYPE::XML)
|
79
100
|
delete = Net::HTTP::Delete.new(uri)
|
80
101
|
delete.set_content_type(content_type)
|
81
102
|
_request(nsc, delete)
|
@@ -89,7 +110,7 @@ module Nexpose
|
|
89
110
|
# @return [Hash] The parametrized URI.
|
90
111
|
|
91
112
|
def parametrize_uri(uri, parameters)
|
92
|
-
uri = uri.concat(('?').concat(parameters.map { |k, v| "#{k}=#{CGI.escape(v
|
113
|
+
uri = uri.concat(('?').concat(parameters.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&'))) if parameters
|
93
114
|
end
|
94
115
|
|
95
116
|
###
|
data/lib/nexpose/dag.rb
CHANGED
@@ -31,9 +31,9 @@ module Nexpose
|
|
31
31
|
# load includes admin users, but save will fail if they are included.
|
32
32
|
admins = nsc.users.select { |u| u.is_admin }.map { |u| u.id }
|
33
33
|
@users.reject! { |id| admins.member? id }
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
params = @id ? { 'entityid' => @id, 'mode' => 'edit' } : { 'entityid' => false, 'mode' => false }
|
35
|
+
uri = AJAX.parametrize_uri('/data/assetGroup/saveAssetGroup', params)
|
36
|
+
data = JSON.parse(AJAX.post(nsc, uri, _to_entity_details, AJAX::CONTENT_TYPE::JSON))
|
37
37
|
data['response'] == 'success.'
|
38
38
|
end
|
39
39
|
|
@@ -53,21 +53,13 @@ module Nexpose
|
|
53
53
|
dag
|
54
54
|
end
|
55
55
|
|
56
|
-
def
|
56
|
+
def _to_entity_details
|
57
57
|
obj = { 'searchCriteria' => @criteria.to_map,
|
58
58
|
'name' => @name,
|
59
59
|
'tag' => @description.nil? ? '' : @description,
|
60
60
|
'dynamic' => true,
|
61
61
|
'users' => @users }
|
62
|
-
|
63
|
-
if @id
|
64
|
-
map['entityid'] = @id
|
65
|
-
map['mode'] = 'edit'
|
66
|
-
else
|
67
|
-
map['entityid'] = false
|
68
|
-
map['mode'] = false
|
69
|
-
end
|
70
|
-
map
|
62
|
+
JSON.generate(obj)
|
71
63
|
end
|
72
64
|
end
|
73
65
|
end
|
data/lib/nexpose/data_table.rb
CHANGED
@@ -28,7 +28,7 @@ module Nexpose
|
|
28
28
|
# 'table-id' => 'site-assets',
|
29
29
|
# 'siteID' => site_id })
|
30
30
|
#
|
31
|
-
def _get_json_table(console, address, parameters, page_size = 500, records = nil)
|
31
|
+
def _get_json_table(console, address, parameters = {}, page_size = 500, records = nil)
|
32
32
|
parameters['dir'] = 'DESC'
|
33
33
|
parameters['startIndex'] = -1
|
34
34
|
parameters['results'] = -1
|
@@ -0,0 +1,191 @@
|
|
1
|
+
module Nexpose
|
2
|
+
|
3
|
+
class Connection
|
4
|
+
|
5
|
+
# Retrieve information about all available connections for dynamic
|
6
|
+
# discovery of assets, including whether or not connections are active.
|
7
|
+
#
|
8
|
+
def list_discovery_connections
|
9
|
+
xml = make_xml('DiscoveryConnectionListingRequest')
|
10
|
+
response = execute(xml, '1.2')
|
11
|
+
connections = []
|
12
|
+
response.res.elements.each('DiscoveryConnectionListingResponse/DiscoveryConnectionSummary') do |conn|
|
13
|
+
connections << DiscoveryConnection.parse(conn)
|
14
|
+
end
|
15
|
+
connections
|
16
|
+
end
|
17
|
+
alias_method :discovery_connections, :list_discovery_connections
|
18
|
+
|
19
|
+
# Delete an existing connection to a target used for dynamic discovery of assets.
|
20
|
+
#
|
21
|
+
# @param [Fixnum] id ID of an existing discovery connection.
|
22
|
+
#
|
23
|
+
def delete_discovery_connection(id)
|
24
|
+
xml = make_xml('DiscoveryConnectionDeleteRequest', { 'id' => id })
|
25
|
+
response = execute(xml, '1.2')
|
26
|
+
response.success
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class DiscoveryConnection
|
31
|
+
include XMLUtils
|
32
|
+
|
33
|
+
module Protocol
|
34
|
+
HTTP = 'HTTP'
|
35
|
+
HTTPS = 'HTTPS'
|
36
|
+
end
|
37
|
+
|
38
|
+
# A unique identifier for this connection.
|
39
|
+
attr_accessor :id
|
40
|
+
|
41
|
+
# A unique name for this connection.
|
42
|
+
attr_accessor :name
|
43
|
+
|
44
|
+
# The IP address or fully qualified domain name of the server.
|
45
|
+
attr_accessor :address
|
46
|
+
|
47
|
+
# A user name that can be used to log into the server.
|
48
|
+
attr_accessor :user
|
49
|
+
|
50
|
+
# The password to use when connecting with the defined user.
|
51
|
+
attr_accessor :password
|
52
|
+
|
53
|
+
# The protocol used for conneting to the server. One of DiscoveryConnection::Protocol
|
54
|
+
attr_accessor :protocol
|
55
|
+
|
56
|
+
# The port used for connecting to the server. A valid port from 1 to 65535.
|
57
|
+
attr_accessor :port
|
58
|
+
|
59
|
+
# Whether or not the connection is active.
|
60
|
+
# Discovery is only possible when the connection is active.
|
61
|
+
attr_accessor :status
|
62
|
+
|
63
|
+
# Create a new discovery connection.
|
64
|
+
#
|
65
|
+
# @param [String] name Name to assign to this connection.
|
66
|
+
# @param [String] address IP or fully qualified domain name of the
|
67
|
+
# connection server.
|
68
|
+
# @param [String] user User name for credentials on this connection.
|
69
|
+
# @param [String] password Password for credentials on this connection.
|
70
|
+
#
|
71
|
+
def initialize(name, address, user, password = nil)
|
72
|
+
@name, @address, @user, @password = name, address, user, password
|
73
|
+
@id = -1
|
74
|
+
@port = 443
|
75
|
+
@protocol = Protocol::HTTPS
|
76
|
+
end
|
77
|
+
|
78
|
+
# Save this discovery connection to a Nexpose console.
|
79
|
+
#
|
80
|
+
# @param [Connection] nsc Connection to a console.
|
81
|
+
#
|
82
|
+
def save(nsc)
|
83
|
+
if @id == -1
|
84
|
+
xml = nsc.make_xml('DiscoveryConnectionCreateRequest')
|
85
|
+
else
|
86
|
+
xml = nsc.make_xml('DiscoveryConnectionUpdateRequest')
|
87
|
+
end
|
88
|
+
xml.add_element(as_xml)
|
89
|
+
response = nsc.execute(xml, '1.2')
|
90
|
+
if response.success
|
91
|
+
ret = REXML::XPath.first(response.res, 'DiscoveryConnectionCreateResponse')
|
92
|
+
@id = ret.attributes['id'].to_i unless ret.nil?
|
93
|
+
end
|
94
|
+
@id
|
95
|
+
end
|
96
|
+
|
97
|
+
# Perform dynamic discover of assets against this connection.
|
98
|
+
#
|
99
|
+
# @param [Connection] nsc Connection to a console.
|
100
|
+
# @param [Criteria] criteria Criteria search object narrowing which assets
|
101
|
+
# to filter.
|
102
|
+
# @return [Array[DiscoveredAsset]] All discovered assets matching the criteria.
|
103
|
+
#
|
104
|
+
def discover(nsc, criteria = nil)
|
105
|
+
parameters = { 'table-id' => 'assetdiscovery',
|
106
|
+
'sort' => 'assetDiscoveryName',
|
107
|
+
'searchCriteria' => criteria.nil? ? 'null' : criteria.to_json,
|
108
|
+
'configID' => @id }
|
109
|
+
data = DataTable._get_json_table(nsc, '/data/discoveryAsset/discoverAssets', parameters)
|
110
|
+
data.map { |a| DiscoveredAsset.parse(a) }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Initiates a connection to a target used for dynamic discovery of assets.
|
114
|
+
# As long as a connection is active, dynamic discovery is continuous.
|
115
|
+
#
|
116
|
+
# @param [Connection] nsc Connection to a console.
|
117
|
+
#
|
118
|
+
def connect(nsc)
|
119
|
+
xml = nsc.make_xml('DiscoveryConnectionConnectRequest', { 'id' => id })
|
120
|
+
response = nsc.execute(xml, '1.2')
|
121
|
+
response.success
|
122
|
+
end
|
123
|
+
|
124
|
+
# Delete this connection from the console.
|
125
|
+
#
|
126
|
+
# @param [Connection] nsc Connection to a console.
|
127
|
+
#
|
128
|
+
def delete(nsc)
|
129
|
+
nsc.delete_discovery_connection(@id)
|
130
|
+
end
|
131
|
+
|
132
|
+
def as_xml
|
133
|
+
xml = REXML::Element.new('DiscoveryConnection')
|
134
|
+
xml.add_attributes({ 'name' => @name,
|
135
|
+
'address' => @address,
|
136
|
+
'port' => @port,
|
137
|
+
'protocol' => @protocol,
|
138
|
+
'user-name' => @user,
|
139
|
+
'password' => @password })
|
140
|
+
xml
|
141
|
+
end
|
142
|
+
|
143
|
+
def to_xml
|
144
|
+
as_xml.to_s
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.parse(xml)
|
148
|
+
conn = new(xml.attributes['name'],
|
149
|
+
xml.attributes['address'],
|
150
|
+
xml.attributes['user-name'])
|
151
|
+
conn.id = xml.attributes['id'].to_i
|
152
|
+
conn.protocol = xml.attributes['protocol']
|
153
|
+
conn.port = xml.attributes['port'].to_i
|
154
|
+
conn.status = xml.attributes['connection-status']
|
155
|
+
conn
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class DiscoveredAsset
|
160
|
+
|
161
|
+
attr_accessor :name
|
162
|
+
attr_accessor :ip
|
163
|
+
attr_accessor :host
|
164
|
+
attr_accessor :datacenter
|
165
|
+
attr_accessor :cluster
|
166
|
+
attr_accessor :pool
|
167
|
+
attr_accessor :os
|
168
|
+
attr_accessor :status
|
169
|
+
|
170
|
+
def initialize(&block)
|
171
|
+
instance_eval &block if block_given?
|
172
|
+
end
|
173
|
+
|
174
|
+
def on?
|
175
|
+
@status == 'On'
|
176
|
+
end
|
177
|
+
|
178
|
+
def self.parse(json)
|
179
|
+
new do |asset|
|
180
|
+
asset.ip = json['IPAddress']
|
181
|
+
asset.os = json['OSName']
|
182
|
+
asset.name = json['assetDiscoveryName']
|
183
|
+
asset.cluster = json['cluster']
|
184
|
+
asset.datacenter = json['datacenter']
|
185
|
+
asset.host = json['host']
|
186
|
+
asset.status = json['powerStatus']
|
187
|
+
asset.pool = json['resourcePool']
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Nexpose
|
2
|
+
module Search
|
3
|
+
module Field
|
4
|
+
|
5
|
+
# Valid Operators: IS, IS_NOT, CONTAINS, NOT_CONTAINS, STARTS_WITH
|
6
|
+
CLUSTER = 'CLUSTER'
|
7
|
+
|
8
|
+
# Valid Operators: IS, IS_NOT
|
9
|
+
DATACENTER ='DATACENTER'
|
10
|
+
|
11
|
+
# Valid Operators: CONTAINS, NOT_CONTAINS
|
12
|
+
GUEST_OS_FAMILY = 'GUEST_OS_FAMILY'
|
13
|
+
|
14
|
+
# Valid Operators: IN, NOT_IN
|
15
|
+
IP_ADDRESS_RANGE = 'IP_ADDRESS'
|
16
|
+
|
17
|
+
# Valid Operators: IN, NOT_IN
|
18
|
+
# Valid Values (See Value::PowerState): ON, OFF, SUSPENDED
|
19
|
+
POWER_STATE = 'POWER_STATE'
|
20
|
+
|
21
|
+
# Valid Operators: CONTAINS, NOT_CONTAINS
|
22
|
+
RESOURCE_POOL_PATH ='RESOURCE_POOL_PATH'
|
23
|
+
|
24
|
+
# Valid Operators: IS, IS_NOT, CONTAINS, NOT_CONTAINS, STARTS_WITH
|
25
|
+
VIRTUAL_MACHINE_NAME = 'VM'
|
26
|
+
end
|
27
|
+
|
28
|
+
module Value
|
29
|
+
module PowerState
|
30
|
+
ON = 'poweredOn'
|
31
|
+
OFF = 'poweredOff'
|
32
|
+
SUSPENDED = 'suspended'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|