Skip to content

Commit 9215b87

Browse files
fix(security): replace passwords whereever they could be used for output
1 parent 2efab17 commit 9215b87

File tree

4 files changed

+38
-4
lines changed

4 files changed

+38
-4
lines changed

hexonet/apiconnector/apiclient.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,23 @@ def disableDebugMode(self):
9090
self.__debugMode = False
9191
return self
9292

93-
def getPOSTData(self, cmd):
93+
def getPOSTData(self, cmd, secured=False):
9494
"""
9595
Serialize given command for POST request including connection configuration data
9696
"""
9797
data = self.__socketConfig.getPOSTData()
98+
if secured:
99+
data = re.sub(r's_pw=[^&]+', 's_pw=***', data)
98100
tmp = ""
99101
if not isinstance(cmd, str):
100102
for key in sorted(cmd.keys()):
101103
if (cmd[key] is not None):
102104
tmp += ("{0}={1}\n").format(key, re.sub('[\r\n]', '', str(cmd[key])))
105+
else:
106+
tmp = cmd
107+
tmp = tmp.rstrip('\n')
108+
if secured:
109+
tmp = re.sub(r'PASSWORD=[^\n]+', 'PASSWORD=***', tmp)
103110
return ("{0}{1}={2}").format(data, quote('s_command'), quote(re.sub('\n$', '', tmp)))
104111

105112
def getSession(self):
@@ -255,6 +262,7 @@ def request(self, cmd):
255262

256263
# request command to API
257264
data = self.getPOSTData(newcmd).encode('UTF-8')
265+
secured = self.getPOSTData(newcmd, True).encode('UTF-8')
258266
# TODO: 300s (to be sure to get an API response)
259267
try:
260268
headers = {
@@ -268,11 +276,11 @@ def request(self, cmd):
268276
req.set_proxy(proxyurl.netloc, proxyurl.scheme)
269277
body = urlopen(req, timeout=self.__socketTimeout).read()
270278
if (self.__debugMode):
271-
print((self.__socketURL, data, body, '\n', '\n'))
279+
print((self.__socketURL, secured, body, '\n', '\n'))
272280
except Exception:
273281
body = rtm.getTemplate("httperror").getPlain()
274282
if (self.__debugMode):
275-
print((self.__socketURL, data, "HTTP communication failed", body, '\n', '\n'))
283+
print((self.__socketURL, secured, "HTTP communication failed", body, '\n', '\n'))
276284
return Response(body, newcmd)
277285

278286
def requestNextResponsePage(self, rr):

hexonet/apiconnector/response.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ def __init__(self, raw, cmd=None):
2424
super(Response, self).__init__(raw)
2525
# The API Command used within this request
2626
self.__command = cmd
27+
if (self.__command is not None) and ('PASSWORD' in self.__command):
28+
self.__command['PASSWORD'] = '***'
2729
# Column names available in this responsse.
2830
# NOTE: this includes also FIRST, LAST, LIMIT, COUNT, TOTAL
2931
# and maybe further specific columns in case of a list query

tests/test_apiclient.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def test_apiclientmethods():
7979

8080
# test string input
8181
enc = cl.getPOSTData('gregergege')
82-
assert enc == 's_entity=54cd&s_command='
82+
assert enc == 's_entity=54cd&s_command=gregergege'
8383

8484
# test object input with null value in parameter
8585
validate = 's_entity=54cd&s_command=COMMAND%3DModifyDomain'
@@ -89,6 +89,20 @@ def test_apiclientmethods():
8989
})
9090
assert enc == validate
9191

92+
# test secured passwords
93+
cl.setCredentials('test.user', 'test.passw0rd')
94+
enc = cl.getPOSTData({
95+
'COMMAND': 'CheckAuthentication',
96+
'SUBUSER': 'test.user',
97+
'PASSWORD': 'test.passw0rd'
98+
}, True)
99+
cl.setCredentials('', '')
100+
expected = (
101+
's_entity=54cd&s_login=test.user&s_pw=***&' +
102+
's_command=COMMAND%3DCheckAuthentication%0APASSWORD%3D%2A%2A%2A%0ASUBUSER%3Dtest.user'
103+
)
104+
assert expected == enc
105+
92106
# #.enableDebugMode()
93107
cl.enableDebugMode()
94108
cl.disableDebugMode()

tests/test_response.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def test_responsemethods():
4141
assert r.getFirstRecordIndex() == 0
4242

4343
# #.getCommandPlain()
44+
# case 1
4445
r = R("", {
4546
"COMMAND": "QueryDomainOptions",
4647
"DOMAIN0": "example.com",
@@ -49,6 +50,15 @@ def test_responsemethods():
4950
expected = "COMMAND = QueryDomainOptions\nDOMAIN0 = example.com\nDOMAIN1 = example.net\n"
5051
assert r.getCommandPlain() == expected
5152

53+
# case secured
54+
r = R("", {
55+
"COMMAND": "CheckAuthentication",
56+
"PASSWORD": "test.passw0rd",
57+
"SUBUSER": "test.user"
58+
})
59+
expected = "COMMAND = CheckAuthentication\nPASSWORD = ***\nSUBUSER = test.user\n"
60+
assert r.getCommandPlain() == expected
61+
5262
# #.getColumns()
5363
r = R(rtm.getTemplate('listP0').getPlain())
5464
cols = r.getColumns()

0 commit comments

Comments
 (0)