Added administration audit and fixed some translations

This commit is contained in:
Adolfo Gómez García 2022-10-05 17:54:07 +02:00
parent 8b3ad295cc
commit d48747abff
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
9 changed files with 132 additions and 20 deletions

View File

@ -43,6 +43,8 @@ from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from uds.core import VERSION, VERSION_STAMP
from . import log
from .handlers import (
Handler,
HandlerError,
@ -61,8 +63,6 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__)
__all__ = ['Handler', 'Dispatcher']
AUTH_TOKEN_HEADER = 'X-Auth-Token'
@ -237,7 +237,9 @@ class Dispatcher(View):
# Dinamycally import children of this package.
package = 'methods'
pkgpath = os.path.join(os.path.dirname(typing.cast(str, sys.modules[__name__].__file__)), package)
pkgpath = os.path.join(
os.path.dirname(typing.cast(str, sys.modules[__name__].__file__)), package
)
for _, name, _ in pkgutil.iter_modules([pkgpath]):
# __import__(__name__ + '.' + package + '.' + name, globals(), locals(), [], 0)
importlib.import_module(

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
@ -28,7 +27,7 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import typing
import logging
@ -42,6 +41,9 @@ from uds.core.util import net
from uds.models import Authenticator, User
from uds.core.managers import cryptoManager
from . import log
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core.util.request import ExtendedHttpRequestWithUser
@ -127,8 +129,8 @@ class Handler:
self,
request: 'ExtendedHttpRequestWithUser',
path: str,
operation: str,
params: typing.Any,
method: str,
params: typing.MutableMapping[str, typing.Any],
*args: str,
**kwargs
):
@ -147,7 +149,7 @@ class Handler:
self._request = request
self._path = path
self._operation = operation
self._operation = method
self._params = params
self._args = args
self._kwargs = kwargs
@ -178,6 +180,9 @@ class Handler:
else:
self._user = User() # Empty user for non authenticated handlers
# Keep track of the operation
log.log_operation(self)
def headers(self) -> typing.Dict[str, str]:
"""
Returns the headers of the REST request (all)

View File

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import typing
import datetime
from uds.models import Log, getSqlDatetime
from uds.core.util import log, config
from uds.core.jobs import Job
if typing.TYPE_CHECKING:
from .handlers import Handler
def log_operation(handler: 'Handler', level: int = log.INFO):
"""
Logs a request
"""
# pylint: disable=import-outside-toplevel
# Global log is used without owner nor type
Log.objects.create(
owner_id=0,
owner_type=log.OWNER_TYPE_REST,
created=getSqlDatetime(),
level=level,
source=log.REST,
data=f'{handler._request.user.pretty_name}: [{handler._request.method}] {handler._request.path}'[
:255
],
)

View File

@ -59,7 +59,7 @@ class Authenticators(ModelHandler):
# Custom get method "search" that requires authenticator id
custom_methods = [('search', True)]
detail = {'users': Users, 'groups': Groups}
save_fields = ['name', 'comments', 'tags', 'priority', 'small_name', 'visible', 'mfa_id']
save_fields = ['name', 'comments', 'tags', 'priority', 'small_name', 'visible', '-mfa_id']
table_title = _('Authenticators')
table_fields = [
@ -140,7 +140,8 @@ class Authenticators(ModelHandler):
)
return g
raise Exception() # Not found
except Exception:
except Exception as e:
logger.info('Type not found: %s', e)
raise NotFound('type not found')
def item_as_dict(self, item: Authenticator) -> typing.Dict[str, typing.Any]:
@ -213,7 +214,7 @@ class Authenticators(ModelHandler):
self, fields: typing.Dict[str, typing.Any]
) -> None: # pylint: disable=too-many-branches,too-many-statements
logger.debug(self._params)
if fields['mfa_id']:
if fields.get('mfa_id'):
try:
mfa = MFA.objects.get(
uuid=processUuid(fields['mfa_id'])

View File

@ -115,7 +115,7 @@ class BaseModelHandler(Handler):
},
}
if 'tab' in field:
v['gui']['tab'] = field['tab']
v['gui']['tab'] = _(field['tab'] or '')
gui.append(v)
return gui
@ -268,7 +268,10 @@ class BaseModelHandler(Handler):
args: typing.Dict[str, str] = {}
try:
for key in fldList:
args[key] = self._params[key]
if key.startswith('-'): # optional
args[key[1:]] = self._params.get(key[1:], '')
else:
args[key] = self._params[key]
# del self._params[key]
except KeyError as e:
raise RequestError('needed parameter not found in data {0}'.format(e))

View File

@ -140,7 +140,11 @@ class Config:
self.set(self._default)
self._data = self._default
except Exception as e:
logger.info('Error accessing db config {0}.{1}'.format(self._section.name(), self._key))
logger.info(
'Error accessing db config {0}.{1}'.format(
self._section.name(), self._key
)
)
logger.exception(e)
self._data = self._default
@ -298,7 +302,9 @@ class Config:
return False
@staticmethod
def getConfigValues(addCrypt: bool = False) -> typing.Mapping[str, typing.Mapping[str, typing.Mapping[str, typing.Any]]]:
def getConfigValues(
addCrypt: bool = False,
) -> typing.Mapping[str, typing.Mapping[str, typing.Mapping[str, typing.Any]]]:
"""
Returns a dictionary with all config values
"""
@ -472,6 +478,11 @@ class GlobalConfig:
'New Max restriction', '0', type=Config.BOOLEAN_FIELD
)
# Maximum security logs duration in days
MAX_AUDIT_LOGS_DURATION: Config.Value = Config.section(SECURITY_SECTION).value(
'Max Audit Logs duration', '365', type=Config.NUMERIC_FIELD
)
# Allowed "trusted sources" for request
TRUSTED_SOURCES: Config.Value = Config.section(SECURITY_SECTION).value(
'Trusted Hosts', '*', type=Config.TEXT_FIELD

View File

@ -48,7 +48,7 @@ OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (
) # @UndefinedVariable
# Logging sources
INTERNAL, ACTOR, TRANSPORT, OSMANAGER, UNKNOWN, WEB, ADMIN, SERVICE = (
INTERNAL, ACTOR, TRANSPORT, OSMANAGER, UNKNOWN, WEB, ADMIN, SERVICE, REST = (
'internal',
'actor',
'transport',
@ -57,6 +57,7 @@ INTERNAL, ACTOR, TRANSPORT, OSMANAGER, UNKNOWN, WEB, ADMIN, SERVICE = (
'web',
'admin',
'service',
'rest',
)
OTHERSTR, DEBUGSTR, INFOSTR, WARNSTR, ERRORSTR, FATALSTR = (
@ -81,6 +82,10 @@ __nameLevels = {
# Reverse dict of names
__valueLevels = {v: k for k, v in __nameLevels.items()}
# Global log owner types:
OWNER_TYPE_GLOBAL = -1
OWNER_TYPE_REST = -2
def logLevelFromStr(level: str) -> int:
"""

