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:
parent
f9628b0ac1
commit
1679f6713b
|
@ -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">
|
||||
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>
|
||||
"""
|
||||
</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
|
||||
|
|
|
@ -9,8 +9,8 @@ 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):
|
||||
|
|
|
@ -1,30 +1,18 @@
|
|||
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>
|
||||
<head></head>
|
||||
<body>
|
||||
<p>{message}</p>
|
||||
<form action="/login" method="post">
|
||||
|
@ -36,7 +24,7 @@ index_template = dedent("""
|
|||
</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(
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,7 +65,7 @@ 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::
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue