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...
|
# TODO: remove once their bug is fixed? idk what this is about yet...
|
||||||
deform<2.0.15
|
deform<2.0.15
|
||||||
|
|
||||||
# TODO: remove this cap and address warnings that follow
|
|
||||||
pyramid<2
|
|
||||||
|
|
||||||
asgiref
|
asgiref
|
||||||
colander
|
colander
|
||||||
ColanderAlchemy
|
ColanderAlchemy
|
||||||
|
@ -65,6 +62,7 @@ install_requires =
|
||||||
paginate_sqlalchemy
|
paginate_sqlalchemy
|
||||||
passlib
|
passlib
|
||||||
Pillow
|
Pillow
|
||||||
|
pyramid
|
||||||
pyramid_beaker>=0.6
|
pyramid_beaker>=0.6
|
||||||
pyramid_deform
|
pyramid_deform
|
||||||
pyramid_exclog
|
pyramid_exclog
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2023 Lance Edgar
|
# Copyright © 2010-2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -133,8 +133,14 @@ def make_pyramid_config(settings, configure_csrf=True):
|
||||||
config.registry['rattail_config'] = rattail_config
|
config.registry['rattail_config'] = rattail_config
|
||||||
|
|
||||||
# configure user authorization / authentication
|
# configure user authorization / authentication
|
||||||
config.set_authorization_policy(TailboneAuthorizationPolicy())
|
# TODO: security policy should become the default, for pyramid 2.x
|
||||||
config.set_authentication_policy(SessionAuthenticationPolicy())
|
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
|
# maybe require CSRF token protection
|
||||||
if configure_csrf:
|
if configure_csrf:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2023 Lance Edgar
|
# Copyright © 2010-2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -27,7 +27,6 @@ Authentication & Authorization
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from rattail import enum
|
|
||||||
from rattail.util import prettify, NOTSET
|
from rattail.util import prettify, NOTSET
|
||||||
|
|
||||||
from zope.interface import implementer
|
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
|
Perform the steps necessary to login the given user. Note that this
|
||||||
returns a ``headers`` dict which you should pass to the redirect.
|
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)
|
headers = remember(request, user.uuid)
|
||||||
if timeout is NOTSET:
|
if timeout is NOTSET:
|
||||||
timeout = session_timeout_for_user(user)
|
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
|
Perform the logout action for the given request. Note that this returns a
|
||||||
``headers`` dict which you should pass to the redirect.
|
``headers`` dict which you should pass to the redirect.
|
||||||
"""
|
"""
|
||||||
|
app = request.rattail_config.get_app()
|
||||||
user = request.user
|
user = request.user
|
||||||
if user:
|
if user:
|
||||||
user.record_event(enum.USER_EVENT_LOGOUT)
|
user.record_event(app.enum.USER_EVENT_LOGOUT)
|
||||||
request.session.delete()
|
request.session.delete()
|
||||||
request.session.invalidate()
|
request.session.invalidate()
|
||||||
headers = forget(request)
|
headers = forget(request)
|
||||||
|
@ -117,7 +118,7 @@ class TailboneAuthenticationPolicy(SessionAuthenticationPolicy):
|
||||||
return user.uuid
|
return user.uuid
|
||||||
|
|
||||||
# otherwise do normal session-based logic
|
# otherwise do normal session-based logic
|
||||||
return super(TailboneAuthenticationPolicy, self).unauthenticated_userid(request)
|
return super().unauthenticated_userid(request)
|
||||||
|
|
||||||
|
|
||||||
@implementer(IAuthorizationPolicy)
|
@implementer(IAuthorizationPolicy)
|
||||||
|
@ -150,6 +151,72 @@ class TailboneAuthorizationPolicy(object):
|
||||||
raise NotImplementedError
|
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):
|
def add_permission_group(config, key, label=None, overwrite=True):
|
||||||
"""
|
"""
|
||||||
Add a permission group to the app configuration.
|
Add a permission group to the app configuration.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
#
|
#
|
||||||
# Rattail -- Retail Software Framework
|
# Rattail -- Retail Software Framework
|
||||||
# Copyright © 2010-2023 Lance Edgar
|
# Copyright © 2010-2024 Lance Edgar
|
||||||
#
|
#
|
||||||
# This file is part of Rattail.
|
# This file is part of Rattail.
|
||||||
#
|
#
|
||||||
|
@ -50,8 +50,14 @@ def make_pyramid_config(settings):
|
||||||
pyramid_config = Configurator(settings=settings, root_factory=app.Root)
|
pyramid_config = Configurator(settings=settings, root_factory=app.Root)
|
||||||
|
|
||||||
# configure user authorization / authentication
|
# configure user authorization / authentication
|
||||||
pyramid_config.set_authentication_policy(TailboneAuthenticationPolicy())
|
# TODO: security policy should become the default, for pyramid 2.x
|
||||||
pyramid_config.set_authorization_policy(TailboneAuthorizationPolicy())
|
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
|
# always require CSRF token protection
|
||||||
pyramid_config.set_default_csrf_options(require_csrf=True,
|
pyramid_config.set_default_csrf_options(require_csrf=True,
|
||||||
|
|
Loading…
Reference in a new issue