commit | author | age
|
c151ad
|
1 |
from zope.deprecation import deprecated |
6b6e43
|
2 |
from zope.interface import providedBy |
4ac0ff
|
3 |
|
0c1c39
|
4 |
from pyramid.interfaces import ( |
CM |
5 |
IAuthenticationPolicy, |
|
6 |
IAuthorizationPolicy, |
|
7 |
ISecuredView, |
4b552e
|
8 |
IView, |
0c1c39
|
9 |
IViewClassifier, |
CM |
10 |
) |
d75fe7
|
11 |
|
475532
|
12 |
from pyramid.compat import map_ |
b60bdb
|
13 |
from pyramid.threadlocal import get_current_registry |
2466f6
|
14 |
|
CM |
15 |
Everyone = 'system.Everyone' |
|
16 |
Authenticated = 'system.Authenticated' |
|
17 |
Allow = 'Allow' |
|
18 |
Deny = 'Deny' |
226b49
|
19 |
|
7a2b72
|
20 |
_marker = object() |
MM |
21 |
|
226b49
|
22 |
class AllPermissionsList(object): |
CM |
23 |
""" Stand in 'permission list' to represent all permissions """ |
1814cd
|
24 |
|
226b49
|
25 |
def __iter__(self): |
1814cd
|
26 |
return iter(()) |
TS |
27 |
|
226b49
|
28 |
def __contains__(self, other): |
CM |
29 |
return True |
1814cd
|
30 |
|
226b49
|
31 |
def __eq__(self, other): |
CM |
32 |
return isinstance(other, self.__class__) |
|
33 |
|
|
34 |
ALL_PERMISSIONS = AllPermissionsList() |
|
35 |
DENY_ALL = (Deny, Everyone, ALL_PERMISSIONS) |
2466f6
|
36 |
|
feceff
|
37 |
NO_PERMISSION_REQUIRED = '__no_permission_required__' |
MM |
38 |
|
3c2f95
|
39 |
def _get_registry(request): |
MR |
40 |
try: |
|
41 |
reg = request.registry |
|
42 |
except AttributeError: |
|
43 |
reg = get_current_registry() # b/c |
|
44 |
return reg |
|
45 |
|
0dcd56
|
46 |
def _get_authentication_policy(request): |
CM |
47 |
registry = _get_registry(request) |
|
48 |
return registry.queryUtility(IAuthenticationPolicy) |
|
49 |
|
4ac0ff
|
50 |
def has_permission(permission, context, request): |
0184b5
|
51 |
""" |
2033ee
|
52 |
A function that calls :meth:`pyramid.request.Request.has_permission` |
SP |
53 |
and returns its result. |
0184b5
|
54 |
|
CM |
55 |
.. deprecated:: 1.5 |
2033ee
|
56 |
Use :meth:`pyramid.request.Request.has_permission` instead. |
4ac0ff
|
57 |
|
0184b5
|
58 |
.. versionchanged:: 1.5a3 |
2033ee
|
59 |
If context is None, then attempt to use the context attribute of self; |
SP |
60 |
if not set, then the AttributeError is propagated. |
3c2f95
|
61 |
""" |
MR |
62 |
return request.has_permission(permission, context) |
a1a9fb
|
63 |
|
0184b5
|
64 |
deprecated( |
CM |
65 |
'has_permission', |
|
66 |
'As of Pyramid 1.5 the "pyramid.security.has_permission" API is now ' |
71ad60
|
67 |
'deprecated. It will be removed in Pyramid 1.8. Use the ' |
c151ad
|
68 |
'"has_permission" method of the Pyramid request instead.' |
0184b5
|
69 |
) |
a1a9fb
|
70 |
|
0184b5
|
71 |
|
CM |
72 |
def authenticated_userid(request): |
|
73 |
""" |
|
74 |
A function that returns the value of the property |
|
75 |
:attr:`pyramid.request.Request.authenticated_userid`. |
|
76 |
|
|
77 |
.. deprecated:: 1.5 |
|
78 |
Use :attr:`pyramid.request.Request.authenticated_userid` instead. |
3c2f95
|
79 |
""" |
MR |
80 |
return request.authenticated_userid |
b54cdb
|
81 |
|
0184b5
|
82 |
deprecated( |
CM |
83 |
'authenticated_userid', |
|
84 |
'As of Pyramid 1.5 the "pyramid.security.authenticated_userid" API is now ' |
71ad60
|
85 |
'deprecated. It will be removed in Pyramid 1.8. Use the ' |
c151ad
|
86 |
'"authenticated_userid" attribute of the Pyramid request instead.' |
0184b5
|
87 |
) |
2526d8
|
88 |
|
0184b5
|
89 |
def unauthenticated_userid(request): |
CM |
90 |
""" |
|
91 |
A function that returns the value of the property |
|
92 |
:attr:`pyramid.request.Request.unauthenticated_userid`. |
|
93 |
|
|
94 |
.. deprecated:: 1.5 |
2033ee
|
95 |
Use :attr:`pyramid.request.Request.unauthenticated_userid` instead. |
3c2f95
|
96 |
""" |
MR |
97 |
return request.unauthenticated_userid |
2526d8
|
98 |
|
0184b5
|
99 |
deprecated( |
CM |
100 |
'unauthenticated_userid', |
|
101 |
'As of Pyramid 1.5 the "pyramid.security.unauthenticated_userid" API is ' |
71ad60
|
102 |
'now deprecated. It will be removed in Pyramid 1.8. Use the ' |
c151ad
|
103 |
'"unauthenticated_userid" attribute of the Pyramid request instead.' |
0184b5
|
104 |
) |
a1a9fb
|
105 |
|
0184b5
|
106 |
def effective_principals(request): |
CM |
107 |
""" |
|
108 |
A function that returns the value of the property |
|
109 |
:attr:`pyramid.request.Request.effective_principals`. |
|
110 |
|
|
111 |
.. deprecated:: 1.5 |
2033ee
|
112 |
Use :attr:`pyramid.request.Request.effective_principals` instead. |
3c2f95
|
113 |
""" |
MR |
114 |
return request.effective_principals |
|
115 |
|
0184b5
|
116 |
deprecated( |
CM |
117 |
'effective_principals', |
|
118 |
'As of Pyramid 1.5 the "pyramid.security.effective_principals" API is ' |
71ad60
|
119 |
'now deprecated. It will be removed in Pyramid 1.8. Use the ' |
c151ad
|
120 |
'"effective_principals" attribute of the Pyramid request instead.' |
0184b5
|
121 |
) |
3c2f95
|
122 |
|
7a2b72
|
123 |
def remember(request, userid=_marker, **kw): |
0184b5
|
124 |
""" |
0dcd56
|
125 |
Returns a sequence of header tuples (e.g. ``[('Set-Cookie', 'foo=abc')]``) |
CM |
126 |
on this request's response. |
0184b5
|
127 |
These headers are suitable for 'remembering' a set of credentials |
c7afe4
|
128 |
implied by the data passed as ``userid`` and ``*kw`` using the |
0184b5
|
129 |
current :term:`authentication policy`. Common usage might look |
CM |
130 |
like so within the body of a view function (``response`` is |
|
131 |
assumed to be a :term:`WebOb` -style :term:`response` object |
06ecaf
|
132 |
computed previously by the view code): |
0184b5
|
133 |
|
06ecaf
|
134 |
.. code-block:: python |
0184b5
|
135 |
|
CM |
136 |
from pyramid.security import remember |
|
137 |
headers = remember(request, 'chrism', password='123', max_age='86400') |
0dcd56
|
138 |
response = request.response |
0184b5
|
139 |
response.headerlist.extend(headers) |
CM |
140 |
return response |
|
141 |
|
|
142 |
If no :term:`authentication policy` is in use, this function will |
072a2c
|
143 |
always return an empty sequence. If used, the composition and |
0184b5
|
144 |
meaning of ``**kw`` must be agreed upon by the calling code and |
CM |
145 |
the effective authentication policy. |
7a2b72
|
146 |
|
MM |
147 |
.. deprecated:: 1.6 |
|
148 |
Renamed the ``principal`` argument to ``userid`` to clarify its |
|
149 |
purpose. |
0184b5
|
150 |
""" |
7a2b72
|
151 |
if userid is _marker: |
MM |
152 |
principal = kw.pop('principal', _marker) |
|
153 |
if principal is _marker: |
|
154 |
raise TypeError( |
|
155 |
'remember() missing 1 required positional argument: ' |
|
156 |
'\'userid\'') |
|
157 |
else: |
|
158 |
deprecated( |
|
159 |
'principal', |
|
160 |
'The "principal" argument was deprecated in Pyramid 1.6. ' |
|
161 |
'It will be removed in Pyramid 1.9. Use the "userid" ' |
c151ad
|
162 |
'argument instead.') |
7a2b72
|
163 |
userid = principal |
0dcd56
|
164 |
policy = _get_authentication_policy(request) |
CM |
165 |
if policy is None: |
|
166 |
return [] |
c7afe4
|
167 |
return policy.remember(request, userid, **kw) |
3c2f95
|
168 |
|
0184b5
|
169 |
def forget(request): |
CM |
170 |
""" |
|
171 |
Return a sequence of header tuples (e.g. ``[('Set-Cookie', |
|
172 |
'foo=abc')]``) suitable for 'forgetting' the set of credentials |
|
173 |
possessed by the currently authenticated user. A common usage |
|
174 |
might look like so within the body of a view function |
|
175 |
(``response`` is assumed to be an :term:`WebOb` -style |
620bde
|
176 |
:term:`response` object computed previously by the view code): |
0184b5
|
177 |
|
620bde
|
178 |
.. code-block:: python |
MM |
179 |
|
|
180 |
from pyramid.security import forget |
|
181 |
headers = forget(request) |
|
182 |
response.headerlist.extend(headers) |
|
183 |
return response |
0184b5
|
184 |
|
CM |
185 |
If no :term:`authentication policy` is in use, this function will |
|
186 |
always return an empty sequence. |
3c2f95
|
187 |
""" |
0dcd56
|
188 |
policy = _get_authentication_policy(request) |
CM |
189 |
if policy is None: |
|
190 |
return [] |
|
191 |
return policy.forget(request) |
64ea2e
|
192 |
|
CM |
193 |
def principals_allowed_by_permission(context, permission): |
3e2f12
|
194 |
""" Provided a ``context`` (a resource object), and a ``permission`` |
8b1f6e
|
195 |
(a string or unicode object), if a :term:`authorization policy` is |
CM |
196 |
in effect, return a sequence of :term:`principal` ids that possess |
|
197 |
the permission in the ``context``. If no authorization policy is |
|
198 |
in effect, this will return a sequence with the single value |
c81aad
|
199 |
:mod:`pyramid.security.Everyone` (the special principal |
c6895b
|
200 |
identifier representing all principals). |
a1a9fb
|
201 |
|
012b97
|
202 |
.. note:: |
M |
203 |
|
|
204 |
even if an :term:`authorization policy` is in effect, |
8b1f6e
|
205 |
some (exotic) authorization policies may not implement the |
CM |
206 |
required machinery for this function; those will cause a |
c6895b
|
207 |
:exc:`NotImplementedError` exception to be raised when this |
a1a9fb
|
208 |
function is invoked. |
CM |
209 |
""" |
41723e
|
210 |
reg = get_current_registry() |
CM |
211 |
policy = reg.queryUtility(IAuthorizationPolicy) |
64ea2e
|
212 |
if policy is None: |
CM |
213 |
return [Everyone] |
|
214 |
return policy.principals_allowed_by_permission(context, permission) |
b54cdb
|
215 |
|
a1a9fb
|
216 |
def view_execution_permitted(context, request, name=''): |
CM |
217 |
""" If the view specified by ``context`` and ``name`` is protected |
8b1f6e
|
218 |
by a :term:`permission`, check the permission associated with the |
CM |
219 |
view using the effective authentication/authorization policies and |
|
220 |
the ``request``. Return a boolean result. If no |
|
221 |
:term:`authorization policy` is in effect, or if the view is not |
6e9640
|
222 |
protected by a permission, return ``True``. If no view can view found, |
MM |
223 |
an exception will be raised. |
|
224 |
|
|
225 |
.. versionchanged:: 1.4a4 |
|
226 |
An exception is raised if no view is found. |
|
227 |
|
|
228 |
""" |
3c2f95
|
229 |
reg = _get_registry(request) |
475532
|
230 |
provides = [IViewClassifier] + map_(providedBy, (request, context)) |
2a842e
|
231 |
# XXX not sure what to do here about using _find_views or analogue; |
CM |
232 |
# for now let's just keep it as-is |
41723e
|
233 |
view = reg.adapters.lookup(provides, ISecuredView, name=name) |
d66bfb
|
234 |
if view is None: |
4b552e
|
235 |
view = reg.adapters.lookup(provides, IView, name=name) |
MM |
236 |
if view is None: |
|
237 |
raise TypeError('No registered view satisfies the constraints. ' |
|
238 |
'It would not make sense to claim that this view ' |
|
239 |
'"is" or "is not" permitted.') |
a1a9fb
|
240 |
return Allowed( |
CM |
241 |
'Allowed: view name %r in context %r (no permission defined)' % |
|
242 |
(name, context)) |
d66bfb
|
243 |
return view.__permitted__(context, request) |
157721
|
244 |
|
012b97
|
245 |
|
7292d4
|
246 |
class PermitsResult(int): |
CM |
247 |
def __new__(cls, s, *args): |
213001
|
248 |
""" |
MM |
249 |
Create a new instance. |
|
250 |
|
|
251 |
:param fmt: A format string explaining the reason for denial. |
|
252 |
:param args: Arguments are stored and used with the format string |
|
253 |
to generate the ``msg``. |
|
254 |
|
|
255 |
""" |
7292d4
|
256 |
inst = int.__new__(cls, cls.boolval) |
CM |
257 |
inst.s = s |
|
258 |
inst.args = args |
|
259 |
return inst |
012b97
|
260 |
|
7292d4
|
261 |
@property |
CM |
262 |
def msg(self): |
213001
|
263 |
""" A string indicating why the result was generated.""" |
7292d4
|
264 |
return self.s % self.args |
CM |
265 |
|
2466f6
|
266 |
def __str__(self): |
17ce57
|
267 |
return self.msg |
CM |
268 |
|
|
269 |
def __repr__(self): |
|
270 |
return '<%s instance at %s with msg %r>' % (self.__class__.__name__, |
|
271 |
id(self), |
|
272 |
self.msg) |
2466f6
|
273 |
|
CM |
274 |
class Denied(PermitsResult): |
213001
|
275 |
""" |
MM |
276 |
An instance of ``Denied`` is returned when a security-related |
fd5ae9
|
277 |
API or other :app:`Pyramid` code denies an action unrelated to |
c6895b
|
278 |
an ACL check. It evaluates equal to all boolean false types. It |
CM |
279 |
has an attribute named ``msg`` describing the circumstances for |
213001
|
280 |
the deny. |
MM |
281 |
|
|
282 |
""" |
7292d4
|
283 |
boolval = 0 |
2466f6
|
284 |
|
CM |
285 |
class Allowed(PermitsResult): |
213001
|
286 |
""" |
MM |
287 |
An instance of ``Allowed`` is returned when a security-related |
fd5ae9
|
288 |
API or other :app:`Pyramid` code allows an action unrelated to |
c6895b
|
289 |
an ACL check. It evaluates equal to all boolean true types. It |
CM |
290 |
has an attribute named ``msg`` describing the circumstances for |
213001
|
291 |
the allow. |
MM |
292 |
|
|
293 |
""" |
7292d4
|
294 |
boolval = 1 |
f66290
|
295 |
|
213001
|
296 |
class ACLPermitsResult(PermitsResult): |
7292d4
|
297 |
def __new__(cls, ace, acl, permission, principals, context): |
213001
|
298 |
""" |
MM |
299 |
Create a new instance. |
|
300 |
|
|
301 |
:param ace: The :term:`ACE` that matched, triggering the result. |
|
302 |
:param acl: The :term:`ACL` containing ``ace``. |
|
303 |
:param permission: The required :term:`permission`. |
|
304 |
:param principals: The list of :term:`principals <principal>` provided. |
|
305 |
:param context: The :term:`context` providing the :term:`lineage` |
|
306 |
searched. |
|
307 |
|
|
308 |
""" |
|
309 |
fmt = ('%s permission %r via ACE %r in ACL %r on context %r for ' |
|
310 |
'principals %r') |
|
311 |
inst = PermitsResult.__new__( |
|
312 |
cls, |
|
313 |
fmt, |
|
314 |
cls.__name__, |
|
315 |
permission, |
|
316 |
ace, |
|
317 |
acl, |
|
318 |
context, |
|
319 |
principals, |
|
320 |
) |
7292d4
|
321 |
inst.permission = permission |
CM |
322 |
inst.ace = ace |
|
323 |
inst.acl = acl |
|
324 |
inst.principals = principals |
|
325 |
inst.context = context |
|
326 |
return inst |
f66290
|
327 |
|
213001
|
328 |
class ACLDenied(ACLPermitsResult, Denied): |
MM |
329 |
""" |
|
330 |
An instance of ``ACLDenied`` is a specialization of |
|
331 |
:class:`pyramid.security.Denied` that represents that a security check |
|
332 |
made explicitly against ACL was denied. It evaluates equal to all |
|
333 |
boolean false types. It also has the following attributes: ``acl``, |
|
334 |
``ace``, ``permission``, ``principals``, and ``context``. These |
|
335 |
attributes indicate the security values involved in the request. Its |
|
336 |
``__str__`` method prints a summary of these attributes for debugging |
|
337 |
purposes. The same summary is available as the ``msg`` attribute. |
17ce57
|
338 |
|
213001
|
339 |
""" |
7292d4
|
340 |
|
213001
|
341 |
class ACLAllowed(ACLPermitsResult, Allowed): |
MM |
342 |
""" |
|
343 |
An instance of ``ACLAllowed`` is a specialization of |
|
344 |
:class:`pyramid.security.Allowed` that represents that a security check |
|
345 |
made explicitly against ACL was allowed. It evaluates equal to all |
|
346 |
boolean true types. It also has the following attributes: ``acl``, |
|
347 |
``ace``, ``permission``, ``principals``, and ``context``. These |
|
348 |
attributes indicate the security values involved in the request. Its |
|
349 |
``__str__`` method prints a summary of these attributes for debugging |
|
350 |
purposes. The same summary is available as the ``msg`` attribute. |
7292d4
|
351 |
|
213001
|
352 |
""" |
7d1da8
|
353 |
|
3c2f95
|
354 |
class AuthenticationAPIMixin(object): |
MR |
355 |
|
|
356 |
def _get_authentication_policy(self): |
|
357 |
reg = _get_registry(self) |
|
358 |
return reg.queryUtility(IAuthenticationPolicy) |
|
359 |
|
|
360 |
@property |
|
361 |
def authenticated_userid(self): |
|
362 |
""" Return the userid of the currently authenticated user or |
|
363 |
``None`` if there is no :term:`authentication policy` in effect or |
0184b5
|
364 |
there is no currently authenticated user. |
CM |
365 |
|
|
366 |
.. versionadded:: 1.5 |
|
367 |
""" |
3c2f95
|
368 |
policy = self._get_authentication_policy() |
MR |
369 |
if policy is None: |
|
370 |
return None |
|
371 |
return policy.authenticated_userid(self) |
|
372 |
|
|
373 |
@property |
|
374 |
def unauthenticated_userid(self): |
|
375 |
""" Return an object which represents the *claimed* (not verified) user |
|
376 |
id of the credentials present in the request. ``None`` if there is no |
|
377 |
:term:`authentication policy` in effect or there is no user data |
|
378 |
associated with the current request. This differs from |
e18385
|
379 |
:attr:`~pyramid.request.Request.authenticated_userid`, because the |
CM |
380 |
effective authentication policy will not ensure that a record |
|
381 |
associated with the userid exists in persistent storage. |
0184b5
|
382 |
|
CM |
383 |
.. versionadded:: 1.5 |
|
384 |
""" |
3c2f95
|
385 |
policy = self._get_authentication_policy() |
MR |
386 |
if policy is None: |
|
387 |
return None |
|
388 |
return policy.unauthenticated_userid(self) |
|
389 |
|
|
390 |
@property |
|
391 |
def effective_principals(self): |
|
392 |
""" Return the list of 'effective' :term:`principal` identifiers |
82ec99
|
393 |
for the ``request``. If no :term:`authentication policy` is in effect, |
MM |
394 |
this will return a one-element list containing the |
|
395 |
:data:`pyramid.security.Everyone` principal. |
0184b5
|
396 |
|
CM |
397 |
.. versionadded:: 1.5 |
|
398 |
""" |
3c2f95
|
399 |
policy = self._get_authentication_policy() |
MR |
400 |
if policy is None: |
|
401 |
return [Everyone] |
|
402 |
return policy.effective_principals(self) |
dd1523
|
403 |
|
3c2f95
|
404 |
class AuthorizationAPIMixin(object): |
MR |
405 |
|
|
406 |
def has_permission(self, permission, context=None): |
e0d1af
|
407 |
""" Given a permission and an optional context, returns an instance of |
CM |
408 |
:data:`pyramid.security.Allowed` if the permission is granted to this |
|
409 |
request with the provided context, or the context already associated |
|
410 |
with the request. Otherwise, returns an instance of |
|
411 |
:data:`pyramid.security.Denied`. This method delegates to the current |
|
412 |
authentication and authorization policies. Returns |
|
413 |
:data:`pyramid.security.Allowed` unconditionally if no authentication |
|
414 |
policy has been registered for this request. If ``context`` is not |
|
415 |
supplied or is supplied as ``None``, the context used is the |
|
416 |
``request.context`` attribute. |
3c2f95
|
417 |
|
MR |
418 |
:param permission: Does this request have the given permission? |
|
419 |
:type permission: unicode, str |
c12603
|
420 |
:param context: A resource object or ``None`` |
3c2f95
|
421 |
:type context: object |
213001
|
422 |
:returns: Either :class:`pyramid.security.Allowed` or |
MM |
423 |
:class:`pyramid.security.Denied`. |
0184b5
|
424 |
|
CM |
425 |
.. versionadded:: 1.5 |
|
426 |
|
3c2f95
|
427 |
""" |
MR |
428 |
if context is None: |
|
429 |
context = self.context |
|
430 |
reg = _get_registry(self) |
|
431 |
authn_policy = reg.queryUtility(IAuthenticationPolicy) |
|
432 |
if authn_policy is None: |
|
433 |
return Allowed('No authentication policy in use.') |
|
434 |
authz_policy = reg.queryUtility(IAuthorizationPolicy) |
|
435 |
if authz_policy is None: |
|
436 |
raise ValueError('Authentication policy registered without ' |
|
437 |
'authorization policy') # should never happen |
|
438 |
principals = authn_policy.effective_principals(self) |
|
439 |
return authz_policy.permits(context, principals, permission) |