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