Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create working demo #5

Merged
merged 8 commits into from
Aug 30, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I insist that password should be salted.
But I'm ok with storing the salt inside passwd field, http://pythonhosted.org/passlib/ is the best way to deal with passwords.
BTW where we do password check?

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