diff --git a/demo/database_auth/handlers.py b/demo/database_auth/handlers.py index b0c6658..3d10332 100644 --- a/demo/database_auth/handlers.py +++ b/demo/database_auth/handlers.py @@ -1,42 +1,31 @@ -import functools +from textwrap import dedent from aiohttp import web -from aiohttp_security import remember, forget, authorized_userid, permits +from aiohttp_security import ( + remember, forget, authorized_userid, + has_permission, login_required, +) from .db_auth import check_credentials -def require(permission): - def wrapper(f): - @functools.wraps(f) - async def wrapped(self, request): - has_perm = await permits(request, permission) - if not has_perm: - message = 'User has no permission {}'.format(permission) - raise web.HTTPForbidden(body=message.encode()) - return await f(self, request) - return wrapped - return wrapper - - class Web(object): - index_template = """ - - - - -

{message}

-
- Login: - - Password: - - -
-Logout - -""" + index_template = dedent(""" + + + +

{message}

+
+ Login: + + Password: + + +
+ Logout + + """) async def index(self, request): username = await authorized_userid(request) @@ -61,19 +50,19 @@ class Web(object): return web.HTTPUnauthorized( body=b'Invalid username/password combination') - @require('public') + @login_required async def logout(self, request): response = web.Response(body=b'You have been logged out') await forget(request, response) return response - @require('public') + @has_permission('public') async def internal_page(self, request): response = web.Response( body=b'This page is visible for all registered users') return response - @require('protected') + @has_permission('protected') async def protected_page(self, request): response = web.Response(body=b'You are on protected page') return response diff --git a/demo/database_auth/main.py b/demo/database_auth/main.py index 3aafff5..43da500 100644 --- a/demo/database_auth/main.py +++ b/demo/database_auth/main.py @@ -9,16 +9,16 @@ from aiopg.sa import create_engine from aioredis import create_pool -from demo.db_auth import DBAuthorizationPolicy -from demo.handlers import Web +from demo.database_auth.db_auth import DBAuthorizationPolicy +from demo.database_auth.handlers import Web async def init(loop): redis_pool = await create_pool(('localhost', 6379)) db_engine = await create_engine(user='aiohttp_security', - password='aiohttp_security', - database='aiohttp_security', - host='127.0.0.1') + password='aiohttp_security', + database='aiohttp_security', + host='127.0.0.1') app = web.Application(loop=loop) app.db_engine = db_engine setup_session(app, RedisStorage(redis_pool)) diff --git a/demo/dictionary_auth/handlers.py b/demo/dictionary_auth/handlers.py index a0d7f7c..5816e8d 100644 --- a/demo/dictionary_auth/handlers.py +++ b/demo/dictionary_auth/handlers.py @@ -1,42 +1,30 @@ -import functools from textwrap import dedent from aiohttp import web -from aiohttp_security import remember, forget, authorized_userid, permits +from aiohttp_security import ( + remember, forget, authorized_userid, + has_permission, login_required, +) from .authz import check_credentials -def require(permission): - def wrapper(f): - @functools.wraps(f) - async def wrapped(request): - has_perm = await permits(request, permission) - if not has_perm: - message = 'User has no permission {}'.format(permission) - raise web.HTTPForbidden(body=message.encode()) - return await f(request) - return wrapped - return wrapper - - index_template = dedent(""" - - - -

{message}

-
- Login: - - Password: - - -
- Logout - - """) + + +

{message}

