diff --git a/demo/simple_example_auth.py b/demo/simple_example_auth.py new file mode 100644 index 0000000..d1da45f --- /dev/null +++ b/demo/simple_example_auth.py @@ -0,0 +1,93 @@ +from aiohttp import web +from aiohttp_session import SimpleCookieStorage, session_middleware +from aiohttp_security import has_permission, \ + is_anonymous, remember, forget, \ + setup as setup_security, SessionIdentityPolicy +from aiohttp_security.abc import AbstractAuthorizationPolicy + + +# Demo authorization policy for only one user. +# User 'jack' has only 'listen' permission. +# For more complicated authorization policies see examples +# in the 'demo' directory. +class SimpleJack_AuthorizationPolicy(AbstractAuthorizationPolicy): + 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 == 'jack': + 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. + """ + return identity == 'jack' and permission in ('listen',) + + +async def handler_root(request): + is_logged = not await is_anonymous(request) + return web.Response(text=''' + Hello, I'm Jack, I'm {logged} logged in.

+ Log me in
+ Log me out

+ Check my permissions, + when i'm logged in and logged out.
+ Can I listen?
+ Can I speak?
+ '''.format( + logged='' if is_logged else 'NOT', + ), content_type='text/html') + + +async def handler_login_jack(request): + redirect_response = web.HTTPFound('/') + await remember(request, redirect_response, 'jack') + raise redirect_response + + +async def handler_logout(request): + redirect_response = web.HTTPFound('/') + await forget(request, redirect_response) + raise redirect_response + + +@has_permission('listen') +async def handler_listen(request): + return web.Response(body="I can listen!") + + +@has_permission('speak') +async def handler_speak(request): + return web.Response(body="I can speak!") + + +async def make_app(): + # + # WARNING!!! + # Never use SimpleCookieStorage on production!!! + # It’s highly insecure!!! + # + + # make app + middleware = session_middleware(SimpleCookieStorage()) + app = web.Application(middlewares=[middleware]) + + # add the routes + app.router.add_route('GET', '/', handler_root) + app.router.add_route('GET', '/login', handler_login_jack) + app.router.add_route('GET', '/logout', handler_logout) + app.router.add_route('GET', '/listen', handler_listen) + app.router.add_route('GET', '/speak', handler_speak) + + # set up policies + policy = SessionIdentityPolicy() + setup_security(app, policy, SimpleJack_AuthorizationPolicy()) + + return app + + +if __name__ == '__main__': + web.run_app(make_app(), port=9000) diff --git a/docs/example.rst b/docs/example.rst index b75a8ed..39c839b 100644 --- a/docs/example.rst +++ b/docs/example.rst @@ -7,63 +7,96 @@ How to Make a Simple Server With Authorization Simple example:: - import asyncio from aiohttp import web - - async def root_handler(request): - text = "Alive and kicking!" - return web.Response(body=text.encode('utf-8')) - - # option 2: auth at a higher level? - # set user_id and allowed in the wsgi handler - @protect('view_user') - async def user_handler(request): - name = request.match_info.get('name', "Anonymous") - text = "Hello, " + name - return web.Response(body=text.encode('utf-8')) + from aiohttp_session import SimpleCookieStorage, session_middleware + from aiohttp_security import has_permission, \ + is_anonymous, remember, forget, \ + setup as setup_security, SessionIdentityPolicy + from aiohttp_security.abc import AbstractAuthorizationPolicy - # option 3: super low - # wsgi doesn't do anything - async def user_update_handler(request): - # identity, asked_permission - user_id = await identity_policy.identify(request) - 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() - raise NotAllowedResponse() + # Demo authorization policy for only one user. + # User 'jack' has only 'listen' permission. + # For more complicated authorization policies see examples + # in the 'demo' directory. + class SimpleJack_AuthorizationPolicy(AbstractAuthorizationPolicy): + 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 == 'jack': + return identity - update_user() + 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. + """ + return identity == 'jack' and permission in ('listen',) - async def init(loop): - # set up identity and auth - auth_policy = DictionaryAuthorizationPolicy({'me': ('view_user',), - 'you': ('view_user', - 'edit_user',)}) - identity_policy = CookieIdentityPolicy() - auth = authorization_middleware(auth_policy, identity_policy) - # wsgi app - app = web.Application(loop=loop, middlewares=*auth) + async def handler_root(request): + is_logged = not await is_anonymous(request) + return web.Response(text=''' + Hello, I'm Jack, I'm {logged} logged in.

+ Log me in
+ Log me out

+ Check my permissions, + when i'm logged in and logged out.
+ Can I listen?
+ Can I speak?
+ '''.format( + logged='' if is_logged else 'NOT', + ), content_type='text/html') + + + async def handler_login_jack(request): + redirect_response = web.HTTPFound('/') + await remember(request, redirect_response, 'jack') + raise redirect_response + + + async def handler_logout(request): + redirect_response = web.HTTPFound('/') + await forget(request, redirect_response) + raise redirect_response + + + @has_permission('listen') + async def handler_listen(request): + return web.Response(body="I can listen!") + + + @has_permission('speak') + async def handler_speak(request): + return web.Response(body="I can speak!") + + + async def make_app(): + # + # WARNING!!! + # Never use SimpleCookieStorage on production!!! + # It’s highly insecure!!! + # + + # make app + middleware = session_middleware(SimpleCookieStorage()) + app = web.Application(middlewares=[middleware]) # add the routes - app.router.add_route('GET', '/', root_handler) - app.router.add_route('GET', '/{user}', user_handler) - app.router.add_route('GET', '/{user}/edit', user_update_handler) + app.router.add_route('GET', '/', handler_root) + app.router.add_route('GET', '/login', handler_login_jack) + app.router.add_route('GET', '/logout', handler_logout) + app.router.add_route('GET', '/listen', handler_listen) + app.router.add_route('GET', '/speak', handler_speak) - # get it started - srv = await loop.create_server(app.make_handler(), - '127.0.0.1', 8080) - print("Server started at http://127.0.0.1:8080") - return srv + # set up policies + policy = SessionIdentityPolicy() + setup_security(app, policy, SimpleJack_AuthorizationPolicy()) + + return app - loop = asyncio.get_event_loop() - loop.run_until_complete(init(loop)) - try: - loop.run_forever() - except KeyboardInterrupt: - pass # TODO put handler cleanup here + if __name__ == '__main__': + web.run_app(make_app(), port=9000)