Partially log requests sent to the API

Payloads containing sensitive content (such as passwords and
tokens) should be hidden.
This commit is contained in:
Jonas Schäfer
2020-03-08 11:12:01 +01:00
parent d6b1ce8773
commit 12276337c1
2 changed files with 31 additions and 8 deletions

View File

@@ -3,6 +3,8 @@ import contextlib
import functools import functools
import hashlib import hashlib
import json import json
import logging
import secrets
import aiohttp import aiohttp
@@ -102,6 +104,9 @@ class ProsodyClient:
self._plain_session = HTTPSessionManager(self.CTX_PLAIN_SESSION) self._plain_session = HTTPSessionManager(self.CTX_PLAIN_SESSION)
self._auth_session = HTTPAuthSessionManager(self.CTX_AUTH_SESSION, self._auth_session = HTTPAuthSessionManager(self.CTX_AUTH_SESSION,
self.SESSION_TOKEN) self.SESSION_TOKEN)
self.logger = logging.getLogger(
".".join([__name__, type(self).__qualname__])
)
self.app = app self.app = app
if app is not None: if app is not None:
self.init_app(app) self.init_app(app)
@@ -140,6 +145,7 @@ class ProsodyClient:
request.add_field("username", jid) request.add_field("username", jid)
request.add_field("password", password) request.add_field("password", password)
self.logger.debug("sending OAuth2 request (payload omitted)")
async with session.post(self._login_endpoint, data=request) as resp: async with session.post(self._login_endpoint, data=request) as resp:
auth_status = resp.status auth_status = resp.status
auth_data = (await resp.read()) auth_data = (await resp.read())
@@ -151,6 +157,7 @@ class ProsodyClient:
# XXX: prosody-modules#1502 # XXX: prosody-modules#1502
if auth_status in [400, 401] or "error" in auth_info: if auth_status in [400, 401] or "error" in auth_info:
self.logger.debug("oauth2 error: %r", auth_info)
# OAuth2 spec says thats what can happen when some stuff is # OAuth2 spec says thats what can happen when some stuff is
# wrong. # wrong.
# we have to interpret the JSON further # we have to interpret the JSON further
@@ -159,6 +166,7 @@ class ProsodyClient:
if auth_status == 200: if auth_status == 200:
token_type = auth_info["token_type"] token_type = auth_info["token_type"]
self.logger.debug("oauth2 success: token_type=%r", token_type)
if token_type != "bearer": if token_type != "bearer":
raise NotImplementedError( raise NotImplementedError(
"unsupported token type: {!r}".format( "unsupported token type: {!r}".format(
@@ -216,18 +224,32 @@ class ProsodyClient:
return wrapped return wrapped
return decorator return decorator
async def _xml_iq_call(self, session, payload, *, headers=None): async def _xml_iq_call(self, session, payload, *, headers=None,
sensitive=False):
headers = headers or {} headers = headers or {}
headers.update({ headers.update({
"Content-Type": "application/xmpp+xml", "Content-Type": "application/xmpp+xml",
"Accept": "application/xmpp+xml", "Accept": "application/xmpp+xml",
}) })
if not payload.get("id"):
payload.set("id", secrets.token_hex(8))
serialised = ET.tostring(payload)
id_ = payload.get("id")
self.logger.debug(
"sending IQ (id=%s): %r",
id_, "(sensitive)" if sensitive else serialised,
)
async with session.post(self._rest_endpoint, async with session.post(self._rest_endpoint,
headers=headers, headers=headers,
data=payload) as resp: data=serialised) as resp:
if resp.status != 200: if resp.status != 200:
abort(resp.status) abort(resp.status)
reply_payload = await resp.read() reply_payload = await resp.read()
self.logger.debug(
"received IQ (in-reply-to id=%s): %r",
id_, "(sensitive)" if sensitive else reply_payload,
)
return ET.fromstring(reply_payload) return ET.fromstring(reply_payload)
async def get_user_info(self): async def get_user_info(self):
@@ -354,7 +376,8 @@ class ProsodyClient:
), ),
headers={ headers={
"Authorization": "Bearer {}".format(token), "Authorization": "Bearer {}".format(token),
} },
sensitive=True,
) )
# TODO: error handling # TODO: error handling
# TODO: obtain a new token using the new password to allow the # TODO: obtain a new token using the new password to allow the

View File

@@ -97,7 +97,7 @@ def make_password_change_request(jid, password):
q = ET.SubElement(req, "query", xmlns="jabber:iq:register") q = ET.SubElement(req, "query", xmlns="jabber:iq:register")
ET.SubElement(q, "username").text = username ET.SubElement(q, "username").text = username
ET.SubElement(q, "password").text = password ET.SubElement(q, "password").text = password
return ET.tostring(req) return req
def make_pubsub_item_put_request(to, node, id_=None): def make_pubsub_item_put_request(to, node, id_=None):
@@ -116,7 +116,7 @@ def make_nickname_set_request(to, nickname):
NODE_USER_NICKNAME, NODE_USER_NICKNAME,
) )
ET.SubElement(item, "nick", xmlns=NS_USER_NICKNAME).text = nickname ET.SubElement(item, "nick", xmlns=NS_USER_NICKNAME).text = nickname
return ET.tostring(req) return req
def make_pubsub_item_request(to, node, id_=None): def make_pubsub_item_request(to, node, id_=None):
@@ -128,7 +128,7 @@ def make_pubsub_item_request(to, node, id_=None):
else: else:
items.set("max_items", "1") items.set("max_items", "1")
return ET.tostring(req) return req
def make_nickname_get_request(to): def make_nickname_get_request(to):
@@ -151,7 +151,7 @@ def make_avatar_data_set_request(to, data, id_):
) )
ET.SubElement(item, "data", xmlns=NS_USER_AVATAR_DATA).text = \ ET.SubElement(item, "data", xmlns=NS_USER_AVATAR_DATA).text = \
base64.b64encode(data).decode("ascii") base64.b64encode(data).decode("ascii")
return ET.tostring(req) return req
def make_avatar_metadata_set_request(to, mimetype: str, id_: str, size: int, def make_avatar_metadata_set_request(to, mimetype: str, id_: str, size: int,
@@ -177,7 +177,7 @@ def make_avatar_metadata_set_request(to, mimetype: str, id_: str, size: int,
attr["height"] = str(height) attr["height"] = str(height)
ET.SubElement(metadata_wrap, "info", xmlns=NS_USER_AVATAR_METADATA, **attr) ET.SubElement(metadata_wrap, "info", xmlns=NS_USER_AVATAR_METADATA, **attr)
return ET.tostring(req) return req
def _require_child(t: ET.Element, tag: str) -> ET.Element: def _require_child(t: ET.Element, tag: str) -> ET.Element: