From 1a9ab6424ee09f8c028876668edd0225e37ee9e2 Mon Sep 17 00:00:00 2001 From: Devin Fee Date: Tue, 19 Sep 2017 01:54:37 -0700 Subject: [PATCH] added simplistic dictionary_auth example (#105) --- demo/{ => database_auth}/db.py | 0 demo/{ => database_auth}/db_auth.py | 0 demo/{ => database_auth}/handlers.py | 0 demo/{ => database_auth}/main.py | 0 demo/{ => database_auth}/sql/init_db.sql | 0 demo/{ => database_auth}/sql/sample_data.sql | 0 demo/dictionary_auth/authz.py | 34 ++++++ demo/dictionary_auth/handlers.py | 107 +++++++++++++++++++ demo/dictionary_auth/main.py | 33 ++++++ demo/dictionary_auth/users.py | 10 ++ 10 files changed, 184 insertions(+) rename demo/{ => database_auth}/db.py (100%) rename demo/{ => database_auth}/db_auth.py (100%) rename demo/{ => database_auth}/handlers.py (100%) rename demo/{ => database_auth}/main.py (100%) rename demo/{ => database_auth}/sql/init_db.sql (100%) rename demo/{ => database_auth}/sql/sample_data.sql (100%) create mode 100644 demo/dictionary_auth/authz.py create mode 100644 demo/dictionary_auth/handlers.py create mode 100644 demo/dictionary_auth/main.py create mode 100644 demo/dictionary_auth/users.py diff --git a/demo/db.py b/demo/database_auth/db.py similarity index 100% rename from demo/db.py rename to demo/database_auth/db.py diff --git a/demo/db_auth.py b/demo/database_auth/db_auth.py similarity index 100% rename from demo/db_auth.py rename to demo/database_auth/db_auth.py diff --git a/demo/handlers.py b/demo/database_auth/handlers.py similarity index 100% rename from demo/handlers.py rename to demo/database_auth/handlers.py diff --git a/demo/main.py b/demo/database_auth/main.py similarity index 100% rename from demo/main.py rename to demo/database_auth/main.py diff --git a/demo/sql/init_db.sql b/demo/database_auth/sql/init_db.sql similarity index 100% rename from demo/sql/init_db.sql rename to demo/database_auth/sql/init_db.sql diff --git a/demo/sql/sample_data.sql b/demo/database_auth/sql/sample_data.sql similarity index 100% rename from demo/sql/sample_data.sql rename to demo/database_auth/sql/sample_data.sql diff --git a/demo/dictionary_auth/authz.py b/demo/dictionary_auth/authz.py new file mode 100644 index 0000000..0f9baae --- /dev/null +++ b/demo/dictionary_auth/authz.py @@ -0,0 +1,34 @@ +from aiohttp_security.abc import AbstractAuthorizationPolicy + + +class DictionaryAuthorizationPolicy(AbstractAuthorizationPolicy): + def __init__(self, user_map): + super().__init__() + self.user_map = user_map + + async def authorized_userid(self, identity): + """Retrieve authorized user id. + Return the user_id of the user identified by the identity + or 'None' if no user exists related to the identity. + """ + if identity in self.user_map: + return identity + + async def permits(self, identity, permission, context=None): + """Check user permissions. + Return True if the identity is allowed the permission in the + current context, else return False. + """ + # pylint: disable=unused-argument + user = self.user_map.get(identity) + if not user: + return False + return permission in user.permissions + + +async def check_credentials(user_map, username, password): + user = user_map.get(username) + if not user: + return False + + return user.password == password diff --git a/demo/dictionary_auth/handlers.py b/demo/dictionary_auth/handlers.py new file mode 100644 index 0000000..2dffe55 --- /dev/null +++ b/demo/dictionary_auth/handlers.py @@ -0,0 +1,107 @@ +import asyncio +import functools +from textwrap import dedent + +from aiohttp import web + +from aiohttp_security import remember, forget, authorized_userid, permits + +from .authz import check_credentials + + +def require(permission): + def wrapper(f): + @asyncio.coroutine + @functools.wraps(f) + def wrapped(request): + has_perm = yield from permits(request, permission) + if not has_perm: + message = 'User has no permission {}'.format(permission) + raise web.HTTPForbidden(body=message.encode()) + return (yield from f(request)) + return wrapped + return wrapper + + +index_template = dedent(""" + + + + +

{message}

+
+ Login: + + Password: + + +
+ Logout + + """) + + +async def index(request): + username = await authorized_userid(request) + if username: + template = index_template.format( + message='Hello, {username}!'.format(username=username)) + else: + template = index_template.format(message='You need to login') + return web.Response( + text=template, + content_type='text/html', + ) + + +async def login(request): + response = web.HTTPFound('/') + form = await request.post() + username = form.get('username') + password = form.get('password') + + verified = await check_credentials(request.app.user_map, username, password) + if verified: + await remember(request, response, username) + return response + + return web.HTTPUnauthorized(body='Invalid username / password combination') + + +@require('public') +async def logout(request): + response = web.Response( + text='You have been logged out', + content_type='text/html', + ) + await forget(request, response) + return response + + +@require('public') +async def internal_page(request): + # pylint: disable=unused-argument + response = web.Response( + text='This page is visible for all registered users', + content_type='text/html', + ) + return response + + +@require('protected') +async def protected_page(request): + # pylint: disable=unused-argument + response = web.Response( + text='You are on protected page', + content_type='text/html', + ) + return response + + +def configure_handlers(app): + router = app.router + router.add_get('/', index, name='index') + router.add_post('/login', login, name='login') + router.add_get('/logout', logout, name='logout') + router.add_get('/public', internal_page, name='public') + router.add_get('/protected', protected_page, name='protected') diff --git a/demo/dictionary_auth/main.py b/demo/dictionary_auth/main.py new file mode 100644 index 0000000..b017ed5 --- /dev/null +++ b/demo/dictionary_auth/main.py @@ -0,0 +1,33 @@ +import base64 +from cryptography import fernet +from aiohttp import web +from aiohttp_session import setup as setup_session +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 + + +def make_app(): + app = web.Application() + app.user_map = user_map + configure_handlers(app) + + # secret_key must be 32 url-safe base64-encoded bytes + fernet_key = fernet.Fernet.generate_key() + secret_key = base64.urlsafe_b64decode(fernet_key) + + storage = EncryptedCookieStorage(secret_key, cookie_name='API_SESSION') + setup_session(app, storage) + + policy = SessionIdentityPolicy() + setup_security(app, policy, DictionaryAuthorizationPolicy(user_map)) + + return app + + +if __name__ == '__main__': + web.run_app(make_app(), port=9000) diff --git a/demo/dictionary_auth/users.py b/demo/dictionary_auth/users.py new file mode 100644 index 0000000..967b2bb --- /dev/null +++ b/demo/dictionary_auth/users.py @@ -0,0 +1,10 @@ +from collections import namedtuple + +User = namedtuple('User', ['username', 'password', 'permissions']) + +user_map = { + user.username: user for user in [ + User('devin', 'password', ('public',)), + User('jack', 'password', ('public', 'protected',)), + ] +}