View File

@ -32,12 +32,14 @@
"""
from importlib import import_module
import logging
import datetime
import typing
from django.conf import settings
from uds.core.util.cache import Cache
from uds.core.jobs import Job
from uds.models import TicketStore
from uds.models import TicketStore, Log, getSqlDatetime
from uds.core.util import config, log
logger = logging.getLogger(__name__)
@ -65,7 +67,6 @@ class TicketStoreCleaner(Job):
class SessionsCleaner(Job):
frecuency = 3600 * 24 * 7 # Once a week will be enough
friendly_name = 'User Sessions cleaner'
@ -83,3 +84,20 @@ class SessionsCleaner(Job):
pass # No problem if no cleanup
logger.debug('Done session cleanup')
class AuditLogCleanup(Job):
frecuency = 60 * 60 * 24 # Once a day
friendly_name = 'Audit Log Cleanup'
def run(self) -> None:
"""
Cleans logs older than days
"""
Log.objects.filter(
date__lt=getSqlDatetime()
- datetime.timedelta(
days=config.GlobalConfig.MAX_AUDIT_LOGS_DURATION.getInt()
),
owner_type=log.OWNER_TYPE_REST,
).delete()

View File

@ -39,11 +39,18 @@ logger = logging.getLogger(__name__)
class Log(models.Model):
"""
Log model associated with an object.
"""Log model associated with an object.
This log is mainly used to keep track of log relative to objects
(such as when a user access a machine, or information related to user logins/logout, errors, ...)
Note:
owner_id can be 0, in wich case, the log is global (not related to any object)
if owner id is 0, these are valid owner_type values:
-1: Global log
-2: REST API log
See :py:mod:`uds.core.util.log` for more information
"""
owner_id = models.IntegerField(db_index=True, default=0)