Source code for chrysalio.security

"""Security functionalities."""

from __future__ import annotations

from pyramid.config import Configurator
from pyramid.request import Request
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.security import Authenticated, Everyone, Allow, ALL_PERMISSIONS

from .lib.i18n import _
from .lib.log import log_info
from .models.dbuser import DBUser


PRINCIPALS = (
    ('group', _('Group management'), (
        ('viewer', _('View all groups'), (
            'group-view',)),
        ('editor', _('Edit or view any group'), (
            'group-edit', 'group-view')),
        ('creator', _('Create a new one or edit or view any group'), (
            'group-create', 'group-edit', 'group-view'))
    )),
    ('user', _('User management'), (
        ('viewer', _('View all users'), (
            'user-view',)),
        ('editor', _('Edit or view any user'), (
            'user-edit', 'user-view')),
        ('creator', _('Create a new one or edit or view any user'), (
            'user-create', 'user-edit', 'user-view'))
    )),
    ('profile', _('Profile management'), (
        ('viewer', _('View any profile'), (
            'profile-view',)),
        ('editor', _('Edit or view any profile'), (
            'profile-edit', 'profile-view')),
        ('creator', _('Create a new one or edit or view any profile'), (
            'profile-create', 'profile-edit', 'profile-view'))
    )),
    ('settings', _('General settings management'), (
        ('viewer', _('View general settings'), (
            'settings-view',)),
        ('editor', _('Edit or view general settings'), (
            'settings-edit', 'settings-view'))
    )),
    ('backup', _('Backup management'), (
        ('creator', _('Backup and restore'), (
            'backup-create',)),
    ))
)


# =============================================================================
[docs] def includeme(configurator: Configurator): """Function to include authentication mechanism. :type configurator: pyramid.config.Configurator :param configurator: Object used to do configuration declaration within the application. """ settings = configurator.get_settings() configurator.set_authorization_policy(ACLAuthorizationPolicy()) configurator.set_authentication_policy(SessionAuthenticationPolicy( secret=settings.get('auth.secret', '-'), hashalg='sha512', cookie_name=settings.get('auth.cookie', 'CIO_AUTH'), secure=settings.get('auth.secure') == 'true', http_only=True, samesite=settings.get('auth.samesite', 'Strict'))) configurator.set_root_factory(RootFactory) configurator.registry['principals'] = list(PRINCIPALS)
# =============================================================================
[docs] class SessionAuthenticationPolicy(AuthTktAuthenticationPolicy): """Authentication policy class based on session.""" _DBUser = DBUser # -------------------------------------------------------------------------
[docs] def authenticated_userid(self, request) -> str | None: """Return the authenticated usr ID or ``None`` if no authenticated user ID can be found. :type request: pyramid.request.Request :param request: Current request. :rtype: :class:`str` or ``None`` """ user = self._user(request) if user is not None: return user['user_id'] return None
# -------------------------------------------------------------------------
[docs] def effective_principals(self, request: Request) -> list: """Return a list of principals including, at least, :class:`pyramid.security.Everyone`. :type request: pyramid.request.Request :param request: Current request. :return: list """ principals = [Everyone] user = self._user(request) if user is not None: principals.append(Authenticated) principals += user['principals'] return principals
# ------------------------------------------------------------------------- @classmethod def _user(cls, request: Request) -> dict | None: """Get user from session. :type request: pyramid.request.Request :param request: Current request. :rtype: dict """ # Already authenticated login = request.unauthenticated_userid if login and 'user' in request.session and \ request.session['user']['login'] == login: return request.session['user'] # Auto login dbuser = cls._DBUser.get(request, login)[0] if dbuser is not None and not dbuser.password_mustchange: # pylint: disable = no-member dbuser.set_session(request) log_info(request, 'autologin') return request.session['user'] if dbuser is not None and dbuser.password_mustchange: request.session['user'] = {'login': ''} return None
# =============================================================================
[docs] class RootFactory(): """Access Control List (ACL) definition. :type request: pyramid.request.Request :param request: Current request. """ # pylint: disable = too-few-public-methods __acl__ = [ (Allow, Authenticated, 'authenticated'), (Allow, 'system.administrator', ALL_PERMISSIONS)] + [ (Allow, '{0}.{1}'.format(group[0], principal[0]), principal[2]) for group in PRINCIPALS for principal in group[2]] # ------------------------------------------------------------------------- def __init__(self, request: Request): """Constructor method."""