Update docs and demo with login required, has permission (#128)

* Work on

* Update docs with login_required and has_permission
This commit is contained in:
Eduard Nabokov 2018-01-24 12:29:20 +02:00 committed by Andrew Svetlov
parent f9628b0ac1
commit 1679f6713b
6 changed files with 84 additions and 108 deletions

View File

@ -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 = """
<!doctype html>
<head>
</head>
<body>
<p>{message}</p>
<form action="/login" method="post">
Login:
<input type="text" name="login">
Password:
<input type="password" name="password">
<input type="submit" value="Login">
</form>
<a href="/logout">Logout</a>
</body>
"""
index_template = dedent("""
<!doctype html>
<head></head>
<body>
<p>{message}</p>
<form action="/login" method="post">
Login:
<input type="text" name="login">
Password:
<input type="password" name="password">
<input type="submit" value="Login">
</form>
<a href="/logout">Logout</a>
</body>
""")
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

View File

@ -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))

View File

@ -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("""
<!doctype html>
<head>
</head>
<body>
<p>{message}</p>
<form action="/login" method="post">
Login:
<input type="text" name="username">
Password:
<input type="password" name="password">
<input type="submit" value="Login">
</form>
<a href="/logout">Logout</a>
</body>
""")
<head></head>
<body>
<p>{message}</p>
<form action="/login" method="post">
Login:
<input type="text" name="username">
Password:
<input type="password" name="password">
<input type="submit" value="Login">
</form>
<a href="/logout">Logout</a>
</body>
""")
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(

View File

@ -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():

View File

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

View File

@ -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.