Let any 'admin' user elevate to 'root' for full system access

But otherwise, let the Administrator role be "normal" and have perms of
its own.  Hopefully cuts down on unwanted screen noise for admins.
This commit is contained in:
Lance Edgar 2016-10-18 16:59:38 -05:00
parent 4599eaad97
commit 6bf60365ba
6 changed files with 102 additions and 48 deletions

View file

@ -106,11 +106,22 @@ def provide_postgresql_settings(settings):
settings.setdefault('tm.attempts', 2)
class Root(dict):
"""
Root factory for Pyramid. This is necessary to make the current request
available to the authorization policy object, which needs it to check if
the current request "is root".
"""
def __init__(self, request):
self.request = request
def make_pyramid_config(settings):
"""
Make a Pyramid config object from the given settings.
"""
config = Configurator(settings=settings)
config = Configurator(settings=settings, root_factory=Root)
# Configure user authentication / authorization.
config.set_authentication_policy(SessionAuthenticationPolicy())

View file

@ -43,9 +43,14 @@ class TailboneAuthorizationPolicy(object):
def permits(self, context, principals, permission):
for userid in principals:
if userid not in (Everyone, Authenticated):
user = Session.query(model.User).get(userid)
if user:
return has_permission(Session(), user, permission)
if context.request.user and context.request.user.uuid == userid:
return context.request.has_perm(permission)
else:
assert False # should no longer happen..right?
user = Session.query(model.User).get(userid)
if user:
if has_permission(Session(), user, permission):
return True
if Everyone in principals:
return has_permission(Session(), None, permission)
return False

View file

@ -61,35 +61,27 @@ def PermissionsFieldRenderer(permissions, include_guest=False, include_authentic
def _render(self, readonly=False, **kwargs):
principal = self.field.model
if isinstance(principal, model.Role) and principal is administrator_role(Session()):
html = HTML.tag('p', c="This is the administrative role; "
"it has full access to the entire system.")
if not readonly:
html += tags.hidden(self.name, value='') # ugly hack..or good idea?
else:
html = ''
for groupkey in sorted(permissions, key=lambda k: permissions[k]['label'].lower()):
inner = HTML.tag('p', c=permissions[groupkey]['label'])
perms = permissions[groupkey]['perms']
rendered = False
for key in sorted(perms, key=lambda p: perms[p]['label'].lower()):
checked = has_permission(Session(), principal, key,
include_guest=include_guest,
include_authenticated=include_authenticated)
if checked or not readonly:
label = perms[key]['label']
if readonly:
span = HTML.tag('span', c="[X]" if checked else "[ ]")
inner += HTML.tag('p', class_='perm', c=span + ' ' + label)
else:
inner += tags.checkbox(self.name + '-' + key,
checked=checked, label=label)
rendered = True
if rendered:
html += HTML.tag('div', class_='group', c=inner)
if not html:
return "(none granted)"
return html
html = ''
for groupkey in sorted(permissions, key=lambda k: permissions[k]['label'].lower()):
inner = HTML.tag('p', c=permissions[groupkey]['label'])
perms = permissions[groupkey]['perms']
rendered = False
for key in sorted(perms, key=lambda p: perms[p]['label'].lower()):
checked = has_permission(Session(), principal, key,
include_guest=include_guest,
include_authenticated=include_authenticated)
if checked or not readonly:
label = perms[key]['label']
if readonly:
span = HTML.tag('span', c="[X]" if checked else "[ ]")
inner += HTML.tag('p', class_='perm', c=span + ' ' + label)
else:
inner += tags.checkbox(self.name + '-' + key,
checked=checked, label=label)
rendered = True
if rendered:
html += HTML.tag('div', class_='group', c=inner)
return html or "(none granted)"
def render(self, **kwargs):
return self._render(**kwargs)

View file

@ -99,3 +99,18 @@ ul.error li {
ul.ui-menu {
max-height: 30em;
}
/******************************
* tweaks for root user
******************************/
.menubar .root-user .ui-button-text,
.menubar .root-user.ui-menu-item a {
background-color: red;
color: black;
font-weight: bold;
}
.menubar .root-user.ui-menu-item a {
padding-left: 1em;
}

View file

@ -29,7 +29,7 @@ from __future__ import unicode_literals, absolute_import
import rattail
from rattail import enum
from rattail.db import model
from rattail.db.auth import has_permission
from rattail.db.auth import has_permission, administrator_role
from pyramid import threadlocal
from pyramid.security import authenticated_userid
@ -108,6 +108,10 @@ def context_found(event):
* 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()``.
* A shortcut method for fetching the referrer, as ``get_referrer()``.
@ -122,13 +126,18 @@ def context_found(event):
if request.user:
Session().set_continuum_user(request.user)
def has_perm(perm):
return has_permission(Session(), request.user, perm)
request.is_admin = request.user and administrator_role(Session()) in request.user.roles
request.is_root = request.is_admin and request.session.get('is_root', False)
def has_perm(name):
if has_permission(Session(), request.user, name):
return True
return request.is_root
request.has_perm = has_perm
def has_any_perm(*perms):
for perm in perms:
if has_permission(Session(), request.user, 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

View file

@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
# Copyright © 2010-2015 Lance Edgar
# Copyright © 2010-2016 Lance Edgar
#
# This file is part of Rattail.
#
@ -24,20 +24,19 @@
Auth Views
"""
from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import
from rattail.db.auth import authenticate_user, set_user_password
import formencode
from pyramid.httpexceptions import HTTPFound
from pyramid.security import remember, forget, authenticated_userid
from pyramid_simpleform import Form
from webhelpers.html import literal
from webhelpers.html import tags
import formencode
from pyramid_simpleform import Form
from ..forms.simpleform import FormRenderer
from ..db import Session
from rattail.db.auth import authenticate_user, set_user_password
from tailbone.db import Session
from tailbone.forms.simpleform import FormRenderer
def forbidden(request):
@ -104,6 +103,24 @@ def logout(request):
return HTTPFound(location=referrer, headers=headers)
def become_root(request):
"""
Elevate the current request to 'root' for full system access.
"""
request.session['is_root'] = True
request.session.flash("You have been elevated to 'root' and now have full system access", 'error')
return HTTPFound(location=request.get_referrer())
def stop_root(request):
"""
Lower the current request from 'root' back to normal access.
"""
request.session['is_root'] = False
request.session.flash("Your normal system access has been restored")
return HTTPFound(location=request.get_referrer())
class CurrentPasswordCorrect(formencode.validators.FancyValidator):
def _to_python(self, value, state):
@ -148,6 +165,8 @@ def change_password(request):
def add_routes(config):
config.add_route('login', '/login')
config.add_route('logout', '/logout')
config.add_route('become_root', '/root/yes')
config.add_route('stop_root', '/root/no')
config.add_route('change_password', '/change-password')
@ -161,5 +180,8 @@ def includeme(config):
config.add_view(logout, route_name='logout')
config.add_view(become_root, route_name='become_root')
config.add_view(stop_root, route_name='stop_root')
config.add_view(change_password, route_name='change_password',
renderer='/change_password.mako')