nexpose 0.2.8 → 0.5.0
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.
- checksums.yaml +4 -4
- data/lib/nexpose.rb +18 -9
- data/lib/nexpose/ajax.rb +127 -0
- data/lib/nexpose/alert.rb +29 -36
- data/lib/nexpose/common.rb +13 -12
- data/lib/nexpose/connection.rb +18 -13
- data/lib/nexpose/creds.rb +16 -55
- data/lib/nexpose/dag.rb +73 -0
- data/lib/nexpose/data_table.rb +134 -0
- data/lib/nexpose/device.rb +111 -0
- data/lib/nexpose/engine.rb +194 -0
- data/lib/nexpose/filter.rb +341 -0
- data/lib/nexpose/group.rb +33 -37
- data/lib/nexpose/manage.rb +4 -0
- data/lib/nexpose/pool.rb +142 -0
- data/lib/nexpose/report.rb +72 -278
- data/lib/nexpose/report_template.rb +249 -0
- data/lib/nexpose/scan.rb +196 -54
- data/lib/nexpose/scan_template.rb +103 -0
- data/lib/nexpose/site.rb +91 -237
- data/lib/nexpose/ticket.rb +173 -119
- data/lib/nexpose/user.rb +11 -3
- data/lib/nexpose/vuln.rb +81 -338
- data/lib/nexpose/vuln_exception.rb +368 -0
- metadata +12 -4
- data/lib/nexpose/misc.rb +0 -35
- data/lib/nexpose/scan_engine.rb +0 -325
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 096649e7275b4dd061fab3b7331e93f66fdec709
|
4
|
+
data.tar.gz: 483e8f15738e9b4297a702ee07c78b9f75381f6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 368763434ec2b93e957cbf596b36eeeddad7728170234dee6eaa72155e30f11d3826bc7503e22952005f2f10b6bc9cce0ad39769355cd1aeff59082d107db335
|
7
|
+
data.tar.gz: a02679981f17023624c99887fd2c9677678c6e1148a302c0aa7e0b1ce17c292bad83810c6a56da2e4614853fd009702a740f5d87fb41eb935a18359592a233ad
|
data/lib/nexpose.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
#
|
4
4
|
=begin
|
5
5
|
|
6
|
-
Copyright (C) 2009-
|
6
|
+
Copyright (C) 2009-2013, Rapid7 LLC
|
7
7
|
All rights reserved.
|
8
8
|
|
9
9
|
Redistribution and use in source and binary forms, with or without modification,
|
@@ -54,25 +54,34 @@ require 'net/http'
|
|
54
54
|
require 'uri'
|
55
55
|
require 'rex/mime'
|
56
56
|
require 'ipaddr'
|
57
|
+
require 'json'
|
57
58
|
require 'nexpose/error'
|
58
59
|
require 'nexpose/util'
|
59
|
-
require 'nexpose/
|
60
|
+
require 'nexpose/alert'
|
61
|
+
require 'nexpose/ajax'
|
60
62
|
require 'nexpose/api_request'
|
63
|
+
require 'nexpose/common'
|
64
|
+
require 'nexpose/creds'
|
65
|
+
require 'nexpose/data_table'
|
66
|
+
require 'nexpose/device'
|
67
|
+
require 'nexpose/engine'
|
68
|
+
require 'nexpose/filter'
|
69
|
+
require 'nexpose/group'
|
70
|
+
require 'nexpose/dag'
|
61
71
|
require 'nexpose/manage'
|
62
|
-
require 'nexpose/
|
72
|
+
require 'nexpose/pool'
|
63
73
|
require 'nexpose/report'
|
74
|
+
require 'nexpose/report_template'
|
75
|
+
require 'nexpose/role'
|
64
76
|
require 'nexpose/scan'
|
65
|
-
require 'nexpose/
|
77
|
+
require 'nexpose/scan_template'
|
66
78
|
require 'nexpose/silo'
|
67
79
|
require 'nexpose/site'
|
68
80
|
require 'nexpose/ticket'
|
81
|
+
require 'nexpose/user'
|
69
82
|
require 'nexpose/vuln'
|
70
|
-
require 'nexpose/
|
83
|
+
require 'nexpose/vuln_exception'
|
71
84
|
require 'nexpose/connection'
|
72
|
-
require 'nexpose/role'
|
73
|
-
require 'nexpose/common'
|
74
|
-
require 'nexpose/group'
|
75
|
-
require 'nexpose/alert'
|
76
85
|
|
77
86
|
module Nexpose
|
78
87
|
|
data/lib/nexpose/ajax.rb
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nexpose
|
4
|
+
|
5
|
+
# Accessor to the Nexpose AJAX API.
|
6
|
+
# These core methods should allow direct access to underlying controllers
|
7
|
+
# in order to test functionality that is not currently exposed
|
8
|
+
# through the XML API.
|
9
|
+
#
|
10
|
+
module AJAX
|
11
|
+
module_function
|
12
|
+
|
13
|
+
# GET call to a Nexpose controller.
|
14
|
+
#
|
15
|
+
# @param [Connection] nsc API connection to a Nexpose console.
|
16
|
+
# @param [String] uri Controller address relative to https://host:port
|
17
|
+
# @param [String] content_type Content type to use when issuing the GET.
|
18
|
+
# @return [String|REXML::Document|Hash] The response from the call.
|
19
|
+
#
|
20
|
+
def get(nsc, uri, content_type = 'text/xml; charset=UTF-8')
|
21
|
+
get = Net::HTTP::Get.new(uri)
|
22
|
+
get.set_content_type(content_type)
|
23
|
+
_request(nsc, get)
|
24
|
+
end
|
25
|
+
|
26
|
+
# PUT call to a Nexpose controller.
|
27
|
+
#
|
28
|
+
# @param [Connection] nsc API connection to a Nexpose console.
|
29
|
+
# @param [String] uri Controller address relative to https://host:port
|
30
|
+
# @param [String|REXML::Document] payload XML document required by the call.
|
31
|
+
# @param [String] content_type Content type to use when issuing the PUT.
|
32
|
+
# @return [String] The response from the call.
|
33
|
+
#
|
34
|
+
def put(nsc, uri, payload = nil, content_type = 'text/xml; charset=UTF-8')
|
35
|
+
put = Net::HTTP::Put.new(uri)
|
36
|
+
put.set_content_type(content_type)
|
37
|
+
put.body = payload.to_s if payload
|
38
|
+
_request(nsc, put)
|
39
|
+
end
|
40
|
+
|
41
|
+
# POST call to a Nexpose controller.
|
42
|
+
#
|
43
|
+
# @param [Connection] nsc API connection to a Nexpose console.
|
44
|
+
# @param [String] uri Controller address relative to https://host:port
|
45
|
+
# @param [String|REXML::Document] payload XML document required by the call.
|
46
|
+
# @param [String] content_type Content type to use when issuing the POST.
|
47
|
+
# @return [String|REXML::Document|Hash] The response from the call.
|
48
|
+
#
|
49
|
+
def post(nsc, uri, payload = nil, content_type = 'text/xml')
|
50
|
+
post = Net::HTTP::Post.new(uri)
|
51
|
+
post.set_content_type(content_type)
|
52
|
+
post.body = payload.to_s if payload
|
53
|
+
_request(nsc, post)
|
54
|
+
end
|
55
|
+
|
56
|
+
# POST call to a Nexpose controller that uses a form-post model.
|
57
|
+
# This is here to support legacy use of POST in old controllers.
|
58
|
+
#
|
59
|
+
# @param [Connection] nsc API connection to a Nexpose console.
|
60
|
+
# @param [String] uri Controller address relative to https://host:port
|
61
|
+
# @param [Hash] parameters Hash of attributes that need to be sent
|
62
|
+
# to the controller.
|
63
|
+
# @param [String] content_type Content type to use when issuing the POST.
|
64
|
+
# @return [Hash] The parsed JSON response from the call.
|
65
|
+
#
|
66
|
+
def form_post(nsc, uri, parameters, content_type = 'application/x-www-form-urlencoded; charset=UTF-8')
|
67
|
+
post = Net::HTTP::Post.new(uri)
|
68
|
+
post.set_content_type(content_type)
|
69
|
+
post.set_form_data(parameters)
|
70
|
+
_request(nsc, post)
|
71
|
+
end
|
72
|
+
|
73
|
+
# DELETE call to a Nexpose controller.
|
74
|
+
#
|
75
|
+
# @param [Connection] nsc API connection to a Nexpose console.
|
76
|
+
# @param [String] uri Controller address relative to https://host:port
|
77
|
+
# @param [String] content_type Content type to use when issuing the DELETE.
|
78
|
+
def delete(nsc, uri, content_type = 'text/xml')
|
79
|
+
delete = Net::HTTP::Delete.new(uri)
|
80
|
+
delete.set_content_type(content_type)
|
81
|
+
_request(nsc, delete)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Append the query parameters to given URI.
|
85
|
+
#
|
86
|
+
# @param [String] uri Controller address relative to https://host:port
|
87
|
+
# @param [Hash] parameters Hash of attributes that need to be sent
|
88
|
+
# to the controller.
|
89
|
+
# @return [Hash] The parametrized URI.
|
90
|
+
|
91
|
+
def parametrize_uri(uri, parameters)
|
92
|
+
uri = uri.concat(('?').concat(parameters.map { |k, v| "#{k}=#{CGI.escape(v[0].to_s)}" }.join('&'))) if parameters
|
93
|
+
end
|
94
|
+
|
95
|
+
###
|
96
|
+
# Internal helper methods
|
97
|
+
|
98
|
+
# Use the Nexpose::Connection to establish a correct HTTPS object.
|
99
|
+
def _https(nsc)
|
100
|
+
http = Net::HTTP.new(nsc.host, nsc.port)
|
101
|
+
http.use_ssl = true
|
102
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
103
|
+
http
|
104
|
+
end
|
105
|
+
|
106
|
+
# Attach necessary header fields.
|
107
|
+
def _headers(nsc, request)
|
108
|
+
request.add_field('nexposeCCSessionID', nsc.session_id)
|
109
|
+
request.add_field('Cookie', "nexposeCCSessionID=#{nsc.session_id}")
|
110
|
+
end
|
111
|
+
|
112
|
+
def _request(nsc, request)
|
113
|
+
http = _https(nsc)
|
114
|
+
_headers(nsc, request)
|
115
|
+
|
116
|
+
# Return response body if request is successful. Brittle.
|
117
|
+
response = http.request(request)
|
118
|
+
case response
|
119
|
+
when Net::HTTPOK
|
120
|
+
response.body
|
121
|
+
else
|
122
|
+
req_type = request.class.name.split('::').last
|
123
|
+
raise Nexpose::APIError.new(response, "#{req_type} request to #{request.path} failed. #{request.body}")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/nexpose/alert.rb
CHANGED
@@ -7,21 +7,16 @@ module Nexpose
|
|
7
7
|
|
8
8
|
# Name for this alert.
|
9
9
|
attr_accessor :name
|
10
|
-
|
11
10
|
# Whether or not this alert is currently active.
|
12
11
|
attr_accessor :enabled
|
13
|
-
|
14
12
|
# Send at most this many alerts per scan.
|
15
13
|
attr_accessor :max_alerts
|
16
|
-
|
17
14
|
# Send alerts based upon scan status.
|
18
15
|
attr_accessor :scan_filter
|
19
|
-
|
20
16
|
# Send alerts based upon vulnerability finding status.
|
21
17
|
attr_accessor :vuln_filter
|
22
|
-
|
23
18
|
# Alert type and its configuration. One of SMTPAlert, SyslogAlert, SNMPAlert
|
24
|
-
attr_accessor :
|
19
|
+
attr_accessor :type
|
25
20
|
|
26
21
|
def initialize(name, enabled = 1, max_alerts = -1)
|
27
22
|
@name, @enabled, @max_alerts = name, enabled, max_alerts
|
@@ -29,13 +24,13 @@ module Nexpose
|
|
29
24
|
|
30
25
|
def to_xml
|
31
26
|
xml = '<Alert'
|
32
|
-
xml << %
|
33
|
-
xml << %
|
34
|
-
xml << %
|
27
|
+
xml << %( name="#{@name}")
|
28
|
+
xml << %( enabled="#{@enabled}")
|
29
|
+
xml << %( maxAlerts="#{@max_alerts}")
|
35
30
|
xml << '>'
|
36
31
|
xml << scan_filter.to_xml
|
37
32
|
xml << vuln_filter.to_xml
|
38
|
-
xml <<
|
33
|
+
xml << type.to_xml
|
39
34
|
xml << '</Alert>'
|
40
35
|
end
|
41
36
|
|
@@ -43,7 +38,7 @@ module Nexpose
|
|
43
38
|
#
|
44
39
|
# @param [REXML::Document] rexml XML document to parse.
|
45
40
|
# @return [Alert] Alert object represented by the XML.
|
46
|
-
#
|
41
|
+
#
|
47
42
|
def self.parse(rexml)
|
48
43
|
name = rexml.attributes['name']
|
49
44
|
rexml.elements.each("//Alert[@name='#{name}']") do |xml|
|
@@ -53,11 +48,11 @@ module Nexpose
|
|
53
48
|
alert.scan_filter = ScanFilter.parse(REXML::XPath.first(xml, "//Alert[@name='#{name}']/scanFilter"))
|
54
49
|
alert.vuln_filter = VulnFilter.parse(REXML::XPath.first(xml, "//Alert[@name='#{name}']/vulnFilter"))
|
55
50
|
if (type = REXML::XPath.first(xml, "//Alert[@name='#{name}']/smtpAlert"))
|
56
|
-
alert.
|
51
|
+
alert.type = SMTPAlert.parse(type)
|
57
52
|
elsif (type = REXML::XPath.first(xml, "//Alert[@name='#{name}']/syslogAlert"))
|
58
|
-
alert.
|
53
|
+
alert.type = SyslogAlert.parse(type)
|
59
54
|
elsif (type = REXML::XPath.first(xml, "//Alert[@name='#{name}']/snmpAlert"))
|
60
|
-
alert.
|
55
|
+
alert.type = SNMPAlert.parse(type)
|
61
56
|
end
|
62
57
|
return alert
|
63
58
|
end
|
@@ -78,11 +73,11 @@ module Nexpose
|
|
78
73
|
|
79
74
|
def to_xml
|
80
75
|
xml = '<scanFilter'
|
81
|
-
xml << %
|
82
|
-
xml << %
|
83
|
-
xml << %
|
84
|
-
xml << %
|
85
|
-
xml << %
|
76
|
+
xml << %( scanStart="#{@start}")
|
77
|
+
xml << %( scanStop="#{@stop}")
|
78
|
+
xml << %( scanFailed="#{@fail}")
|
79
|
+
xml << %( scanResumed="#{@resume}")
|
80
|
+
xml << %( scanPaused="#{@pause}")
|
86
81
|
xml << '/>'
|
87
82
|
end
|
88
83
|
|
@@ -99,6 +94,7 @@ module Nexpose
|
|
99
94
|
# Set values to 1 to enable and 0 to disable.
|
100
95
|
#
|
101
96
|
class VulnFilter
|
97
|
+
|
102
98
|
# Only alert on vulnerability findings with a severity level greater than this level.
|
103
99
|
# Range is 0 to 10.
|
104
100
|
# Values in the UI correspond as follows:
|
@@ -116,10 +112,10 @@ module Nexpose
|
|
116
112
|
|
117
113
|
def to_xml
|
118
114
|
xml = '<vulnFilter'
|
119
|
-
xml << %
|
120
|
-
xml << %
|
121
|
-
xml << %
|
122
|
-
xml << %
|
115
|
+
xml << %( severityThreshold="#{@severity}")
|
116
|
+
xml << %( confirmed="#{@confirmed}")
|
117
|
+
xml << %( unconfirmed="#{@unconfirmed}")
|
118
|
+
xml << %( potential="#{@potential}")
|
123
119
|
xml << '/>'
|
124
120
|
end
|
125
121
|
|
@@ -151,7 +147,7 @@ module Nexpose
|
|
151
147
|
|
152
148
|
def to_xml
|
153
149
|
xml = '<syslogAlert'
|
154
|
-
xml << %
|
150
|
+
xml << %( server="#{replace_entities(server)}">)
|
155
151
|
xml << '</syslogAlert>'
|
156
152
|
end
|
157
153
|
end
|
@@ -180,8 +176,8 @@ module Nexpose
|
|
180
176
|
|
181
177
|
def to_xml
|
182
178
|
xml = '<snmpAlert'
|
183
|
-
xml << %
|
184
|
-
xml << %
|
179
|
+
xml << %( community="#{replace_entities(community)}")
|
180
|
+
xml << %( server="#{replace_entities(server)}">)
|
185
181
|
xml << '</snmpAlert>'
|
186
182
|
end
|
187
183
|
end
|
@@ -191,15 +187,12 @@ module Nexpose
|
|
191
187
|
#
|
192
188
|
class SMTPAlert
|
193
189
|
|
194
|
-
# The
|
190
|
+
# The e-mail address of the sender.
|
195
191
|
attr_accessor :sender
|
196
|
-
|
197
|
-
# The server to sent this alert
|
192
|
+
# The server to sent this alert.
|
198
193
|
attr_accessor :server
|
199
|
-
|
200
|
-
# Limit the text for mobile devices
|
194
|
+
# Limit the text for mobile devices.
|
201
195
|
attr_accessor :limit_text
|
202
|
-
|
203
196
|
# Array of strings with the e-mail addresses of the intended recipients.
|
204
197
|
attr_accessor :recipients
|
205
198
|
|
@@ -210,7 +203,7 @@ module Nexpose
|
|
210
203
|
@recipients = []
|
211
204
|
end
|
212
205
|
|
213
|
-
# Adds a new
|
206
|
+
# Adds a new recipient to the alert.
|
214
207
|
def add_recipient(recipient)
|
215
208
|
@recipients << recipient
|
216
209
|
end
|
@@ -219,9 +212,9 @@ module Nexpose
|
|
219
212
|
|
220
213
|
def to_xml
|
221
214
|
xml = '<smtpAlert'
|
222
|
-
xml << %
|
223
|
-
xml << %
|
224
|
-
xml << %
|
215
|
+
xml << %( sender="#{replace_entities(sender)}")
|
216
|
+
xml << %( server="#{replace_entities(server)}")
|
217
|
+
xml << %( limitText="#{limit_text}">)
|
225
218
|
recipients.each do |recpt|
|
226
219
|
xml << "<recipient>#{replace_entities(recpt)}</recipient>"
|
227
220
|
end
|
data/lib/nexpose/common.rb
CHANGED
@@ -27,6 +27,7 @@ module Nexpose
|
|
27
27
|
# the application will send reports via e-mail to access-list members and
|
28
28
|
# non-members.
|
29
29
|
class Email
|
30
|
+
|
30
31
|
# Send as file attachment or zipped file to individuals who are not members
|
31
32
|
# of the report access list. One of: file|zip
|
32
33
|
attr_accessor :send_as
|
@@ -55,17 +56,17 @@ module Nexpose
|
|
55
56
|
|
56
57
|
def to_xml
|
57
58
|
xml = '<Email'
|
58
|
-
xml << %
|
59
|
-
xml << %
|
60
|
-
xml << %
|
61
|
-
xml << %
|
59
|
+
xml << %( toAllAuthorized='#{@toAllAuthorized ? 1 : 0}')
|
60
|
+
xml << %( sendToOwnerAs='#{@send_to_owner_as}') if @send_to_owner_as
|
61
|
+
xml << %( sendToAclAs='#{@send_to_acl_as}') if @send_to_acl_as
|
62
|
+
xml << %( sendAs='#{@send_as}') if @send_as
|
62
63
|
xml << '>'
|
63
|
-
xml << %
|
64
|
-
xml << %
|
64
|
+
xml << %(<Sender>#{@sender}</Sender>) if @sender
|
65
|
+
xml << %(<SmtpRelayServer>#{@smtp_relay_server}</SmtpRelayServer>) if @smtp_relay_server
|
65
66
|
if @recipients
|
66
67
|
xml << '<Recipients>'
|
67
68
|
@recipients.each do |recipient|
|
68
|
-
xml << %
|
69
|
+
xml << %(<Recipient>#{recipient}</Recipient>)
|
69
70
|
end
|
70
71
|
xml << '</Recipients>'
|
71
72
|
end
|
@@ -121,11 +122,11 @@ module Nexpose
|
|
121
122
|
end
|
122
123
|
|
123
124
|
def to_xml
|
124
|
-
xml = %
|
125
|
-
xml << %
|
126
|
-
xml << %
|
127
|
-
xml << %
|
128
|
-
xml << %
|
125
|
+
xml = %(<Schedule enabled='#{@enabled ? 1 : 0}' type='#{@type}' interval='#{@interval}' start='#{@start}')
|
126
|
+
xml << %( maxDuration='#{@max_duration}') if @max_duration
|
127
|
+
xml << %( notValidAfter='#{@not_valid_after}') if @not_valid_after
|
128
|
+
xml << %( incremental='#{@incremental ? 1 : 0}') if @incremental
|
129
|
+
xml << %( repeaterType='#{@repeater_type}') if @repeater_type
|
129
130
|
xml << '/>'
|
130
131
|
end
|
131
132
|
|
data/lib/nexpose/connection.rb
CHANGED
@@ -4,9 +4,12 @@ module Nexpose
|
|
4
4
|
# Object that represents a connection to a Nexpose Security Console.
|
5
5
|
#
|
6
6
|
# === Examples
|
7
|
-
# # Create a new Nexpose
|
7
|
+
# # Create a new Nexpose::Connection on the default port
|
8
8
|
# nsc = Connection.new('10.1.40.10', 'nxadmin', 'password')
|
9
9
|
#
|
10
|
+
# # Create a new Nexpose::Connection from a URI or "URI" String
|
11
|
+
# nsc = Connection.from_uri('https://10.1.40.10:3780', 'nxadmin', 'password')
|
12
|
+
#
|
10
13
|
# # Login to NSC and Establish a Session ID
|
11
14
|
# nsc.login
|
12
15
|
#
|
@@ -17,7 +20,7 @@ module Nexpose
|
|
17
20
|
# puts 'Login Failure'
|
18
21
|
# end
|
19
22
|
#
|
20
|
-
# #
|
23
|
+
# # Logout
|
21
24
|
# logout_success = nsc.logout
|
22
25
|
#
|
23
26
|
class Connection
|
@@ -42,7 +45,13 @@ module Nexpose
|
|
42
45
|
# The last XML response received by this object, useful for debugging.
|
43
46
|
attr_reader :response_xml
|
44
47
|
|
45
|
-
#
|
48
|
+
# A constructor to load a Connection object from a URI
|
49
|
+
def self.from_uri(uri, user, pass, silo_id = nil)
|
50
|
+
uri = URI.parse(uri)
|
51
|
+
new(uri.host, user, pass, uri.port, silo_id)
|
52
|
+
end
|
53
|
+
|
54
|
+
# A constructor for Connection
|
46
55
|
def initialize(ip, user, pass, port = 3780, silo_id = nil)
|
47
56
|
@host = ip
|
48
57
|
@port = port
|
@@ -57,25 +66,21 @@ module Nexpose
|
|
57
66
|
def login
|
58
67
|
begin
|
59
68
|
login_hash = {'sync-id' => 0, 'password' => @password, 'user-id' => @username}
|
60
|
-
|
61
|
-
login_hash['silo-id'] = @silo_id
|
62
|
-
end
|
69
|
+
login_hash['silo-id'] = @silo_id if @silo_id
|
63
70
|
r = execute(make_xml('LoginRequest', login_hash))
|
71
|
+
if r.success
|
72
|
+
@session_id = r.sid
|
73
|
+
true
|
74
|
+
end
|
64
75
|
rescue APIError
|
65
76
|
raise AuthenticationFailed.new(r)
|
66
77
|
end
|
67
|
-
if (r.success)
|
68
|
-
@session_id = r.sid
|
69
|
-
true
|
70
|
-
end
|
71
78
|
end
|
72
79
|
|
73
80
|
# Logout of the current connection
|
74
81
|
def logout
|
75
82
|
r = execute(make_xml('LogoutRequest', {'sync-id' => 0}))
|
76
|
-
if
|
77
|
-
return true
|
78
|
-
end
|
83
|
+
return true if r.success
|
79
84
|
raise APIError.new(r, 'Logout failed')
|
80
85
|
end
|
81
86
|
|