Skip to content

Commit

Permalink
Create working demo (#5)
Browse files Browse the repository at this point in the history
* Add docs example and update demo source code

* Fail with debug

* Fix tests

* Update sql scripts

* Update docs

* Add checking for the password

* Update documentation with launch/usage example

* Launch flake8 instead of pep8 and pyflakes
  • Loading branch information
bmwant authored and asvetlov committed Aug 30, 2016
1 parent fec22f9 commit 820dcc8
Show file tree
Hide file tree
Showing 17 changed files with 413 additions and 58 deletions.
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ install:
- pip install coveralls

script:
- pyflakes aiohttp_security tests
- pep8 aiohttp_security tests
- flake8 aiohttp_security tests
- coverage run --source=aiohttp_security setup.py test

after_success:
Expand Down
8 changes: 8 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ __ aiohttp_web_

Usage
-----
To install type ``pip install aiohttp_security``.
Launch ``make doc`` and see examples or look under **demo** directory for a
sample project.

Develop
-------

``pip install -r requirements-dev``


License
Expand Down
2 changes: 1 addition & 1 deletion aiohttp_security/cookies_identity.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Identity polocy for storing info directly into HTTP cookie.
"""Identity policy for storing info directly into HTTP cookie.
Use mostly for demonstration purposes, SessionIdentityPolicy is much
more handy.
Expand Down
2 changes: 1 addition & 1 deletion aiohttp_security/session_identity.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Identity policy for storing info into aiohttp_session session.
aiohttp_session.setup() should be called on application initialization
to conffigure aiohttp_session properly.
to configure aiohttp_session properly.
"""

import asyncio
Expand Down
1 change: 0 additions & 1 deletion demo/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
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('salt', sa.String(256), nullable=False),
sa.Column('is_superuser', sa.Boolean, nullable=False,
server_default='FALSE'),
sa.Column('disabled', sa.Boolean, nullable=False,
Expand Down
56 changes: 44 additions & 12 deletions demo/db_auth.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import asyncio

import sqlalchemy as sa

from aiohttp_security.abc import AbstractAuthorizationPolicy
from passlib.hash import sha256_crypt

from . import db

Expand All @@ -11,11 +13,11 @@ def __init__(self, dbengine):
self.dbengine = dbengine

@asyncio.coroutine
def authorized_user_id(self, identity):
def authorized_userid(self, identity):
with (yield from self.dbengine) as conn:
where = [db.users.c.login == identity,
not db.users.c.disabled]
query = db.users.count().where(sa.and_(*where))
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
Expand All @@ -24,12 +26,42 @@ def authorized_user_id(self, identity):

@asyncio.coroutine
def permits(self, identity, permission, context=None):
if identity is None:
return False

with (yield from self.dbengine) as conn:
where = [db.users.c.login == identity,
not db.users.c.disabled]
record = self.data.get(identity)
if record is not None:
# TODO: implement actual permission checker
if permission in record:
return True
return False
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
80 changes: 60 additions & 20 deletions demo/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,93 @@

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)
has_perm = yield from permits(request, permission)
if not has_perm:
raise web.HTTPForbidden()
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:
@require('public')
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):
pass
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

@require('public')
@asyncio.coroutine
def login(self, request):
pass
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

@require('protected')
return web.HTTPUnauthorized(
body=b'Invalid username/password combination')

@require('public')
@asyncio.coroutine
def logout(self, request):
pass
response = web.Response(body=b'You have been logged out')
yield from forget(request, response)
return response

@require('public')
@asyncio.coroutine
def public(self, request):
pass
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(self, request):
pass
def protected_page(self, request):
response = web.Response(body=b'You are on protected page')
return response

@asyncio.coroutine
def configure(self, app):
app.add_route('GET', '/', self.index, name='index')
app.add_route('POST', '/login', self.login, name='login')
app.add_route('POST', '/logout', self.logout, name='logout')
app.add_route('GET', '/public', self.public, name='public')
app.add_route('GET', '/protected', self.protected, name='protected')
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')
19 changes: 12 additions & 7 deletions demo/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@
@asyncio.coroutine
def init(loop):
redis_pool = yield from create_pool(('localhost', 6379))
dbengine = yield from create_engine(user='aiohttp_security',
password='aiohttp_security',
database='aiohttp_security',
host='127.0.0.1')
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(dbengine))
DBAuthorizationPolicy(db_engine))

web_handlers = Web()
yield from web_handlers.configure(app)
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")
print('Server started at http://127.0.0.1:8080')
return srv, app, handler


Expand All @@ -54,3 +55,7 @@ def main():
loop.run_forever()
except KeyboardInterrupt:
loop.run_until_complete((finalize(srv, app, handler)))


if __name__ == '__main__':
main()
5 changes: 5 additions & 0 deletions demo/sql/init_db.sql
Original file line number Diff line number Diff line change
@@ -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;
38 changes: 38 additions & 0 deletions demo/sql/sample_data.sql
Original file line number Diff line number Diff line change
@@ -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');
9 changes: 6 additions & 3 deletions docs/example.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
.. _aiohttp-security-example:

===============================================
How to Make a Simple Server With Authorization
==============================================
===============================================


.. code::python
Simple example::

import asyncio
from aiohttp import web
Expand All @@ -13,7 +16,7 @@ How to Make a Simple Server With Authorization
return web.Response(body=text.encode('utf-8'))

# option 2: auth at a higher level?
# set user_id and allowed in the wsgo handler
# set user_id and allowed in the wsgi handler
@protect('view_user')
@asyncio.coroutine
def user_handler(request):
Expand Down
Loading

0 comments on commit 820dcc8

Please sign in to comment.