+
+ Login: + + Password: + + +
+ Logout + +""") async def index(request): @@ -58,7 +46,8 @@ async def login(request): username = form.get('username') password = form.get('password') - verified = await check_credentials(request.app.user_map, username, password) + verified = await check_credentials( + request.app.user_map, username, password) if verified: await remember(request, response, username) return response @@ -66,7 +55,7 @@ async def login(request): return web.HTTPUnauthorized(body='Invalid username / password combination') -@require('public') +@login_required async def logout(request): response = web.Response( text='You have been logged out', @@ -76,7 +65,7 @@ async def logout(request): return response -@require('public') +@has_permission('public') async def internal_page(request): # pylint: disable=unused-argument response = web.Response( @@ -86,7 +75,7 @@ async def internal_page(request): return response -@require('protected') +@has_permission('protected') async def protected_page(request): # pylint: disable=unused-argument response = web.Response( diff --git a/demo/dictionary_auth/main.py b/demo/dictionary_auth/main.py index b017ed5..b4fe2b4 100644 --- a/demo/dictionary_auth/main.py +++ b/demo/dictionary_auth/main.py @@ -6,9 +6,9 @@ from aiohttp_session.cookie_storage import EncryptedCookieStorage from aiohttp_security import setup as setup_security from aiohttp_security import SessionIdentityPolicy -from .authz import DictionaryAuthorizationPolicy -from .handlers import configure_handlers -from .users import user_map +from demo.dictionary_auth.authz import DictionaryAuthorizationPolicy +from demo.dictionary_auth.handlers import configure_handlers +from demo.dictionary_auth.users import user_map def make_app(): diff --git a/docs/example.rst b/docs/example.rst index 7498add..b75a8ed 100644 --- a/docs/example.rst +++ b/docs/example.rst @@ -28,9 +28,9 @@ Simple example:: async def user_update_handler(request): # identity, asked_permission user_id = await identity_policy.identify(request) - identity = await auth_policy.authorized_user_id(user_id) - allowed = await request.auth_policy.permits(identity, - asked_permission) + identity = await auth_policy.authorized_userid(user_id) + allowed = await request.auth_policy.permits( + identity, asked_permission) if not allowed: # how is this pluggable as well? # ? return NotAllowedStream() @@ -56,7 +56,7 @@ Simple example:: # get it started srv = await loop.create_server(app.make_handler(), - '127.0.0.1', 8080) + '127.0.0.1', 8080) print("Server started at http://127.0.0.1:8080") return srv diff --git a/docs/example_db_auth.rst b/docs/example_db_auth.rst index ec87685..07d44ef 100644 --- a/docs/example_db_auth.rst +++ b/docs/example_db_auth.rst @@ -21,12 +21,14 @@ Launch these sql scripts to init database and fill it with sample data: ``psql template1 < demo/sql/init_db.sql`` -and then +and ``psql template1 < demo/sql/sample_data.sql`` -You will have two tables for storing users and their permissions +Now you have two tables: + +- for storing users +--------------+ | users | @@ -42,7 +44,7 @@ You will have two tables for storing users and their permissions | disabled | +--------------+ -and second table is permissions table: +- for storing their permissions +-----------------+ | permissions | @@ -63,12 +65,12 @@ First one should have these methods: *identify*, *remember* and *forget*. For second one: *authorized_userid* and *permits*. We will use built-in *SessionIdentityPolicy* and write our own database-based authorization policy. -In our example we will lookup database by user login and if present return +In our example we will lookup database by user login and if presents then return this identity:: async def authorized_userid(self, identity): - async with self.dbengine as conn: + async with self.dbengine as conn: where = sa.and_(db.users.c.login == identity, sa.not_(db.users.c.disabled)) query = db.users.count().where(where) @@ -79,7 +81,7 @@ this identity:: return None -For permission check we will fetch the user first, check if he is superuser +For permission checking we will fetch the user first, check if he is superuser (all permissions are allowed), otherwise check if permission is explicitly set for that user:: @@ -95,7 +97,7 @@ for that user:: user = await ret.fetchone() if user is not None: user_id = user[0] - is_superuser = user[4] + is_superuser = user[3] if is_superuser: return True @@ -140,37 +142,33 @@ Once we have all the code in place we can install it for our application:: Now we have authorization and can decorate every other view with access rights -based on permissions. This simple decorator (for class-based handlers) will -help to do that:: +based on permissions. There are already implemented two decorators:: - def require(permission): - def wrapper(f): - @functools.wraps(f) - async def wrapped(self, request): - has_perm = await permits(request, permission) - if not has_perm: - message = 'User has no permission {}'.format(permission) - raise web.HTTPForbidden(body=message.encode()) - return await f(self, request) - return wrapped - return wrapper + from aiohttp_security import has_permission, login_required - -For each view you need to protect just apply the decorator on it:: +For each view you need to protect - just apply the decorator on it:: class Web: - @require('protected') + @has_permission('protected') async def protected_page(self, request): response = web.Response(body=b'You are on protected page') return response +or:: -If someone will try to access this protected page he will see:: + class Web: + @login_required + async def logout(self, request): + response = web.Response(body=b'You have been logged out') + await forget(request, response) + return response - 403, User has no permission "protected" +If someone try to access that protected page he will see:: + + 403: Forbidden -The best part about it is that you can implement any logic you want until it +The best part of it - you can implement any logic you want until it follows the API conventions. Launch application @@ -178,7 +176,7 @@ Launch application For working with passwords there is a good library passlib_. Once you've created some users you want to check their credentials on login. Similar -function may do what you trying to accomplish:: +function may do what you are trying to accomplish:: from passlib.hash import sha256_crypt @@ -197,8 +195,8 @@ function may do what you trying to accomplish:: Final step is to launch your application:: - python demo/main.py + python demo/database_auth/main.py -Try to login with admin/moderator/user accounts (with *password* password) +Try to login with admin/moderator/user accounts (with **password** password) and access **/public** or **/protected** endpoints.