Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from zope.deprecation import deprecated 

2from zope.interface import providedBy 

3 

4from pyramid.interfaces import ( 

5 IAuthenticationPolicy, 

6 IAuthorizationPolicy, 

7 ISecuredView, 

8 IView, 

9 IViewClassifier, 

10) 

11 

12from pyramid.compat import map_ 

13from pyramid.threadlocal import get_current_registry 

14 

15Everyone = 'system.Everyone' 

16Authenticated = 'system.Authenticated' 

17Allow = 'Allow' 

18Deny = 'Deny' 

19 

20 

21class AllPermissionsList(object): 

22 """ Stand in 'permission list' to represent all permissions """ 

23 

24 def __iter__(self): 

25 return iter(()) 

26 

27 def __contains__(self, other): 

28 return True 

29 

30 def __eq__(self, other): 

31 return isinstance(other, self.__class__) 

32 

33 

34ALL_PERMISSIONS = AllPermissionsList() 

35DENY_ALL = (Deny, Everyone, ALL_PERMISSIONS) 

36 

37NO_PERMISSION_REQUIRED = '__no_permission_required__' 

38 

39 

40def _get_registry(request): 

41 try: 

42 reg = request.registry 

43 except AttributeError: 

44 reg = get_current_registry() # b/c 

45 return reg 

46 

47 

48def _get_authentication_policy(request): 

49 registry = _get_registry(request) 

50 return registry.queryUtility(IAuthenticationPolicy) 

51 

52 

53def has_permission(permission, context, request): 

54 """ 

55 A function that calls :meth:`pyramid.request.Request.has_permission` 

56 and returns its result. 

57 

58 .. deprecated:: 1.5 

59 Use :meth:`pyramid.request.Request.has_permission` instead. 

60 

61 .. versionchanged:: 1.5a3 

62 If context is None, then attempt to use the context attribute of self; 

63 if not set, then the AttributeError is propagated. 

64 """ 

65 return request.has_permission(permission, context) 

66 

67 

68deprecated( 

69 'has_permission', 

70 'As of Pyramid 1.5 the "pyramid.security.has_permission" API is now ' 

71 'deprecated. It will be removed in Pyramid 1.8. Use the ' 

72 '"has_permission" method of the Pyramid request instead.', 

73) 

74 

75 

76def authenticated_userid(request): 

77 """ 

78 A function that returns the value of the property 

79 :attr:`pyramid.request.Request.authenticated_userid`. 

80 

81 .. deprecated:: 1.5 

82 Use :attr:`pyramid.request.Request.authenticated_userid` instead. 

83 """ 

84 return request.authenticated_userid 

85 

86 

87deprecated( 

88 'authenticated_userid', 

89 'As of Pyramid 1.5 the "pyramid.security.authenticated_userid" API is now ' 

90 'deprecated. It will be removed in Pyramid 1.8. Use the ' 

91 '"authenticated_userid" attribute of the Pyramid request instead.', 

92) 

93 

94 

95def unauthenticated_userid(request): 

96 """ 

97 A function that returns the value of the property 

98 :attr:`pyramid.request.Request.unauthenticated_userid`. 

99 

100 .. deprecated:: 1.5 

101 Use :attr:`pyramid.request.Request.unauthenticated_userid` instead. 

102 """ 

103 return request.unauthenticated_userid 

104 

105 

106deprecated( 

107 'unauthenticated_userid', 

108 'As of Pyramid 1.5 the "pyramid.security.unauthenticated_userid" API is ' 

109 'now deprecated. It will be removed in Pyramid 1.8. Use the ' 

110 '"unauthenticated_userid" attribute of the Pyramid request instead.', 

111) 

112 

113 

114def effective_principals(request): 

115 """ 

116 A function that returns the value of the property 

117 :attr:`pyramid.request.Request.effective_principals`. 

118 

119 .. deprecated:: 1.5 

120 Use :attr:`pyramid.request.Request.effective_principals` instead. 

121 """ 

122 return request.effective_principals 

