Add support for Pyramid 2.x; new security policy
custom apps are still free to use pyramid 1.x new security policy is only used if config file says so
This commit is contained in:
parent
85d62a8e38
commit
8b4b3de336
|
@ -49,9 +49,6 @@ install_requires =
|
|||
# TODO: remove once their bug is fixed? idk what this is about yet...
|
||||
deform<2.0.15
|
||||
|
||||
# TODO: remove this cap and address warnings that follow
|
||||
pyramid<2
|
||||
|
||||
asgiref
|
||||
colander
|
||||
ColanderAlchemy
|
||||
|
@ -65,6 +62,7 @@ install_requires =
|
|||
paginate_sqlalchemy
|
||||
passlib
|
||||
Pillow
|
||||
pyramid
|
||||
pyramid_beaker>=0.6
|
||||
pyramid_deform
|
||||
pyramid_exclog
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2023 Lance Edgar
|
||||
# Copyright © 2010-2024 Lance Edgar
|
||||
#
|
||||
# This file is part of Rattail.
|
||||
#
|
||||
|
@ -133,8 +133,14 @@ def make_pyramid_config(settings, configure_csrf=True):
|
|||
config.registry['rattail_config'] = rattail_config
|
||||
|
||||
# configure user authorization / authentication
|
||||
config.set_authorization_policy(TailboneAuthorizationPolicy())
|
||||
config.set_authentication_policy(SessionAuthenticationPolicy())
|
||||
# TODO: security policy should become the default, for pyramid 2.x
|
||||
if rattail_config.getbool('tailbone', 'pyramid.use_security_policy',
|
||||
usedb=False, default=False):
|
||||
from tailbone.auth import TailboneSecurityPolicy
|
||||
config.set_security_policy(TailboneSecurityPolicy())
|
||||
else:
|
||||
config.set_authorization_policy(TailboneAuthorizationPolicy())
|
||||
config.set_authentication_policy(SessionAuthenticationPolicy())
|
||||
|
||||
# maybe require CSRF token protection
|
||||
if configure_csrf:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2023 Lance Edgar
|
||||
# Copyright © 2010-2024 Lance Edgar
|
||||
#
|
||||
# This file is part of Rattail.
|
||||
#
|
||||
|
@ -27,7 +27,6 @@ Authentication & Authorization
|
|||
import logging
|
||||
import re
|
||||
|
||||
from rattail import enum
|
||||
from rattail.util import prettify, NOTSET
|
||||
|
||||
from zope.interface import implementer
|
||||
|
@ -46,7 +45,8 @@ def login_user(request, user, timeout=NOTSET):
|
|||
Perform the steps necessary to login the given user. Note that this
|
||||
returns a ``headers`` dict which you should pass to the redirect.
|
||||
"""
|
||||
user.record_event(enum.USER_EVENT_LOGIN)
|
||||
app = request.rattail_config.get_app()
|
||||
user.record_event(app.enum.USER_EVENT_LOGIN)
|
||||
headers = remember(request, user.uuid)
|
||||
if timeout is NOTSET:
|
||||
timeout = session_timeout_for_user(user)
|
||||
|
@ -60,9 +60,10 @@ def logout_user(request):
|
|||
Perform the logout action for the given request. Note that this returns a
|
||||
``headers`` dict which you should pass to the redirect.
|
||||
"""
|
||||
app = request.rattail_config.get_app()
|
||||
user = request.user
|
||||
if user:
|
||||
user.record_event(enum.USER_EVENT_LOGOUT)
|
||||
user.record_event(app.enum.USER_EVENT_LOGOUT)
|
||||
request.session.delete()
|
||||
request.session.invalidate()
|
||||
headers = forget(request)
|
||||
|
@ -117,7 +118,7 @@ class TailboneAuthenticationPolicy(SessionAuthenticationPolicy):
|
|||
return user.uuid
|
||||
|
||||
# otherwise do normal session-based logic
|
||||
return super(TailboneAuthenticationPolicy, self).unauthenticated_userid(request)
|
||||
return super().unauthenticated_userid(request)
|
||||
|
||||
|
||||
@implementer(IAuthorizationPolicy)
|
||||
|
@ -150,6 +151,72 @@ class TailboneAuthorizationPolicy(object):
|
|||
raise NotImplementedError
|
||||
|
||||
|
||||
class TailboneSecurityPolicy:
|
||||
|
||||
def __init__(self, api_mode=False):
|
||||
from pyramid.authentication import SessionAuthenticationHelper
|
||||
from pyramid.request import RequestLocalCache
|
||||
|
||||
self.api_mode = api_mode
|
||||
self.session_helper = SessionAuthenticationHelper()
|
||||
self.identity_cache = RequestLocalCache(self.load_identity)
|
||||
|
||||
def load_identity(self, request):
|
||||
config = request.registry.settings.get('rattail_config')
|
||||
app = config.get_app()
|
||||
user = None
|
||||
|
||||
if self.api_mode:
|
||||
|
||||
# determine/load user from header token if present
|
||||
credentials = request.headers.get('Authorization')
|
||||
if credentials:
|
||||
match = re.match(r'^Bearer (\S+)$', credentials)
|
||||
if match:
|
||||
token = match.group(1)
|
||||
auth = app.get_auth_handler()
|
||||
user = auth.authenticate_user_token(Session(), token)
|
||||
|
||||
if not user:
|
||||
|
||||
# fetch user uuid from current session
|
||||
uuid = self.session_helper.authenticated_userid(request)
|
||||
if not uuid:
|
||||
return
|
||||
|
||||
# fetch user object from db
|
||||
model = app.model
|
||||
user = Session.get(model.User, uuid)
|
||||
if not user:
|
||||
return
|
||||
|
||||
# this user is responsible for data changes in current request
|
||||
Session().set_continuum_user(user)
|
||||
return user
|
||||
|
||||
def identity(self, request):
|
||||
return self.identity_cache.get_or_create(request)
|
||||
|
||||
def authenticated_userid(self, request):
|
||||
user = self.identity(request)
|
||||
if user is not None:
|
||||
return user.uuid
|
||||
|
||||
def remember(self, request, userid, **kw):
|
||||
return self.session_helper.remember(request, userid, **kw)
|
||||
|
||||
def forget(self, request, **kw):
|
||||
return self.session_helper.forget(request, **kw)
|
||||
|
||||
def permits(self, request, context, permission):
|
||||
config = request.registry.settings.get('rattail_config')
|
||||
app = config.get_app()
|
||||
auth = app.get_auth_handler()
|
||||
|
||||
user = self.identity(request)
|
||||
return auth.has_permission(Session(), user, permission)
|
||||
|
||||
|
||||
def add_permission_group(config, key, label=None, overwrite=True):
|
||||
"""
|
||||
Add a permission group to the app configuration.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2023 Lance Edgar
|
||||
# Copyright © 2010-2024 Lance Edgar
|
||||
#
|
||||
# This file is part of Rattail.
|
||||
#
|
||||
|
@ -50,8 +50,14 @@ def make_pyramid_config(settings):
|
|||
pyramid_config = Configurator(settings=settings, root_factory=app.Root)
|
||||
|
||||
# configure user authorization / authentication
|
||||
pyramid_config.set_authentication_policy(TailboneAuthenticationPolicy())
|
||||
pyramid_config.set_authorization_policy(TailboneAuthorizationPolicy())
|
||||
# TODO: security policy should become the default, for pyramid 2.x
|
||||
if rattail_config.getbool('tailbone', 'pyramid.use_security_policy',
|
||||
usedb=False, default=False):
|
||||
from tailbone.auth import TailboneSecurityPolicy
|
||||
pyramid_config.set_security_policy(TailboneSecurityPolicy(api_mode=True))
|
||||
else:
|
||||
pyramid_config.set_authentication_policy(TailboneAuthenticationPolicy())
|
||||
pyramid_config.set_authorization_policy(TailboneAuthorizationPolicy())
|
||||
|
||||
# always require CSRF token protection
|
||||
pyramid_config.set_default_csrf_options(require_csrf=True,
|
||||
|
|
Loading…
Reference in a new issue