Skip to content

Commit 3c26112

Browse files
committed
Add python2 compatibility, use defusedxml
1 parent cdf3814 commit 3c26112

File tree

14 files changed

+140
-50
lines changed

14 files changed

+140
-50
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ build
44
.eggs
55
stormshield.sns.sslclient.egg-info
66
__pycache__
7+
*.pyc

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ Concerning the SSL validation:
116116

117117
## Tests
118118

119-
Warning: tests require a remote SNS appliance.
119+
Warning: some tests require a remote SNS appliance.
120120

121121
`$ PASSWORD=password APPLIANCE=10.0.0.254 python3 setup.py test`
122122

bin/snscli

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
#!/usr/bin/python
1+
#!/usr/bin/env python
22

33
""" cli to connect to Stormshield Network Security appliances"""
44

5+
from __future__ import unicode_literals
56
import sys
67
import os
78
import re
89
import logging
910
import readline
1011
import getpass
1112
import atexit
12-
import xml.dom.minidom
13+
import defusedxml.minidom
1314
import begin
1415
from pygments import highlight
1516
from pygments.lexers import XmlLexer
@@ -47,20 +48,18 @@ def make_completer():
4748
return results[state]
4849
return custom_complete
4950

50-
@begin.start(auto_convert=True, short_args=False, lexical_order=True)
51-
@begin.logging
52-
def main(host: 'Remote UTM' = None,
53-
ip: 'Remote UTM ip' = None,
54-
usercert: 'User certificate file' = None,
55-
cabundle: 'CA bundle file' = None,
56-
password: 'Password' = None,
57-
port: 'Remote port' = 443,
58-
user: 'User name' = 'admin',
59-
sslverifypeer: 'Strict SSL CA check' = True,
60-
sslverifyhost: 'Strict SSL host name check' = True,
61-
credentials: 'Privilege list' = None,
62-
script: 'Command script' = None,
63-
outputformat: 'Output format (ini|xml)' = 'ini'):
51+
def main(host = None,
52+
ip = None,
53+
usercert = None,
54+
cabundle = None,
55+
password = None,
56+
port = 443,
57+
user = 'admin',
58+
sslverifypeer = True,
59+
sslverifyhost = True,
60+
credentials = None,
61+
script = None,
62+
outputformat = 'ini'):
6463

