From 68098b94a3d1f2a6ddee0ab85a14094be059bdb6 Mon Sep 17 00:00:00 2001 From: deniallugo Date: Thu, 6 Sep 2018 15:32:01 +0300 Subject: [PATCH] Merge remote-tracking branch 'upstream/master' --- .gitignore | 7 +- .travis.yml | 5 +- CHANGES.txt | 15 +- aiohttp_security/__init__.py | 8 +- aiohttp_security/abc.py | 13 ++ aiohttp_security/api.py | 73 +++++----- aiohttp_security/jwt_identity.py | 2 +- aiohttp_security/session_identity.py | 1 + demo/database_auth/handlers.py | 12 +- demo/database_auth/main.py | 2 +- demo/dictionary_auth/handlers.py | 10 +- demo/simple_example_auth.py | 6 +- docs/example.rst | 17 +-- docs/example_db_auth.rst | 10 +- docs/index.rst | 1 + docs/reference.rst | 78 +++++++++-- docs/usage.rst | 110 +++++++++++---- requirements-dev.txt | 13 +- setup.cfg | 4 + setup.py | 2 +- tests/test_auth_policy.py | 26 +++- tests/test_cookies_identity.py | 22 +-- tests/test_dict_autz.py | 200 +++++++++++++++++++-------- tests/test_jwt_identity.py | 12 +- tests/test_no_auth.py | 12 +- tests/test_no_identity.py | 12 +- tests/test_session_identity.py | 24 ++-- 27 files changed, 474 insertions(+), 223 deletions(-) create mode 100644 setup.cfg diff --git a/.gitignore b/.gitignore index 8af43e73..0aaf9534 100644 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,9 @@ docs/_build/ # PyBuilder target/ -coverage \ No newline at end of file +coverage +.pytest_cache + +#IDE stuff +.idea/* +.vscode/* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 17348ad5..0e029997 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,11 +28,12 @@ env: deploy: provider: pypi - user: andrew.svetlov + user: aio-libs-bot password: - secure: "JdBvuOBA/198ognVDOY/qZpIKGXfCx47725kyJo/SpQ3nP+x0GLZb3PMQkR0jfSWWkx6Sisk3vOCYsoWclPyPzp+o4ZpfM8yAjHNFmtbr+k+XJdUEApEiWb6/Y3g7DCyY2Qa/L8IYlyABPWrrJI/nld2sKm5kmhFpR/z3HfeFtINP6Ivp34dUOkeRP6kOvCi9d6GyWnvTRnhlybAnk/Ngrroh8XrbKHdDv0zkQkshF8+pmxVzwao4C6S5ld5cFXIYZHLBA9lNC3zgvOMuFeGPUEN9vab3q77MvaiMIuTC9QjcgIhfw3gabH2u7knqfFzqqzXMaVptx5z8o1JtsxMyYt5NVBqS4NPIljpZjaoS/CASHJlRxniJiYfjvjOtFEcfGMNtZj8ZYsGR0nuP2jwzgpEHHWIs4qL0Y8h9t7pGirxCuQcnY10sr+Y+JKaZNJsugNLgbqE2aaZUye5gjDcEj9WY8kKNZXucLP7c0McJuwPqplDEO4CQouMttcKSYkA0QoETmpAFqaXCaMs3p/glOoU2ZyHSH9mXWir69yo84ymb2NlGPMTAstXlv/g/oLmLMSq7lbl6cSUnO1/wxBGlyfv5AAq/75YUaqsgYofzN5CjUgA3m6NedvbWxLUJaxVQ7nduYGEQKDvGEBmzCNv6CdVRCjQ9J1xX3XzkVheQGc=" + secure: "fHbpT6AuRM+K6hg0nWT7ov/qJAAFJ7N5Mot1Z02QVHv9+XXJsqSmzJHMyv4FNWSO+IG9ulJ/wrVpQ/wr5S1FiCVJYpMFJP/71fqT7MbrgUg+ovbGrs1AfJHHQtVc91Az9Yl2nP+wzJCplJIuxO8IVjKHS87QxupzMHapo97ItYM6yvCNzIP+3JjvZyl5/ocqdUpl4KiS/tzXbiaBSlVgmI/013EbD5U36wcz2AAszTcBzKTDJh0BF4wr4brHnVPKr4gRSZPZRYduZ7WXh0rJt/aGyNGm9siYkKhuE+pzd/6vIbN3keKEhAjafCl4+Z3a0eL0ACyt8CBCBHf9/n4KYm+KPwLe3NYWKkO6qCJpZ+bMNfQInKiEoWJx9KDaKjdCVivlKY+abaJiF/thO4udunn3PfPz2O8MlkZRoTVqASN1sP60cULTlxfLi8x0RVqMKIHejNQi/AN8/4poCPFfFOOia/WQqq1pD45vJh8pNxsc6IEAjhHUgvMDnK0DBkEs4i2catZKc2YPEjgAkvplvTE4tH8Tzyj5EvMwM56h2zfByeKs9ojkvzyhPLhWq7d8JTPWPAyj72FsrpGm12cLU/E9g9KKj6Hg5E3F0+V2Zs7wXc+fT1ovC5/NRL2WpT2+k1wF/9Q7ZrbQ9InunHXYU7GJCFzRE8XlcRsBGPcR3Ls=" distributions: "sdist bdist_wheel" on: tags: true all_branches: true python: 3.6 + condition: $PYTHONASYNCIODEBUG = "" \ No newline at end of file diff --git a/CHANGES.txt b/CHANGES.txt index 42ac4f07..6c7d59af 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,10 +1,23 @@ Changes ======= +0.3.0 (2018-09-06) +------------------ + +- Deprecate ``login_required`` and ``has_permission`` decorators. + Use ``check_authorized`` and ``check_permission`` helper functions instead. + +- Bump supported ``aiohttp`` version to 3.0+ + +- Enable strong warnings mode for test suite, clean-up all deprecation + warnings. + +- Polish documentation + 0.2.0 (2017-11-17) ------------------ -- Add `is_anonymous`, `login_required`, `has_permission` helpers (#114) +- Add ``is_anonymous``, ``login_required``, ``has_permission`` helpers (#114) 0.1.2 (2017-10-17) ------------------ diff --git a/aiohttp_security/__init__.py b/aiohttp_security/__init__.py index d6eaccfb..1346b3b8 100644 --- a/aiohttp_security/__init__.py +++ b/aiohttp_security/__init__.py @@ -1,13 +1,13 @@ from .abc import (AbstractAuthorizationPolicy, AbstractIdentityPolicy, AbstractAuthenticationPolicy) from .api import (authorized_userid, forget, has_permission, is_anonymous, - login_required, permits, remember, setup, provide_user, - authenticate_user) + login_required, permits, remember, + authenticate_user, setup, check_authorized, check_permission) from .cookies_identity import CookiesIdentityPolicy from .session_identity import SessionIdentityPolicy from .jwt_identity import JWTIdentityPolicy -__version__ = '0.2.1' +__version__ = '0.3.0' __all__ = ('AbstractIdentityPolicy', 'AbstractAuthorizationPolicy', 'AbstractAuthenticationPolicy', @@ -17,4 +17,4 @@ 'authenticate_user', 'permits', 'setup', 'is_anonymous', 'login_required', 'has_permission', - 'provide_user') + 'check_authorized', 'check_permission') diff --git a/aiohttp_security/abc.py b/aiohttp_security/abc.py index 862abd88..be324aa6 100644 --- a/aiohttp_security/abc.py +++ b/aiohttp_security/abc.py @@ -1,5 +1,6 @@ import abc + # see http://plope.com/pyramid_auth_design_api_postmortem @@ -48,3 +49,15 @@ async def authorized_userid(self, identity): or 'None' if no user exists related to the identity. """ pass + + +class AbstractAuthenticationPolicy(metaclass=abc.ABCMeta): + + @abc.abstractmethod + async def authenticate_user(self, context, credentials): + """Retrieve authorized user. + + Return the user by the user authentication credentials + or 'None' if no user exists related to provided credentials. + """ + pass diff --git a/aiohttp_security/api.py b/aiohttp_security/api.py index 13d7c986..d8eb02ac 100644 --- a/aiohttp_security/api.py +++ b/aiohttp_security/api.py @@ -1,4 +1,5 @@ import enum +import warnings from aiohttp import web from aiohttp_security.abc import (AbstractIdentityPolicy, AbstractAuthorizationPolicy, @@ -61,13 +62,19 @@ async def authorized_userid(request): async def authenticate_user(credentials, context): - app = context.get('app') + """Authenticate user by credentials + + :param credentials: Authenticate credentials for AuthenticationPolicy + :param context: context can be dict or object, you can send request like context + :return: User + """ + app = context.get('app') or getattr(context, 'app', None) if app is None: raise Exception('No app in context') authenticate_policy = app.get(AUTH_KEY) if authenticate_policy is None: return None - user = await authenticate_policy.authentificate_user(credentials, context) + user = await authenticate_policy.authenticate_user(credentials, context) return user @@ -99,6 +106,15 @@ async def is_anonymous(request): return False +async def check_authorized(request): + """Checker that raises HTTPUnauthorized for anonymous users. + """ + userid = await authorized_userid(request) + if userid is None: + raise web.HTTPUnauthorized() + return userid + + def login_required(fn): """Decorator that restrict access only for authorized users. @@ -118,43 +134,34 @@ async def wrapped(*args, **kwargs): "or `def handler(self, request)`.") raise RuntimeError(msg) - userid = await authorized_userid(request) - if userid is None: - raise web.HTTPUnauthorized - - ret = await fn(*args, **kwargs, user=userid) - return ret + await check_authorized(request) + return await fn(*args, **kwargs) + warnings.warn("login_required decorator is deprecated, " + "use check_authorized instead", + DeprecationWarning) return wrapped -def provide_user(fn): - """Decorator that add user to function with request +async def check_permission(request, permission, context=None): + """Checker that passes only to authoraised users with given permission. - Decorator add extra argument "user" + If user is not authorized - raises HTTPUnauthorized, + if user is authorized and does not have permission - + raises HTTPForbidden. """ - @wraps(fn) - async def wrapped(*args, **kwargs): - request = args[-1] - if not isinstance(request, web.BaseRequest): - msg = ("Incorrect decorator usage. " - "Expecting `def handler(request)` " - "or `def handler(self, request)`.") - raise RuntimeError(msg) - - userid = await authorized_userid(request) - ret = await fn(*args, **kwargs, user=userid) - return ret - - return wrapped + await check_authorized(request) + allowed = await permits(request, permission, context) + if not allowed: + raise web.HTTPForbidden() def has_permission( permission, context=None, ): - """Decorator that restrict access only for authorized users + """Decorator that restricts access only for authorized users with correct permissions. If user is not authorized - raises HTTPUnauthorized, @@ -174,18 +181,14 @@ async def wrapped(*args, **kwargs): "or `def handler(self, request)`.") raise RuntimeError(msg) - userid = await authorized_userid(request) - if userid is None: - raise web.HTTPUnauthorized - - allowed = await permits(request, permission, context) - if not allowed: - raise web.HTTPForbidden - ret = await fn(*args, **kwargs, user=userid) - return ret + await check_permission(request, permission, context) + return await fn(*args, **kwargs) return wrapped + warnings.warn("has_permission decorator is deprecated, " + "use check_permission instead", + DeprecationWarning) return wrapper diff --git a/aiohttp_security/jwt_identity.py b/aiohttp_security/jwt_identity.py index 1df01dfe..bd6ffe7e 100644 --- a/aiohttp_security/jwt_identity.py +++ b/aiohttp_security/jwt_identity.py @@ -35,7 +35,7 @@ async def identify(self, request): identity = jwt.decode(token, self.secret, - algorithm=self.algorithm) + algorithms=[self.algorithm]) return identity async def remember(self, *args, **kwargs): # pragma: no cover diff --git a/aiohttp_security/session_identity.py b/aiohttp_security/session_identity.py index 13d197a2..08fb1570 100644 --- a/aiohttp_security/session_identity.py +++ b/aiohttp_security/session_identity.py @@ -6,6 +6,7 @@ try: from aiohttp_session import get_session + HAS_AIOHTTP_SESSION = True except ImportError: # pragma: no cover HAS_AIOHTTP_SESSION = False diff --git a/demo/database_auth/handlers.py b/demo/database_auth/handlers.py index 3d103320..0cc94845 100644 --- a/demo/database_auth/handlers.py +++ b/demo/database_auth/handlers.py @@ -4,7 +4,7 @@ from aiohttp_security import ( remember, forget, authorized_userid, - has_permission, login_required, + check_permission, check_authorized, ) from .db_auth import check_credentials @@ -45,25 +45,25 @@ async def login(self, request): db_engine = request.app.db_engine if await check_credentials(db_engine, login, password): await remember(request, response, login) - return response + raise response - return web.HTTPUnauthorized( + raise web.HTTPUnauthorized( body=b'Invalid username/password combination') - @login_required async def logout(self, request): + await check_authorized(request) response = web.Response(body=b'You have been logged out') await forget(request, response) return response - @has_permission('public') async def internal_page(self, request): + await check_permission(request, 'public') response = web.Response( body=b'This page is visible for all registered users') return response - @has_permission('protected') async def protected_page(self, request): + await check_permission(request, 'protected') response = web.Response(body=b'You are on protected page') return response diff --git a/demo/database_auth/main.py b/demo/database_auth/main.py index 43da500d..822df44b 100644 --- a/demo/database_auth/main.py +++ b/demo/database_auth/main.py @@ -19,7 +19,7 @@ async def init(loop): password='aiohttp_security', database='aiohttp_security', host='127.0.0.1') - app = web.Application(loop=loop) + app = web.Application() app.db_engine = db_engine setup_session(app, RedisStorage(redis_pool)) setup_security(app, diff --git a/demo/dictionary_auth/handlers.py b/demo/dictionary_auth/handlers.py index 5816e8df..6c19babd 100644 --- a/demo/dictionary_auth/handlers.py +++ b/demo/dictionary_auth/handlers.py @@ -4,7 +4,7 @@ from aiohttp_security import ( remember, forget, authorized_userid, - has_permission, login_required, + check_permission, check_authorized, ) from .authz import check_credentials @@ -55,8 +55,8 @@ async def login(request): return web.HTTPUnauthorized(body='Invalid username / password combination') -@login_required async def logout(request): + await check_authorized(request) response = web.Response( text='You have been logged out', content_type='text/html', @@ -65,9 +65,8 @@ async def logout(request): return response -@has_permission('public') async def internal_page(request): - # pylint: disable=unused-argument + await check_permission(request, 'public') response = web.Response( text='This page is visible for all registered users', content_type='text/html', @@ -75,9 +74,8 @@ async def internal_page(request): return response -@has_permission('protected') async def protected_page(request): - # pylint: disable=unused-argument + await check_permission(request, 'protected') response = web.Response( text='You are on protected page', content_type='text/html', diff --git a/demo/simple_example_auth.py b/demo/simple_example_auth.py index d1da45f2..5b4b82d9 100644 --- a/demo/simple_example_auth.py +++ b/demo/simple_example_auth.py @@ -1,6 +1,6 @@ from aiohttp import web from aiohttp_session import SimpleCookieStorage, session_middleware -from aiohttp_security import has_permission, \ +from aiohttp_security import check_permission, \ is_anonymous, remember, forget, \ setup as setup_security, SessionIdentityPolicy from aiohttp_security.abc import AbstractAuthorizationPolicy @@ -54,13 +54,13 @@ async def handler_logout(request): raise redirect_response -@has_permission('listen') async def handler_listen(request): + await check_permission(request, 'listen') return web.Response(body="I can listen!") -@has_permission('speak') async def handler_speak(request): + await check_permission(request, 'speak') return web.Response(body="I can speak!") diff --git a/docs/example.rst b/docs/example.rst index 39c839b3..945d386a 100644 --- a/docs/example.rst +++ b/docs/example.rst @@ -9,7 +9,7 @@ Simple example:: from aiohttp import web from aiohttp_session import SimpleCookieStorage, session_middleware - from aiohttp_security import has_permission, \ + from aiohttp_security import check_permission, \ is_anonymous, remember, forget, \ setup as setup_security, SessionIdentityPolicy from aiohttp_security.abc import AbstractAuthorizationPolicy @@ -63,13 +63,13 @@ Simple example:: raise redirect_response - @has_permission('listen') async def handler_listen(request): + await check_permission(request, 'listen') return web.Response(body="I can listen!") - @has_permission('speak') async def handler_speak(request): + await check_permission(request, 'speak') return web.Response(body="I can speak!") @@ -85,11 +85,12 @@ Simple example:: app = web.Application(middlewares=[middleware]) # add the routes - app.router.add_route('GET', '/', handler_root) - app.router.add_route('GET', '/login', handler_login_jack) - app.router.add_route('GET', '/logout', handler_logout) - app.router.add_route('GET', '/listen', handler_listen) - app.router.add_route('GET', '/speak', handler_speak) + app.add_routes([ + web.get('/', handler_root), + web.get('/login', handler_login_jack), + web.get('/logout', handler_logout), + web.get('/listen', handler_listen), + web.get('/speak', handler_speak)]) # set up policies policy = SessionIdentityPolicy() diff --git a/docs/example_db_auth.rst b/docs/example_db_auth.rst index 07d44efd..5e9463af 100644 --- a/docs/example_db_auth.rst +++ b/docs/example_db_auth.rst @@ -133,7 +133,7 @@ Once we have all the code in place we can install it for our application:: password='aiohttp_security', database='aiohttp_security', host='127.0.0.1') - app = web.Application(loop=loop) + app = web.Application() setup_session(app, RedisStorage(redis_pool)) setup_security(app, SessionIdentityPolicy(), @@ -142,23 +142,23 @@ 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. There are already implemented two decorators:: +based on permissions. There are already implemented two helpers:: - from aiohttp_security import has_permission, login_required + from aiohttp_security import check_authorized, check_permission For each view you need to protect - just apply the decorator on it:: class Web: - @has_permission('protected') async def protected_page(self, request): + await check_permission(request, 'protected') response = web.Response(body=b'You are on protected page') return response or:: class Web: - @login_required async def logout(self, request): + await check_authorized(request) response = web.Response(body=b'You have been logged out') await forget(request, response) return response diff --git a/docs/index.rst b/docs/index.rst index f2ff4879..17591041 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,6 +3,7 @@ aiohttp_security The library provides security for :ref:`aiohttp.web`. +The current version is |version| Contents -------- diff --git a/docs/reference.rst b/docs/reference.rst index 788e372a..f3297e08 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -13,6 +13,19 @@ Public API functions ==================== +.. function:: setup(app, identity_policy, autz_policy) + + Setup :mod:`aiohttp` application with security policies. + + :param app: aiohttp :class:`aiohttp.web.Application` instance. + + :param identity_policy: indentification policy, an + :class:`AbstractIdentityPolicy` instance. + + :param autz_policy: authorization policy, an + :class:`AbstractAuthorizationPolicy` instance. + + .. coroutinefunction:: remember(request, response, identity, **kwargs) Remember *identity* in *response*, e.g. by storing a cookie or @@ -50,6 +63,41 @@ Public API functions descendants like :class:`aiohttp.web.Response`. +.. coroutinefunction:: check_authorized(request) + + Checker that doesn't pass if user is not authorized by *request*. + + :param request: :class:`aiohttp.web.Request` object. + + :return str: authorized user ID if success + + :raise: :class:`aiohttp.web.HTTPUnauthorized` for anonymous users. + + Usage:: + + async def handler(request): + await check_authorized(request) + # this line is never executed for anonymous users + + +.. coroutinefunction:: check_permission(request, permission) + + Checker that doesn't pass if user has no requested permission. + + :param request: :class:`aiohttp.web.Request` object. + + :raise: :class:`aiohttp.web.HTTPUnauthorized` for anonymous users. + + :raise: :class:`aiohttp.web.HTTPForbidden` if user is + authorized but has no access rights. + + Usage:: + + async def handler(request): + await check_permission(request, 'read') + # this line is never executed if a user has no read permission + + .. coroutinefunction:: authorized_userid(request) Retrieve :term:`userid`. @@ -78,7 +126,8 @@ Public API functions :param request: :class:`aiohttp.web.Request` object. - :param permission: Requested :term:`permission`. :class:`str` or :class:`enum.Enum` object. + :param permission: Requested :term:`permission`. :class:`str` or + :class:`enum.Enum` object. :param context: additional object may be passed into :meth:`AbstractAuthorizationPolicy.permission` @@ -92,7 +141,8 @@ Public API functions Checks if user is anonymous user. - Return ``True`` if user is not remembered in request, otherwise returns ``False``. + Return ``True`` if user is not remembered in request, otherwise + returns ``False``. :param request: :class:`aiohttp.web.Request` object. @@ -103,29 +153,27 @@ Public API functions Raises :class:`aiohttp.web.HTTPUnauthorized` if user is not authorized. + .. deprecated:: 0.3 + + Use :func:`check_authorized` async function. + .. decorator:: has_permission(permission) Decorator for handlers that checks if user is authorized and has correct permission. - Raises :class:`aiohttp.web.HTTPUnauthorized` if user is not authorized. - Raises :class:`aiohttp.web.HTTPForbidden` if user is authorized but has no access rights. - - :param str permission: requested :term:`permission`. - - -.. function:: setup(app, identity_policy, autz_policy) + Raises :class:`aiohttp.web.HTTPUnauthorized` if user is not + authorized. - Setup :mod:`aiohttp` application with security policies. + Raises :class:`aiohttp.web.HTTPForbidden` if user is + authorized but has no access rights. - :param app: aiohttp :class:`aiohttp.web.Application` instance. + :param str permission: requested :term:`permission`. - :param identity_policy: indentification policy, an - :class:`AbstractIdentityPolicy` instance. + .. deprecated:: 0.3 - :param autz_policy: authorization policy, an - :class:`AbstractAuthorizationPolicy` instance. + Use :func:`check_authorized` async function. Abstract policies diff --git a/docs/usage.rst b/docs/usage.rst index 9fe28c51..a772a4b3 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -11,22 +11,30 @@ First of all, what is *aiohttp_security* about? -*aiohttp_security* is a set of public API functions as well as a reference standard for implementation details for securing access to assets served by a wsgi server. -Assets are secured using authentication and authorization as explained below. *aiohttp_security* is part of the *aio_libs* project which takes advantage of asynchronous -processing using Python's asyncio library. +*aiohttp-security* is a set of public API functions as well as a +reference standard for implementation details for securing access to +assets served by a wsgi server. + +Assets are secured using authentication and authorization as explained +below. *aiohttp-security* is part of the +`aio-libs `_ project which takes advantage +of asynchronous processing using Python's asyncio library. Public API ========== -The API is agnostic to the low level implementation details such that all client code only needs to implement the endpoints as provided by the API (instead of calling policy -code directly (see explanation below)). +The API is agnostic to the low level implementation details such that +all client code only needs to implement the endpoints as provided by +the API (instead of calling policy code directly (see explanation +below)). Via the API an application can: (i) remember a user in a local session (:func:`remember`), (ii) forget a user in a local session (:func:`forget`), -(iii) retrieve the :term:`userid` (:func:`authorized_userid`) of a remembered user from an :term:`identity` (discussed below), and +(iii) retrieve the :term:`userid` (:func:`authorized_userid`) of a + remembered user from an :term:`identity` (discussed below), and (iv) check the :term:`permission` of a remembered user (:func:`permits`). The library internals are built on top of two concepts: @@ -34,52 +42,100 @@ The library internals are built on top of two concepts: 1) :term:`authentication`, and 2) :term:`authorization`. -There are abstract base classes for both types as well as several pre-built implementations -that are shipped with the library. However, the end user is free to build their own implementations. -The library comes with two pre-built identity policies; one that uses cookies, and one that uses sessions [#f1]_. -It is envisioned that in most use cases developers will use one of the provided identity policies (Cookie or Session) and -implement their own authorization policy. +There are abstract base classes for both types as well as several +pre-built implementations that are shipped with the library. However, +the end user is free to build their own implementations. + +The library comes with two pre-built identity policies; one that uses +cookies, and one that uses sessions [#f1]_. It is envisioned that in +most use cases developers will use one of the provided identity +policies (Cookie or Session) and implement their own authorization +policy. The workflow is as follows: 1) User is authenticated. This has to be implemented by the developer. -2) Once user is authenticated an identity string has to be created for that user. This has to be implemented by the developer. -3) The identity string is passed to the Identity Policy's remember method and the user is now remembered (Cookie or Session if using built-in). *Only once a user is remembered can the other API methods:* :func:`permits`, :func:`forget`, *and* :func:`authorized_userid` *be invoked* . -4) If the user tries to access a restricted asset the :func:`permits` method is called. Usually assets are protected using the **@aiohttp_security.has_permission(**\ *permission*\ **)** decorator. This should return True if permission is granted. +2) Once user is authenticated an identity string has to be created for + that user. This has to be implemented by the developer. +3) The identity string is passed to the Identity Policy's remember + method and the user is now remembered (Cookie or Session if using + built-in). *Only once a user is remembered can the other API + methods:* :func:`permits`, :func:`forget`, *and* + :func:`authorized_userid` *be invoked* . +4) If the user tries to access a restricted asset the :func:`permits` + method is called. Usually assets are protected using the + :func:`check_permission` helper. This should return True if + permission is granted. - The :func:`permits` method is implemented by the developer as part of the :class:`AbstractAuthorizationPolicy` and passed to the application at runtime via setup. - In addition a :func:`@aiohttp_security.login_required decorator` also exists that requires no permissions (i.e. doesn't call :func:`permits` method) but only requires that the user is remembered (i.e. authenticated/logged in). +The :func:`permits` method is implemented by the developer as part of +the :class:`AbstractAuthorizationPolicy` and passed to the +application at runtime via setup. + +In addition a :func:`check_authorized` also +exists that requires no permissions (i.e. doesn't call :func:`permits` +method) but only requires that the user is remembered +(i.e. authenticated/logged in). Authentication ============== -Authentication is the process where a user's identity is verified. It confirms who the user is. This is traditionally done using a user name and password (note: this is not the only way). -A authenticated user has no access rights, rather an authenticated user merely confirms that the user exists and that the user is who they say they are. -In *aiohttp_security* the developer is responsible for their own authentication mechanism. *aiohttp_security* only requires that the authentication result in a identity string which -corresponds to a user's id in the underlying system. +Authentication is the process where a user's identity is verified. It +confirms who the user is. This is traditionally done using a user name +and password (note: this is not the only way). + +A authenticated user has no access rights, rather an authenticated +user merely confirms that the user exists and that the user is who +they say they are. - *Note:* :term:`identity` is a string that is shared between the browser and the server. Therefore it is recommended that a random string such as a uuid or hash is used rather than things like a database primary key, user login/email, etc. +In *aiohttp_security* the developer is responsible for their own +authentication mechanism. *aiohttp_security* only requires that the +authentication result in a identity string which corresponds to a +user's id in the underlying system. + +.. note:: + + :term:`identity` is a string that is shared between the browser and + the server. Therefore it is recommended that a random string + such as a uuid or hash is used rather than things like a + database primary key, user login/email, etc. Identity Policy -============== +=============== -Once a user is authenticated the *aiohttp_security* API is invoked for storing, retrieving, and removing a user's :term:`identity`. This is accommplished via AbstractIdentityPolicy's :func:`remember`, :func:`identify`, and :func:`forget` methods. The Identity Policy is therefore the mechanism by which a authenticated user is persisted in the system. +Once a user is authenticated the *aiohttp_security* API is invoked for +storing, retrieving, and removing a user's :term:`identity`. This is +accommplished via AbstractIdentityPolicy's :func:`remember`, +:func:`identify`, and :func:`forget` methods. The Identity Policy is +therefore the mechanism by which a authenticated user is persisted in +the system. -*aiohttp_security* has two built in identity policy's for this purpose. :term:`CookiesIdentityPolicy` that uses cookies and :term:`SessionIdentityPolicy` that uses sessions via :term:`aiohttp.session` library. +*aiohttp_security* has two built in identity policy's for this +purpose. :class:`CookiesIdentityPolicy` that uses cookies and +:class:`SessionIdentityPolicy` that uses sessions via +``aiohttp-session`` library. Authorization ============== -Once a user is authenticated (see above) it means that the user has an :term:`identity`. This :term:`identity` can now be used for checking access rights or :term:`permission` using a :term:`authorization` policy. +Once a user is authenticated (see above) it means that the user has an +:term:`identity`. This :term:`identity` can now be used for checking +access rights or :term:`permission` using a :term:`authorization` +policy. + The authorization policy's :func:`permits()` method is used for this purpose. -When :class:`aiohttp.web.Request` has an :term:`identity` it means the user has been authenticated and therefore has an :term:`identity` that can be checked by the :term:`authorization` policy. +When :class:`aiohttp.web.Request` has an :term:`identity` it means the +user has been authenticated and therefore has an :term:`identity` that +can be checked by the :term:`authorization` policy. - As noted above, :term:`identity` is a string that is shared between the browser and the server. Therefore it is recommended that a random string such as a uuid or hash is used rather than things like a database primary key, user login/email, etc. +As noted above, :term:`identity` is a string that is shared between +the browser and the server. Therefore it is recommended that a +random string such as a uuid or hash is used rather than things like +a database primary key, user login/email, etc. .. rubric:: Footnotes diff --git a/requirements-dev.txt b/requirements-dev.txt index 9e042a09..0eef8a3b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,17 +1,18 @@ -e . flake8==3.5.0 -pytest==3.6.1 +async-timeout==3.0 +pytest==3.7.4 pytest-cov==2.5.1 pytest-mock==1.10.0 coverage==4.5.1 -sphinx==1.7.5 +sphinx==1.7.8 pep257==0.7.0 aiohttp-session==2.5.1 -aiopg[sa]==0.14.0 +aiopg[sa]==0.15.0 aioredis==1.1.0 hiredis==0.2.0 passlib==1.7.1 -cryptography==2.2.2 -aiohttp==3.3.2 +cryptography==2.3.1 +aiohttp==3.4.2 pytest-aiohttp==0.3.0 -pyjwt==1.6.4 \ No newline at end of file +pyjwt==1.6.4 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..8d6446c1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[tool:pytest] +testpaths = tests +filterwarnings= + error diff --git a/setup.py b/setup.py index fa926573..22179511 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ def read(f): return open(os.path.join(os.path.dirname(__file__), f)).read().strip() -install_requires = ['aiohttp>=0.18'] +install_requires = ['aiohttp>=3.0.0'] tests_require = install_requires + ['pytest'] extras_require = {'session': 'aiohttp-session'} diff --git a/tests/test_auth_policy.py b/tests/test_auth_policy.py index 78e1e3f5..5a9e4da8 100644 --- a/tests/test_auth_policy.py +++ b/tests/test_auth_policy.py @@ -8,7 +8,7 @@ class Auth(AbstractAuthenticationPolicy): - async def authentificate_user(self, credentials, context=None): + async def authenticate_user(self, credentials, context=None): username = credentials.get('username') password = credentials.get('password') @@ -33,17 +33,35 @@ async def authorized_userid(self, identity): return None -async def test_authenticate_user(loop, test_client): +async def test_authenticate_user(loop, aiohttp_client): async def login(request): context = {'app': request.app} credentials = await request.json() user = await authenticate_user(credentials, context) return web.Response(text=user) - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz(), Auth()) app.router.add_route('POST', '/login', login) - client = await test_client(app) + client = await aiohttp_client(app) + + resp = await client.post('/login', + json={'username': 'UserID', 'password': 'pass'}) + assert 200 == resp.status + txt = await resp.text() + assert 'Andrew' == txt + + +async def test_authenticate_user_by_request(loop, aiohttp_client): + async def login(request): + credentials = await request.json() + user = await authenticate_user(credentials, request) + return web.Response(text=user) + + app = web.Application() + _setup(app, CookiesIdentityPolicy(), Autz(), Auth()) + app.router.add_route('POST', '/login', login) + client = await aiohttp_client(app) resp = await client.post('/login', json={'username': 'UserID', 'password': 'pass'}) diff --git a/tests/test_cookies_identity.py b/tests/test_cookies_identity.py index 8d739e6a..a003690d 100644 --- a/tests/test_cookies_identity.py +++ b/tests/test_cookies_identity.py @@ -15,23 +15,23 @@ async def authorized_userid(self, identity): pass -async def test_remember(loop, test_client): +async def test_remember(loop, aiohttp_client): async def handler(request): response = web.Response() await remember(request, response, 'Andrew') return response - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz()) app.router.add_route('GET', '/', handler) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.get('/') assert 200 == resp.status assert 'Andrew' == resp.cookies['AIOHTTP_SECURITY'].value -async def test_identify(loop, test_client): +async def test_identify(loop, aiohttp_client): async def create(request): response = web.Response() @@ -44,11 +44,11 @@ async def check(request): assert 'Andrew' == user_id return web.Response() - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz()) app.router.add_route('GET', '/', check) app.router.add_route('POST', '/', create) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.post('/') assert 200 == resp.status await resp.release() @@ -56,7 +56,7 @@ async def check(request): assert 200 == resp.status -async def test_forget(loop, test_client): +async def test_forget(loop, aiohttp_client): async def index(request): return web.Response() @@ -64,19 +64,19 @@ async def index(request): async def login(request): response = web.HTTPFound(location='/') await remember(request, response, 'Andrew') - return response + raise response async def logout(request): response = web.HTTPFound(location='/') await forget(request, response) - return response + raise response - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz()) app.router.add_route('GET', '/', index) app.router.add_route('POST', '/login', login) app.router.add_route('POST', '/logout', logout) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.post('/login') assert 200 == resp.status assert str(resp.url).endswith('/') diff --git a/tests/test_dict_autz.py b/tests/test_dict_autz.py index 83300c1f..30d86e45 100644 --- a/tests/test_dict_autz.py +++ b/tests/test_dict_autz.py @@ -1,10 +1,12 @@ import enum +import pytest from aiohttp import web from aiohttp_security import setup as _setup from aiohttp_security import (AbstractAuthorizationPolicy, authorized_userid, forget, has_permission, is_anonymous, - login_required, permits, remember, provide_user) + login_required, permits, remember, + check_authorized, check_permission) from aiohttp_security.cookies_identity import CookiesIdentityPolicy @@ -23,23 +25,22 @@ async def authorized_userid(self, identity): return None -async def test_authorized_userid(loop, test_client): - +async def test_authorized_userid(loop, aiohttp_client): async def login(request): response = web.HTTPFound(location='/') await remember(request, response, 'UserID') - return response + raise response async def check(request): userid = await authorized_userid(request) assert 'Andrew' == userid return web.Response(text=userid) - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz()) app.router.add_route('GET', '/', check) app.router.add_route('POST', '/login', login) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.post('/login') assert 200 == resp.status @@ -47,23 +48,22 @@ async def check(request): assert 'Andrew' == txt -async def test_provide_user(loop, test_client): - +async def test_provide_user(loop, aiohttp_client): async def login(request): response = web.HTTPFound(location='/') await remember(request, response, 'UserID') - return response + raise response - @provide_user - async def check(request, user): + async def check(request): + user = await authorized_userid(request) assert 'Andrew' == user return web.Response(text=user) - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz()) app.router.add_route('GET', '/', check) app.router.add_route('POST', '/login', login) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.post('/login') assert 200 == resp.status @@ -71,23 +71,22 @@ async def check(request, user): assert 'Andrew' == txt -async def test_authorized_userid_not_authorized(loop, test_client): - +async def test_authorized_userid_not_authorized(loop, aiohttp_client): async def check(request): userid = await authorized_userid(request) assert userid is None return web.Response() - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz()) app.router.add_route('GET', '/', check) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.get('/') assert 200 == resp.status -async def test_permits_enum_permission(loop, test_client): +async def test_permits_enum_permission(loop, aiohttp_client): class Permission(enum.Enum): READ = '101' WRITE = '102' @@ -110,7 +109,7 @@ async def authorized_userid(self, identity): async def login(request): response = web.HTTPFound(location='/') await remember(request, response, 'UserID') - return response + raise response async def check(request): ret = await permits(request, Permission.READ) @@ -121,17 +120,16 @@ async def check(request): assert not ret return web.Response() - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz()) app.router.add_route('GET', '/', check) app.router.add_route('POST', '/login', login) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.post('/login') assert 200 == resp.status -async def test_permits_unauthorized(loop, test_client): - +async def test_permits_unauthorized(loop, aiohttp_client): async def check(request): ret = await permits(request, 'read') assert not ret @@ -141,38 +139,37 @@ async def check(request): assert not ret return web.Response() - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz()) app.router.add_route('GET', '/', check) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.get('/') assert 200 == resp.status -async def test_is_anonymous(loop, test_client): - +async def test_is_anonymous(loop, aiohttp_client): async def index(request): is_anon = await is_anonymous(request) if is_anon: - return web.HTTPUnauthorized() - return web.HTTPOk() + raise web.HTTPUnauthorized() + return web.Response() async def login(request): response = web.HTTPFound(location='/') await remember(request, response, 'UserID') - return response + raise response async def logout(request): response = web.HTTPFound(location='/') await forget(request, response) - return response + raise response - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz()) app.router.add_route('GET', '/', index) app.router.add_route('POST', '/login', login) app.router.add_route('POST', '/logout', logout) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.get('/') assert web.HTTPUnauthorized.status_code == resp.status @@ -185,27 +182,62 @@ async def logout(request): assert web.HTTPUnauthorized.status_code == resp.status -async def test_login_required(loop, test_client): - @login_required - async def index(request, user): - return web.HTTPOk() +async def test_login_required(loop, aiohttp_client): + with pytest.raises(DeprecationWarning): + @login_required + async def index(request): + return web.Response() + + async def login(request): + response = web.HTTPFound(location='/') + await remember(request, response, 'UserID') + raise response + + async def logout(request): + response = web.HTTPFound(location='/') + await forget(request, response) + raise response + + app = web.Application() + _setup(app, CookiesIdentityPolicy(), Autz()) + app.router.add_route('GET', '/', index) + app.router.add_route('POST', '/login', login) + app.router.add_route('POST', '/logout', logout) + + client = await aiohttp_client(app) + resp = await client.get('/') + assert web.HTTPUnauthorized.status_code == resp.status + + await client.post('/login') + resp = await client.get('/') + assert web.HTTPOk.status_code == resp.status + + await client.post('/logout') + resp = await client.get('/') + assert web.HTTPUnauthorized.status_code == resp.status + + +async def test_check_authorized(loop, aiohttp_client): + async def index(request): + await check_authorized(request) + return web.Response() async def login(request): response = web.HTTPFound(location='/') await remember(request, response, 'UserID') - return response + raise response async def logout(request): response = web.HTTPFound(location='/') await forget(request, response) - return response + raise response - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz()) app.router.add_route('GET', '/', index) app.router.add_route('POST', '/login', login) app.router.add_route('POST', '/logout', logout) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.get('/') assert web.HTTPUnauthorized.status_code == resp.status @@ -218,38 +250,94 @@ async def logout(request): assert web.HTTPUnauthorized.status_code == resp.status -async def test_has_permission(loop, test_client): - - @has_permission('read') - async def index_read(request, user): - return web.HTTPOk() +async def test_has_permission(loop, aiohttp_client): + with pytest.warns(DeprecationWarning): + @has_permission('read') + async def index_read(request): + return web.Response() + + @has_permission('write') + async def index_write(request): + return web.Response() + + @has_permission('forbid') + async def index_forbid(request): + return web.Response() + + async def login(request): + response = web.HTTPFound(location='/') + await remember(request, response, 'UserID') + return response + + async def logout(request): + response = web.HTTPFound(location='/') + await forget(request, response) + raise response + + app = web.Application() + _setup(app, CookiesIdentityPolicy(), Autz()) + app.router.add_route('GET', '/permission/read', index_read) + app.router.add_route('GET', '/permission/write', index_write) + app.router.add_route('GET', '/permission/forbid', index_forbid) + app.router.add_route('POST', '/login', login) + app.router.add_route('POST', '/logout', logout) + client = await aiohttp_client(app) + + resp = await client.get('/permission/read') + assert web.HTTPUnauthorized.status_code == resp.status + resp = await client.get('/permission/write') + assert web.HTTPUnauthorized.status_code == resp.status + resp = await client.get('/permission/forbid') + assert web.HTTPUnauthorized.status_code == resp.status + + await client.post('/login') + resp = await client.get('/permission/read') + assert web.HTTPOk.status_code == resp.status + resp = await client.get('/permission/write') + assert web.HTTPOk.status_code == resp.status + resp = await client.get('/permission/forbid') + assert web.HTTPForbidden.status_code == resp.status + + await client.post('/logout') + resp = await client.get('/permission/read') + assert web.HTTPUnauthorized.status_code == resp.status + resp = await client.get('/permission/write') + assert web.HTTPUnauthorized.status_code == resp.status + resp = await client.get('/permission/forbid') + assert web.HTTPUnauthorized.status_code == resp.status + + +async def test_check_permission(loop, aiohttp_client): + async def index_read(request): + await check_permission(request, 'read') + return web.Response() - @has_permission('write') - async def index_write(request, user): - return web.HTTPOk() + async def index_write(request): + await check_permission(request, 'write') + return web.Response() - @has_permission('forbid') - async def index_forbid(request, user): - return web.HTTPOk() + async def index_forbid(request): + await check_permission(request, 'forbid') + return web.Response() async def login(request): response = web.HTTPFound(location='/') await remember(request, response, 'UserID') - return response + raise response async def logout(request): response = web.HTTPFound(location='/') await forget(request, response) - return response + raise response - app = web.Application(loop=loop) + app = web.Application() _setup(app, CookiesIdentityPolicy(), Autz()) app.router.add_route('GET', '/permission/read', index_read) app.router.add_route('GET', '/permission/write', index_write) app.router.add_route('GET', '/permission/forbid', index_forbid) app.router.add_route('POST', '/login', login) app.router.add_route('POST', '/logout', logout) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.get('/permission/read') assert web.HTTPUnauthorized.status_code == resp.status diff --git a/tests/test_jwt_identity.py b/tests/test_jwt_identity.py index bb5dd026..813deac2 100644 --- a/tests/test_jwt_identity.py +++ b/tests/test_jwt_identity.py @@ -35,7 +35,7 @@ async def test_no_pyjwt_installed(mocker): JWTIdentityPolicy('secret') -async def test_identify(loop, make_token, test_client): +async def test_identify(loop, make_token, aiohttp_client): kwt_secret_key = 'Key' token = make_token({'login': 'Andrew'}, kwt_secret_key) @@ -46,17 +46,17 @@ async def check(request): assert 'Andrew' == identity['login'] return web.Response() - app = web.Application(loop=loop) + app = web.Application() _setup(app, JWTIdentityPolicy(kwt_secret_key), Autz()) app.router.add_route('GET', '/', check) - client = await test_client(app) + client = await aiohttp_client(app) headers = {'Authorization': 'Bearer {}'.format(token.decode('utf-8'))} resp = await client.get('/', headers=headers) assert 200 == resp.status -async def test_identify_broken_scheme(loop, make_token, test_client): +async def test_identify_broken_scheme(loop, make_token, aiohttp_client): kwt_secret_key = 'Key' token = make_token({'login': 'Andrew'}, kwt_secret_key) @@ -71,11 +71,11 @@ async def check(request): return web.Response() - app = web.Application(loop=loop) + app = web.Application() _setup(app, JWTIdentityPolicy(kwt_secret_key), Autz()) app.router.add_route('GET', '/', check) - client = await test_client(app) + client = await aiohttp_client(app) headers = {'Authorization': 'Token {}'.format(token.decode('utf-8'))} resp = await client.get('/', headers=headers) assert 400 == resp.status diff --git a/tests/test_no_auth.py b/tests/test_no_auth.py index 55051df7..c995da47 100644 --- a/tests/test_no_auth.py +++ b/tests/test_no_auth.py @@ -2,21 +2,21 @@ from aiohttp_security import authorized_userid, permits -async def test_authorized_userid(loop, test_client): +async def test_authorized_userid(loop, aiohttp_client): async def check(request): userid = await authorized_userid(request) assert userid is None return web.Response() - app = web.Application(loop=loop) + app = web.Application() app.router.add_route('GET', '/', check) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.get('/') assert 200 == resp.status -async def test_permits(loop, test_client): +async def test_permits(loop, aiohttp_client): async def check(request): ret = await permits(request, 'read') @@ -27,8 +27,8 @@ async def check(request): assert ret return web.Response() - app = web.Application(loop=loop) + app = web.Application() app.router.add_route('GET', '/', check) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.get('/') assert 200 == resp.status diff --git a/tests/test_no_identity.py b/tests/test_no_identity.py index 208521b0..c86eb48a 100644 --- a/tests/test_no_identity.py +++ b/tests/test_no_identity.py @@ -2,15 +2,15 @@ from aiohttp_security import remember, forget -async def test_remember(loop, test_client): +async def test_remember(loop, aiohttp_client): async def do_remember(request): response = web.Response() await remember(request, response, 'Andrew') - app = web.Application(loop=loop) + app = web.Application() app.router.add_route('POST', '/', do_remember) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.post('/') assert 500 == resp.status assert (('Security subsystem is not initialized, ' @@ -18,15 +18,15 @@ async def do_remember(request): resp.reason) -async def test_forget(loop, test_client): +async def test_forget(loop, aiohttp_client): async def do_forget(request): response = web.Response() await forget(request, response) - app = web.Application(loop=loop) + app = web.Application() app.router.add_route('POST', '/', do_forget) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.post('/') assert 500 == resp.status assert (('Security subsystem is not initialized, ' diff --git a/tests/test_session_identity.py b/tests/test_session_identity.py index d6377a88..b697f93a 100644 --- a/tests/test_session_identity.py +++ b/tests/test_session_identity.py @@ -20,14 +20,14 @@ async def authorized_userid(self, identity): @pytest.fixture -def make_app(loop): - app = web.Application(loop=loop) +def make_app(): + app = web.Application() setup_session(app, SimpleCookieStorage()) setup_security(app, SessionIdentityPolicy(), Autz()) return app -async def test_remember(make_app, test_client): +async def test_remember(make_app, aiohttp_client): async def handler(request): response = web.Response() @@ -37,12 +37,12 @@ async def handler(request): async def check(request): session = await get_session(request) assert session['AIOHTTP_SECURITY'] == 'Andrew' - return web.HTTPOk() + return web.Response() app = make_app() app.router.add_route('GET', '/', handler) app.router.add_route('GET', '/check', check) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.get('/') assert 200 == resp.status @@ -50,7 +50,7 @@ async def check(request): assert 200 == resp.status -async def test_identify(make_app, test_client): +async def test_identify(make_app, aiohttp_client): async def create(request): response = web.Response() @@ -66,7 +66,7 @@ async def check(request): app = make_app() app.router.add_route('GET', '/', check) app.router.add_route('POST', '/', create) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.post('/') assert 200 == resp.status @@ -74,28 +74,28 @@ async def check(request): assert 200 == resp.status -async def test_forget(make_app, test_client): +async def test_forget(make_app, aiohttp_client): async def index(request): session = await get_session(request) - return web.HTTPOk(text=session.get('AIOHTTP_SECURITY', '')) + return web.Response(text=session.get('AIOHTTP_SECURITY', '')) async def login(request): response = web.HTTPFound(location='/') await remember(request, response, 'Andrew') - return response + raise response async def logout(request): response = web.HTTPFound('/') await forget(request, response) - return response + raise response app = make_app() app.router.add_route('GET', '/', index) app.router.add_route('POST', '/login', login) app.router.add_route('POST', '/logout', logout) - client = await test_client(app) + client = await aiohttp_client(app) resp = await client.post('/login') assert 200 == resp.status