3
0
Fork 0

feat: add support for admin user to become / stop being root

This commit is contained in:
Lance Edgar 2024-08-05 14:21:54 -05:00
parent a2ba88ca8f
commit fc339ba81b
9 changed files with 335 additions and 22 deletions

View file

@ -138,9 +138,13 @@ class WuttaSecurityPolicy:
return self.session_helper.forget(request, **kw)
def permits(self, request, context, permission):
# nb. root user can do anything
if getattr(request, 'is_root', False):
return True
config = request.registry.settings['wutta_config']
app = config.get_app()
auth = app.get_auth_handler()
user = self.identity(request)
return auth.has_permission(self.db_session, user, permission)

View file

@ -63,6 +63,16 @@ def new_request(event):
Reference to the app :term:`config object`.
.. method:: request.get_referrer(default=None)
Request method to get the "canonical" HTTP referrer value.
This has logic to check for referrer in the request params,
user session etc.
:param default: Optional default URL if none is found in
request params/session. If no default is specified,
the ``'home'`` route is used.
.. attribute:: request.use_oruga
Flag indicating whether the frontend should be displayed using
@ -75,6 +85,19 @@ def new_request(event):
request.wutta_config = config
def get_referrer(default=None):
if request.params.get('referrer'):
return request.params['referrer']
if request.session.get('referrer'):
return request.session.pop('referrer')
referrer = getattr(request, 'referrer', None)
if (not referrer or referrer == request.current_route_url()
or not referrer.startswith(request.host_url)):
referrer = default or request.route_url('home')
return referrer
request.get_referrer = get_referrer
def use_oruga(request):
spec = config.get('wuttaweb.oruga_detector.spec')
if spec:
@ -104,22 +127,44 @@ def new_request_set_user(event, db_session=None):
:class:`~wuttjamaican:wuttjamaican.db.model.auth.User` instance
(if logged in), or ``None``.
:param db_session: Optional :term:`db session` to use, instead of
:class:`wuttaweb.db.Session`. Probably only useful for tests.
.. attribute:: request.is_admin
Flag indicating whether current user is a member of the
Administrator role.
.. attribute:: request.is_root
Flag indicating whether user is currently elevated to root
privileges. This is only possible if :attr:`request.is_admin`
is also true.
"""
request = event.request
config = request.registry.settings['wutta_config']
app = config.get_app()
model = app.model
def user(request):
uuid = request.authenticated_userid
if uuid:
session = db_session or Session()
model = app.model
return session.get(model.User, uuid)
request.set_property(user, reify=True)
def is_admin(request):
auth = app.get_auth_handler()
return auth.user_is_admin(request.user)
request.set_property(is_admin, reify=True)
def is_root(request):
if request.is_admin:
if request.session.get('is_root', False):
return True
return False
request.set_property(is_root, reify=True)
def before_render(event):
"""

View file

@ -314,8 +314,29 @@
<%def name="render_user_menu()">
% if request.user:
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">${request.user}</a>
<a class="navbar-link ${'has-background-danger has-text-white' if request.is_root else ''}">${request.user}</a>
<div class="navbar-dropdown">
% if request.is_root:
${h.form(url('stop_root'), ref='stopBeingRootForm')}
## TODO
## ${h.csrf_token(request)}
<input type="hidden" name="referrer" value="${request.current_route_url()}" />
<a @click="stopBeingRoot()"
class="navbar-item has-background-danger has-text-white">
Stop being root
</a>
${h.end_form()}
% elif request.is_admin:
${h.form(url('become_root'), ref='startBeingRootForm')}
## TODO
## ${h.csrf_token(request)}
<input type="hidden" name="referrer" value="${request.current_route_url()}" />
<a @click="startBeingRoot()"
class="navbar-item has-background-danger has-text-white">
Become root
</a>
${h.end_form()}
% endif
${h.link_to("Change Password", url('change_password'), class_='navbar-item')}
${h.link_to("Logout", url('logout'), class_='navbar-item')}
</div>
@ -359,6 +380,18 @@
const key = 'menu_' + hash + '_shown'
this[key] = !this[key]
},
% if request.is_admin:
startBeingRoot() {
this.$refs.startBeingRootForm.submit()
},
stopBeingRoot() {
this.$refs.stopBeingRootForm.submit()
},
% endif
},
}

View file

@ -198,6 +198,48 @@ class AuthView(View):
if auth.check_user_password(user, value):
node.raise_invalid("New password must be different from old password.")
def become_root(self):
"""
Elevate the current request to 'root' for full system access.
This is only allowed if current (authenticated) user is a
member of the Administrator role. Also note that GET is not
allowed for this view, only POST.
See also :meth:`stop_root()`.
"""
if self.request.method != 'POST':
raise self.forbidden()
if not self.request.is_admin:
raise self.forbidden()
self.request.session['is_root'] = True
self.request.session.flash("You have been elevated to 'root' and now have full system access")
url = self.request.get_referrer()
return self.redirect(url)
def stop_root(self):
"""
Lower the current request from 'root' back to normal access.
Also note that GET is not allowed for this view, only POST.
See also :meth:`become_root()`.
"""
if self.request.method != 'POST':
raise self.forbidden()
if not self.request.is_admin:
raise self.forbidden()
self.request.session['is_root'] = False
self.request.session.flash("Your normal system access has been restored")
url = self.request.get_referrer()
return self.redirect(url)
@classmethod
def defaults(cls, config):
cls._auth_defaults(config)
@ -222,6 +264,18 @@ class AuthView(View):
route_name='change_password',
renderer='/auth/change_password.mako')
# become root
config.add_route('become_root', '/root/yes',
request_method='POST')
config.add_view(cls, attr='become_root',
route_name='become_root')
# stop root
config.add_route('stop_root', '/root/no',
request_method='POST')
config.add_view(cls, attr='stop_root',
route_name='stop_root')
def defaults(config, **kwargs):
base = globals()

View file

@ -55,6 +55,14 @@ class View:
self.config = self.request.wutta_config
self.app = self.config.get_app()
def forbidden(self):
"""
Convenience method, to raise a HTTP 403 Forbidden exception::
raise self.forbidden()
"""
return httpexceptions.HTTPForbidden()
def make_form(self, **kwargs):
"""
Make and return a new :class:`~wuttaweb.forms.base.Form`