Source code for cubicweb.pyramid.core

# copyright 2017 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# copyright 2014-2016 UNLISH S.A.S. (Montpellier, FRANCE), all rights reserved.
#
# contact https://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
#
# CubicWeb is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb.  If not, see <https://www.gnu.org/licenses/>.

"""Binding of CubicWeb connection to Pyramid request."""

import logging

from pyramid import httpexceptions

from cubicweb.pyramid import tools
from cubicweb.server import session as cwsession

log = logging.getLogger(__name__)


class Connection(cwsession.Connection):
    """A specialised Connection that access the session data through a
    property.

    This behavior makes sure the actual session data is not loaded until
    actually accessed.
    """

    def __init__(self, session, *args, **kw):
        super().__init__(session._repo, session._user, *args, **kw)
        self.session = session
        self.lang = session._cached_lang

    def _get_session_data(self):
        return self.session.data

    def _set_session_data(self, data):
        pass

    _session_data = property(_get_session_data, _set_session_data)


class Session:
    """A Session that access the session data through a property.

    Along with :class:`Connection`, it avoid any load of the pyramid session
    data until it is actually accessed.
    """

    def __init__(self, pyramid_request, user, repo):
        self._pyramid_request = pyramid_request
        self._user = user
        self._repo = repo

    @property
    def anonymous_session(self):
        # XXX for now, anonymous_user only exists in webconfig (and testconfig).
        # It will only be present inside all-in-one instance.
        # there is plan to move it down to global config.
        if not hasattr(self._repo.config, "anonymous_user"):
            # not a web or test config, no anonymous user
            return False
        return self._user.login == self._repo.config.anonymous_user()[0]

    def get_data(self):
        if not getattr(self, "_protect_data_access", False):
            self._data_accessed = True
            return self._pyramid_request.session

    def set_data(self, data):
        if getattr(self, "_data_accessed", False):
            self._pyramid_request.session.clear()
            self._pyramid_request.session.update(data)

    data = property(get_data, set_data)

    def new_cnx(self):
        self._protect_data_access = True
        try:
            return Connection(self)
        finally:
            self._protect_data_access = False


[docs]def _cw_cnx(request): """Obtains a cw session from a pyramid request The connection will be commited or rolled-back in a request finish callback (this is temporary, we should make use of the transaction manager in a later version). Not meant for direct use, use ``request.cw_cnx`` instead. :param request: A pyramid request :returns type: :class:`cubicweb.server.session.Connection` """ session = request.cw_session if session is None: return None cnx = session.new_cnx() def commit_state(cnx): return cnx.commit_state def cleanup(request): try: if request.exception is not None and not isinstance( request.exception, (httpexceptions.HTTPSuccessful, httpexceptions.HTTPRedirection), ): cnx.rollback() elif commit_state(cnx) == "uncommitable": cnx.rollback() else: cnx.commit() finally: cnx.__exit__(None, None, None) request.add_finished_callback(cleanup) cnx.__enter__() return cnx
[docs]def repo_connect(request, repo, eid): """A lightweight version of :meth:`cubicweb.server.repository.Repository.connect` that does not keep track of opened sessions, removing the need of closing them""" user, lang = tools.cached_build_user(repo, eid) session = Session(request, user, repo) session._cached_lang = lang tools.cnx_attach_entity(session, user) return session
[docs]def _cw_session(request): """Obtains a cw session from a pyramid request :param request: A pyramid request :returns type: :class:`cubicweb.server.session.Session` Not meant for direct use, use ``request.cw_session`` instead. """ repo = request.registry["cubicweb.repository"] if not request.authenticated_userid: eid = request.registry.get("cubicweb.anonymous_eid") if eid is None: return None session = repo_connect(request, repo, eid=eid) else: session = request._cw_cached_session return session
[docs]def get_principals(login, request): """Returns the group names of the authenticated user. This function is meant to be used as an authentication policy callback. It also pre-open the cubicweb session and put it in request._cw_cached_session for later usage by :func:`_cw_session`. .. note:: If the default authentication policy is not used, make sure this function gets called by the active authentication policy. :param login: A cubicweb user eid :param request: A pyramid request :returns: A list of group names """ repo = request.registry["cubicweb.repository"] try: session = repo_connect(request, repo, eid=login) request._cw_cached_session = session except Exception: log.exception("Failed") raise with session.new_cnx() as cnx: with cnx.security_enabled(read=False): return { group for group, in cnx.execute( "Any GN WHERE U in_group G, G name GN, U eid %(userid)s", {"userid": login}, ) }
[docs]def includeme(config): """Enables the core features of Pyramid CubicWeb. Automatically called by the 'pyramid' command, or via ``config.include('cubicweb.pyramid.code')``. In the later case, the following registry entries must be defined first: 'cubicweb.config' A cubicweb 'config' instance. 'cubicweb.repository' The correponding cubicweb repository. 'cubicweb.registry' The vreg. """ repo = config.registry["cubicweb.repository"] with repo.internal_cnx() as cnx: login = config.registry["cubicweb.config"].anonymous_user()[0] if login is not None: config.registry["cubicweb.anonymous_eid"] = ( cnx.find("CWUser", login=login).one().eid ) config.add_request_method(_cw_session, name="cw_session", property=True, reify=True) config.add_request_method(_cw_cnx, name="cw_cnx", property=True, reify=True) cwcfg = config.registry["cubicweb.config"] for cube in cwcfg.cubes(): pkgname = f"cubicweb_{cube}" mod = __import__(pkgname) if hasattr(mod, "includeme"): config.include(pkgname)