2f8411ba2f
still get the occasional server error when handling what should be a simple 404 request e.g. for /wp-login.php error indicates there is no `request.has_perm()` at the time, so hoping this moves it earlier in the life cycle so it *will* exist..
271 lines
10 KiB
Python
271 lines
10 KiB
Python
# -*- coding: utf-8; -*-
|
|
################################################################################
|
|
#
|
|
# Rattail -- Retail Software Framework
|
|
# Copyright © 2010-2023 Lance Edgar
|
|
#
|
|
# This file is part of Rattail.
|
|
#
|
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
# terms of the GNU General Public License as published by the Free Software
|
|
# Foundation, either version 3 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# Rattail 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 General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
################################################################################
|
|
"""
|
|
Event Subscribers
|
|
"""
|
|
|
|
import six
|
|
import json
|
|
import datetime
|
|
|
|
import rattail
|
|
|
|
import colander
|
|
import deform
|
|
from pyramid import threadlocal
|
|
from webhelpers2.html import tags
|
|
|
|
import tailbone
|
|
from tailbone import helpers
|
|
from tailbone.db import Session
|
|
from tailbone.config import csrf_header_name, should_expose_websockets
|
|
from tailbone.menus import make_simple_menus
|
|
from tailbone.util import get_global_search_options
|
|
|
|
|
|
def new_request(event):
|
|
"""
|
|
Identify the current user, and cache their current permissions. Also adds
|
|
the ``rattail_config`` attribute to the request.
|
|
|
|
A global Rattail ``config`` should already be present within the Pyramid
|
|
application registry's settings, which would normally be accessed via::
|
|
|
|
request.registry.settings['rattail_config']
|
|
|
|
This function merely "promotes" that config object so that it is more
|
|
directly accessible, a la::
|
|
|
|
request.rattail_config
|
|
|
|
.. note::
|
|
This of course assumes that a Rattail ``config`` object *has* in fact
|
|
already been placed in the application registry settings. If this is
|
|
not the case, this function will do nothing.
|
|
|
|
Also, attach some goodies to the request object:
|
|
|
|
* The currently logged-in user instance (if any), as ``user``.
|
|
|
|
* ``is_admin`` flag indicating whether user has the Administrator role.
|
|
|
|
* ``is_root`` flag indicating whether user is currently elevated to root.
|
|
|
|
* A shortcut method for permission checking, as ``has_perm()``.
|
|
"""
|
|
request = event.request
|
|
rattail_config = request.registry.settings.get('rattail_config')
|
|
# TODO: why would this ever be null?
|
|
if rattail_config:
|
|
request.rattail_config = rattail_config
|
|
|
|
def user(request):
|
|
user = None
|
|
uuid = request.authenticated_userid
|
|
if uuid:
|
|
model = request.rattail_config.get_model()
|
|
user = Session.get(model.User, uuid)
|
|
if user:
|
|
Session().set_continuum_user(user)
|
|
return user
|
|
|
|
request.set_property(user, reify=True)
|
|
|
|
# assign client IP address to the session, for sake of versioning
|
|
Session().continuum_remote_addr = request.client_addr
|
|
|
|
request.is_admin = bool(request.user) and request.user.is_admin()
|
|
request.is_root = request.is_admin and request.session.get('is_root', False)
|
|
|
|
# TODO: why would this ever be null?
|
|
if rattail_config:
|
|
|
|
app = rattail_config.get_app()
|
|
auth = app.get_auth_handler()
|
|
request.tailbone_cached_permissions = auth.get_permissions(
|
|
Session(), request.user)
|
|
|
|
def has_perm(name):
|
|
if name in request.tailbone_cached_permissions:
|
|
return True
|
|
return request.is_root
|
|
request.has_perm = has_perm
|
|
|
|
def has_any_perm(*names):
|
|
for name in names:
|
|
if has_perm(name):
|
|
return True
|
|
return False
|
|
request.has_any_perm = has_any_perm
|
|
|
|
|
|
def before_render(event):
|
|
"""
|
|
Adds goodies to the global template renderer context.
|
|
"""
|
|
|
|
request = event.get('request') or threadlocal.get_current_request()
|
|
rattail_config = request.rattail_config
|
|
|
|
renderer_globals = event
|
|
renderer_globals['rattail_app'] = request.rattail_config.get_app()
|
|
renderer_globals['app_title'] = request.rattail_config.app_title()
|
|
renderer_globals['h'] = helpers
|
|
renderer_globals['url'] = request.route_url
|
|
renderer_globals['rattail'] = rattail
|
|
renderer_globals['tailbone'] = tailbone
|
|
renderer_globals['model'] = request.rattail_config.get_model()
|
|
renderer_globals['enum'] = request.rattail_config.get_enum()
|
|
renderer_globals['six'] = six
|
|
renderer_globals['json'] = json
|
|
renderer_globals['datetime'] = datetime
|
|
renderer_globals['colander'] = colander
|
|
renderer_globals['deform'] = deform
|
|
renderer_globals['csrf_header_name'] = csrf_header_name(request.rattail_config)
|
|
|
|
# theme - we only want do this for classic web app, *not* API
|
|
# TODO: so, clearly we need a better way to distinguish the two
|
|
if 'tailbone.theme' in request.registry.settings:
|
|
renderer_globals['theme'] = request.registry.settings['tailbone.theme']
|
|
# note, this is just a global flag; user still needs permission to see picker
|
|
expose_picker = request.rattail_config.getbool('tailbone', 'themes.expose_picker',
|
|
default=False)
|
|
renderer_globals['expose_theme_picker'] = expose_picker
|
|
if expose_picker:
|
|
# tailbone's config extension provides a default theme selection,
|
|
# so the default we specify here *probably* should not matter
|
|
available = request.rattail_config.getlist('tailbone', 'themes',
|
|
default=['falafel'])
|
|
if 'default' not in available:
|
|
available.insert(0, 'default')
|
|
options = [tags.Option(theme) for theme in available]
|
|
renderer_globals['theme_picker_options'] = options
|
|
|
|
# heck while we're assuming the classic web app here...
|
|
# (we don't want this to happen for the API either!)
|
|
# TODO: just..awful *shrug*
|
|
# note that we assume "simple" menus nowadays
|
|
if request.rattail_config.getbool('tailbone', 'menus.simple', default=True):
|
|
renderer_globals['menus'] = make_simple_menus(request)
|
|
|
|
# TODO: ugh, same deal here
|
|
renderer_globals['messaging_enabled'] = request.rattail_config.getbool(
|
|
'tailbone', 'messaging.enabled', default=False)
|
|
|
|
# background color may be set per-request, by some apps
|
|
if hasattr(request, 'background_color') and request.background_color:
|
|
renderer_globals['background_color'] = request.background_color
|
|
else: # otherwise we use the one from config
|
|
renderer_globals['background_color'] = request.rattail_config.get(
|
|
'tailbone', 'background_color')
|
|
|
|
# TODO: remove this hack once nothing references it
|
|
renderer_globals['buefy_0_8'] = False
|
|
|
|
# maybe set custom stylesheet
|
|
css = None
|
|
if request.user:
|
|
css = request.rattail_config.get('tailbone.{}'.format(request.user.uuid),
|
|
'buefy_css')
|
|
if not css:
|
|
css = request.rattail_config.get('tailbone', 'theme.falafel.buefy_css')
|
|
renderer_globals['buefy_css'] = css
|
|
|
|
# add global search data for quick access
|
|
renderer_globals['global_search_data'] = get_global_search_options(request)
|
|
|
|
# here we globally declare widths for grid filter pseudo-columns
|
|
widths = request.rattail_config.get('tailbone', 'grids.filters.column_widths')
|
|
if widths:
|
|
widths = widths.split(';')
|
|
if len(widths) < 2:
|
|
widths = None
|
|
if not widths:
|
|
widths = ['15em', '15em']
|
|
renderer_globals['filter_fieldname_width'] = widths[0]
|
|
renderer_globals['filter_verb_width'] = widths[1]
|
|
|
|
# declare global support for websockets, or lack thereof
|
|
renderer_globals['expose_websockets'] = should_expose_websockets(rattail_config)
|
|
|
|
|
|
def add_inbox_count(event):
|
|
"""
|
|
Adds the current user's inbox message count to the global renderer context.
|
|
|
|
Note that this is not enabled by default; to turn it on you must do this:
|
|
|
|
config.add_subscriber('tailbone.subscribers.add_inbox_count', 'pyramid.events.BeforeRender')
|
|
"""
|
|
request = event.get('request') or threadlocal.get_current_request()
|
|
if request.user:
|
|
renderer_globals = event
|
|
enum = request.rattail_config.get_enum()
|
|
model = request.rattail_config.get_model()
|
|
renderer_globals['inbox_count'] = Session.query(model.Message)\
|
|
.outerjoin(model.MessageRecipient)\
|
|
.filter(model.MessageRecipient.recipient == Session.merge(request.user))\
|
|
.filter(model.MessageRecipient.status == enum.MESSAGE_STATUS_INBOX)\
|
|
.count()
|
|
|
|
|
|
def context_found(event):
|
|
"""
|
|
Attach some more goodies to the request object:
|
|
|
|
The following is attached to the request:
|
|
|
|
* ``get_referrer()`` function
|
|
|
|
* ``get_session_timeout()`` function
|
|
"""
|
|
request = event.request
|
|
|
|
def get_referrer(default=None, **kwargs):
|
|
if request.params.get('referrer'):
|
|
return request.params['referrer']
|
|
if request.session.get('referrer'):
|
|
return request.session.pop('referrer')
|
|
referrer = request.referrer
|
|
if (not referrer or referrer == request.current_route_url()
|
|
or not referrer.startswith(request.host_url)):
|
|
if default:
|
|
referrer = default
|
|
else:
|
|
referrer = request.route_url('home')
|
|
return referrer
|
|
request.get_referrer = get_referrer
|
|
|
|
def get_session_timeout():
|
|
"""
|
|
Returns the timeout in effect for the current session
|
|
"""
|
|
return request.session.get('_timeout')
|
|
request.get_session_timeout = get_session_timeout
|
|
|
|
|
|
def includeme(config):
|
|
config.add_subscriber(new_request, 'pyramid.events.NewRequest')
|
|
config.add_subscriber(before_render, 'pyramid.events.BeforeRender')
|
|
config.add_subscriber(context_found, 'pyramid.events.ContextFound')
|