123 

124 

125deprecated( 

126 'effective_principals', 

127 'As of Pyramid 1.5 the "pyramid.security.effective_principals" API is ' 

128 'now deprecated. It will be removed in Pyramid 1.8. Use the ' 

129 '"effective_principals" attribute of the Pyramid request instead.', 

130) 

131 

132 

133def remember(request, userid, **kw): 

134 """ 

135 Returns a sequence of header tuples (e.g. ``[('Set-Cookie', 'foo=abc')]``) 

136 on this request's response. 

137 These headers are suitable for 'remembering' a set of credentials 

138 implied by the data passed as ``userid`` and ``*kw`` using the 

139 current :term:`authentication policy`. Common usage might look 

140 like so within the body of a view function (``response`` is 

141 assumed to be a :term:`WebOb` -style :term:`response` object 

142 computed previously by the view code): 

143 

144 .. code-block:: python 

145 

146 from pyramid.security import remember 

147 headers = remember(request, 'chrism', password='123', max_age='86400') 

148 response = request.response 

149 response.headerlist.extend(headers) 

150 return response 

151 

152 If no :term:`authentication policy` is in use, this function will 

153 always return an empty sequence. If used, the composition and 

154 meaning of ``**kw`` must be agreed upon by the calling code and 

155 the effective authentication policy. 

156 

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. 

163 """ 

164 policy = _get_authentication_policy(request) 

165 if policy is None: 

166 return [] 

167 return policy.remember(request, userid, **kw) 

168 

169 

170def forget(request): 

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 

177 :term:`response` object computed previously by the view code): 

178 

179 .. code-block:: python 

180 

181 from pyramid.security import forget 

182 headers = forget(request) 

183 response.headerlist.extend(headers) 

184 return response 

185 

186 If no :term:`authentication policy` is in use, this function will 

187 always return an empty sequence. 

188 """ 

189 policy = _get_authentication_policy(request) 

190 if policy is None: 

191 return [] 

192 return policy.forget(request) 

193 

194 

195def principals_allowed_by_permission(context, permission): 

196 """ Provided a ``context`` (a resource object), and a ``permission`` 

197 (a string or unicode object), if an :term:`authorization policy` is 

198 in effect, return a sequence of :term:`principal` ids that possess 

199 the permission in the ``context``. If no authorization policy is 

200 in effect, this will return a sequence with the single value 

201 :mod:`pyramid.security.Everyone` (the special principal 

202 identifier representing all principals). 

203 

204 .. note:: 

205 

206 Even if an :term:`authorization policy` is in effect, 

207 some (exotic) authorization policies may not implement the 

208 required machinery for this function; those will cause a 

209 :exc:`NotImplementedError` exception to be raised when this 

210 function is invoked. 

211 """ 

212 reg = get_current_registry() 

213 policy = reg.queryUtility(IAuthorizationPolicy) 

214 if policy is None: 

215 return [Everyone] 

216 return policy.principals_allowed_by_permission(context, permission) 

217 

218 

219def view_execution_permitted(context, request, name=''): 

220 """ If the view specified by ``context`` and ``name`` is protected 

221 by a :term:`permission`, check the permission associated with the 

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 

225 protected by a permission, return ``True``. If no view can view found, 

226 an exception will be raised. 

227 

228 .. versionchanged:: 1.4a4 

229 An exception is raised if no view is found. 

230 

