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) 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): def make_pyramid_config(settings):
""" """
Make a Pyramid config object from the given 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. # Configure user authentication / authorization.
config.set_authentication_policy(SessionAuthenticationPolicy()) config.set_authentication_policy(SessionAuthenticationPolicy())

View file

@ -43,9 +43,14 @@ class TailboneAuthorizationPolicy(object):
def permits(self, context, principals, permission): def permits(self, context, principals, permission):
for userid in principals: for userid in principals:
if userid not in (Everyone, Authenticated): if userid not in (Everyone, Authenticated):
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) user = Session.query(model.User).get(userid)
if user: if user:
return has_permission(Session(), user, permission) if has_permission(Session(), user, permission):
return True
if Everyone in principals: if Everyone in principals:
return has_permission(Session(), None, permission) return has_permission(Session(), None, permission)
return False return False

View file

@ -61,12 +61,6 @@ def PermissionsFieldRenderer(permissions, include_guest=False, include_authentic
def _render(self, readonly=False, **kwargs): def _render(self, readonly=False, **kwargs):
principal = self.field.model 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 = '' html = ''
for groupkey in sorted(permissions, key=lambda k: permissions[k]['label'].lower()): for groupkey in sorted(permissions, key=lambda k: permissions[k]['label'].lower()):
inner = HTML.tag('p', c=permissions[groupkey]['label']) inner = HTML.tag('p', c=permissions[groupkey]['label'])
@ -87,9 +81,7 @@ def PermissionsFieldRenderer(permissions, include_guest=False, include_authentic
rendered = True rendered = True
if rendered: if rendered:
html += HTML.tag('div', class_='group', c=inner) html += HTML.tag('div', class_='group', c=inner)
if not html: return html or "(none granted)"
return "(none granted)"
return html
def render(self, **kwargs): def render(self, **kwargs):
return self._render(**kwargs) return self._render(**kwargs)

View file

@ -99,3 +99,18 @@ ul.error li {
ul.ui-menu { ul.ui-menu {
max-height: 30em; 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 import rattail
from rattail import enum from rattail import enum
from rattail.db import model 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 import threadlocal
from pyramid.security import authenticated_userid from pyramid.security import authenticated_userid
@ -108,6 +108,10 @@ def context_found(event):
* The currently logged-in user instance (if any), as ``user``. * 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 permission checking, as ``has_perm()``.
* A shortcut method for fetching the referrer, as ``get_referrer()``. * A shortcut method for fetching the referrer, as ``get_referrer()``.
@ -122,13 +126,18 @@ def context_found(event):
if request.user: if request.user:
Session().set_continuum_user(request.user) Session().set_continuum_user(request.user)
def has_perm(perm): request.is_admin = request.user and administrator_role(Session()) in request.user.roles
return has_permission(Session(), request.user, perm) 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 request.has_perm = has_perm
def has_any_perm(*perms): def has_any_perm(*names):
for perm in perms: for name in names:
if has_permission(Session(), request.user, perm): if has_perm(name):
return True return True
return False return False
request.has_any_perm = has_any_perm request.has_any_perm = has_any_perm

View file

@ -2,7 +2,7 @@
################################################################################ ################################################################################
# #
# Rattail -- Retail Software Framework # Rattail -- Retail Software Framework
# Copyright © 2010-2015 Lance Edgar # Copyright © 2010-2016 Lance Edgar
# #
# This file is part of Rattail. # This file is part of Rattail.
# #
@ -24,20 +24,19 @@
Auth Views 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.httpexceptions import HTTPFound
from pyramid.security import remember, forget, authenticated_userid from pyramid.security import remember, forget, authenticated_userid
from pyramid_simpleform import Form
from webhelpers.html import literal from webhelpers.html import literal
from webhelpers.html import tags from webhelpers.html import tags
import formencode from tailbone.db import Session
from pyramid_simpleform import Form from tailbone.forms.simpleform import FormRenderer
from ..forms.simpleform import FormRenderer
from ..db import Session
from rattail.db.auth import authenticate_user, set_user_password
def forbidden(request): def forbidden(request):
@ -104,6 +103,24 @@ def logout(request):
return HTTPFound(location=referrer, headers=headers) 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): class CurrentPasswordCorrect(formencode.validators.FancyValidator):
def _to_python(self, value, state): def _to_python(self, value, state):
@ -148,6 +165,8 @@ def change_password(request):
def add_routes(config): def add_routes(config):
config.add_route('login', '/login') config.add_route('login', '/login')
config.add_route('logout', '/logout') 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') config.add_route('change_password', '/change-password')
@ -161,5 +180,8 @@ def includeme(config):
config.add_view(logout, route_name='logout') 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', config.add_view(change_password, route_name='change_password',
renderer='/change_password.mako') renderer='/change_password.mako')