feat: add permission checks for menus, view routes
This commit is contained in:
parent
675b51cac2
commit
e3942ce65e
|
@ -272,9 +272,8 @@ class MenuHandler(GenericHandler):
|
||||||
current user.
|
current user.
|
||||||
"""
|
"""
|
||||||
perm = item.get('perm')
|
perm = item.get('perm')
|
||||||
# TODO
|
if perm:
|
||||||
# if perm:
|
return request.has_perm(perm)
|
||||||
# return request.has_perm(perm)
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _mark_allowed(self, request, menus):
|
def _mark_allowed(self, request, menus):
|
||||||
|
|
|
@ -139,6 +139,16 @@ def new_request_set_user(
|
||||||
pyramid_config.add_subscriber('wuttaweb.subscribers.new_request_set_user',
|
pyramid_config.add_subscriber('wuttaweb.subscribers.new_request_set_user',
|
||||||
'pyramid.events.NewRequest')
|
'pyramid.events.NewRequest')
|
||||||
|
|
||||||
|
You may wish to "supplement" this hook by registering your own
|
||||||
|
custom hook and then invoking this one as needed. You can then
|
||||||
|
pass certain params to override only parts of the logic:
|
||||||
|
|
||||||
|
:param user_getter: Optional getter function to retrieve the user
|
||||||
|
from database, instead of :func:`default_user_getter()`.
|
||||||
|
|
||||||
|
:param db_session: Optional :term:`db session` to use,
|
||||||
|
instead of :class:`wuttaweb.db.Session`.
|
||||||
|
|
||||||
This will add to the request object:
|
This will add to the request object:
|
||||||
|
|
||||||
.. attribute:: request.user
|
.. attribute:: request.user
|
||||||
|
@ -158,19 +168,36 @@ def new_request_set_user(
|
||||||
privileges. This is only possible if :attr:`request.is_admin`
|
privileges. This is only possible if :attr:`request.is_admin`
|
||||||
is also true.
|
is also true.
|
||||||
|
|
||||||
You may wish to "supplement" this hook by registering your own
|
.. attribute:: request.user_permissions
|
||||||
custom hook and then invoking this one as needed. You can then
|
|
||||||
pass certain params to override only parts of the logic:
|
|
||||||
|
|
||||||
:param user_getter: Optional getter function to retrieve the user
|
The ``set`` of permission names which are granted to the
|
||||||
from database, instead of :func:`default_user_getter()`.
|
current user.
|
||||||
|
|
||||||
|
This set is obtained by calling
|
||||||
|
:meth:`~wuttjamaican:wuttjamaican.auth.AuthHandler.get_permissions()`.
|
||||||
|
|
||||||
|
.. function:: request.has_perm(name)
|
||||||
|
|
||||||
|
Shortcut to check if current user has the given permission::
|
||||||
|
|
||||||
|
if not request.has_perm('users.edit'):
|
||||||
|
raise self.forbidden()
|
||||||
|
|
||||||
|
.. function:: request.has_any_perm(*names)
|
||||||
|
|
||||||
|
Shortcut to check if current user has any of the given
|
||||||
|
permissions::
|
||||||
|
|
||||||
|
if request.has_any_perm('users.list', 'users.view'):
|
||||||
|
return "can either list or view"
|
||||||
|
else:
|
||||||
|
raise self.forbidden()
|
||||||
|
|
||||||
:param db_session: Optional :term:`db session` to use,
|
|
||||||
instead of :class:`wuttaweb.db.Session`.
|
|
||||||
"""
|
"""
|
||||||
request = event.request
|
request = event.request
|
||||||
config = request.registry.settings['wutta_config']
|
config = request.registry.settings['wutta_config']
|
||||||
app = config.get_app()
|
app = config.get_app()
|
||||||
|
auth = app.get_auth_handler()
|
||||||
|
|
||||||
# request.user
|
# request.user
|
||||||
if db_session:
|
if db_session:
|
||||||
|
@ -179,7 +206,6 @@ def new_request_set_user(
|
||||||
|
|
||||||
# request.is_admin
|
# request.is_admin
|
||||||
def is_admin(request):
|
def is_admin(request):
|
||||||
auth = app.get_auth_handler()
|
|
||||||
return auth.user_is_admin(request.user)
|
return auth.user_is_admin(request.user)
|
||||||
request.set_property(is_admin, reify=True)
|
request.set_property(is_admin, reify=True)
|
||||||
|
|
||||||
|
@ -191,6 +217,29 @@ def new_request_set_user(
|
||||||
return False
|
return False
|
||||||
request.set_property(is_root, reify=True)
|
request.set_property(is_root, reify=True)
|
||||||
|
|
||||||
|
# request.user_permissions
|
||||||
|
def user_permissions(request):
|
||||||
|
session = db_session or Session()
|
||||||
|
return auth.get_permissions(session, request.user)
|
||||||
|
request.set_property(user_permissions, reify=True)
|
||||||
|
|
||||||
|
# request.has_perm()
|
||||||
|
def has_perm(name):
|
||||||
|
if request.is_root:
|
||||||
|
return True
|
||||||
|
if name in request.user_permissions:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
request.has_perm = has_perm
|
||||||
|
|
||||||
|
# request.has_any_perm()
|
||||||
|
def has_any_perm(*names):
|
||||||
|
for name in names:
|
||||||
|
if request.has_perm(name):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
request.has_any_perm = has_any_perm
|
||||||
|
|
||||||
|
|
||||||
def before_render(event):
|
def before_render(event):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -222,7 +222,7 @@
|
||||||
% else:
|
% else:
|
||||||
<h1 class="title">${index_title}</h1>
|
<h1 class="title">${index_title}</h1>
|
||||||
% endif
|
% endif
|
||||||
% if master and master.creatable and not master.creating:
|
% if master and master.creatable and not master.creating and master.has_perm('create'):
|
||||||
<wutta-button once type="is-primary"
|
<wutta-button once type="is-primary"
|
||||||
tag="a" href="${url(f'{route_prefix}.create')}"
|
tag="a" href="${url(f'{route_prefix}.create')}"
|
||||||
icon-left="plus"
|
icon-left="plus"
|
||||||
|
@ -235,8 +235,7 @@
|
||||||
|
|
||||||
<div class="level-right">
|
<div class="level-right">
|
||||||
|
|
||||||
## TODO
|
% if master and master.configurable and not master.configuring and master.has_perm('configure'):
|
||||||
% if master and master.configurable and not master.configuring:
|
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
<wutta-button once type="is-primary"
|
<wutta-button once type="is-primary"
|
||||||
tag="a" href="${url(f'{route_prefix}.configure')}"
|
tag="a" href="${url(f'{route_prefix}.configure')}"
|
||||||
|
|
|
@ -102,10 +102,50 @@ class CommonView(View):
|
||||||
# assign admin role
|
# assign admin role
|
||||||
admin = auth.get_role_administrator(session)
|
admin = auth.get_role_administrator(session)
|
||||||
user.roles.append(admin)
|
user.roles.append(admin)
|
||||||
|
admin.notes = ("users in this role may \"become root\".\n\n"
|
||||||
|
"it's recommended not to grant other perms to this role.")
|
||||||
|
|
||||||
# ensure all built-in roles exist
|
# initialize built-in roles
|
||||||
auth.get_role_authenticated(session)
|
authed = auth.get_role_authenticated(session)
|
||||||
auth.get_role_anonymous(session)
|
authed.notes = ("this role represents any user who *is* logged in.\n\n"
|
||||||
|
"you may grant any perms you like to it.")
|
||||||
|
anon = auth.get_role_anonymous(session)
|
||||||
|
anon.notes = ("this role represents any user who is *not* logged in.\n\n"
|
||||||
|
"you may grant any perms you like to it.")
|
||||||
|
|
||||||
|
# also make "Site Admin" role
|
||||||
|
site_admin_perms = [
|
||||||
|
'appinfo.list',
|
||||||
|
'appinfo.configure',
|
||||||
|
'people.list',
|
||||||
|
'people.create',
|
||||||
|
'people.view',
|
||||||
|
'people.edit',
|
||||||
|
'people.delete',
|
||||||
|
'roles.list',
|
||||||
|
'roles.create',
|
||||||
|
'roles.view',
|
||||||
|
'roles.edit',
|
||||||
|
'roles.edit_builtin',
|
||||||
|
'roles.delete',
|
||||||
|
'settings.list',
|
||||||
|
'settings.create',
|
||||||
|
'settings.view',
|
||||||
|
'settings.edit',
|
||||||
|
'settings.delete',
|
||||||
|
'users.list',
|
||||||
|
'users.create',
|
||||||
|
'users.view',
|
||||||
|
'users.edit',
|
||||||
|
'users.delete',
|
||||||
|
]
|
||||||
|
admin2 = model.Role(name="Site Admin")
|
||||||
|
admin2.notes = ("this is the \"daily driver\" admin role.\n\n"
|
||||||
|
"you may grant any perms you like to it.")
|
||||||
|
session.add(admin2)
|
||||||
|
user.roles.append(admin2)
|
||||||
|
for perm in site_admin_perms:
|
||||||
|
auth.grant_permission(admin2, perm)
|
||||||
|
|
||||||
# maybe make person
|
# maybe make person
|
||||||
if data['first_name'] or data['last_name']:
|
if data['first_name'] or data['last_name']:
|
||||||
|
|
|
@ -198,6 +198,8 @@ class MasterView(View):
|
||||||
i.e. it should have an :meth:`edit()` view. Default value is
|
i.e. it should have an :meth:`edit()` view. Default value is
|
||||||
``True``.
|
``True``.
|
||||||
|
|
||||||
|
See also :meth:`is_editable()`.
|
||||||
|
|
||||||
.. attribute:: deletable
|
.. attribute:: deletable
|
||||||
|
|
||||||
Boolean indicating whether the view model supports "deleting" -
|
Boolean indicating whether the view model supports "deleting" -
|
||||||
|
@ -802,6 +804,43 @@ class MasterView(View):
|
||||||
# support methods
|
# support methods
|
||||||
##############################
|
##############################
|
||||||
|
|
||||||
|
def has_perm(self, name):
|
||||||
|
"""
|
||||||
|
Shortcut to check if current user has the given permission.
|
||||||
|
|
||||||
|
This will automatically add the :attr:`permission_prefix` to
|
||||||
|
``name`` before passing it on to
|
||||||
|
:func:`~wuttaweb.subscribers.request.has_perm()`.
|
||||||
|
|
||||||
|
For instance within the
|
||||||
|
:class:`~wuttaweb.views.users.UserView` these give the same
|
||||||
|
result::
|
||||||
|
|
||||||
|
self.request.has_perm('users.edit')
|
||||||
|
|
||||||
|
self.has_perm('edit')
|
||||||
|
|
||||||
|
So this shortcut only applies to permissions defined for the
|
||||||
|
current master view. The first example above must still be
|
||||||
|
used to check for "foreign" permissions (i.e. any needing a
|
||||||
|
different prefix).
|
||||||
|
"""
|
||||||
|
permission_prefix = self.get_permission_prefix()
|
||||||
|
return self.request.has_perm(f'{permission_prefix}.{name}')
|
||||||
|
|
||||||
|
def has_any_perm(self, *names):
|
||||||
|
"""
|
||||||
|
Shortcut to check if current user has any of the given
|
||||||
|
permissions.
|
||||||
|
|
||||||
|
This calls :meth:`has_perm()` until one returns ``True``. If
|
||||||
|
none do, returns ``False``.
|
||||||
|
"""
|
||||||
|
for name in names:
|
||||||
|
if self.has_perm(name):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def render_to_response(self, template, context):
|
def render_to_response(self, template, context):
|
||||||
"""
|
"""
|
||||||
Locate and render an appropriate template, with the given
|
Locate and render an appropriate template, with the given
|
||||||
|
@ -937,15 +976,15 @@ class MasterView(View):
|
||||||
|
|
||||||
# TODO: should split this off into index_get_grid_actions() ?
|
# TODO: should split this off into index_get_grid_actions() ?
|
||||||
|
|
||||||
if self.viewable:
|
if self.viewable and self.has_perm('view'):
|
||||||
actions.append(self.make_grid_action('view', icon='eye',
|
actions.append(self.make_grid_action('view', icon='eye',
|
||||||
url=self.get_action_url_view))
|
url=self.get_action_url_view))
|
||||||
|
|
||||||
if self.editable:
|
if self.editable and self.has_perm('edit'):
|
||||||
actions.append(self.make_grid_action('edit', icon='edit',
|
actions.append(self.make_grid_action('edit', icon='edit',
|
||||||
url=self.get_action_url_edit))
|
url=self.get_action_url_edit))
|
||||||
|
|
||||||
if self.deletable:
|
if self.deletable and self.has_perm('delete'):
|
||||||
actions.append(self.make_grid_action('delete', icon='trash',
|
actions.append(self.make_grid_action('delete', icon='trash',
|
||||||
url=self.get_action_url_delete,
|
url=self.get_action_url_delete,
|
||||||
link_class='has-text-danger'))
|
link_class='has-text-danger'))
|
||||||
|
@ -1137,13 +1176,18 @@ class MasterView(View):
|
||||||
|
|
||||||
def get_action_url_edit(self, obj, i):
|
def get_action_url_edit(self, obj, i):
|
||||||
"""
|
"""
|
||||||
Returns the "edit" grid action URL for the given object.
|
Returns the "edit" grid action URL for the given object, if
|
||||||
|
applicable.
|
||||||
|
|
||||||
Most typically this is like ``/widgets/XXX/edit`` where
|
Most typically this is like ``/widgets/XXX/edit`` where
|
||||||
``XXX`` represents the object's key/ID.
|
``XXX`` represents the object's key/ID.
|
||||||
|
|
||||||
Calls :meth:`get_action_url()` under the hood.
|
This first calls :meth:`is_editable()` and if that is false,
|
||||||
|
this method will return ``None``.
|
||||||
|
|
||||||
|
Calls :meth:`get_action_url()` to generate the true URL.
|
||||||
"""
|
"""
|
||||||
|
if self.is_editable(obj):
|
||||||
return self.get_action_url('edit', obj)
|
return self.get_action_url('edit', obj)
|
||||||
|
|
||||||
def get_action_url_delete(self, obj, i):
|
def get_action_url_delete(self, obj, i):
|
||||||
|
@ -1162,6 +1206,19 @@ class MasterView(View):
|
||||||
if self.is_deletable(obj):
|
if self.is_deletable(obj):
|
||||||
return self.get_action_url('delete', obj)
|
return self.get_action_url('delete', obj)
|
||||||
|
|
||||||
|
def is_editable(self, obj):
|
||||||
|
"""
|
||||||
|
Returns a boolean indicating whether "edit" should be allowed
|
||||||
|
for the given model instance (and for current user).
|
||||||
|
|
||||||
|
By default this always return ``True``; subclass can override
|
||||||
|
if needed.
|
||||||
|
|
||||||
|
Note that the use of this method implies :attr:`editable` is
|
||||||
|
true, so the method does not need to check that flag.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
def is_deletable(self, obj):
|
def is_deletable(self, obj):
|
||||||
"""
|
"""
|
||||||
Returns a boolean indicating whether "delete" should be
|
Returns a boolean indicating whether "delete" should be
|
||||||
|
@ -1634,7 +1691,8 @@ class MasterView(View):
|
||||||
if cls.listable:
|
if cls.listable:
|
||||||
config.add_route(route_prefix, f'{url_prefix}/')
|
config.add_route(route_prefix, f'{url_prefix}/')
|
||||||
config.add_view(cls, attr='index',
|
config.add_view(cls, attr='index',
|
||||||
route_name=route_prefix)
|
route_name=route_prefix,
|
||||||
|
permission=f'{permission_prefix}.list')
|
||||||
config.add_wutta_permission(permission_prefix,
|
config.add_wutta_permission(permission_prefix,
|
||||||
f'{permission_prefix}.list',
|
f'{permission_prefix}.list',
|
||||||
f"Browse / search {model_title_plural}")
|
f"Browse / search {model_title_plural}")
|
||||||
|
@ -1644,7 +1702,8 @@ class MasterView(View):
|
||||||
config.add_route(f'{route_prefix}.create',
|
config.add_route(f'{route_prefix}.create',
|
||||||
f'{url_prefix}/new')
|
f'{url_prefix}/new')
|
||||||
config.add_view(cls, attr='create',
|
config.add_view(cls, attr='create',
|
||||||
route_name=f'{route_prefix}.create')
|
route_name=f'{route_prefix}.create',
|
||||||
|
permission=f'{permission_prefix}.create')
|
||||||
config.add_wutta_permission(permission_prefix,
|
config.add_wutta_permission(permission_prefix,
|
||||||
f'{permission_prefix}.create',
|
f'{permission_prefix}.create',
|
||||||
f"Create new {model_title}")
|
f"Create new {model_title}")
|
||||||
|
@ -1654,7 +1713,8 @@ class MasterView(View):
|
||||||
instance_url_prefix = cls.get_instance_url_prefix()
|
instance_url_prefix = cls.get_instance_url_prefix()
|
||||||
config.add_route(f'{route_prefix}.view', instance_url_prefix)
|
config.add_route(f'{route_prefix}.view', instance_url_prefix)
|
||||||
config.add_view(cls, attr='view',
|
config.add_view(cls, attr='view',
|
||||||
route_name=f'{route_prefix}.view')
|
route_name=f'{route_prefix}.view',
|
||||||
|
permission=f'{permission_prefix}.view')
|
||||||
config.add_wutta_permission(permission_prefix,
|
config.add_wutta_permission(permission_prefix,
|
||||||
f'{permission_prefix}.view',
|
f'{permission_prefix}.view',
|
||||||
f"View {model_title}")
|
f"View {model_title}")
|
||||||
|
@ -1665,7 +1725,8 @@ class MasterView(View):
|
||||||
config.add_route(f'{route_prefix}.edit',
|
config.add_route(f'{route_prefix}.edit',
|
||||||
f'{instance_url_prefix}/edit')
|
f'{instance_url_prefix}/edit')
|
||||||
config.add_view(cls, attr='edit',
|
config.add_view(cls, attr='edit',
|
||||||
route_name=f'{route_prefix}.edit')
|
route_name=f'{route_prefix}.edit',
|
||||||
|
permission=f'{permission_prefix}.edit')
|
||||||
config.add_wutta_permission(permission_prefix,
|
config.add_wutta_permission(permission_prefix,
|
||||||
f'{permission_prefix}.edit',
|
f'{permission_prefix}.edit',
|
||||||
f"Edit {model_title}")
|
f"Edit {model_title}")
|
||||||
|
@ -1676,7 +1737,8 @@ class MasterView(View):
|
||||||
config.add_route(f'{route_prefix}.delete',
|
config.add_route(f'{route_prefix}.delete',
|
||||||
f'{instance_url_prefix}/delete')
|
f'{instance_url_prefix}/delete')
|
||||||
config.add_view(cls, attr='delete',
|
config.add_view(cls, attr='delete',
|
||||||
route_name=f'{route_prefix}.delete')
|
route_name=f'{route_prefix}.delete',
|
||||||
|
permission=f'{permission_prefix}.delete')
|
||||||
config.add_wutta_permission(permission_prefix,
|
config.add_wutta_permission(permission_prefix,
|
||||||
f'{permission_prefix}.delete',
|
f'{permission_prefix}.delete',
|
||||||
f"Delete {model_title}")
|
f"Delete {model_title}")
|
||||||
|
@ -1686,7 +1748,8 @@ class MasterView(View):
|
||||||
config.add_route(f'{route_prefix}.configure',
|
config.add_route(f'{route_prefix}.configure',
|
||||||
f'{url_prefix}/configure')
|
f'{url_prefix}/configure')
|
||||||
config.add_view(cls, attr='configure',
|
config.add_view(cls, attr='configure',
|
||||||
route_name=f'{route_prefix}.configure')
|
route_name=f'{route_prefix}.configure',
|
||||||
|
permission=f'{permission_prefix}.configure')
|
||||||
config.add_wutta_permission(permission_prefix,
|
config.add_wutta_permission(permission_prefix,
|
||||||
f'{permission_prefix}.configure',
|
f'{permission_prefix}.configure',
|
||||||
f"Configure {model_title_plural}")
|
f"Configure {model_title_plural}")
|
||||||
|
|
|
@ -69,6 +69,22 @@ class RoleView(MasterView):
|
||||||
# notes
|
# notes
|
||||||
g.set_renderer('notes', self.grid_render_notes)
|
g.set_renderer('notes', self.grid_render_notes)
|
||||||
|
|
||||||
|
def is_editable(self, role):
|
||||||
|
""" """
|
||||||
|
session = self.app.get_session(role)
|
||||||
|
auth = self.app.get_auth_handler()
|
||||||
|
|
||||||
|
# only "root" can edit admin role
|
||||||
|
if role is auth.get_role_administrator(session):
|
||||||
|
return self.request.is_root
|
||||||
|
|
||||||
|
# other built-in roles require special perm
|
||||||
|
if role in (auth.get_role_authenticated(session),
|
||||||
|
auth.get_role_anonymous(session)):
|
||||||
|
return self.has_perm('edit_builtin')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def is_deletable(self, role):
|
def is_deletable(self, role):
|
||||||
""" """
|
""" """
|
||||||
session = self.app.get_session(role)
|
session = self.app.get_session(role)
|
||||||
|
@ -228,6 +244,21 @@ class RoleView(MasterView):
|
||||||
else:
|
else:
|
||||||
auth.revoke_permission(role, pkey)
|
auth.revoke_permission(role, pkey)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def defaults(cls, config):
|
||||||
|
cls._defaults(config)
|
||||||
|
cls._role_defaults(config)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _role_defaults(cls, config):
|
||||||
|
permission_prefix = cls.get_permission_prefix()
|
||||||
|
model_title_plural = cls.get_model_title_plural()
|
||||||
|
|
||||||
|
# perm to edit built-in roles
|
||||||
|
config.add_wutta_permission(permission_prefix,
|
||||||
|
f'{permission_prefix}.edit_builtin',
|
||||||
|
f"Edit the Built-in {model_title_plural}")
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
@ -3,20 +3,15 @@
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
from wuttjamaican.conf import WuttaConfig
|
|
||||||
|
|
||||||
from pyramid import testing
|
|
||||||
|
|
||||||
from wuttaweb import menus as mod
|
from wuttaweb import menus as mod
|
||||||
|
from tests.util import WebTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestMenuHandler(TestCase):
|
class TestMenuHandler(WebTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.config = WuttaConfig()
|
self.setup_web()
|
||||||
self.app = self.config.get_app()
|
|
||||||
self.handler = mod.MenuHandler(self.config)
|
self.handler = mod.MenuHandler(self.config)
|
||||||
self.request = testing.DummyRequest()
|
|
||||||
|
|
||||||
def test_make_admin_menu(self):
|
def test_make_admin_menu(self):
|
||||||
menus = self.handler.make_admin_menu(self.request)
|
menus = self.handler.make_admin_menu(self.request)
|
||||||
|
@ -27,7 +22,27 @@ class TestMenuHandler(TestCase):
|
||||||
self.assertIsInstance(menus, list)
|
self.assertIsInstance(menus, list)
|
||||||
|
|
||||||
def test_is_allowed(self):
|
def test_is_allowed(self):
|
||||||
# TODO: this should test auth/perm handling
|
model = self.app.model
|
||||||
|
auth = self.app.get_auth_handler()
|
||||||
|
|
||||||
|
# user with perms
|
||||||
|
barney = model.User(username='barney')
|
||||||
|
self.session.add(barney)
|
||||||
|
blokes = model.Role(name="Blokes")
|
||||||
|
self.session.add(blokes)
|
||||||
|
barney.roles.append(blokes)
|
||||||
|
auth.grant_permission(blokes, 'appinfo.list')
|
||||||
|
self.request.user = barney
|
||||||
|
|
||||||
|
# perm not granted to user
|
||||||
|
item = {'perm': 'appinfo.configure'}
|
||||||
|
self.assertFalse(self.handler._is_allowed(self.request, item))
|
||||||
|
|
||||||
|
# perm *is* granted to user
|
||||||
|
item = {'perm': 'appinfo.list'}
|
||||||
|
self.assertTrue(self.handler._is_allowed(self.request, item))
|
||||||
|
|
||||||
|
# perm not required
|
||||||
item = {}
|
item = {}
|
||||||
self.assertTrue(self.handler._is_allowed(self.request, item))
|
self.assertTrue(self.handler._is_allowed(self.request, item))
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from wuttjamaican.conf import WuttaConfig
|
from wuttjamaican.conf import WuttaConfig
|
||||||
|
|
||||||
|
@ -210,6 +210,137 @@ class TestNewRequestSetUser(TestCase):
|
||||||
self.assertTrue(self.request.is_admin)
|
self.assertTrue(self.request.is_admin)
|
||||||
self.assertTrue(self.request.is_root)
|
self.assertTrue(self.request.is_root)
|
||||||
|
|
||||||
|
def test_user_permissions(self):
|
||||||
|
model = self.app.model
|
||||||
|
auth = self.app.get_auth_handler()
|
||||||
|
event = MagicMock(request=self.request)
|
||||||
|
|
||||||
|
# anonymous user
|
||||||
|
self.assertFalse(hasattr(self.request, 'user_permissions'))
|
||||||
|
subscribers.new_request_set_user(event, db_session=self.session)
|
||||||
|
self.assertEqual(self.request.user_permissions, set())
|
||||||
|
|
||||||
|
# reset
|
||||||
|
del self.request.user_permissions
|
||||||
|
|
||||||
|
# add user to role with perms
|
||||||
|
blokes = model.Role(name="Blokes")
|
||||||
|
self.session.add(blokes)
|
||||||
|
auth.grant_permission(blokes, 'appinfo.list')
|
||||||
|
self.user.roles.append(blokes)
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
# authenticated user, with perms
|
||||||
|
self.request.user = self.user
|
||||||
|
subscribers.new_request_set_user(event, db_session=self.session)
|
||||||
|
self.assertEqual(self.request.user_permissions, {'appinfo.list'})
|
||||||
|
|
||||||
|
def test_has_perm(self):
|
||||||
|
model = self.app.model
|
||||||
|
auth = self.app.get_auth_handler()
|
||||||
|
event = MagicMock(request=self.request)
|
||||||
|
|
||||||
|
# anonymous user
|
||||||
|
self.assertFalse(hasattr(self.request, 'has_perm'))
|
||||||
|
subscribers.new_request_set_user(event, db_session=self.session)
|
||||||
|
self.assertFalse(self.request.has_perm('appinfo.list'))
|
||||||
|
|
||||||
|
# reset
|
||||||
|
del self.request.user_permissions
|
||||||
|
del self.request.has_perm
|
||||||
|
del self.request.has_any_perm
|
||||||
|
|
||||||
|
# add user to role with perms
|
||||||
|
blokes = model.Role(name="Blokes")
|
||||||
|
self.session.add(blokes)
|
||||||
|
auth.grant_permission(blokes, 'appinfo.list')
|
||||||
|
self.user.roles.append(blokes)
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
# authenticated user, with perms
|
||||||
|
self.request.user = self.user
|
||||||
|
subscribers.new_request_set_user(event, db_session=self.session)
|
||||||
|
self.assertTrue(self.request.has_perm('appinfo.list'))
|
||||||
|
|
||||||
|
# reset
|
||||||
|
del self.request.user_permissions
|
||||||
|
del self.request.has_perm
|
||||||
|
del self.request.has_any_perm
|
||||||
|
|
||||||
|
# drop user from role, no more perms
|
||||||
|
self.user.roles.remove(blokes)
|
||||||
|
self.session.commit()
|
||||||
|
subscribers.new_request_set_user(event, db_session=self.session)
|
||||||
|
self.assertFalse(self.request.has_perm('appinfo.list'))
|
||||||
|
|
||||||
|
# reset
|
||||||
|
del self.request.user_permissions
|
||||||
|
del self.request.has_perm
|
||||||
|
del self.request.has_any_perm
|
||||||
|
del self.request.is_admin
|
||||||
|
del self.request.is_root
|
||||||
|
|
||||||
|
# root user always has perms
|
||||||
|
admin = auth.get_role_administrator(self.session)
|
||||||
|
self.user.roles.append(admin)
|
||||||
|
self.session.commit()
|
||||||
|
self.request.session['is_root'] = True
|
||||||
|
subscribers.new_request_set_user(event, db_session=self.session)
|
||||||
|
self.assertTrue(self.request.has_perm('appinfo.list'))
|
||||||
|
|
||||||
|
def test_has_any_perm(self):
|
||||||
|
model = self.app.model
|
||||||
|
auth = self.app.get_auth_handler()
|
||||||
|
event = MagicMock(request=self.request)
|
||||||
|
|
||||||
|
# anonymous user
|
||||||
|
self.assertFalse(hasattr(self.request, 'has_any_perm'))
|
||||||
|
subscribers.new_request_set_user(event, db_session=self.session)
|
||||||
|
self.assertFalse(self.request.has_any_perm('appinfo.list'))
|
||||||
|
|
||||||
|
# reset
|
||||||
|
del self.request.user_permissions
|
||||||
|
del self.request.has_perm
|
||||||
|
del self.request.has_any_perm
|
||||||
|
|
||||||
|
# add user to role with perms
|
||||||
|
blokes = model.Role(name="Blokes")
|
||||||
|
self.session.add(blokes)
|
||||||
|
auth.grant_permission(blokes, 'appinfo.list')
|
||||||
|
self.user.roles.append(blokes)
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
# authenticated user, with perms
|
||||||
|
self.request.user = self.user
|
||||||
|
subscribers.new_request_set_user(event, db_session=self.session)
|
||||||
|
self.assertTrue(self.request.has_any_perm('appinfo.list', 'appinfo.view'))
|
||||||
|
|
||||||
|
# reset
|
||||||
|
del self.request.user_permissions
|
||||||
|
del self.request.has_perm
|
||||||
|
del self.request.has_any_perm
|
||||||
|
|
||||||
|
# drop user from role, no more perms
|
||||||
|
self.user.roles.remove(blokes)
|
||||||
|
self.session.commit()
|
||||||
|
subscribers.new_request_set_user(event, db_session=self.session)
|
||||||
|
self.assertFalse(self.request.has_any_perm('appinfo.list'))
|
||||||
|
|
||||||
|
# reset
|
||||||
|
del self.request.user_permissions
|
||||||
|
del self.request.has_perm
|
||||||
|
del self.request.has_any_perm
|
||||||
|
del self.request.is_admin
|
||||||
|
del self.request.is_root
|
||||||
|
|
||||||
|
# root user always has perms
|
||||||
|
admin = auth.get_role_administrator(self.session)
|
||||||
|
self.user.roles.append(admin)
|
||||||
|
self.session.commit()
|
||||||
|
self.request.session['is_root'] = True
|
||||||
|
subscribers.new_request_set_user(event, db_session=self.session)
|
||||||
|
self.assertTrue(self.request.has_any_perm('appinfo.list'))
|
||||||
|
|
||||||
|
|
||||||
class TestBeforeRender(TestCase):
|
class TestBeforeRender(TestCase):
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ class WebTestCase(DataTestCase):
|
||||||
|
|
||||||
def setup_web(self):
|
def setup_web(self):
|
||||||
self.setup_db()
|
self.setup_db()
|
||||||
self.request = testing.DummyRequest()
|
self.request = self.make_request()
|
||||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||||
'wutta_config': self.config,
|
'wutta_config': self.config,
|
||||||
'mako.directories': ['wuttaweb:templates'],
|
'mako.directories': ['wuttaweb:templates'],
|
||||||
|
@ -78,6 +78,9 @@ class WebTestCase(DataTestCase):
|
||||||
testing.tearDown()
|
testing.tearDown()
|
||||||
self.teardown_db()
|
self.teardown_db()
|
||||||
|
|
||||||
|
def make_request(self):
|
||||||
|
return testing.DummyRequest()
|
||||||
|
|
||||||
|
|
||||||
class NullMenuHandler(MenuHandler):
|
class NullMenuHandler(MenuHandler):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -331,6 +331,64 @@ class TestMasterView(WebTestCase):
|
||||||
# support methods
|
# support methods
|
||||||
##############################
|
##############################
|
||||||
|
|
||||||
|
def test_has_perm(self):
|
||||||
|
model = self.app.model
|
||||||
|
auth = self.app.get_auth_handler()
|
||||||
|
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_name='Setting'):
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
# anonymous user
|
||||||
|
self.assertFalse(view.has_perm('list'))
|
||||||
|
self.assertFalse(self.request.has_perm('list'))
|
||||||
|
|
||||||
|
# reset
|
||||||
|
del self.request.user_permissions
|
||||||
|
|
||||||
|
# make user with perms
|
||||||
|
barney = model.User(username='barney')
|
||||||
|
self.session.add(barney)
|
||||||
|
blokes = model.Role(name="Blokes")
|
||||||
|
self.session.add(blokes)
|
||||||
|
barney.roles.append(blokes)
|
||||||
|
auth.grant_permission(blokes, 'settings.list')
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
# this user has perms
|
||||||
|
self.request.user = barney
|
||||||
|
self.assertTrue(view.has_perm('list'))
|
||||||
|
self.assertTrue(self.request.has_perm('settings.list'))
|
||||||
|
|
||||||
|
def test_has_any_perm(self):
|
||||||
|
model = self.app.model
|
||||||
|
auth = self.app.get_auth_handler()
|
||||||
|
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_name='Setting'):
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
# anonymous user
|
||||||
|
self.assertFalse(view.has_any_perm('list', 'view'))
|
||||||
|
self.assertFalse(self.request.has_any_perm('settings.list', 'settings.view'))
|
||||||
|
|
||||||
|
# reset
|
||||||
|
del self.request.user_permissions
|
||||||
|
|
||||||
|
# make user with perms
|
||||||
|
barney = model.User(username='barney')
|
||||||
|
self.session.add(barney)
|
||||||
|
blokes = model.Role(name="Blokes")
|
||||||
|
self.session.add(blokes)
|
||||||
|
barney.roles.append(blokes)
|
||||||
|
auth.grant_permission(blokes, 'settings.view')
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
# this user has perms
|
||||||
|
self.request.user = barney
|
||||||
|
self.assertTrue(view.has_any_perm('list', 'view'))
|
||||||
|
self.assertTrue(self.request.has_any_perm('settings.list', 'settings.view'))
|
||||||
|
|
||||||
def test_render_to_response(self):
|
def test_render_to_response(self):
|
||||||
self.pyramid_config.include('wuttaweb.views.common')
|
self.pyramid_config.include('wuttaweb.views.common')
|
||||||
self.pyramid_config.include('wuttaweb.views.auth')
|
self.pyramid_config.include('wuttaweb.views.auth')
|
||||||
|
@ -387,6 +445,28 @@ class TestMasterView(WebTestCase):
|
||||||
grid = view.make_model_grid(session=self.session)
|
grid = view.make_model_grid(session=self.session)
|
||||||
self.assertIs(grid.model_class, model.Setting)
|
self.assertIs(grid.model_class, model.Setting)
|
||||||
|
|
||||||
|
# no actions by default
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_class=model.Setting):
|
||||||
|
grid = view.make_model_grid(session=self.session)
|
||||||
|
self.assertEqual(grid.actions, [])
|
||||||
|
|
||||||
|
# now let's test some more actions logic
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_class=model.Setting,
|
||||||
|
viewable=True,
|
||||||
|
editable=True,
|
||||||
|
deletable=True):
|
||||||
|
|
||||||
|
# should have 3 actions now, but for lack of perms
|
||||||
|
grid = view.make_model_grid(session=self.session)
|
||||||
|
self.assertEqual(len(grid.actions), 0)
|
||||||
|
|
||||||
|
# but root user has perms, so gets 3 actions
|
||||||
|
with patch.object(self.request, 'is_root', new=True):
|
||||||
|
grid = view.make_model_grid(session=self.session)
|
||||||
|
self.assertEqual(len(grid.actions), 3)
|
||||||
|
|
||||||
def test_get_grid_data(self):
|
def test_get_grid_data(self):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
self.app.save_setting(self.session, 'foo', 'bar')
|
self.app.save_setting(self.session, 'foo', 'bar')
|
||||||
|
@ -468,6 +548,57 @@ class TestMasterView(WebTestCase):
|
||||||
self.request.matchdict = {'name': 'blarg'}
|
self.request.matchdict = {'name': 'blarg'}
|
||||||
self.assertRaises(HTTPNotFound, view.get_instance, session=self.session)
|
self.assertRaises(HTTPNotFound, view.get_instance, session=self.session)
|
||||||
|
|
||||||
|
def test_get_action_url_view(self):
|
||||||
|
model = self.app.model
|
||||||
|
setting = model.Setting(name='foo', value='bar')
|
||||||
|
self.session.add(setting)
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_class=model.Setting):
|
||||||
|
master.MasterView.defaults(self.pyramid_config)
|
||||||
|
view = self.make_view()
|
||||||
|
url = view.get_action_url_view(setting, 0)
|
||||||
|
self.assertEqual(url, self.request.route_url('settings.view', name='foo'))
|
||||||
|
|
||||||
|
def test_get_action_url_edit(self):
|
||||||
|
model = self.app.model
|
||||||
|
setting = model.Setting(name='foo', value='bar')
|
||||||
|
self.session.add(setting)
|
||||||
|
self.session.commit()
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_class=model.Setting):
|
||||||
|
master.MasterView.defaults(self.pyramid_config)
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
# typical
|
||||||
|
url = view.get_action_url_edit(setting, 0)
|
||||||
|
self.assertEqual(url, self.request.route_url('settings.edit', name='foo'))
|
||||||
|
|
||||||
|
# but null if instance not editable
|
||||||
|
with patch.object(view, 'is_editable', return_value=False):
|
||||||
|
url = view.get_action_url_edit(setting, 0)
|
||||||
|
self.assertIsNone(url)
|
||||||
|
|
||||||
|
def test_get_action_url_delete(self):
|
||||||
|
model = self.app.model
|
||||||
|
setting = model.Setting(name='foo', value='bar')
|
||||||
|
self.session.add(setting)
|
||||||
|
self.session.commit()
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_class=model.Setting):
|
||||||
|
master.MasterView.defaults(self.pyramid_config)
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
# typical
|
||||||
|
url = view.get_action_url_delete(setting, 0)
|
||||||
|
self.assertEqual(url, self.request.route_url('settings.delete', name='foo'))
|
||||||
|
|
||||||
|
# but null if instance not deletable
|
||||||
|
with patch.object(view, 'is_deletable', return_value=False):
|
||||||
|
url = view.get_action_url_delete(setting, 0)
|
||||||
|
self.assertIsNone(url)
|
||||||
|
|
||||||
def test_make_model_form(self):
|
def test_make_model_form(self):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,42 @@ class TestRoleView(WebTestCase):
|
||||||
view.configure_grid(grid)
|
view.configure_grid(grid)
|
||||||
self.assertTrue(grid.is_linked('name'))
|
self.assertTrue(grid.is_linked('name'))
|
||||||
|
|
||||||
|
def test_is_editable(self):
|
||||||
|
model = self.app.model
|
||||||
|
auth = self.app.get_auth_handler()
|
||||||
|
blokes = model.Role(name="Blokes")
|
||||||
|
self.session.add(blokes)
|
||||||
|
self.session.commit()
|
||||||
|
view = self.make_view()
|
||||||
|
|
||||||
|
admin = auth.get_role_administrator(self.session)
|
||||||
|
authed = auth.get_role_authenticated(self.session)
|
||||||
|
anon = auth.get_role_anonymous(self.session)
|
||||||
|
|
||||||
|
# editable by default
|
||||||
|
self.assertTrue(view.is_editable(blokes))
|
||||||
|
|
||||||
|
# built-in roles not editable by default
|
||||||
|
self.assertFalse(view.is_editable(admin))
|
||||||
|
self.assertFalse(view.is_editable(authed))
|
||||||
|
self.assertFalse(view.is_editable(anon))
|
||||||
|
|
||||||
|
# reset
|
||||||
|
del self.request.user_permissions
|
||||||
|
|
||||||
|
barney = model.User(username='barney')
|
||||||
|
self.session.add(barney)
|
||||||
|
barney.roles.append(blokes)
|
||||||
|
auth.grant_permission(blokes, 'roles.edit_builtin')
|
||||||
|
self.session.commit()
|
||||||
|
|
||||||
|
# user with perms can edit *some* built-in
|
||||||
|
self.request.user = barney
|
||||||
|
self.assertTrue(view.is_editable(authed))
|
||||||
|
self.assertTrue(view.is_editable(anon))
|
||||||
|
# nb. not this one yet
|
||||||
|
self.assertFalse(view.is_editable(admin))
|
||||||
|
|
||||||
def test_is_deletable(self):
|
def test_is_deletable(self):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
auth = self.app.get_auth_handler()
|
auth = self.app.get_auth_handler()
|
||||||
|
|
Loading…
Reference in a new issue