added simplistic dictionary_auth example (#105)

This commit is contained in:
Devin Fee
2017-09-19 01:54:37 -07:00
committed by Andrew Svetlov
parent b0895806af
commit 1a9ab6424e
10 changed files with 184 additions and 0 deletions

34
demo/database_auth/db.py Normal file
View File

@@ -0,0 +1,34 @@
import sqlalchemy as sa
metadata = sa.MetaData()
users = sa.Table(
'users', metadata,
sa.Column('id', sa.Integer, nullable=False),
sa.Column('login', sa.String(256), nullable=False),
sa.Column('passwd', sa.String(256), nullable=False),
sa.Column('is_superuser', sa.Boolean, nullable=False,
server_default='FALSE'),
sa.Column('disabled', sa.Boolean, nullable=False,
server_default='FALSE'),
# indices
sa.PrimaryKeyConstraint('id', name='user_pkey'),
sa.UniqueConstraint('login', name='user_login_key'),
)
permissions = sa.Table(
'permissions', metadata,
sa.Column('id', sa.Integer, nullable=False),
sa.Column('user_id', sa.Integer, nullable=False),
sa.Column('perm_name', sa.String(64), nullable=False),
# indices
sa.PrimaryKeyConstraint('id', name='permission_pkey'),
sa.ForeignKeyConstraint(['user_id'], [users.c.id],
name='user_permission_fkey',
ondelete='CASCADE'),
)

View File

@@ -0,0 +1,67 @@
import asyncio
import sqlalchemy as sa
from aiohttp_security.abc import AbstractAuthorizationPolicy
from passlib.hash import sha256_crypt
from . import db
class DBAuthorizationPolicy(AbstractAuthorizationPolicy):
def __init__(self, dbengine):
self.dbengine = dbengine
@asyncio.coroutine
def authorized_userid(self, identity):
with (yield from self.dbengine) as conn:
where = sa.and_(db.users.c.login == identity,
sa.not_(db.users.c.disabled))
query = db.users.count().where(where)
ret = yield from conn.scalar(query)
if ret:
return identity
else:
return None
@asyncio.coroutine
def permits(self, identity, permission, context=None):
if identity is None:
return False
with (yield from self.dbengine) as conn:
where = sa.and_(db.users.c.login == identity,
sa.not_(db.users.c.disabled))
query = db.users.select().where(where)
ret = yield from conn.execute(query)
user = yield from ret.fetchone()
if user is not None:
user_id = user[0]
is_superuser = user[3]
if is_superuser:
return True
where = db.permissions.c.user_id == user_id
query = db.permissions.select().where(where)
ret = yield from conn.execute(query)
result = yield from ret.fetchall()
if ret is not None:
for record in result:
if record.perm_name == permission:
return True
return False
@asyncio.coroutine
def check_credentials(db_engine, username, password):
with (yield from db_engine) as conn:
where = sa.and_(db.users.c.login == username,
sa.not_(db.users.c.disabled))
query = db.users.select().where(where)
ret = yield from conn.execute(query)
user = yield from ret.fetchone()
if user is not None:
hash = user[2]
return sha256_crypt.verify(password, hash)
return False

View File

@@ -0,0 +1,95 @@
import asyncio
import functools
from aiohttp import web
from aiohttp_security import remember, forget, authorized_userid, permits
from .db_auth import check_credentials
def require(permission):
def wrapper(f):
@asyncio.coroutine
@functools.wraps(f)
def wrapped(self, 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(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>
"""
@asyncio.coroutine
def index(self, request):
username = yield from authorized_userid(request)
if username:
template = self.index_template.format(
message='Hello, {username}!'.format(username=username))
else:
template = self.index_template.format(message='You need to login')
response = web.Response(body=template.encode())
return response
@asyncio.coroutine
def login(self, request):
response = web.HTTPFound('/')
form = yield from request.post()
login = form.get('login')
password = form.get('password')
db_engine = request.app.db_engine
if (yield from check_credentials(db_engine, login, password)):
yield from remember(request, response, login)
return response
return web.HTTPUnauthorized(
body=b'Invalid username/password combination')
@require('public')
@asyncio.coroutine
def logout(self, request):
response = web.Response(body=b'You have been logged out')
yield from forget(request, response)
return response
@require('public')
@asyncio.coroutine
def internal_page(self, request):
response = web.Response(
body=b'This page is visible for all registered users')
return response
@require('protected')
@asyncio.coroutine
def protected_page(self, request):
response = web.Response(body=b'You are on protected page')
return response
def configure(self, app):
router = app.router
router.add_route('GET', '/', self.index, name='index')
router.add_route('POST', '/login', self.login, name='login')
router.add_route('GET', '/logout', self.logout, name='logout')
router.add_route('GET', '/public', self.internal_page, name='public')
router.add_route('GET', '/protected', self.protected_page,
name='protected')

View File

@@ -0,0 +1,61 @@
import asyncio
from aiohttp import web
from aiohttp_session import setup as setup_session
from aiohttp_session.redis_storage import RedisStorage
from aiohttp_security import setup as setup_security
from aiohttp_security import SessionIdentityPolicy
from aiopg.sa import create_engine
from aioredis import create_pool
from demo.db_auth import DBAuthorizationPolicy
from demo.handlers import Web
@asyncio.coroutine
def init(loop):
redis_pool = yield from create_pool(('localhost', 6379))
db_engine = yield from create_engine(user='aiohttp_security',
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))
setup_security(app,
SessionIdentityPolicy(),
DBAuthorizationPolicy(db_engine))
web_handlers = Web()
web_handlers.configure(app)
handler = app.make_handler()
srv = yield from loop.create_server(handler, '127.0.0.1', 8080)
print('Server started at http://127.0.0.1:8080')
return srv, app, handler
@asyncio.coroutine
def finalize(srv, app, handler):
sock = srv.sockets[0]
app.loop.remove_reader(sock.fileno())
sock.close()
yield from handler.finish_connections(1.0)
srv.close()
yield from srv.wait_closed()
yield from app.finish()
def main():
loop = asyncio.get_event_loop()
srv, app, handler = loop.run_until_complete(init(loop))
try:
loop.run_forever()
except KeyboardInterrupt:
loop.run_until_complete((finalize(srv, app, handler)))
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,5 @@
CREATE USER aiohttp_security WITH PASSWORD 'aiohttp_security';
DROP DATABASE IF EXISTS aiohttp_security;
CREATE DATABASE aiohttp_security;
ALTER DATABASE aiohttp_security OWNER TO aiohttp_security;
GRANT ALL PRIVILEGES ON DATABASE aiohttp_security TO aiohttp_security;

View File

@@ -0,0 +1,38 @@
-- create users table
CREATE TABLE IF NOT EXISTS users
(
id integer NOT NULL,
login character varying(256) NOT NULL,
passwd character varying(256) NOT NULL,
is_superuser boolean NOT NULL DEFAULT false,
disabled boolean NOT NULL DEFAULT false,
CONSTRAINT user_pkey PRIMARY KEY (id),
CONSTRAINT user_login_key UNIQUE (login)
);
-- and permissions for them
CREATE TABLE IF NOT EXISTS permissions
(
id integer NOT NULL,
user_id integer NOT NULL,
perm_name character varying(64) NOT NULL,
CONSTRAINT permission_pkey PRIMARY KEY (id),
CONSTRAINT user_permission_fkey FOREIGN KEY (user_id)
REFERENCES users (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE
);
-- insert some data
INSERT INTO users(id, login, passwd, is_superuser, disabled)
VALUES (1, 'admin', '$5$rounds=535000$2kqN9fxCY6Xt5/pi$tVnh0xX87g/IsnOSuorZG608CZDFbWIWBr58ay6S4pD', TRUE, FALSE);
INSERT INTO users(id, login, passwd, is_superuser, disabled)
VALUES (2, 'moderator', '$5$rounds=535000$2kqN9fxCY6Xt5/pi$tVnh0xX87g/IsnOSuorZG608CZDFbWIWBr58ay6S4pD', FALSE, FALSE);
INSERT INTO users(id, login, passwd, is_superuser, disabled)
VALUES (3, 'user', '$5$rounds=535000$2kqN9fxCY6Xt5/pi$tVnh0xX87g/IsnOSuorZG608CZDFbWIWBr58ay6S4pD', FALSE, FALSE);
INSERT INTO permissions(id, user_id, perm_name)
VALUES (1, 2, 'protected');
INSERT INTO permissions(id, user_id, perm_name)
VALUES (2, 2, 'public');
INSERT INTO permissions(id, user_id, perm_name)
VALUES (3, 3, 'public');