nexpose 7.0.0 → 7.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -3
- data/Gemfile.lock +1 -1
- data/lib/nexpose/ajax.rb +12 -16
- data/lib/nexpose/alert.rb +20 -21
- data/lib/nexpose/api.rb +3 -3
- data/lib/nexpose/asset.rb +23 -23
- data/lib/nexpose/blackout.rb +6 -14
- data/lib/nexpose/common.rb +87 -92
- data/lib/nexpose/connection.rb +8 -10
- data/lib/nexpose/console.rb +9 -9
- data/lib/nexpose/dag.rb +2 -2
- data/lib/nexpose/data_table.rb +8 -12
- data/lib/nexpose/device.rb +35 -34
- data/lib/nexpose/discovery.rb +69 -69
- data/lib/nexpose/discovery/filter.rb +7 -8
- data/lib/nexpose/engine.rb +22 -21
- data/lib/nexpose/error.rb +7 -5
- data/lib/nexpose/external.rb +21 -16
- data/lib/nexpose/filter.rb +51 -52
- data/lib/nexpose/global_blackout.rb +6 -7
- data/lib/nexpose/global_settings.rb +2 -3
- data/lib/nexpose/group.rb +25 -19
- data/lib/nexpose/json_serializer.rb +4 -14
- data/lib/nexpose/maint.rb +8 -9
- data/lib/nexpose/manage.rb +2 -2
- data/lib/nexpose/multi_tenant_user.rb +42 -42
- data/lib/nexpose/password_policy.rb +14 -14
- data/lib/nexpose/pool.rb +6 -5
- data/lib/nexpose/report.rb +30 -34
- data/lib/nexpose/report_template.rb +17 -18
- data/lib/nexpose/role.rb +64 -55
- data/lib/nexpose/scan.rb +77 -60
- data/lib/nexpose/scan_template.rb +17 -17
- data/lib/nexpose/scheduled_backup.rb +8 -8
- data/lib/nexpose/scheduled_maintenance.rb +9 -9
- data/lib/nexpose/shared_credential.rb +30 -33
- data/lib/nexpose/shared_secret.rb +5 -5
- data/lib/nexpose/silo.rb +68 -66
- data/lib/nexpose/silo_profile.rb +47 -50
- data/lib/nexpose/site.rb +101 -123
- data/lib/nexpose/site_credentials.rb +15 -17
- data/lib/nexpose/tag.rb +73 -80
- data/lib/nexpose/ticket.rb +45 -42
- data/lib/nexpose/user.rb +45 -45
- data/lib/nexpose/util.rb +1 -1
- data/lib/nexpose/version.rb +1 -1
- data/lib/nexpose/vuln.rb +45 -43
- data/lib/nexpose/vuln_def.rb +7 -7
- data/lib/nexpose/vuln_exception.rb +35 -36
- data/lib/nexpose/wait.rb +32 -28
- data/lib/nexpose/web_credentials.rb +34 -36
- metadata +2 -2
data/lib/nexpose/pool.rb
CHANGED
@@ -21,7 +21,7 @@ module Nexpose
|
|
21
21
|
arr
|
22
22
|
end
|
23
23
|
|
24
|
-
|
24
|
+
alias engine_pools list_engine_pools
|
25
25
|
end
|
26
26
|
|
27
27
|
# A summary of an engine pool.
|
@@ -36,8 +36,8 @@ module Nexpose
|
|
36
36
|
attr_reader :scope
|
37
37
|
|
38
38
|
def initialize(id, name, scope = 'silo')
|
39
|
-
@id
|
40
|
-
@name
|
39
|
+
@id = id.to_i
|
40
|
+
@name = name
|
41
41
|
@scope = scope
|
42
42
|
end
|
43
43
|
|
@@ -74,7 +74,9 @@ module Nexpose
|
|
74
74
|
attr_accessor :engines
|
75
75
|
|
76
76
|
def initialize(name, scope = 'silo', id = -1)
|
77
|
-
@name
|
77
|
+
@name = name
|
78
|
+
@scope = scope
|
79
|
+
@id = id.to_i
|
78
80
|
@engines = []
|
79
81
|
end
|
80
82
|
|
@@ -100,7 +102,6 @@ module Nexpose
|
|
100
102
|
xml = %(<EnginePoolDetailsRequest session-id="#{connection.session_id}">)
|
101
103
|
xml << %(<EnginePool name="#{name}" scope="#{scope}"/>)
|
102
104
|
xml << '</EnginePoolDetailsRequest>'
|
103
|
-
|
104
105
|
r = connection.execute(xml, '1.2')
|
105
106
|
if r.success
|
106
107
|
r.res.elements.each('EnginePoolDetailsResponse/EnginePool') do |pool|
|
data/lib/nexpose/report.rb
CHANGED
@@ -19,7 +19,7 @@ module Nexpose
|
|
19
19
|
reports
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
alias reports list_reports
|
23
23
|
|
24
24
|
# Generate a new report using the specified report definition.
|
25
25
|
def generate_report(report_id, wait = false)
|
@@ -38,7 +38,7 @@ module Nexpose
|
|
38
38
|
return summary unless summary.status == 'Started'
|
39
39
|
sleep 5
|
40
40
|
so_far += 5
|
41
|
-
if so_far % 60
|
41
|
+
if (so_far % 60).zero?
|
42
42
|
puts "Still waiting. Current status: #{summary.status}"
|
43
43
|
end
|
44
44
|
end
|
@@ -101,13 +101,13 @@ module Nexpose
|
|
101
101
|
attr_reader :scope
|
102
102
|
|
103
103
|
def initialize(config_id, name, template_id, status, generated_on, uri, scope)
|
104
|
-
@config_id
|
105
|
-
@name
|
106
|
-
@template_id
|
107
|
-
@status
|
104
|
+
@config_id = config_id.to_i
|
105
|
+
@name = name
|
106
|
+
@template_id = template_id
|
107
|
+
@status = status
|
108
108
|
@generated_on = generated_on
|
109
|
-
@uri
|
110
|
-
@scope
|
109
|
+
@uri = uri
|
110
|
+
@scope = scope
|
111
111
|
end
|
112
112
|
|
113
113
|
def self.parse(xml)
|
@@ -138,11 +138,11 @@ module Nexpose
|
|
138
138
|
attr_reader :uri
|
139
139
|
|
140
140
|
def initialize(id, config_id, status, generated_on, uri)
|
141
|
-
@id
|
142
|
-
@config_id
|
143
|
-
@status
|
141
|
+
@id = id
|
142
|
+
@config_id = config_id.to_i
|
143
|
+
@status = status
|
144
144
|
@generated_on = generated_on
|
145
|
-
@uri
|
145
|
+
@uri = uri
|
146
146
|
end
|
147
147
|
|
148
148
|
# Delete this report.
|
@@ -194,11 +194,11 @@ module Nexpose
|
|
194
194
|
|
195
195
|
def initialize(template_id, format, site_id = nil, owner = nil, time_zone = nil)
|
196
196
|
@template_id = template_id
|
197
|
-
@format
|
198
|
-
@owner
|
199
|
-
@time_zone
|
197
|
+
@format = format
|
198
|
+
@owner = owner
|
199
|
+
@time_zone = time_zone
|
200
200
|
|
201
|
-
@filters
|
201
|
+
@filters = []
|
202
202
|
@filters << Filter.new('site', site_id) if site_id
|
203
203
|
end
|
204
204
|
|
@@ -289,15 +289,14 @@ module Nexpose
|
|
289
289
|
|
290
290
|
# Construct a basic ReportConfig object.
|
291
291
|
def initialize(name, template_id, format, id = -1, owner = nil, time_zone = nil)
|
292
|
-
@name
|
292
|
+
@name = name
|
293
293
|
@template_id = template_id
|
294
|
-
@format
|
295
|
-
@id
|
296
|
-
@owner
|
297
|
-
@time_zone
|
298
|
-
|
299
|
-
@
|
300
|
-
@users = []
|
294
|
+
@format = format
|
295
|
+
@id = id
|
296
|
+
@owner = owner
|
297
|
+
@time_zone = time_zone
|
298
|
+
@filters = []
|
299
|
+
@users = []
|
301
300
|
end
|
302
301
|
|
303
302
|
# Retrieve the configuration for an existing report definition.
|
@@ -306,7 +305,7 @@ module Nexpose
|
|
306
305
|
ReportConfig.parse(connection.execute(xml))
|
307
306
|
end
|
308
307
|
|
309
|
-
|
308
|
+
alias get load
|
310
309
|
|
311
310
|
# Build and save a report configuration against the specified site using
|
312
311
|
# the supplied type and format.
|
@@ -436,11 +435,8 @@ module Nexpose
|
|
436
435
|
%(<filter id="#{replace_entities(@id)}" type="#{@type}" />)
|
437
436
|
end
|
438
437
|
|
439
|
-
def ==(
|
440
|
-
|
441
|
-
(object.instance_of?(self.class) &&
|
442
|
-
object.type == @type &&
|
443
|
-
object.id == @id)
|
438
|
+
def ==(other)
|
439
|
+
other.equal?(self) || (other.instance_of?(self.class) && other.type == @type && other.id == @id)
|
444
440
|
end
|
445
441
|
|
446
442
|
def self.parse(xml)
|
@@ -466,8 +462,8 @@ module Nexpose
|
|
466
462
|
|
467
463
|
def initialize(after_scan, scheduled, schedule = nil)
|
468
464
|
@after_scan = after_scan
|
469
|
-
@scheduled
|
470
|
-
@schedule
|
465
|
+
@scheduled = scheduled
|
466
|
+
@schedule = schedule
|
471
467
|
end
|
472
468
|
|
473
469
|
def to_xml
|
@@ -610,9 +606,9 @@ module Nexpose
|
|
610
606
|
xml.elements.each('//credentials') do |creds|
|
611
607
|
credential = ExportCredential.new(creds.text)
|
612
608
|
# The following attributes may not exist.
|
613
|
-
credential.user_id
|
609
|
+
credential.user_id = creds.attributes['userid']
|
614
610
|
credential.password = creds.attributes['password']
|
615
|
-
credential.realm
|
611
|
+
credential.realm = creds.attributes['realm']
|
616
612
|
return credential
|
617
613
|
end
|
618
614
|
nil
|
@@ -19,7 +19,7 @@ module Nexpose
|
|
19
19
|
templates
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
alias report_templates list_report_templates
|
23
23
|
|
24
24
|
# Deletes an existing, custom report template.
|
25
25
|
# Cannot delete built-in templates.
|
@@ -53,11 +53,11 @@ module Nexpose
|
|
53
53
|
attr_reader :description
|
54
54
|
|
55
55
|
def initialize(id, name, type, scope, built_in, description)
|
56
|
-
@id
|
57
|
-
@name
|
58
|
-
@type
|
59
|
-
@scope
|
60
|
-
@built_in
|
56
|
+
@id = id
|
57
|
+
@name = name
|
58
|
+
@type = type
|
59
|
+
@scope = scope
|
60
|
+
@built_in = built_in
|
61
61
|
@description = description
|
62
62
|
end
|
63
63
|
|
@@ -109,19 +109,18 @@ module Nexpose
|
|
109
109
|
attr_accessor :attributes
|
110
110
|
# Display asset names with IPs.
|
111
111
|
attr_accessor :show_asset_names
|
112
|
-
alias
|
113
|
-
alias
|
112
|
+
alias show_device_names show_asset_names
|
113
|
+
alias show_device_names= show_asset_names=
|
114
114
|
|
115
115
|
def initialize(name, type = 'document', id = -1, scope = 'silo', built_in = false)
|
116
|
-
@name
|
117
|
-
@type
|
118
|
-
@id
|
119
|
-
@scope
|
120
|
-
@built_in
|
121
|
-
|
122
|
-
@
|
123
|
-
@
|
124
|
-
@attributes = []
|
116
|
+
@name = name
|
117
|
+
@type = type
|
118
|
+
@id = id
|
119
|
+
@scope = scope
|
120
|
+
@built_in = built_in
|
121
|
+
@sections = []
|
122
|
+
@properties = {}
|
123
|
+
@attributes = []
|
125
124
|
@show_asset_names = false
|
126
125
|
end
|
127
126
|
|
@@ -219,7 +218,7 @@ module Nexpose
|
|
219
218
|
attr_accessor :properties
|
220
219
|
|
221
220
|
def initialize(name)
|
222
|
-
@name
|
221
|
+
@name = name
|
223
222
|
@properties = {}
|
224
223
|
end
|
225
224
|
|
data/lib/nexpose/role.rb
CHANGED
@@ -4,44 +4,44 @@ module Nexpose
|
|
4
4
|
module Privilege
|
5
5
|
|
6
6
|
module Global
|
7
|
-
CREATE_REPORTS
|
8
|
-
CONFIGURE_GLOBAL_SETTINGS
|
9
|
-
MANAGE_SITES
|
10
|
-
MANAGE_ASSET_GROUPS
|
7
|
+
CREATE_REPORTS = 'CreateReports'
|
8
|
+
CONFIGURE_GLOBAL_SETTINGS = 'ConfigureGlobalSettings'
|
9
|
+
MANAGE_SITES = 'ManageSites'
|
10
|
+
MANAGE_ASSET_GROUPS = 'ManageAssetGroups'
|
11
11
|
MANAGE_DYNAMIC_ASSET_GROUPS = 'ManageDynamicAssetGroups'
|
12
|
-
MANAGE_SCAN_TEMPLATES
|
13
|
-
MANAGE_REPORT_TEMPLATES
|
12
|
+
MANAGE_SCAN_TEMPLATES = 'ManageScanTemplates'
|
13
|
+
MANAGE_REPORT_TEMPLATES = 'ManageReportTemplates'
|
14
14
|
GENERATE_RESTRICTED_REPORTS = 'GenerateRestrictedReports'
|
15
|
-
MANAGE_SCAN_ENGINES
|
16
|
-
SUBMIT_VULN_EXCEPTIONS
|
17
|
-
APPROVE_VULN_EXCEPTIONS
|
18
|
-
DELETE_VULN_EXCEPTIONS
|
19
|
-
CREATE_TICKETS
|
20
|
-
CLOSE_TICKETS
|
21
|
-
TICKET_ASSIGNEE
|
22
|
-
ADD_USERS_TO_SITE
|
23
|
-
ADD_USERS_TO_GROUP
|
24
|
-
ADD_USERS_TO_REPORT
|
25
|
-
MANAGE_POLICIES
|
26
|
-
MANAGE_TAGS
|
15
|
+
MANAGE_SCAN_ENGINES = 'ManageScanEngines'
|
16
|
+
SUBMIT_VULN_EXCEPTIONS = 'SubmitVulnExceptions'
|
17
|
+
APPROVE_VULN_EXCEPTIONS = 'ApproveVulnExceptions'
|
18
|
+
DELETE_VULN_EXCEPTIONS = 'DeleteVulnExceptions'
|
19
|
+
CREATE_TICKETS = 'CreateTickets'
|
20
|
+
CLOSE_TICKETS = 'CloseTickets'
|
21
|
+
TICKET_ASSIGNEE = 'TicketAssignee'
|
22
|
+
ADD_USERS_TO_SITE = 'AddUsersToSite'
|
23
|
+
ADD_USERS_TO_GROUP = 'AddUsersToGroup'
|
24
|
+
ADD_USERS_TO_REPORT = 'AddUsersToReport'
|
25
|
+
MANAGE_POLICIES = 'ManagePolicies'
|
26
|
+
MANAGE_TAGS = 'ManageTags'
|
27
27
|
end
|
28
28
|
|
29
29
|
module Site
|
30
|
-
VIEW_ASSET_DATA
|
31
|
-
CONFIGURE_ALERTS
|
32
|
-
CONFIGURE_CREDENTIALS
|
33
|
-
CONFIGURE_ENGINES
|
30
|
+
VIEW_ASSET_DATA = 'ViewAssetData' # NOTE Duplicated between Site and AssetGroup
|
31
|
+
CONFIGURE_ALERTS = 'ConfigureAlerts'
|
32
|
+
CONFIGURE_CREDENTIALS = 'ConfigureCredentials'
|
33
|
+
CONFIGURE_ENGINES = 'ConfigureEngines'
|
34
34
|
CONFIGURE_SCAN_TEMPLATES = 'ConfigureScanTemplates'
|
35
35
|
CONFIGURE_SCHEDULE_SCANS = 'ConfigureScheduleScans'
|
36
|
-
CONFIGURE_SITE_SETTINGS
|
37
|
-
CONFIGURE_TARGETS
|
38
|
-
MANUAL_SCANS
|
39
|
-
PURGE_DATA
|
36
|
+
CONFIGURE_SITE_SETTINGS = 'ConfigureSiteSettings'
|
37
|
+
CONFIGURE_TARGETS = 'ConfigureTargets'
|
38
|
+
MANUAL_SCANS = 'ManualScans'
|
39
|
+
PURGE_DATA = 'PurgeData'
|
40
40
|
end
|
41
41
|
|
42
42
|
module AssetGroup
|
43
43
|
CONFIGURE_ASSETS = 'ConfigureAssets'
|
44
|
-
VIEW_ASSET_DATA
|
44
|
+
VIEW_ASSET_DATA = 'ViewAssetData' # NOTE Duplicated between Site and AssetGroup
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -51,27 +51,27 @@ module Nexpose
|
|
51
51
|
# Returns a summary list of all roles.
|
52
52
|
#
|
53
53
|
def role_listing
|
54
|
-
xml
|
55
|
-
r
|
54
|
+
xml = make_xml('RoleListingRequest')
|
55
|
+
r = execute(xml, '1.2')
|
56
56
|
roles = []
|
57
57
|
if r.success
|
58
58
|
r.res.elements.each('RoleListingResponse/RoleSummary') do |summary|
|
59
|
-
roles << RoleSummary
|
59
|
+
roles << RoleSummary.parse(summary)
|
60
60
|
end
|
61
61
|
end
|
62
62
|
roles
|
63
63
|
end
|
64
64
|
|
65
|
-
|
65
|
+
alias roles role_listing
|
66
66
|
|
67
67
|
def role_delete(role, scope = Scope::SILO)
|
68
68
|
xml = make_xml('RoleDeleteRequest')
|
69
|
-
xml.add_element('Role', {'name' => role, 'scope' => scope})
|
69
|
+
xml.add_element('Role', { 'name' => role, 'scope' => scope })
|
70
70
|
response = execute(xml, '1.2')
|
71
71
|
response.success
|
72
72
|
end
|
73
73
|
|
74
|
-
|
74
|
+
alias delete_role role_delete
|
75
75
|
end
|
76
76
|
|
77
77
|
# Role summary object encapsulating information about a role.
|
@@ -98,7 +98,12 @@ module Nexpose
|
|
98
98
|
attr_accessor :scope
|
99
99
|
|
100
100
|
def initialize(name, full_name, id, description, enabled = true, scope = Scope::SILO)
|
101
|
-
@name
|
101
|
+
@name = name
|
102
|
+
@full_name = full_name
|
103
|
+
@id = id.to_i
|
104
|
+
@description = description
|
105
|
+
@enabled = enabled
|
106
|
+
@scope = scope
|
102
107
|
end
|
103
108
|
|
104
109
|
def self.parse(xml)
|
@@ -116,12 +121,12 @@ module Nexpose
|
|
116
121
|
|
117
122
|
# Constants, mapping UI terms to role names expected by API.
|
118
123
|
|
119
|
-
GLOBAL_ADMINISTRATOR
|
120
|
-
ASSET_OWNER
|
124
|
+
GLOBAL_ADMINISTRATOR = 'global-admin'
|
125
|
+
ASSET_OWNER = 'system-admin'
|
121
126
|
CONTROLS_INSIGHT_ONLY = 'controls-insight-only'
|
122
|
-
SECURITY_MANAGER
|
123
|
-
SITE_OWNER
|
124
|
-
USER
|
127
|
+
SECURITY_MANAGER = 'security-manager'
|
128
|
+
SITE_OWNER = 'site-admin'
|
129
|
+
USER = 'user'
|
125
130
|
|
126
131
|
# Array of all privileges which are enabled for this role.
|
127
132
|
# Note: Although the underlying XML has different requirements, this only checks for presence.
|
@@ -133,7 +138,11 @@ module Nexpose
|
|
133
138
|
attr_accessor :existing
|
134
139
|
|
135
140
|
def initialize(name, full_name, id = -1, enabled = true, scope = Scope::SILO)
|
136
|
-
@name
|
141
|
+
@name = name
|
142
|
+
@full_name = full_name
|
143
|
+
@id = id.to_i
|
144
|
+
@enabled = enabled
|
145
|
+
@scope = scope
|
137
146
|
@privileges = []
|
138
147
|
end
|
139
148
|
|
@@ -147,7 +156,7 @@ module Nexpose
|
|
147
156
|
#
|
148
157
|
def self.load(nsc, name, scope = Scope::SILO)
|
149
158
|
xml = nsc.make_xml('RoleDetailsRequest')
|
150
|
-
xml.add_element('Role', {'name' => name, 'scope' => scope})
|
159
|
+
xml.add_element('Role', { 'name' => name, 'scope' => scope })
|
151
160
|
response = APIRequest.execute(nsc.url, xml, '1.2')
|
152
161
|
|
153
162
|
if response.success
|
@@ -156,7 +165,7 @@ module Nexpose
|
|
156
165
|
end
|
157
166
|
end
|
158
167
|
|
159
|
-
|
168
|
+
alias get load
|
160
169
|
|
161
170
|
# Create or save a Role to the Nexpose console.
|
162
171
|
#
|
@@ -170,9 +179,9 @@ module Nexpose
|
|
170
179
|
end
|
171
180
|
xml.add_element(as_xml)
|
172
181
|
|
173
|
-
response
|
174
|
-
xml
|
175
|
-
@id
|
182
|
+
response = APIRequest.execute(nsc.url, xml, '1.2')
|
183
|
+
xml = REXML::XPath.first(response.res, 'RoleCreateResponse')
|
184
|
+
@id = xml.attributes['id'].to_i unless @existing
|
176
185
|
@existing = true
|
177
186
|
response.success
|
178
187
|
end
|
@@ -186,9 +195,9 @@ module Nexpose
|
|
186
195
|
# @return [Role] requested role.
|
187
196
|
#
|
188
197
|
def self.copy(nsc, name, scope = Scope::SILO)
|
189
|
-
role
|
190
|
-
role.name
|
191
|
-
role.id
|
198
|
+
role = load(nsc, name, scope)
|
199
|
+
role.name = role.full_name = nil
|
200
|
+
role.id = -1
|
192
201
|
role.existing = false
|
193
202
|
role
|
194
203
|
end
|
@@ -230,29 +239,29 @@ module Nexpose
|
|
230
239
|
|
231
240
|
def as_xml
|
232
241
|
xml = REXML::Element.new('Role')
|
233
|
-
xml.add_attributes({'name' => @name, 'full-name' => @full_name, 'enabled' => enabled
|
242
|
+
xml.add_attributes({ 'name' => @name, 'full-name' => @full_name, 'enabled' => enabled, 'scope' => @scope })
|
234
243
|
xml.add_attribute('id', @id) if @id > 0
|
235
244
|
xml.add_element('Description').text = @description
|
236
245
|
|
237
246
|
site_privileges = xml.add_element('SitePrivileges')
|
238
|
-
Privilege::Site
|
247
|
+
Privilege::Site.constants.each do |field|
|
239
248
|
as_s = Privilege::Site.const_get(field)
|
240
249
|
enabled = privileges.member? as_s
|
241
|
-
site_privileges.add_element(
|
250
|
+
site_privileges.add_element(as_s, { 'enabled' => enabled })
|
242
251
|
end
|
243
252
|
|
244
253
|
asset_group_privileges = xml.add_element('AssetGroupPrivileges')
|
245
|
-
Privilege::AssetGroup
|
254
|
+
Privilege::AssetGroup.constants.each do |field|
|
246
255
|
as_s = Privilege::AssetGroup.const_get(field)
|
247
256
|
enabled = privileges.member? as_s
|
248
|
-
asset_group_privileges.add_element(
|
257
|
+
asset_group_privileges.add_element(as_s, { 'enabled' => enabled })
|
249
258
|
end
|
250
259
|
|
251
260
|
global_privileges = xml.add_element('GlobalPrivileges')
|
252
|
-
Privilege::Global
|
261
|
+
Privilege::Global.constants.each do |field|
|
253
262
|
as_s = Privilege::Global.const_get(field)
|
254
263
|
enabled = privileges.member? as_s
|
255
|
-
global_privileges.add_element(
|
264
|
+
global_privileges.add_element(as_s, { 'enabled' => enabled })
|
256
265
|
end
|
257
266
|
|
258
267
|
xml
|