6564
for handler in logging.getLogger().handlers:
6665
if handler.__class__ == logging.StreamHandler:
@@ -123,7 +122,7 @@ def main(host: 'Remote UTM' = None,
123122
logging.error(str(exception))
124123
sys.exit(1)
125124
if outputformat == 'xml':
126-
print(highlight(xml.dom.minidom.parseString(response.xml).toprettyxml(),
125+
print(highlight(defusedxml.minidom.parseString(response.xml).toprettyxml(),
127126
XmlLexer(), TerminalFormatter()))
128127
else:
129128
print(response.output)
@@ -191,7 +190,30 @@ def main(host: 'Remote UTM' = None,
191190
logging.error(str(exception))
192191
else:
193192
if outputformat == 'xml':
194-
print(highlight(xml.dom.minidom.parseString(response.xml).toprettyxml(),
193+
print(highlight(defusedxml.minidom.parseString(response.xml).toprettyxml(),
195194
XmlLexer(), TerminalFormatter()))
196195
else:
197196
print(response.output)
197+
198+
# set function parameter annotations manually for compatibility with python2
199+
main.__annotations__ = {
200+
'host': 'Remote UTM',
201+
'ip': 'Remote UTM ip',
202+
'usercert': 'User certificate file',
203+
'cabundle': 'CA bundle file',
204+
'password': 'Password',
205+
'port': 'Remote port',
206+
'user': 'User name',
207+
'sslverifypeer': 'Strict SSL CA check',
208+
'sslverifyhost': 'Strict SSL host name check',
209+
'credentials': 'Privilege list',
210+
'script': 'Command script',
211+
'outputformat': 'Output format (ini|xml)'
212+
}
213+
# use correct input function with python2
214+
try:
215+
input = raw_input
216+
except NameError:
217+
pass
218+
219+
begin.start(begin.logging(main))

setup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,15 @@
2626
'requests',
2727
'requests_toolbelt',
2828
'colorlog',
29-
'pyreadline; platform_system == "Windows"'
29+
'defusedxml',
30+
'pyreadline; platform_system == "Windows"',
31+
'py2-ipaddress; python_version < "3"'
3032
],
3133
include_package_data=True,
3234
tests_require=["nose"],
3335
test_suite='nose.collector',
3436
classifiers=[
37+
"Programming Language :: Python :: 2",
3538
"Programming Language :: Python :: 3",
3639
"License :: Apache License 2.0",
3740
"Operating System :: OS Independent",

stormshield/sns/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "1.0.0.beta"
1+
__version__ = "1.0.0.beta1"
22

33
# major.minor.patch
44
# major: breaking API change

stormshield/sns/configparser.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
in ini/section format.
88
"""
99

10+
import sys
1011
import re
1112
from shlex import shlex
1213
from requests.structures import CaseInsensitiveDict
@@ -50,10 +51,10 @@ def __init__(self, text):
5051
lines = text.splitlines()
5152

5253
# strip serverd headers if needed
53-
group = self.SERVERD_HEAD_RE.match(lines[0])
54-
if group:
54+
match = self.SERVERD_HEAD_RE.match(lines[0])
55+
if match:
5556
del lines[0]
56-
self.format = group[1]
57+
self.format = match.group(1)
5758
if self.SERVERD_TAIL_RE.match(lines[-1]):
5859
del lines[-1]
5960

@@ -76,9 +77,9 @@ def __init__(self, text):
7677
continue
7778

7879
# section header
79-
group = self.SECTION_RE.match(line)
80-
if group:
81-
section = group[1]
80+
match = self.SECTION_RE.match(line)
81+
if match:
82+
section = match.group(1)
8283
if self.format == 'section':
8384
self.data[section] = CaseInsensitiveDict()
8485
else:
@@ -88,19 +89,22 @@ def __init__(self, text):
8889
if self.format == "list":
8990
self.data[section].append(line)
9091
elif self.format == "section_line":
92+
# fix encoding for python2
93+
if sys.version_info[0] < 3:
94+
line = line.encode('utf-8')
9195
# parse token=value token2=value2
9296
lexer = shlex(line, posix=True)
9397
lexer.wordchars += "=.-*:,"
9498
parsed = {}
9599
for word in lexer:
96100
# ignore anything else than token=value
97101
if '=' in word:
98-
token, value = word.split("=", maxsplit=1)
102+
token, value = word.split("=", 1)
99103
parsed[token] = value
100104
self.data[section].append(parsed)
101105
else:
102106
# section
103-
(token, value) = line.split("=", maxsplit=1)
107+
(token, value) = line.split("=", 1)
104108
self.data[section][token] = unquote(value)
105109

106110

stormshield/sns/crc.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@
8282
def compute_crc32(data):
8383
""" Returns the hex string of CRC value of DATA """
8484

85+
# python2 convert str to bytes
86+
if type(data) == str:
87+
data = bytearray(data)
88+
8589
n = len(data)
8690
crc = CRC32_init
8791

@@ -93,6 +97,10 @@ def compute_crc32(data):
9397
def update_crc32(data, crc):
9498
""" Incremental CRC, returns updated CRC. """
9599

100+
# python2 convert str to bytes
101+
if type(data) == str:
102+
data = bytearray(data)
103+
96104
n = len(data)
97105

98106
for i in range(0, n):

stormshield/sns/sslclient.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
23

34
"""
45
stormshield.sns.sslclient
@@ -8,13 +9,14 @@
89
and Response class to handle API answers.
910
"""
1011

12+
from __future__ import unicode_literals
1113
import os
1214
import ipaddress
1315
import base64
1416
import logging
1517
import re
1618
import platform
17-
import xml.etree.ElementTree as ElementTree
19+
import defusedxml.ElementTree as ElementTree
1820
import requests
1921
from requests.adapters import HTTPAdapter, DEFAULT_POOLSIZE, DEFAULT_RETRIES, DEFAULT_POOLBLOCK
2022
from requests.packages.urllib3.poolmanager import PoolManager
@@ -339,7 +341,7 @@ def connect(self):
339341
self.logger.log(logging.DEBUG, request.text)
340342

341343
try:
342-
nws_node = ElementTree.fromstring(request.text)
344+
nws_node = ElementTree.fromstring(request.content)
343345
msg = nws_node.attrib['msg']
344346
except:
345347
raise AuthenticationError("Can't decode authentication result")
@@ -359,7 +361,7 @@ def connect(self):
359361
self.logger.log(logging.DEBUG, request.text)
360362

361363
if request.status_code == requests.codes.OK:
362-
nws_node = ElementTree.fromstring(request.text)
364+
nws_node = ElementTree.fromstring(request.content)
363365
ret = int(nws_node.attrib['code'])
364366
msg = nws_node.attrib['msg']
365367

@@ -421,13 +423,13 @@ def send_command(self, command):
421423

422424
request = self.session.get(
423425
self.baseurl + '/api/command?sessionid=' + self.sessionid +
424-
'&cmd=' + requests.compat.quote(command), # manually done since we need %20 encoding
426+
'&cmd=' + requests.compat.quote(command.encode('utf-8')), # manually done since we need %20 encoding
425427
headers=self.headers)
426428

427429
self.logger.log(logging.DEBUG, request.text)
428430

429431
if request.status_code == requests.codes.OK:
430-
nws_node = ElementTree.fromstring(request.text)
432+
nws_node = ElementTree.fromstring(request.content)
431433
code = int(nws_node.attrib['code'])
432434
self.nws_parse(code)
433435
serverd = nws_node[0]
@@ -440,7 +442,7 @@ def send_command(self, command):
440442
response = Response(ret=serverd_ret,
441443
code=serverd_code,
442444
msg=serverd_msg,
443-
output=format_output(request.text),
445+
output=format_output(request.content),
444446
xml=request.text)
445447

446448
#multiline answer get the final code
@@ -523,14 +525,14 @@ def upload(self, filename):
523525
uploadh.close()
524526

525527
if request.status_code == requests.codes.OK:
526-
nws_node = ElementTree.fromstring(request.text)
528+
nws_node = ElementTree.fromstring(request.content)
527529
code = int(nws_node.attrib['code'])
528530
self.nws_parse(code)
529531

530532
return Response(code=nws_node[0].get('code'),
531533
ret=int(nws_node[0].get('ret')),
532534
msg=nws_node[0].get('msg'),
533-
output=format_output(request.text),
535+
output=format_output(request.content),
534536
xml=request.text)
535537

536538
raise ServerError("HTTP error {}".format(request.status_code))

tests/test_auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import unittest
55
from stormshield.sns.sslclient import SSLClient
66

7-
APPLIANCE=os.getenv('APPLIANCE', "")
7+
APPLIANCE = os.getenv('APPLIANCE', "")
88
SERIAL = os.getenv('SERIAL', "")
99
PASSWORD = os.getenv('PASSWORD', "")
1010

tests/test_cert.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import unittest
55
from stormshield.sns.sslclient import SSLClient
66

7-
APPLIANCE=os.getenv('APPLIANCE', "")
7+
APPLIANCE = os.getenv('APPLIANCE', "")
88
FQDN = os.getenv('FQDN', "")
99
PASSWORD = os.getenv('PASSWORD', "")
1010
CABUNDLE = os.getenv('CABUNDLE', "")
@@ -26,11 +26,11 @@ def test_sslverifypeer(self):
2626
client = SSLClient(host=APPLIANCE, user='admin', password=PASSWORD)
2727
self.fail("SSLClient should have failed (untrusted CA)")
2828
except Exception as exception:
29-
self.assertTrue(1==1, "SSLClient did not connect (untrusted CA)")
29+
self.assertTrue(True, "SSLClient did not connect (untrusted CA)")
3030

3131
try:
3232
client = SSLClient(host=APPLIANCE, user='admin', password=PASSWORD, sslverifypeer=False)
33-
self.assertTrue(1==1, "SSLClient connects with sslverifypeer=True")
33+
self.assertTrue(True, "SSLClient connects with sslverifypeer=False")
3434
except Exception as exception:
3535
print(exception)
3636
self.fail("SSLClient did not connect")

0 commit comments

Comments
 (0)