diff --git a/server/src/uds/REST/log.py b/server/src/uds/REST/log.py index 1468e565..194f0277 100644 --- a/server/src/uds/REST/log.py +++ b/server/src/uds/REST/log.py @@ -31,7 +31,7 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com """ import typing -from uds.models import Log, getSqlDatetime +from uds import models from uds.core.util.log import ( REST, OWNER_TYPE_AUDIT, @@ -40,31 +40,68 @@ from uds.core.util.log import ( WARNING, ERROR, CRITICAL, - # These are legacy support until full removal - WARN, - FATAL, ) - if typing.TYPE_CHECKING: from .handlers import Handler +# This structct allows us to perform the following: +# If path has ".../providers/[uuid]/..." we will replace uuid with "provider nanme" sourrounded by [] +# If path has ".../services/[uuid]/..." we will replace uuid with "service name" sourrounded by [] +# If path has ".../users/[uuid]/..." we will replace uuid with "user name" sourrounded by [] +# If path has ".../groups/[uuid]/..." we will replace uuid with "group name" sourrounded by [] +UUID_REPLACER = ( + ('providers', models.Provider), + ('services', models.Service), + ('users', models.User), + ('groups', models.Group), +) -def log_operation(handler: typing.Optional['Handler'], response_code: int, level: int = INFO): + +def replacePath(path: str) -> str: + """Replaces uuids in path with names + All paths are in the form .../type/uuid/... + """ + for type, model in UUID_REPLACER: + if f'/{type}/' in path: + try: + uuid = path.split(f'/{type}/')[1].split('/')[0] + name = model.objects.get(uuid=uuid).name # type: ignore + path = path.replace(uuid, f'[{name}]') + except Exception: + pass + + return path + + +def log_operation( + handler: typing.Optional['Handler'], response_code: int, level: int = INFO +): """ Logs a request """ if not handler: return # Nothing to log + + path = handler._request.path + + # If a common request, and no error, we don't log it because it's useless and a waste of resources + if response_code < 400 and any( + x in path for x in ('overview', 'tableinfo', 'gui', 'types', 'system') + ): + return + + path = replacePath(path) + username = handler._request.user.pretty_name if handler._request.user else 'Unknown' # Global log is used without owner nor type - Log.objects.create( + models.Log.objects.create( owner_id=0, owner_type=OWNER_TYPE_AUDIT, - created=getSqlDatetime(), + created=models.getSqlDatetime(), level=level, source=REST, - data=f'{username}: [{handler._request.method}/{response_code}] {handler._request.path}'[ + data=f'{handler._request.ip} {username}: [{handler._request.method}/{response_code}] {path}'[ :255 ], ) diff --git a/server/src/uds/core/ui/user_interface.py b/server/src/uds/core/ui/user_interface.py index a8c5fe03..ab6f0ec8 100644 --- a/server/src/uds/core/ui/user_interface.py +++ b/server/src/uds/core/ui/user_interface.py @@ -163,7 +163,7 @@ class gui: # Helper to convert an item to a dict def choiceFromValue(val: typing.Union[str, typing.Dict[str, str]]) -> typing.Dict[str, str]: if isinstance(val, str): - return {'id': val, 'name': val} + return {'id': val, 'text': val} # Return a deepcopy return copy.deepcopy(val) diff --git a/server/src/uds/mfas/SMS/mfa.py b/server/src/uds/mfas/SMS/mfa.py index d86e9137..9dedce4c 100644 --- a/server/src/uds/mfas/SMS/mfa.py +++ b/server/src/uds/mfas/SMS/mfa.py @@ -227,6 +227,7 @@ class SMSMFA(mfas.MFA): cls.networks.setValues([ gui.choiceItem(v.uuid, v.name) for v in models.Network.objects.all().order_by('name') + if v.uuid ]) def composeSmsUrl(self, userId: str, userName: str, code: str, phone: str) -> str: diff --git a/server/src/uds/reports/lists/audit.py b/server/src/uds/reports/lists/audit.py index 0090ab08..db0b70d8 100644 --- a/server/src/uds/reports/lists/audit.py +++ b/server/src/uds/reports/lists/audit.py @@ -80,7 +80,7 @@ class ListReportAuditCSV(ListReport): def genData(self) -> typing.Generator[typing.Tuple, None, None]: # Xtract user method, response_code and request from data # the format is "user: [method/response_code] request" - rx = re.compile(r'(?P[^:]*): \[(?P[^/]*)/(?P[^\]]*)\] (?P.*)') + rx = re.compile(r'(?P[^ ]*) (?P[^:]*): \[(?P[^/]*)/(?P[^\]]*)\] (?P.*)') start = self.startDate.datetime().replace(hour=0, minute=0, second=0, microsecond=0) end = self.endDate.datetime().replace(hour=23, minute=59, second=59, microsecond=999999) @@ -91,10 +91,6 @@ class ListReportAuditCSV(ListReport): m = rx.match(i.data) if m is not None: - # Skip fields with 200 and one of this words on the request - # overview, tableinfo, gui, types, system - if m.group('response_code') == '200' and any(x in m.group('request') for x in ('overview', 'tableinfo', 'gui', 'types', 'system')): - continue # Convert response code to an string if 200, else, to an error response_code = { '200': 'OK', @@ -105,10 +101,11 @@ class ListReportAuditCSV(ListReport): '405': 'Method Not Allowed', '500': 'Internal Server Error', '501': 'Not Implemented', - }.get(m.group('response_code'), 'CODE: ' + m.group('response_code')) + }.get(m.group('response_code'), 'Code: ' + m.group('response_code')) yield ( i.created, i.level_str, + m.group('ip'), m.group('user'), m.group('method'), response_code, @@ -120,7 +117,7 @@ class ListReportAuditCSV(ListReport): writer = csv.writer(output) writer.writerow( - [ugettext('Date'), ugettext('Level'), ugettext('User'), ugettext('Method'), ugettext('Response code'), ugettext('Request')] + [ugettext('Date'), ugettext('Level'), ugettext('IP'), ugettext('User'), ugettext('Method'), ugettext('Response code'), ugettext('Request')] ) for l in self.genData(): diff --git a/server/src/uds/templates/uds/reports/lists/audit.html b/server/src/uds/templates/uds/reports/lists/audit.html new file mode 100644 index 00000000..f27d4e5b --- /dev/null +++ b/server/src/uds/templates/uds/reports/lists/audit.html @@ -0,0 +1,33 @@ +{% load l10n i18n %} + + + Users Report + + + + + + + + + + + + + + + + {% for log in logs %} + + + + + + {% endfor %} + +
{% trans 'Date' %}{% trans 'Level' %}{% trans 'Message' %}
{{ log.date }}{{ log.level }}{{ log.message }}
+ + + \ No newline at end of file