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