Coverage for .tox/coverage/lib/python3.11/site-packages/wuttaweb/auth.py: 100%
68 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-27 21:18 -0500
« prev ^ index » next coverage.py v7.6.1, created at 2024-08-27 21:18 -0500
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# wuttaweb -- Web App for Wutta Framework
5# Copyright © 2024 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24Auth Utility Logic
25"""
27import re
29from pyramid.authentication import SessionAuthenticationHelper
30from pyramid.request import RequestLocalCache
31from pyramid.security import remember, forget
33from wuttaweb.db import Session
36def login_user(request, user):
37 """
38 Perform the steps necessary to "login" the given user. This
39 returns a ``headers`` dict which you should pass to the final
40 redirect, like so::
42 from pyramid.httpexceptions import HTTPFound
44 headers = login_user(request, user)
45 return HTTPFound(location='/', headers=headers)
47 .. warning::
49 This logic does not "authenticate" the user! It assumes caller
50 has already authenticated the user and they are safe to login.
52 See also :func:`logout_user()`.
53 """
54 headers = remember(request, user.uuid)
55 return headers
58def logout_user(request):
59 """
60 Perform the logout action for the given request. This returns a
61 ``headers`` dict which you should pass to the final redirect, like
62 so::
64 from pyramid.httpexceptions import HTTPFound
66 headers = logout_user(request)
67 return HTTPFound(location='/', headers=headers)
69 See also :func:`login_user()`.
70 """
71 request.session.delete()
72 request.session.invalidate()
73 headers = forget(request)
74 return headers
77class WuttaSecurityPolicy:
78 """
79 Pyramid :term:`security policy` for WuttaWeb.
81 For more on the Pyramid details, see :doc:`pyramid:narr/security`.
83 But the idea here is that you should be able to just use this,
84 without thinking too hard::
86 from pyramid.config import Configurator
87 from wuttaweb.auth import WuttaSecurityPolicy
89 pyramid_config = Configurator()
90 pyramid_config.set_security_policy(WuttaSecurityPolicy())
92 This security policy will then do the following:
94 * use the request "web session" for auth storage (e.g. current
95 ``user.uuid``)
96 * check permissions as needed, by calling
97 :meth:`~wuttjamaican:wuttjamaican.auth.AuthHandler.has_permission()`
98 for current user
100 :param db_session: Optional :term:`db session` to use, instead of
101 :class:`wuttaweb.db.sess.Session`. Probably only useful for
102 tests.
103 """
105 def __init__(self, db_session=None):
106 self.session_helper = SessionAuthenticationHelper()
107 self.identity_cache = RequestLocalCache(self.load_identity)
108 self.db_session = db_session or Session()
110 def load_identity(self, request):
111 config = request.registry.settings['wutta_config']
112 app = config.get_app()
113 model = app.model
115 # fetch user uuid from current session
116 uuid = self.session_helper.authenticated_userid(request)
117 if not uuid:
118 return
120 # fetch user object from db
121 user = self.db_session.get(model.User, uuid)
122 if not user:
123 return
125 return user
127 def identity(self, request):
128 return self.identity_cache.get_or_create(request)
130 def authenticated_userid(self, request):
131 user = self.identity(request)
132 if user is not None:
133 return user.uuid
135 def remember(self, request, userid, **kw):
136 return self.session_helper.remember(request, userid, **kw)
138 def forget(self, request, **kw):
139 return self.session_helper.forget(request, **kw)
141 def permits(self, request, context, permission):
143 # nb. root user can do anything
144 if getattr(request, 'is_root', False):
145 return True
147 config = request.registry.settings['wutta_config']
148 app = config.get_app()
149 auth = app.get_auth_handler()
150 user = self.identity(request)
151 return auth.has_permission(self.db_session, user, permission)
154def add_permission_group(pyramid_config, groupkey, label=None, overwrite=True):
155 """
156 Pyramid directive to add a "permission group" to the app's
157 awareness.
159 The app must be made aware of all permissions, so they are exposed
160 when editing a
161 :class:`~wuttjamaican:wuttjamaican.db.model.auth.Role`. The logic
162 for discovering permissions is in
163 :meth:`~wuttaweb.views.roles.RoleView.get_available_permissions()`.
165 This is usually called from within a master view's
166 :meth:`~wuttaweb.views.master.MasterView.defaults()` to establish
167 the permission group which applies to the view model.
169 A simple example of usage::
171 pyramid_config.add_permission_group('widgets', label="Widgets")
173 :param groupkey: Unique key for the permission group. In the
174 context of a master view, this will be the same as
175 :attr:`~wuttaweb.views.master.MasterView.permission_prefix`.
177 :param label: Optional label for the permission group. If not
178 specified, it is derived from ``groupkey``.
180 :param overwrite: If the permission group was already established,
181 this flag controls whether the group's label should be
182 overwritten (with ``label``).
184 See also :func:`add_permission()`.
185 """
186 config = pyramid_config.get_settings()['wutta_config']
187 app = config.get_app()
188 def action():
189 perms = pyramid_config.get_settings().get('wutta_permissions', {})
190 if overwrite or groupkey not in perms:
191 group = perms.setdefault(groupkey, {'key': groupkey})
192 group['label'] = label or app.make_title(groupkey)
193 pyramid_config.add_settings({'wutta_permissions': perms})
194 pyramid_config.action(None, action)
197def add_permission(pyramid_config, groupkey, key, label=None):
198 """
199 Pyramid directive to add a single "permission" to the app's
200 awareness.
202 The app must be made aware of all permissions, so they are exposed
203 when editing a
204 :class:`~wuttjamaican:wuttjamaican.db.model.auth.Role`. The logic
205 for discovering permissions is in
206 :meth:`~wuttaweb.views.roles.RoleView.get_available_permissions()`.
208 This is usually called from within a master view's
209 :meth:`~wuttaweb.views.master.MasterView.defaults()` to establish
210 "known" permissions based on master view feature flags
211 (:attr:`~wuttaweb.views.master.MasterView.viewable`,
212 :attr:`~wuttaweb.views.master.MasterView.editable`, etc.).
214 A simple example of usage::
216 pyramid_config.add_permission('widgets', 'widgets.polish',
217 label="Polish all the widgets")
219 :param groupkey: Unique key for the permission group. In the
220 context of a master view, this will be the same as
221 :attr:`~wuttaweb.views.master.MasterView.permission_prefix`.
223 :param key: Unique key for the permission. This should be the
224 "complete" permission name which includes the permission
225 prefix.
227 :param label: Optional label for the permission. If not
228 specified, it is derived from ``key``.
230 See also :func:`add_permission_group()`.
231 """
232 def action():
233 config = pyramid_config.get_settings()['wutta_config']
234 app = config.get_app()
235 perms = pyramid_config.get_settings().get('wutta_permissions', {})
236 group = perms.setdefault(groupkey, {'key': groupkey})
237 group.setdefault('label', app.make_title(groupkey))
238 perm = group.setdefault('perms', {}).setdefault(key, {'key': key})
239 perm['label'] = label or app.make_title(key)
240 pyramid_config.add_settings({'wutta_permissions': perms})
241 pyramid_config.action(None, action)