pyramid_cubicweb/__init__.py
author Christophe de Vienne <christophe@unlish.com>
Tue, 15 Jul 2014 14:25:15 +0200
changeset 11485 3905c9f06d0e
parent 11484 39768d122f97
child 11487 04252e9ff549
permissions -rw-r--r--
Use short-lived cubicweb sessions to let pyramid actually handle the web sessions Related to #4291173

from cubicweb.web.request import CubicWebRequestBase
from cubicweb.cwconfig import CubicWebConfiguration
from cubicweb import repoapi

import cubicweb
import cubicweb.web

from pyramid import security
from pyramid.httpexceptions import HTTPSeeOther

from pyramid_cubicweb import authplugin

import weakref

import logging

log = logging.getLogger(__name__)


class CubicWebPyramidRequest(CubicWebRequestBase):
    def __init__(self, request):
        self._request = request

        self.path = request.upath_info

        vreg = request.registry['cubicweb.appli'].vreg
        https = request.scheme == 'https'

        post = request.params
        headers_in = request.headers

        super(CubicWebPyramidRequest, self).__init__(vreg, https, post,
                                                     headers=headers_in)

    def is_secure(self):
        return self._request.scheme == 'https'

    def relative_path(self, includeparams=True):
        path = self._request.path[1:]
        if includeparams and self._request.query_string:
            return '%s?%s' % (path, self._request.query_string)
        return path

    def instance_uri(self):
        return self._request.application_url

    def get_full_path(self):
        path = self._request.path
        if self._request.query_string:
            return '%s?%s' % (path, self._request.query_string)
        return path

    def http_method(self):
        return self._request.method

    def _set_status_out(self, value):
        self._request.response.status_int = value

    def _get_status_out(self):
        return self._request.response.status_int

    status_out = property(_get_status_out, _set_status_out)


def render_view(request, vid, **kwargs):
    vreg = request.registry['cubicweb.registry']
    # XXX The select() function could, know how to handle a pyramid
    # request, and feed it directly to the views that supports it.
    # On the other hand, we could refine the View concept and decide it works
    # with a cnx, and never with a WebRequest

    view = vreg['views'].select(vid, request.cw_request, **kwargs)

    view.set_stream()
    view.render()
    return view._stream.getvalue()


def login(request):
    repo = request.registry['cubicweb.repository']

    response = request.response
    user_eid = None

    if '__login' in request.params:
        login = request.params['__login']
        password = request.params['__password']

        try:
            with repo.internal_cnx() as cnx:
                user = repo.authenticate_user(cnx, login, password=password)
                user_eid = user.eid
        except cubicweb.AuthenticationError:
            raise

    if user_eid is not None:
        headers = security.remember(request, user_eid)

        raise HTTPSeeOther(
            request.params.get('postlogin_path', '/'),
            headers=headers)

        response.headerlist.extend(headers)

    response.text = render_view(request, 'login')
    return response


def _cw_cnx(request):
    cnx = repoapi.ClientConnection(request.cw_session)

    def cleanup(request):
        if request.exception is not None:
            cnx.rollback()
        else:
            cnx.commit()
        cnx.__exit__(None, None, None)

    request.add_finished_callback(cleanup)
    cnx.__enter__()
    return cnx


def _cw_close_session(request):
    request.cw_session.close()


def _cw_session(request):
    """Obtains a cw session from a pyramid request"""
    repo = request.registry['cubicweb.repository']
    config = request.registry['cubicweb.config']

    if not request.authenticated_userid:
        login, password = config.anonymous_user()
        sessionid = repo.connect(login, password=password)
        session = repo._sessions[sessionid]
        request.add_finished_callback(_cw_close_session)
    else:
        session = request._cw_cached_session

    # XXX Ideally we store the cw session data in the pyramid session.
    # BUT some data in the cw session data dictionnary makes pyramid fail.
    #session.data = request.session

    return session


def _cw_request(request):
    req = CubicWebPyramidRequest(request)
    req.set_cnx(request.cw_cnx)
    return req


def get_principals(login, request):
    repo = request.registry['cubicweb.repository']

    try:
        sessionid = repo.connect(
            str(login), __pyramid_directauth=authplugin.EXT_TOKEN)
        session = repo._sessions[sessionid]
        request._cw_cached_session = session
        request.add_finished_callback(_cw_close_session)
    except:
        log.exception("Failed")
        raise

    return session.user.groups


from pyramid.authentication import SessionAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.session import SignedCookieSessionFactory


def hello_world(request):
    request.response.text = \
        u"<html><body>Hello %s</body></html>" % request.cw_cnx.user.login
    return request.response


def includeme(config):
    appid = config.registry.settings['cubicweb.instance']
    cwconfig = CubicWebConfiguration.config_for(appid)

    config.set_session_factory(
        SignedCookieSessionFactory(
            secret=config.registry.settings['session.secret']
        ))

    config.set_authentication_policy(
        SessionAuthenticationPolicy(callback=get_principals))
    config.set_authorization_policy(ACLAuthorizationPolicy())

    config.registry['cubicweb.config'] = cwconfig
    config.registry['cubicweb.repository'] = repo = cwconfig.repository()
    config.registry['cubicweb.registry'] = repo.vreg

    repo.system_source.add_authentifier(authplugin.DirectAuthentifier())

    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)
    config.add_request_method(
        _cw_request, name='cw_request', property=True, reify=True)

    config.add_route('login', '/login')
    config.add_view(login, route_name='login')

    config.add_route('hello', '/hello')
    config.add_view(hello_world, route_name='hello')

    config.include('pyramid_cubicweb.handler')