Create working demo (#5)
* 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
This commit is contained in:
parent
fec22f971c
commit
820dcc8d93
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -9,7 +9,6 @@ users = sa.Table(
|
|||
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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -11,11 +13,11 @@ class DBAuthorizationPolicy(AbstractAuthorizationPolicy):
|
|||
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
|
||||
|
@ -24,12 +26,42 @@ class DBAuthorizationPolicy(AbstractAuthorizationPolicy):
|
|||
|
||||
@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
|
||||
|
|
|
@ -3,53 +3,93 @@ 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)
|
||||
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')
|
||||
@asyncio.coroutine
|
||||
def logout(self, request):
|
||||
pass
|
||||
return web.HTTPUnauthorized(
|
||||
body=b'Invalid username/password combination')
|
||||
|
||||
@require('public')
|
||||
@asyncio.coroutine
|
||||
def public(self, request):
|
||||
pass
|
||||
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(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
demo/main.py
19
demo/main.py
|
@ -16,22 +16,23 @@ from demo.handlers import Web
|
|||
@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
|
||||
|
||||
|
||||
|
@ -54,3 +55,7 @@ def main():
|
|||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
loop.run_until_complete((finalize(srv, app, handler)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -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;
|
|
@ -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');
|
|
@ -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
|
||||
|
@ -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):
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
.. _aiohttp-security-example-db-auth:
|
||||
|
||||
===========================================
|
||||
Permissions with PostgreSQL-based storage
|
||||
===========================================
|
||||
|
||||
Make sure that you have PostgreSQL and Redis servers up and running.
|
||||
If you want the full source code in advance or for comparison, check out
|
||||
the `demo source`_.
|
||||
|
||||
.. _demo source:
|
||||
https://github.com/aio-libs/aiohttp_security/tree/master/demo
|
||||
|
||||
.. _passlib:
|
||||
https://pythonhosted.org/passlib/
|
||||
|
||||
Database
|
||||
--------
|
||||
|
||||
Launch these sql scripts to init database and fill it with sample data:
|
||||
|
||||
``psql template1 < demo/sql/init_db.sql``
|
||||
|
||||
and then
|
||||
|
||||
``psql template1 < demo/sql/sample_data.sql``
|
||||
|
||||
|
||||
You will have two tables for storing users and their permissions
|
||||
|
||||
+--------------+
|
||||
| users |
|
||||
+==============+
|
||||
| id |
|
||||
+--------------+
|
||||
| login |
|
||||
+--------------+
|
||||
| passwd |
|
||||
+--------------+
|
||||
| is_superuser |
|
||||
+--------------+
|
||||
| disabled |
|
||||
+--------------+
|
||||
|
||||
and second table is permissions table:
|
||||
|
||||
+-----------------+
|
||||
| permissions |
|
||||
+=================+
|
||||
| id |
|
||||
+-----------------+
|
||||
| user_id |
|
||||
+-----------------+
|
||||
| permission_name |
|
||||
+-----------------+
|
||||
|
||||
|
||||
Writing policies
|
||||
----------------
|
||||
|
||||
You need to implement two entities: *IdentityPolicy* and *AuthorizationPolicy*.
|
||||
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
|
||||
this identity::
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
||||
For permission check 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::
|
||||
|
||||
@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[4]
|
||||
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
|
||||
|
||||
|
||||
Setup
|
||||
-----
|
||||
|
||||
Once we have all the code in place we can install it for our application::
|
||||
|
||||
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 .db_auth import DBAuthorizationPolicy
|
||||
|
||||
|
||||
@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')
|
||||
app = web.Application(loop=loop)
|
||||
setup_session(app, RedisStorage(redis_pool))
|
||||
setup_security(app,
|
||||
SessionIdentityPolicy(),
|
||||
DBAuthorizationPolicy(dbengine))
|
||||
return app
|
||||
|
||||
|
||||
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::
|
||||
|
||||
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
|
||||
|
||||
|
||||
For each view you need to protect just apply the decorator on it::
|
||||
|
||||
class Web:
|
||||
@require('protected')
|
||||
@asyncio.coroutine
|
||||
def protected_page(self, request):
|
||||
response = web.Response(body=b'You are on protected page')
|
||||
return response
|
||||
|
||||
|
||||
If someone will try to access this protected page he will see::
|
||||
|
||||
403, User has no permission "protected"
|
||||
|
||||
|
||||
The best part about it is that you can implement any logic you want until it
|
||||
follows the API conventions.
|
||||
|
||||
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::
|
||||
|
||||
from passlib.hash import sha256_crypt
|
||||
|
||||
@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
|
||||
|
||||
|
||||
Final step is to launch your application::
|
||||
|
||||
python demo/main.py
|
||||
|
||||
|
||||
Try to login with admin/moderator/user accounts (with *password* password)
|
||||
and access **/public** or **/protected** endpoints.
|
|
@ -20,6 +20,7 @@ Contents:
|
|||
usage
|
||||
reference
|
||||
example
|
||||
example_db_auth
|
||||
glossary
|
||||
|
||||
|
||||
|
|
|
@ -8,3 +8,6 @@ alabaster>=0.6.2
|
|||
pep257
|
||||
aiohttp_session>=0.4.0
|
||||
aiopg[sa]
|
||||
aioredis==0.2.8
|
||||
hiredis==0.2.0
|
||||
passlib==1.6.5
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import aiohttp
|
||||
import asyncio
|
||||
import gc
|
||||
import pytest
|
||||
import socket
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
import aiohttp
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
|
||||
|
@ -79,7 +81,8 @@ class Client:
|
|||
while path.startswith('/'):
|
||||
path = path[1:]
|
||||
url = self._url + path
|
||||
return self._session.get(url, **kwargs)
|
||||
resp = self._session.get(url, **kwargs)
|
||||
return resp
|
||||
|
||||
def post(self, path, **kwargs):
|
||||
while path.startswith('/'):
|
||||
|
@ -97,6 +100,7 @@ class Client:
|
|||
@pytest.yield_fixture
|
||||
def create_app_and_client(create_server, loop):
|
||||
client = None
|
||||
cookie_jar = aiohttp.CookieJar(loop=loop, unsafe=True)
|
||||
|
||||
@asyncio.coroutine
|
||||
def maker(*, server_params=None, client_params=None):
|
||||
|
@ -108,7 +112,11 @@ def create_app_and_client(create_server, loop):
|
|||
app, url = yield from create_server(**server_params)
|
||||
if client_params is None:
|
||||
client_params = {}
|
||||
client = Client(aiohttp.ClientSession(loop=loop, **client_params), url)
|
||||
|
||||
client = Client(
|
||||
aiohttp.ClientSession(loop=loop, cookie_jar=cookie_jar),
|
||||
url
|
||||
)
|
||||
return app, client
|
||||
|
||||
yield maker
|
||||
|
|
|
@ -34,7 +34,7 @@ def test_remember(create_app_and_client):
|
|||
app.router.add_route('GET', '/', handler)
|
||||
resp = yield from client.get('/')
|
||||
assert 200 == resp.status
|
||||
assert 'Andrew' == client.cookies['AIOHTTP_SECURITY'].value
|
||||
assert 'Andrew' == resp.cookies['AIOHTTP_SECURITY'].value
|
||||
yield from resp.release()
|
||||
|
||||
|
||||
|
@ -98,5 +98,6 @@ def test_forget(create_app_and_client):
|
|||
resp = yield from client.post('/logout')
|
||||
assert 200 == resp.status
|
||||
assert resp.url.endswith('/')
|
||||
assert '' == client.cookies['AIOHTTP_SECURITY'].value
|
||||
with pytest.raises(KeyError):
|
||||
_ = client.cookies['AIOHTTP_SECURITY'] # noqa
|
||||
yield from resp.release()
|
||||
|
|
|
@ -7,8 +7,8 @@ from aiohttp_security import (remember, forget,
|
|||
from aiohttp_security import setup as setup_security
|
||||
from aiohttp_security.session_identity import SessionIdentityPolicy
|
||||
from aiohttp_security.api import IDENTITY_KEY
|
||||
from aiohttp_session import (SimpleCookieStorage, session_middleware,
|
||||
get_session)
|
||||
from aiohttp_session import SimpleCookieStorage, get_session
|
||||
from aiohttp_session import setup as setup_session
|
||||
|
||||
|
||||
class Autz(AbstractAuthorizationPolicy):
|
||||
|
@ -27,7 +27,7 @@ def create_app_and_client2(create_app_and_client):
|
|||
@asyncio.coroutine
|
||||
def maker(*args, **kwargs):
|
||||
app, client = yield from create_app_and_client(*args, **kwargs)
|
||||
app.middlewares.append(session_middleware(SimpleCookieStorage()))
|
||||
setup_session(app, SimpleCookieStorage())
|
||||
setup_security(app, SessionIdentityPolicy(), Autz())
|
||||
return app, client
|
||||
return maker
|
||||
|
@ -82,6 +82,7 @@ def test_identify(create_app_and_client2):
|
|||
resp = yield from client.post('/')
|
||||
assert 200 == resp.status
|
||||
yield from resp.release()
|
||||
|
||||
resp = yield from client.get('/')
|
||||
assert 200 == resp.status
|
||||
yield from resp.release()
|
||||
|
@ -103,7 +104,7 @@ def test_forget(create_app_and_client2):
|
|||
|
||||
@asyncio.coroutine
|
||||
def logout(request):
|
||||
response = web.HTTPFound(location='/')
|
||||
response = web.HTTPFound('/')
|
||||
yield from forget(request, response)
|
||||
return response
|
||||
|
||||
|
@ -111,12 +112,14 @@ def test_forget(create_app_and_client2):
|
|||
app.router.add_route('GET', '/', index)
|
||||
app.router.add_route('POST', '/login', login)
|
||||
app.router.add_route('POST', '/logout', logout)
|
||||
|
||||
resp = yield from client.post('/login')
|
||||
assert 200 == resp.status
|
||||
assert resp.url.endswith('/')
|
||||
txt = yield from resp.text()
|
||||
assert 'Andrew' == txt
|
||||
yield from resp.release()
|
||||
|
||||
resp = yield from client.post('/logout')
|
||||
assert 200 == resp.status
|
||||
assert resp.url.endswith('/')
|
||||
|
|
Loading…
Reference in New Issue