Add class_has_permission decorator
This decorator adds permission for each method of `aiohttp.web.View` class
This commit is contained in:
parent
cb1ca8a671
commit
2371a9574b
|
@ -1,5 +1,6 @@
|
||||||
from .abc import AbstractAuthorizationPolicy, AbstractIdentityPolicy
|
from .abc import AbstractAuthorizationPolicy, AbstractIdentityPolicy
|
||||||
from .api import (authorized_userid, forget, has_permission, is_anonymous,
|
from .api import (authorized_userid, forget, has_permission,
|
||||||
|
class_has_permission, is_anonymous,
|
||||||
login_required, permits, remember, setup)
|
login_required, permits, remember, setup)
|
||||||
from .cookies_identity import CookiesIdentityPolicy
|
from .cookies_identity import CookiesIdentityPolicy
|
||||||
from .session_identity import SessionIdentityPolicy
|
from .session_identity import SessionIdentityPolicy
|
||||||
|
@ -11,4 +12,4 @@ __all__ = ('AbstractIdentityPolicy', 'AbstractAuthorizationPolicy',
|
||||||
'CookiesIdentityPolicy', 'SessionIdentityPolicy',
|
'CookiesIdentityPolicy', 'SessionIdentityPolicy',
|
||||||
'remember', 'forget', 'authorized_userid',
|
'remember', 'forget', 'authorized_userid',
|
||||||
'permits', 'setup', 'is_anonymous',
|
'permits', 'setup', 'is_anonymous',
|
||||||
'login_required', 'has_permission')
|
'login_required', 'has_permission', 'class_has_permission')
|
||||||
|
|
|
@ -145,7 +145,6 @@ def has_permission(
|
||||||
userid = await authorized_userid(request)
|
userid = await authorized_userid(request)
|
||||||
if userid is None:
|
if userid is None:
|
||||||
raise web.HTTPUnauthorized
|
raise web.HTTPUnauthorized
|
||||||
|
|
||||||
allowed = await permits(request, permission, context)
|
allowed = await permits(request, permission, context)
|
||||||
if not allowed:
|
if not allowed:
|
||||||
raise web.HTTPForbidden
|
raise web.HTTPForbidden
|
||||||
|
@ -157,6 +156,41 @@ def has_permission(
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def class_has_permission(permission_prefix, context=None):
|
||||||
|
"""Decorator that restrict access only for authorized users
|
||||||
|
with correct permissions for each method of a `aiohttp.web.View`
|
||||||
|
class.
|
||||||
|
|
||||||
|
The needed permission to perform:
|
||||||
|
- POST request is `.create` prefixed by `prefix`
|
||||||
|
- GET request is `.read` prefixed by `prefix`
|
||||||
|
- PATCH or PUT request is `.update` prefixed by `prefix`
|
||||||
|
- DELETE request is `.delete` prefixed by `prefix`
|
||||||
|
|
||||||
|
If user is not authorized - raises HTTPUnauthorized,
|
||||||
|
if user is authorized and does not have permission -
|
||||||
|
raises HTTPForbidden.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(cls):
|
||||||
|
methods = {'post': 'create',
|
||||||
|
'get': 'read',
|
||||||
|
'put': 'update',
|
||||||
|
'patch': 'update',
|
||||||
|
'delete': 'delete'}
|
||||||
|
|
||||||
|
for method_name, permission in methods.items():
|
||||||
|
method = getattr(cls, method_name, None)
|
||||||
|
if method is not None:
|
||||||
|
decorator = has_permission(
|
||||||
|
'{}.{}'.format(permission_prefix, permission),
|
||||||
|
context)
|
||||||
|
setattr(cls, method_name, decorator(method))
|
||||||
|
|
||||||
|
return cls
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def setup(app, identity_policy, autz_policy):
|
def setup(app, identity_policy, autz_policy):
|
||||||
assert isinstance(identity_policy, AbstractIdentityPolicy), identity_policy
|
assert isinstance(identity_policy, AbstractIdentityPolicy), identity_policy
|
||||||
assert isinstance(autz_policy, AbstractAuthorizationPolicy), autz_policy
|
assert isinstance(autz_policy, AbstractAuthorizationPolicy), autz_policy
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
from aiohttp import web
|
||||||
|
from aiohttp_security import setup as _setup
|
||||||
|
from aiohttp_security import (AbstractAuthorizationPolicy,
|
||||||
|
forget, class_has_permission,
|
||||||
|
remember)
|
||||||
|
from aiohttp_security.cookies_identity import CookiesIdentityPolicy
|
||||||
|
|
||||||
|
|
||||||
|
class Autz(AbstractAuthorizationPolicy):
|
||||||
|
|
||||||
|
user_permission_map = {
|
||||||
|
'user_1': {'bike.read'},
|
||||||
|
'user_2': {'bike.create'},
|
||||||
|
'user_3': {'bike.update'},
|
||||||
|
'user_4': {'bike.delete'}
|
||||||
|
}
|
||||||
|
|
||||||
|
async def permits(self, identity, permission, context=None):
|
||||||
|
if identity in self.user_permission_map:
|
||||||
|
return permission in self.user_permission_map[identity]
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def authorized_userid(self, identity):
|
||||||
|
if identity in self.user_permission_map:
|
||||||
|
return identity
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_class_has_permission(loop, test_client):
|
||||||
|
|
||||||
|
@class_has_permission('bike')
|
||||||
|
class BikeView(web.View):
|
||||||
|
|
||||||
|
async def get(self):
|
||||||
|
return web.HTTPOk()
|
||||||
|
|
||||||
|
async def post(self):
|
||||||
|
return web.HTTPOk()
|
||||||
|
|
||||||
|
async def put(self):
|
||||||
|
return web.HTTPOk()
|
||||||
|
|
||||||
|
async def patch(self):
|
||||||
|
return web.HTTPOk()
|
||||||
|
|
||||||
|
async def delete(self):
|
||||||
|
return web.HTTPOk()
|
||||||
|
|
||||||
|
class SessionView(web.View):
|
||||||
|
async def post(self):
|
||||||
|
user = self.request.match_info.get('user')
|
||||||
|
response = web.HTTPFound(location='/')
|
||||||
|
await remember(self.request, response, user)
|
||||||
|
return response
|
||||||
|
|
||||||
|
async def delete(self):
|
||||||
|
response = web.HTTPFound(location='/')
|
||||||
|
await forget(self.request, response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
app = web.Application(loop=loop)
|
||||||
|
_setup(app, CookiesIdentityPolicy(), Autz())
|
||||||
|
|
||||||
|
app.router.add_route(
|
||||||
|
'*', '/permission', BikeView)
|
||||||
|
app.router.add_route(
|
||||||
|
'*', '/session/{user}', SessionView)
|
||||||
|
|
||||||
|
client = await test_client(app)
|
||||||
|
|
||||||
|
resp = await client.get('/permission')
|
||||||
|
assert web.HTTPUnauthorized.status_code == resp.status
|
||||||
|
resp = await client.post('/permission')
|
||||||
|
assert web.HTTPUnauthorized.status_code == resp.status
|
||||||
|
resp = await client.delete('/permission')
|
||||||
|
assert web.HTTPUnauthorized.status_code == resp.status
|
||||||
|
|
||||||
|
await client.post('/session/user_1')
|
||||||
|
resp = await client.get('/permission')
|
||||||
|
assert web.HTTPOk.status_code == resp.status
|
||||||
|
resp = await client.post('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.put('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.patch('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.delete('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
|
||||||
|
await client.post('/session/user_2')
|
||||||
|
resp = await client.get('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.post('/permission')
|
||||||
|
assert web.HTTPOk.status_code == resp.status
|
||||||
|
resp = await client.put('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.patch('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.delete('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
|
||||||
|
await client.post('/session/user_3')
|
||||||
|
resp = await client.get('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.post('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.put('/permission')
|
||||||
|
assert web.HTTPOk.status_code == resp.status
|
||||||
|
resp = await client.patch('/permission')
|
||||||
|
assert web.HTTPOk.status_code == resp.status
|
||||||
|
resp = await client.delete('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
|
||||||
|
await client.post('/session/user_4')
|
||||||
|
resp = await client.get('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.post('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.put('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.patch('/permission')
|
||||||
|
assert web.HTTPForbidden.status_code == resp.status
|
||||||
|
resp = await client.delete('/permission')
|
||||||
|
assert web.HTTPOk.status_code == resp.status
|
||||||
|
|
||||||
|
await client.delete('/session/user_4')
|
||||||
|
resp = await client.get('/permission')
|
||||||
|
assert web.HTTPUnauthorized.status_code == resp.status
|
||||||
|
resp = await client.post('/permission')
|
||||||
|
assert web.HTTPUnauthorized.status_code == resp.status
|
||||||
|
resp = await client.put('/permission')
|
||||||
|
assert web.HTTPUnauthorized.status_code == resp.status
|
||||||
|
resp = await client.patch('/permission')
|
||||||
|
assert web.HTTPUnauthorized.status_code == resp.status
|
||||||
|
resp = await client.delete('/permission')
|
||||||
|
assert web.HTTPUnauthorized.status_code == resp.status
|
Loading…
Reference in New Issue