231 """ 

232 reg = _get_registry(request) 

233 provides = [IViewClassifier] + map_(providedBy, (request, context)) 

234 # XXX not sure what to do here about using _find_views or analogue; 

235 # for now let's just keep it as-is 

236 view = reg.adapters.lookup(provides, ISecuredView, name=name) 

237 if view is None: 

238 view = reg.adapters.lookup(provides, IView, name=name) 

239 if view is None: 

240 raise TypeError( 

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 ) 

245 return Allowed( 

246 'Allowed: view name %r in context %r (no permission defined)' 

247 % (name, context) 

248 ) 

249 return view.__permitted__(context, request) 

250 

251 

252class PermitsResult(int): 

253 def __new__(cls, s, *args): 

254 """ 

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 """ 

262 inst = int.__new__(cls, cls.boolval) 

263 inst.s = s 

264 inst.args = args 

265 return inst 

266 

267 @property 

268 def msg(self): 

269 """ A string indicating why the result was generated.""" 

270 return self.s % self.args 

271 

272 def __str__(self): 

273 return self.msg 

274 

275 def __repr__(self): 

276 return '<%s instance at %s with msg %r>' % ( 

277 self.__class__.__name__, 

278 id(self), 

279 self.msg, 

280 ) 

281 

282 

283class Denied(PermitsResult): 

284 """ 

285 An instance of ``Denied`` is returned when a security-related 

286 API or other :app:`Pyramid` code denies an action unrelated to 

287 an ACL check. It evaluates equal to all boolean false types. It 

288 has an attribute named ``msg`` describing the circumstances for 

289 the deny. 

290 

291 """ 

292 

293 boolval = 0 

294 

295 

296class Allowed(PermitsResult): 

297 """ 

298 An instance of ``Allowed`` is returned when a security-related 

299 API or other :app:`Pyramid` code allows an action unrelated to 

300 an ACL check. It evaluates equal to all boolean true types. It 

301 has an attribute named ``msg`` describing the circumstances for 

302 the allow. 

303 

304 """ 

305 

306 boolval = 1 

307 

308 

309class ACLPermitsResult(PermitsResult): 

310 def __new__(cls, ace, acl, permission, principals, context): 

311 """ 

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 """ 

322 fmt = ( 

323 '%s permission %r via ACE %r in ACL %r on context %r for ' 

324 'principals %r' 

325 ) 

326 inst = PermitsResult.__new__( 

327 cls, fmt, cls.__name__, permission, ace, acl, context, principals 

328 ) 

329 inst.permission = permission 

330 inst.ace = ace 

331 inst.acl = acl 

332 inst.principals = principals 

333 inst.context = context 

334 return inst 

335 

336 

337class ACLDenied(ACLPermitsResult, Denied): 

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. 

347 

348 """ 

349 

350 

351class ACLAllowed(ACLPermitsResult, Allowed): 

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. 

361 

362 """ 

363 

364 

365class AuthenticationAPIMixin(object): 

366 def _get_authentication_policy(self): 

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 

374 there is no currently authenticated user. 

375 

376 .. versionadded:: 1.5 

377 """ 

378 policy = self._get_authentication_policy() 

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 

389 :attr:`~pyramid.request.Request.authenticated_userid`, because the 

390 effective authentication policy will not ensure that a record 

391 associated with the userid exists in persistent storage. 

392 

393 .. versionadded:: 1.5 

394 """ 

395 policy = self._get_authentication_policy() 

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 

403 for the ``request``. If no :term:`authentication policy` is in effect, 

404 this will return a one-element list containing the 

405 :data:`pyramid.security.Everyone` principal. 

406 

407 .. versionadded:: 1.5 

408 """ 

409 policy = self._get_authentication_policy() 

410 if policy is None: 

411 return [Everyone] 

412 return policy.effective_principals(self) 

413 

414 

415class AuthorizationAPIMixin(object): 

416 def has_permission(self, permission, context=None): 

417 """ Given a permission and an optional context, returns an instance of 

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. 

427 

428 :param permission: Does this request have the given permission? 

429 :type permission: unicode, str 

430 :param context: A resource object or ``None`` 

431 :type context: object 

432 :returns: Either :class:`pyramid.security.Allowed` or 

433 :class:`pyramid.security.Denied`. 

434 

435 .. versionadded:: 1.5 

436 

437 """ 

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: 

446 raise ValueError( 

447 'Authentication policy registered without ' 

448 'authorization policy' 

449 ) # should never happen 

450 principals = authn_policy.effective_principals(self) 

451 return authz_policy.permits(context, principals, permission)