102 Commits

Author SHA1 Message Date
pyup-bot
140804e4b5 Update aiohttp from 3.0.7 to 3.0.9 2018-03-19 20:14:07 +07:00
pyup-bot
6d9f5f03ed Update cryptography from 2.1.4 to 2.2 2018-03-19 20:14:06 +07:00
pyup.io bot
363f3b71c0 Scheduled weekly dependency update for week 10 (#138)
* Update pytest from 3.4.1 to 3.4.2

* Update aiohttp from 3.0.6 to 3.0.7
2018-03-12 19:20:07 +02:00
Oleh Kuchuk
9da55517b2 correct example linking (#136) 2018-03-06 14:21:40 +02:00
pyup.io bot
d375b22f1b Scheduled weekly dependency update for week 09 (#135)
* Update pytest from 3.3.2 to 3.4.1

* Update coverage from 4.4.2 to 4.5.1

* Update sphinx from 1.6.6 to 1.7.1

* Update aiohttp-session from 2.1.0 to 2.3.0

* Update aioredis from 1.0.0 to 1.1.0

* Update aiohttp from 2.3.9 to 3.0.6
2018-03-05 19:38:35 +02:00
Andrew Svetlov
4506c306a7 Relax travis matrix 2018-02-26 17:50:56 +02:00
Phil Elson
9b9b848fdd Two trivial typos. (#129) 2018-02-01 10:50:58 +02:00
Eduard Nabokov
1679f6713b Update docs and demo with login required, has permission (#128)
* Work on

* Update docs with login_required and has_permission
2018-01-24 12:29:20 +02:00
pyup.io bot
f9628b0ac1 Update aiohttp from 2.3.7 to 2.3.9 (#127) 2018-01-23 11:31:22 +02:00
pyup.io bot
f8940c0696 Scheduled weekly dependency update for week 01 (#126)
* Update pytest from 3.3.1 to 3.3.2

* Update sphinx from 1.6.5 to 1.6.6

* Update aiopg from 0.13.1 to 0.13.2
2018-01-08 23:42:12 +02:00
Grygorii Yermolenko
5d1195b85d Small updates in readme and docs (#125)
- add info about installation with extra ([]session])
- add links to examples
- move `Public api` header into related section
2018-01-03 12:09:11 +02:00
Andrew Svetlov
8360095011 Fix years 2018-01-02 14:31:39 +02:00
Grygorii Yermolenko
d89f6b7e3d Fix in demos related to async-await syntax (#123)
- remove 3.4 version from setup.py
- add cryprography package to requirements since it is used in
dictionary_auth
- add a few missed async kwords
2018-01-02 14:31:19 +02:00
pyup.io bot
db7dbd9b07 Scheduled weekly dependency update for week 00 (#124)
* Update pytest from 3.2.5 to 3.3.1

* Update aiohttp-session from 1.2.1 to 2.1.0

* Update aiohttp from 2.3.3 to 2.3.7

* Update pytest-aiohttp from 0.1.3 to 0.3.0
2018-01-02 14:28:32 +02:00
Andrew Svetlov
5b2ff779c3 Switch to async/await syntax 2017-12-13 16:51:46 +02:00
pyup.io bot
b9dee120c3 Scheduled weekly dependency update for week 47 (#115)
* Update pytest from 3.2.3 to 3.2.5

* Update aiohttp-session from 1.2.0 to 1.2.1

* Update aioredis from 0.3.5 to 1.0.0

* Update aiohttp from 2.3.2 to 2.3.3
2017-11-22 19:59:14 +02:00
Andrew Svetlov
aff9a6a915 Bump to 0.2.0 2017-11-17 17:54:46 +02:00
Andrew Lytvyn
92e6fec6f5 is_anonymous, login_required, has_permission helpers (#114)
* add is_anonymous helper function and login_required, has_permission decorators

* add docs for `is_anonymous`, `login_required` and `has_permission` functions

* permission can be `enum.Enum` object; cover with tests

* isort fix
2017-11-17 17:51:40 +02:00
pyup.io bot
810312b508 Scheduled weekly dependency update for week 46 (#113)
* Update coverage from 4.4.1 to 4.4.2

* Update aiohttp-session from 1.0.1 to 1.2.0

* Update aioredis from 0.3.4 to 0.3.5

* Update aiohttp from 2.3.1 to 2.3.2
2017-11-13 16:46:55 +02:00
pyup.io bot
18a04b879e Scheduled weekly dependency update for week 44 (#111)
* Update flake8 from 3.4.1 to 3.5.0

* Update sphinx from 1.6.4 to 1.6.5

* Update aioredis from 0.3.3 to 0.3.4

* Update aiohttp from 2.2.5 to 2.3.1
2017-10-30 16:41:06 +02:00
Andrew Svetlov
cd833edb6c Update trove classifiers 2017-10-17 12:41:11 +03:00
Andrew Svetlov
0694e6e084 Add autodeployment on travis 2017-10-17 12:39:18 +03:00
Andrew Svetlov
63870d992e Bump to 0.1.2 2017-10-17 12:37:09 +03:00
Alex Kuzmenko
aef24fae3d aiohttp-session is not required (#107)
* aiohttp_session is not required

* fix code coverage

* has_aiohttp_session -> HAS_AIOHTTP_SESSION
2017-10-17 12:32:53 +03:00
Nikolay Novik
747ec05cfb Merge pull request #109 from alxpy/alxpy-patch-7
Added new python versions to travis-ci
2017-10-10 16:51:15 +03:00
Alex Kuzmenko
66391b9f25 added new python versions to travis-ci 2017-10-10 16:44:17 +03:00
pyup.io bot
a46ca6ec68 Scheduled weekly dependency update for week 41 (#106)
* Update pytest from 3.2.2 to 3.2.3

* Update sphinx from 1.6.3 to 1.6.4
2017-10-10 14:39:07 +03:00
Devin Fee
1a9ab6424e added simplistic dictionary_auth example (#105) 2017-09-19 11:54:37 +03:00
pyup.io bot
b0895806af Scheduled weekly dependency update for week 38 (#104)
* Update pytest-cov from 2.4.0 to 2.5.1

* Update coverage from 4.3.4 to 4.4.1

* Update pytest-aiohttp from 0.1.2 to 0.1.3
2017-09-18 18:27:15 +03:00
Nikolay Novik
dbac083096 Merge pull request #102 from aio-libs/pyup-update-aiohttp-session-1.0.0-to-1.0.1
Update aiohttp-session to 1.0.1
2017-09-14 21:11:53 +03:00
pyup.io bot
5811ab90c8 Update pytest from 3.2.0 to 3.2.2 (#99) 2017-09-14 13:20:20 +03:00
Andrew Svetlov
4a0a078976 Create .pyup.yml 2017-09-14 13:20:00 +03:00
pyup-bot
22c51b5fd0 Update aiohttp-session from 1.0.0 to 1.0.1 2017-09-14 13:19:18 +03:00
pyup.io bot
921506c3c4 Update aiopg from 0.13.0 to 0.13.1 (#100) 2017-09-14 13:18:49 +03:00
pyup.io bot
d30a158345 Update flake8 from 3.3.0 to 3.4.1 (#97) 2017-08-04 12:59:21 +03:00
pyup.io bot
df198ae8d1 Update aiohttp from 2.2.3 to 2.2.5 (#96) 2017-08-04 12:26:39 +03:00
pyup.io bot
05a4cc03d2 Update aiohttp-session from 0.8.0 to 1.0.0 (#91) 2017-08-04 12:25:48 +03:00
pyup.io bot
bbb41dd3ad Update pytest from 3.1.3 to 3.2.0 (#94) 2017-08-02 09:29:27 +03:00
pyup.io bot
f716fb353b Update pytest from 3.1.2 to 3.1.3 (#89) 2017-07-24 09:59:09 +03:00
Thijs Triemstra
e99ce6ba23 update passlib url (#90) 2017-07-24 09:58:54 +03:00
pyup.io bot
6ebf9355fc Update aiohttp from 2.2.2 to 2.2.3 (#88) 2017-07-04 19:26:50 +03:00
pyup.io bot
d473f57e8e Update aioredis from 0.3.2 to 0.3.3 (#84) 2017-07-03 13:04:48 +03:00
pyup.io bot
7ab68c9f6d Update sphinx from 1.6.2 to 1.6.3 (#85) 2017-07-03 13:04:30 +03:00
pyup.io bot
b7dbe3ae16 Update aiohttp from 2.2.0 to 2.2.2 (#87) 2017-07-03 13:04:10 +03:00
Nikolay Novik
1b52d4b76b Merge pull request #82 from aio-libs/pyup-update-aiohttp-2.1.0-to-2.2.0
Update aiohttp to 2.2.0
2017-06-29 00:31:08 +03:00
Nikolay Novik
5d89f5feed Merge pull request #83 from aio-libs/pyup-update-aioredis-0.3.1-to-0.3.2
Update aioredis to 0.3.2
2017-06-29 00:30:54 +03:00
pyup-bot
640ae0fe72 Update aioredis from 0.3.1 to 0.3.2 2017-06-21 11:29:34 +03:00
pyup.io bot
352d1f4526 Update pytest from 3.1.1 to 3.1.2 (#81) 2017-06-20 16:10:45 +03:00
pyup-bot
aab14aedae Update aiohttp from 2.1.0 to 2.2.0 2017-06-20 14:51:33 +03:00
Nikolay Novik
6e0864d088 Merge pull request #80 from aio-libs/pyup-update-pytest-3.1.0-to-3.1.1
Update pytest to 3.1.1
2017-06-08 22:23:59 +03:00
pyup-bot
4de9d8f5f9 Update pytest from 3.1.0 to 3.1.1 2017-05-31 05:14:57 -07:00
Thanos Lefteris
1009a29a63 Add asyncio trove classifier (#69) 2017-05-28 14:08:53 -07:00
pyup.io bot
867c8d8e07 Update pytest from 3.0.7 to 3.1.0 (#77) 2017-05-28 14:08:13 -07:00
pyup.io bot
118f1f473a Update sphinx from 1.5.5 to 1.6.2 (#79) 2017-05-28 14:07:47 -07:00
pyup.io bot
d276618899 Update aioredis from 0.3.0 to 0.3.1 (#71) 2017-05-26 09:37:32 -07:00
pyup.io bot
86a7733815 Update aiohttp from 2.0.7 to 2.1.0 (#78) 2017-05-26 09:37:13 -07:00
pyup.io bot
8432f680e4 Update passlib from 1.7.0 to 1.7.1 (#67) 2017-04-13 20:45:28 +03:00
pyup.io bot
0d2d2a1c96 Update aiohttp from 2.0.6 to 2.0.7 (#66) 2017-04-13 19:47:20 +03:00
Nikolay Novik
d714f82319 Merge pull request #38 from aio-libs/pyup-update-aioredis-0.2.9-to-0.3.0
Update aioredis to 0.3.0
2017-04-09 10:36:28 +03:00
Nikolay Novik
14bd43fe4f Merge pull request #58 from aio-libs/pyup-update-flake8-3.2.1-to-3.3.0
Update flake8 to 3.3.0
2017-04-09 10:35:50 +03:00
Nikolay Novik
401ebf130a Merge pull request #62 from aio-libs/pyup-update-sphinx-1.5.3-to-1.5.5
Update sphinx to 1.5.5
2017-04-09 10:35:38 +03:00
Nikolay Novik
c84a82d3dc Merge pull request #63 from aio-libs/pyup-update-aiohttp-2.0.3-to-2.0.6
Update aiohttp to 2.0.6
2017-04-09 10:35:27 +03:00
pyup-bot
08bdad3d85 Update aiohttp from 2.0.3 to 2.0.6 2017-04-06 18:26:21 +03:00
pyup-bot
d7b6a911b9 Update sphinx from 1.5.3 to 1.5.5 2017-04-03 18:48:41 +03:00
pyup-bot
65cebd3ff0 Update flake8 from 3.2.1 to 3.3.0 2017-03-26 00:59:55 +02:00
Nikolay Novik
c13d9b4c70 Merge pull request #49 from aio-libs/pyup-update-sphinx-1.5.2-to-1.5.3
Update sphinx to 1.5.3
2017-03-26 00:59:35 +02:00
Nikolay Novik
d591ad3caa Merge pull request #50 from aio-libs/pyup-update-pytest-3.0.6-to-3.0.7
Update pytest to 3.0.7
2017-03-26 00:59:25 +02:00
Nikolay Novik
73e55280b7 Merge pull request #57 from aio-libs/pyup-update-aiohttp-1.2.0-to-2.0.3
Update aiohttp to 2.0.3
2017-03-25 17:10:03 +02:00
pyup-bot
367d388cff Update aiohttp from 1.2.0 to 2.0.3 2017-03-24 23:50:06 +02:00
Nickolai Novik
d652f29df5 make compatible tests 2017-03-22 00:10:14 +02:00
pyup-bot
face5ddaa2 Update pytest from 3.0.6 to 3.0.7 2017-03-14 23:25:40 +02:00
pyup-bot
c523b4fa49 Update sphinx from 1.5.2 to 1.5.3 2017-02-25 22:30:33 -09:00
Nikolay Novik
6e4355ce3c Merge pull request #40 from alxpy/alxpy-patch-2
Fix code coverage
2017-01-25 16:16:15 +02:00
Nikolay Novik
4c92553761 Merge pull request #41 from alxpy/alxpy-patch-3
Add some labels
2017-01-25 16:15:52 +02:00
Alex Kuzmenko
d76ecf59fc Add pypi label 2017-01-25 13:29:57 +02:00
Alex Kuzmenko
a990a657c4 Add some labels 2017-01-25 00:51:02 +02:00
Alex Kuzmenko
ede5beeb41 Fix code coverage 2017-01-25 00:39:39 +02:00
Nikolay Novik
d22d3a1d1a Merge pull request #36 from aio-libs/pyup-update-sphinx-1.5.1-to-1.5.2
Update sphinx to 1.5.2
2017-01-24 22:40:35 +02:00
pyup-bot
962e022090 Update aioredis from 0.2.9 to 0.3.0 2017-01-24 22:30:10 +02:00
pyup-bot
4b1740a8e3 Update sphinx from 1.5.1 to 1.5.2 2017-01-24 22:29:42 +02:00
Nikolay Novik
a2bf4c9c00 Merge pull request #34 from aio-libs/pyup-update-pytest-3.0.5-to-3.0.6
Update pytest to 3.0.6
2017-01-24 22:29:18 +02:00
Nikolay Novik
c4f3c06476 Merge pull request #35 from aio-libs/pyup-update-coverage-4.3-to-4.3.4
Update coverage to 4.3.4
2017-01-24 22:29:02 +02:00
pyup-bot
929468b684 Update coverage from 4.3 to 4.3.4 2017-01-24 22:11:22 +02:00
pyup-bot
7e68c44de6 Update pytest from 3.0.5 to 3.0.6 2017-01-24 22:11:15 +02:00
Nikolay Novik
7d4a4802f3 Merge pull request #33 from aio-libs/jettify-patch-1
Update .travis.yml
2017-01-24 22:10:49 +02:00
Nikolay Novik
7bd5c37c8a Update .travis.yml 2017-01-24 22:06:19 +02:00
Nikolay Novik
87942913c9 Merge pull request #30 from alxpy/update_readme
Update readme
2017-01-24 14:50:49 +02:00
Nikolay Novik
69743859a1 Merge pull request #32 from alxpy/travis_ci_labels
Add travis-ci labels
2017-01-24 14:49:09 +02:00
Alex Kuzmenko
1d596c4a3d Add travis-ci labels 2017-01-24 14:36:34 +02:00
Nikolay Novik
88b8ffbf5c Merge pull request #20 from alanc10n/DocOptionsFix
Remove unknown option github_style
2017-01-24 12:59:59 +02:00
Alex Kuzmenko
076c7f1d5f Update readme 2017-01-24 02:31:06 +02:00
Alan Christianson
474209d945 Remove unknown option github_style 2017-01-09 13:46:48 -07:00
pyup.io bot
131ee39bf6 Update coverage from 4.2 to 4.3 (#17) 2016-12-28 05:53:49 +02:00
pyup.io bot
8163d72b2d Update aiohttp from 1.1.6 to 1.2.0 (#15) 2016-12-28 05:53:14 +02:00
Nikolay Novik
679db05664 Merge pull request #14 from aio-libs/pyup-update-sphinx-1.5-to-1.5.1
Update sphinx to 1.5.1
2016-12-13 18:17:03 +02:00
pyup-bot
1ace967956 Update sphinx from 1.5 to 1.5.1 2016-12-13 18:17:14 +03:00
pyup.io bot
67d5590439 Update pytest from 3.0.4 to 3.0.5 (#13) 2016-12-05 18:37:59 +02:00
pyup.io bot
deae9e74e1 Update sphinx from 1.4.9 to 1.5 (#12) 2016-12-05 14:03:16 +02:00
pyup.io bot
76a6ecf86a Update aiohttp-session from 0.7.1 to 0.8.0 (#11) 2016-12-05 14:03:01 +02:00
pyup.io bot
2fc73acfb7 Update aiopg from 0.12.0 to 0.13.0 (#10) 2016-12-02 16:09:24 +02:00
pyup.io bot
2202eb3faa Update aiohttp from 1.1.5 to 1.1.6 (#9) 2016-12-02 15:37:00 +02:00
pyup.io bot
9cd055b36e Initial Update (#8)
* Pin flake8 to latest version 3.2.1

* Update passlib from 1.6.5 to 1.7.0

* Pin aiohttp to latest version 1.1.5

* Pin aiohttp-session to latest version 0.7.1

* Update aioredis from 0.2.8 to 0.2.9

* Pin pep257 to latest version 0.7.0

* Pin pytest-aiohttp to latest version 0.1.2

* Pin aiopg to latest version 0.12.0

* Pin pytest to latest version 3.0.4

* Pin sphinx to latest version 1.4.9

* Pin coverage to latest version 4.2

* Pin pytest-cov to latest version 2.4.0
2016-11-24 13:13:11 +02:00
34 changed files with 843 additions and 483 deletions

4
.pyup.yml Normal file
View File

@@ -0,0 +1,4 @@
# Label PRs with `deps-update` label
label_prs: deps-update
schedule: every week

View File

@@ -1,17 +1,22 @@
language: python language: python
python: python:
- 3.4
- 3.5 - 3.5
- 3.6
- 3.7-dev
- nightly - nightly
matrix:
allow_failures:
- python: 3.7-dev
- python: nightly
install: install:
- pip install --upgrade pip - pip install --upgrade pip
- pip install -r requirements-dev.txt - pip install -r requirements-dev.txt
- pip install codecov - pip install codecov
script: script:
- flake8 aiohttp_security tests - make coverage
- coverage run --source=aiohttp_security setup.py test
after_success: after_success:
- codecov - codecov
@@ -20,3 +25,14 @@ env:
matrix: matrix:
- PYTHONASYNCIODEBUG=x - PYTHONASYNCIODEBUG=x
- PYTHONASYNCIODEBUG= - PYTHONASYNCIODEBUG=
deploy:
provider: pypi
user: andrew.svetlov
password:
secure: "JdBvuOBA/198ognVDOY/qZpIKGXfCx47725kyJo/SpQ3nP+x0GLZb3PMQkR0jfSWWkx6Sisk3vOCYsoWclPyPzp+o4ZpfM8yAjHNFmtbr+k+XJdUEApEiWb6/Y3g7DCyY2Qa/L8IYlyABPWrrJI/nld2sKm5kmhFpR/z3HfeFtINP6Ivp34dUOkeRP6kOvCi9d6GyWnvTRnhlybAnk/Ngrroh8XrbKHdDv0zkQkshF8+pmxVzwao4C6S5ld5cFXIYZHLBA9lNC3zgvOMuFeGPUEN9vab3q77MvaiMIuTC9QjcgIhfw3gabH2u7knqfFzqqzXMaVptx5z8o1JtsxMyYt5NVBqS4NPIljpZjaoS/CASHJlRxniJiYfjvjOtFEcfGMNtZj8ZYsGR0nuP2jwzgpEHHWIs4qL0Y8h9t7pGirxCuQcnY10sr+Y+JKaZNJsugNLgbqE2aaZUye5gjDcEj9WY8kKNZXucLP7c0McJuwPqplDEO4CQouMttcKSYkA0QoETmpAFqaXCaMs3p/glOoU2ZyHSH9mXWir69yo84ymb2NlGPMTAstXlv/g/oLmLMSq7lbl6cSUnO1/wxBGlyfv5AAq/75YUaqsgYofzN5CjUgA3m6NedvbWxLUJaxVQ7nduYGEQKDvGEBmzCNv6CdVRCjQ9J1xX3XzkVheQGc="
distributions: "sdist bdist_wheel"
on:
tags: true
all_branches: true
python: 3.6

View File

@@ -1,2 +1,12 @@
Changes Changes
======= =======
0.2.0 (2017-11-17)
------------------
- Add `is_anonymous`, `login_required`, `has_permission` helpers (#114)
0.1.2 (2017-10-17)
------------------
- Make aiohttp-session optional dependency (#107)

View File

@@ -186,7 +186,7 @@ Apache License
same "printed page" as the copyright notice for easier same "printed page" as the copyright notice for easier
identification within third-party archives. identification within third-party archives.
Copyright 2015-2016 Andrew Svetlov Copyright 2015-2018 Andrew Svetlov and aio-libs team.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@@ -1,17 +1,47 @@
aiohttp_security aiohttp_security
================ ================
.. image:: https://travis-ci.org/aio-libs/aiohttp-security.svg?branch=master
:target: https://travis-ci.org/aio-libs/aiohttp-security
.. image:: https://codecov.io/github/aio-libs/aiohttp-security/coverage.svg?branch=master
:target: https://codecov.io/github/aio-libs/aiohttp-security
.. image:: https://readthedocs.org/projects/aiohttp-security/badge/?version=latest
:target: https://aiohttp-security.readthedocs.io/
.. image:: https://img.shields.io/pypi/v/aiohttp-security.svg
:target: https://pypi.python.org/pypi/aiohttp-security
The library provides identity and autorization for `aiohttp.web`__. The library provides identity and authorization for `aiohttp.web`__.
.. _aiohttp_web: http://aiohttp.readthedocs.org/en/latest/web.html .. _aiohttp_web: http://aiohttp.readthedocs.org/en/latest/web.html
__ aiohttp_web_ __ aiohttp_web_
Usage Installation
----- ------------
To install type ``pip install aiohttp_security``. Simplest case (authorization via cookies) ::
Launch ``make doc`` and see examples or look under **demo** directory for a
sample project. $ pip install aiohttp_security
With `aiohttp-session` support ::
$ pip install aiohttp_security[session]
Examples
--------
Take a look at examples:
`Basic example`_
`Example with DB auth`_
.. _`Basic example`: docs/example.rst
.. _`Example with db auth`: docs/example_db_auth.rst
and demos at **demo** directory.
Documentation
-------------
https://aiohttp-security.readthedocs.io/
Develop Develop
------- -------

View File

@@ -1,13 +1,14 @@
from .abc import AbstractIdentityPolicy, AbstractAuthorizationPolicy from .abc import AbstractAuthorizationPolicy, AbstractIdentityPolicy
from .api import remember, forget, setup, authorized_userid, permits from .api import (authorized_userid, forget, has_permission, is_anonymous,
login_required, permits, remember, setup)
from .cookies_identity import CookiesIdentityPolicy from .cookies_identity import CookiesIdentityPolicy
from .session_identity import SessionIdentityPolicy from .session_identity import SessionIdentityPolicy
__version__ = '0.2.0'
__version__ = '0.1.1'
__all__ = ('AbstractIdentityPolicy', 'AbstractAuthorizationPolicy', __all__ = ('AbstractIdentityPolicy', 'AbstractAuthorizationPolicy',
'CookiesIdentityPolicy', 'SessionIdentityPolicy', 'CookiesIdentityPolicy', 'SessionIdentityPolicy',
'remember', 'forget', 'authorized_userid', 'remember', 'forget', 'authorized_userid',
'permits', 'setup') 'permits', 'setup', 'is_anonymous',
'login_required', 'has_permission')

View File

@@ -1,21 +1,18 @@
import abc import abc
import asyncio
# see http://plope.com/pyramid_auth_design_api_postmortem # see http://plope.com/pyramid_auth_design_api_postmortem
class AbstractIdentityPolicy(metaclass=abc.ABCMeta): class AbstractIdentityPolicy(metaclass=abc.ABCMeta):
@asyncio.coroutine
@abc.abstractmethod @abc.abstractmethod
def identify(self, request): async def identify(self, request):
"""Return the claimed identity of the user associated request or """Return the claimed identity of the user associated request or
``None`` if no identity can be found associated with the request.""" ``None`` if no identity can be found associated with the request."""
pass pass
@asyncio.coroutine
@abc.abstractmethod @abc.abstractmethod
def remember(self, request, response, identity, **kwargs): async def remember(self, request, response, identity, **kwargs):
"""Remember identity. """Remember identity.
Modify response object by filling it's headers with remembered user. Modify response object by filling it's headers with remembered user.
@@ -25,9 +22,8 @@ class AbstractIdentityPolicy(metaclass=abc.ABCMeta):
""" """
pass pass
@asyncio.coroutine
@abc.abstractmethod @abc.abstractmethod
def forget(self, request, response): async def forget(self, request, response):
""" Modify response which can be used to 'forget' the """ Modify response which can be used to 'forget' the
current identity on subsequent requests.""" current identity on subsequent requests."""
pass pass
@@ -35,9 +31,8 @@ class AbstractIdentityPolicy(metaclass=abc.ABCMeta):
class AbstractAuthorizationPolicy(metaclass=abc.ABCMeta): class AbstractAuthorizationPolicy(metaclass=abc.ABCMeta):
@asyncio.coroutine
@abc.abstractmethod @abc.abstractmethod
def permits(self, identity, permission, context=None): async def permits(self, identity, permission, context=None):
"""Check user permissions. """Check user permissions.
Return True if the identity is allowed the permission in the Return True if the identity is allowed the permission in the
@@ -45,9 +40,8 @@ class AbstractAuthorizationPolicy(metaclass=abc.ABCMeta):
""" """
pass pass
@asyncio.coroutine
@abc.abstractmethod @abc.abstractmethod
def authorized_userid(self, identity): async def authorized_userid(self, identity):
"""Retrieve authorized user id. """Retrieve authorized user id.
Return the user_id of the user identified by the identity Return the user_id of the user identified by the identity

View File

@@ -1,19 +1,19 @@
import asyncio import enum
from aiohttp import web from aiohttp import web
from aiohttp_security.abc import (AbstractIdentityPolicy, from aiohttp_security.abc import (AbstractIdentityPolicy,
AbstractAuthorizationPolicy) AbstractAuthorizationPolicy)
from functools import wraps
IDENTITY_KEY = 'aiohttp_security_identity_policy' IDENTITY_KEY = 'aiohttp_security_identity_policy'
AUTZ_KEY = 'aiohttp_security_autz_policy' AUTZ_KEY = 'aiohttp_security_autz_policy'
@asyncio.coroutine async def remember(request, response, identity, **kwargs):
def remember(request, response, identity, **kwargs):
"""Remember identity into response. """Remember identity into response.
The action is performed by identity_policy.remember() The action is performed by identity_policy.remember()
Usually the idenity is stored in user cookies homehow but may be Usually the identity is stored in user cookies somehow but may be
pushed into custom header also. pushed into custom header also.
""" """
assert isinstance(identity, str), identity assert isinstance(identity, str), identity
@@ -26,11 +26,10 @@ def remember(request, response, identity, **kwargs):
# output and rendered page we add same message to *reason* and # output and rendered page we add same message to *reason* and
# *text* arguments. # *text* arguments.
raise web.HTTPInternalServerError(reason=text, text=text) raise web.HTTPInternalServerError(reason=text, text=text)
yield from identity_policy.remember(request, response, identity, **kwargs) await identity_policy.remember(request, response, identity, **kwargs)
@asyncio.coroutine async def forget(request, response):
def forget(request, response):
"""Forget previously remembered identity. """Forget previously remembered identity.
Usually it clears cookie or server-side storage to forget user Usually it clears cookie or server-side storage to forget user
@@ -44,36 +43,110 @@ def forget(request, response):
# output and rendered page we add same message to *reason* and # output and rendered page we add same message to *reason* and
# *text* arguments. # *text* arguments.
raise web.HTTPInternalServerError(reason=text, text=text) raise web.HTTPInternalServerError(reason=text, text=text)
yield from identity_policy.forget(request, response) await identity_policy.forget(request, response)
@asyncio.coroutine async def authorized_userid(request):
def authorized_userid(request):
identity_policy = request.app.get(IDENTITY_KEY) identity_policy = request.app.get(IDENTITY_KEY)
autz_policy = request.app.get(AUTZ_KEY) autz_policy = request.app.get(AUTZ_KEY)
if identity_policy is None or autz_policy is None: if identity_policy is None or autz_policy is None:
return None return None
identity = yield from identity_policy.identify(request) identity = await identity_policy.identify(request)
if identity is None: if identity is None:
return None # non-registered user has None user_id return None # non-registered user has None user_id
user_id = yield from autz_policy.authorized_userid(identity) user_id = await autz_policy.authorized_userid(identity)
return user_id return user_id
@asyncio.coroutine async def permits(request, permission, context=None):
def permits(request, permission, context=None): assert isinstance(permission, (str, enum.Enum)), permission
assert isinstance(permission, str), permission
assert permission assert permission
identity_policy = request.app.get(IDENTITY_KEY) identity_policy = request.app.get(IDENTITY_KEY)
autz_policy = request.app.get(AUTZ_KEY) autz_policy = request.app.get(AUTZ_KEY)
if identity_policy is None or autz_policy is None: if identity_policy is None or autz_policy is None:
return True return True
identity = yield from identity_policy.identify(request) identity = await identity_policy.identify(request)
# non-registered user still may has some permissions # non-registered user still may has some permissions
access = yield from autz_policy.permits(identity, permission, context) access = await autz_policy.permits(identity, permission, context)
return access return access
async def is_anonymous(request):
"""Check if user is anonymous.
User is considered anonymous if there is not identity
in request.
"""
identity_policy = request.app.get(IDENTITY_KEY)
if identity_policy is None:
return True
identity = await identity_policy.identify(request)
if identity is None:
return True
return False
def login_required(fn):
"""Decorator that restrict access only for authorized users.
User is considered authorized if authorized_userid
returns some value.
"""
@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)
if userid is None:
raise web.HTTPUnauthorized
ret = await fn(*args, **kwargs)
return ret
return wrapped
def has_permission(
permission,
context=None,
):
"""Decorator that restrict access only for authorized users
with correct permissions.
If user is not authorized - raises HTTPUnauthorized,
if user is authorized and does not have permission -
raises HTTPForbidden.
"""
def wrapper(fn):
@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)
if userid is None:
raise web.HTTPUnauthorized
allowed = await permits(request, permission, context)
if not allowed:
raise web.HTTPForbidden
ret = await fn(*args, **kwargs)
return ret
return wrapped
return wrapper
def setup(app, identity_policy, autz_policy): def setup(app, identity_policy, autz_policy):
assert isinstance(identity_policy, AbstractIdentityPolicy), identity_policy assert isinstance(identity_policy, AbstractIdentityPolicy), identity_policy
assert isinstance(autz_policy, AbstractAuthorizationPolicy), autz_policy assert isinstance(autz_policy, AbstractAuthorizationPolicy), autz_policy

View File

@@ -5,8 +5,6 @@ more handy.
""" """
import asyncio
from .abc import AbstractIdentityPolicy from .abc import AbstractIdentityPolicy
@@ -19,19 +17,16 @@ class CookiesIdentityPolicy(AbstractIdentityPolicy):
self._cookie_name = 'AIOHTTP_SECURITY' self._cookie_name = 'AIOHTTP_SECURITY'
self._max_age = 30 * 24 * 3600 self._max_age = 30 * 24 * 3600
@asyncio.coroutine async def identify(self, request):
def identify(self, request):
identity = request.cookies.get(self._cookie_name) identity = request.cookies.get(self._cookie_name)
return identity return identity
@asyncio.coroutine async def remember(self, request, response, identity, max_age=sentinel,
def remember(self, request, response, identity, max_age=sentinel, **kwargs):
**kwargs):
if max_age is sentinel: if max_age is sentinel:
max_age = self._max_age max_age = self._max_age
response.set_cookie(self._cookie_name, identity, response.set_cookie(self._cookie_name, identity,
max_age=max_age, **kwargs) max_age=max_age, **kwargs)
@asyncio.coroutine async def forget(self, request, response):
def forget(self, request, response):
response.del_cookie(self._cookie_name) response.del_cookie(self._cookie_name)

View File

@@ -4,9 +4,11 @@ aiohttp_session.setup() should be called on application initialization
to configure aiohttp_session properly. to configure aiohttp_session properly.
""" """
import asyncio try:
from aiohttp_session import get_session
from aiohttp_session import get_session HAS_AIOHTTP_SESSION = True
except ImportError: # pragma: no cover
HAS_AIOHTTP_SESSION = False
from .abc import AbstractIdentityPolicy from .abc import AbstractIdentityPolicy
@@ -16,17 +18,18 @@ class SessionIdentityPolicy(AbstractIdentityPolicy):
def __init__(self, session_key='AIOHTTP_SECURITY'): def __init__(self, session_key='AIOHTTP_SECURITY'):
self._session_key = session_key self._session_key = session_key
@asyncio.coroutine if not HAS_AIOHTTP_SESSION: # pragma: no cover
def identify(self, request): raise ImportError(
session = yield from get_session(request) 'SessionIdentityPolicy requires `aiohttp_session`')
async def identify(self, request):
session = await get_session(request)
return session.get(self._session_key) return session.get(self._session_key)
@asyncio.coroutine async def remember(self, request, response, identity, **kwargs):
def remember(self, request, response, identity, **kwargs): session = await get_session(request)
session = yield from get_session(request)
session[self._session_key] = identity session[self._session_key] = identity
@asyncio.coroutine async def forget(self, request, response):
def forget(self, request, response): session = await get_session(request)
session = yield from get_session(request)
session.pop(self._session_key, None) session.pop(self._session_key, None)

View File

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

View File

@@ -0,0 +1,77 @@
from textwrap import dedent
from aiohttp import web
from aiohttp_security import (
remember, forget, authorized_userid,
has_permission, login_required,
)
from .db_auth import check_credentials
class Web(object):
index_template = dedent("""
<!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>
""")
async def index(self, request):
username = await 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
async def login(self, request):
response = web.HTTPFound('/')
form = await request.post()
login = form.get('login')
password = form.get('password')
db_engine = request.app.db_engine
if await check_credentials(db_engine, login, password):
await remember(request, response, login)
return response
return web.HTTPUnauthorized(
body=b'Invalid username/password combination')
@login_required
async def logout(self, 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):
response = web.Response(
body=b'This page is visible for all registered users')
return response
@has_permission('protected')
async def protected_page(self, request):
response = web.Response(body=b'You are on protected page')
return response
def configure(self, app):
router = app.router
router.add_route('GET', '/', self.index, name='index')
router.add_route('POST', '/login', self.login, name='login')
router.add_route('GET', '/logout', self.logout, name='logout')
router.add_route('GET', '/public', self.internal_page, name='public')
router.add_route('GET', '/protected', self.protected_page,
name='protected')

View File

@@ -9,17 +9,16 @@ from aiopg.sa import create_engine
from aioredis import create_pool from aioredis import create_pool
from demo.db_auth import DBAuthorizationPolicy from demo.database_auth.db_auth import DBAuthorizationPolicy
from demo.handlers import Web from demo.database_auth.handlers import Web
@asyncio.coroutine async def init(loop):
def init(loop): redis_pool = await create_pool(('localhost', 6379))
redis_pool = yield from create_pool(('localhost', 6379)) db_engine = await create_engine(user='aiohttp_security',
db_engine = yield from create_engine(user='aiohttp_security', password='aiohttp_security',
password='aiohttp_security', database='aiohttp_security',
database='aiohttp_security', host='127.0.0.1')
host='127.0.0.1')
app = web.Application(loop=loop) app = web.Application(loop=loop)
app.db_engine = db_engine app.db_engine = db_engine
setup_session(app, RedisStorage(redis_pool)) setup_session(app, RedisStorage(redis_pool))
@@ -31,21 +30,20 @@ def init(loop):
web_handlers.configure(app) web_handlers.configure(app)
handler = app.make_handler() handler = app.make_handler()
srv = yield from loop.create_server(handler, '127.0.0.1', 8080) srv = await 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 return srv, app, handler
@asyncio.coroutine async def finalize(srv, app, handler):
def finalize(srv, app, handler):
sock = srv.sockets[0] sock = srv.sockets[0]
app.loop.remove_reader(sock.fileno()) app.loop.remove_reader(sock.fileno())
sock.close() sock.close()
yield from handler.finish_connections(1.0) await handler.finish_connections(1.0)
srv.close() srv.close()
yield from srv.wait_closed() await srv.wait_closed()
yield from app.finish() await app.finish()
def main(): def main():

View File

@@ -0,0 +1,34 @@
from aiohttp_security.abc import AbstractAuthorizationPolicy
class DictionaryAuthorizationPolicy(AbstractAuthorizationPolicy):
def __init__(self, user_map):
super().__init__()
self.user_map = user_map
async def authorized_userid(self, identity):
"""Retrieve authorized user id.
Return the user_id of the user identified by the identity
or 'None' if no user exists related to the identity.
"""
if identity in self.user_map:
return identity
async def permits(self, identity, permission, context=None):
"""Check user permissions.
Return True if the identity is allowed the permission in the
current context, else return False.
"""
# pylint: disable=unused-argument
user = self.user_map.get(identity)
if not user:
return False
return permission in user.permissions
async def check_credentials(user_map, username, password):
user = user_map.get(username)
if not user:
return False
return user.password == password

View File

@@ -0,0 +1,94 @@
from textwrap import dedent
from aiohttp import web
from aiohttp_security import (
remember, forget, authorized_userid,
has_permission, login_required,
)
from .authz import check_credentials
index_template = dedent("""
<!doctype html>
<head></head>
<body>
<p>{message}</p>
<form action="/login" method="post">
Login:
<input type="text" name="username">
Password:
<input type="password" name="password">
<input type="submit" value="Login">
</form>
<a href="/logout">Logout</a>
</body>
""")
async def index(request):
username = await authorized_userid(request)
if username:
template = index_template.format(
message='Hello, {username}!'.format(username=username))
else:
template = index_template.format(message='You need to login')
return web.Response(
text=template,
content_type='text/html',
)
async def login(request):
response = web.HTTPFound('/')
form = await request.post()
username = form.get('username')
password = form.get('password')
verified = await check_credentials(
request.app.user_map, username, password)
if verified:
await remember(request, response, username)
return response
return web.HTTPUnauthorized(body='Invalid username / password combination')
@login_required
async def logout(request):
response = web.Response(
text='You have been logged out',
content_type='text/html',
)
await forget(request, response)
return response
@has_permission('public')
async def internal_page(request):
# pylint: disable=unused-argument
response = web.Response(
text='This page is visible for all registered users',
content_type='text/html',
)
return response
@has_permission('protected')
async def protected_page(request):
# pylint: disable=unused-argument
response = web.Response(
text='You are on protected page',
content_type='text/html',
)
return response
def configure_handlers(app):
router = app.router
router.add_get('/', index, name='index')
router.add_post('/login', login, name='login')
router.add_get('/logout', logout, name='logout')
router.add_get('/public', internal_page, name='public')
router.add_get('/protected', protected_page, name='protected')

View File

@@ -0,0 +1,33 @@
import base64
from cryptography import fernet
from aiohttp import web
from aiohttp_session import setup as setup_session
from aiohttp_session.cookie_storage import EncryptedCookieStorage
from aiohttp_security import setup as setup_security
from aiohttp_security import SessionIdentityPolicy
from demo.dictionary_auth.authz import DictionaryAuthorizationPolicy
from demo.dictionary_auth.handlers import configure_handlers
from demo.dictionary_auth.users import user_map
def make_app():
app = web.Application()
app.user_map = user_map
configure_handlers(app)
# secret_key must be 32 url-safe base64-encoded bytes
fernet_key = fernet.Fernet.generate_key()
secret_key = base64.urlsafe_b64decode(fernet_key)
storage = EncryptedCookieStorage(secret_key, cookie_name='API_SESSION')
setup_session(app, storage)
policy = SessionIdentityPolicy()
setup_security(app, policy, DictionaryAuthorizationPolicy(user_map))
return app
if __name__ == '__main__':
web.run_app(make_app(), port=9000)

View File

@@ -0,0 +1,10 @@
from collections import namedtuple
User = namedtuple('User', ['username', 'password', 'permissions'])
user_map = {
user.username: user for user in [
User('devin', 'password', ('public',)),
User('jack', 'password', ('public', 'protected',)),
]
}

View File

@@ -1,95 +0,0 @@
import asyncio
import functools
from aiohttp import web
from aiohttp_security import remember, forget, authorized_userid, permits
from .db_auth import check_credentials
def require(permission):
def wrapper(f):
@asyncio.coroutine
@functools.wraps(f)
def wrapped(self, request):
has_perm = yield from permits(request, permission)
if not has_perm:
message = 'User has no permission {}'.format(permission)
raise web.HTTPForbidden(body=message.encode())
return (yield from f(self, request))
return wrapped
return wrapper
class Web(object):
index_template = """
<!doctype html>
<head>
</head>
<body>
<p>{message}</p>
<form action="/login" method="post">
Login:
<input type="text" name="login">
Password:
<input type="password" name="password">
<input type="submit" value="Login">
</form>
<a href="/logout">Logout</a>
</body>
"""
@asyncio.coroutine
def index(self, request):
username = yield from authorized_userid(request)
if username:
template = self.index_template.format(
message='Hello, {username}!'.format(username=username))
else:
template = self.index_template.format(message='You need to login')
response = web.Response(body=template.encode())
return response
@asyncio.coroutine
def login(self, request):
response = web.HTTPFound('/')
form = yield from request.post()
login = form.get('login')
password = form.get('password')
db_engine = request.app.db_engine
if (yield from check_credentials(db_engine, login, password)):
yield from remember(request, response, login)
return response
return web.HTTPUnauthorized(
body=b'Invalid username/password combination')
@require('public')
@asyncio.coroutine
def logout(self, request):
response = web.Response(body=b'You have been logged out')
yield from forget(request, response)
return response
@require('public')
@asyncio.coroutine
def internal_page(self, request):
response = web.Response(
body=b'This page is visible for all registered users')
return response
@require('protected')
@asyncio.coroutine
def protected_page(self, request):
response = web.Response(body=b'You are on protected page')
return response
def configure(self, app):
router = app.router
router.add_route('GET', '/', self.index, name='index')
router.add_route('POST', '/login', self.login, name='login')
router.add_route('GET', '/logout', self.logout, name='logout')
router.add_route('GET', '/public', self.internal_page, name='public')
router.add_route('GET', '/protected', self.protected_page,
name='protected')

View File

@@ -142,9 +142,9 @@ html_theme_options = {
'logo': 'aiohttp-icon-128x128.png', 'logo': 'aiohttp-icon-128x128.png',
'description': 'Authorization and identity for aoihttp', 'description': 'Authorization and identity for aoihttp',
'github_user': 'aio-libs', 'github_user': 'aio-libs',
'github_repo': 'aiohttp_security', 'github_repo': 'aiohttp-security',
'github_button': True, 'github_button': True,
'github_style': 'star', 'github_type': 'star',
'github_banner': True, 'github_banner': True,
'travis_button': True, 'travis_button': True,
'codecov_button': True, 'codecov_button': True,

View File

@@ -10,16 +10,14 @@ Simple example::
import asyncio import asyncio
from aiohttp import web from aiohttp import web
@asyncio.coroutine async def root_handler(request):
def root_handler(request):
text = "Alive and kicking!" text = "Alive and kicking!"
return web.Response(body=text.encode('utf-8')) return web.Response(body=text.encode('utf-8'))
# option 2: auth at a higher level? # option 2: auth at a higher level?
# set user_id and allowed in the wsgi handler # set user_id and allowed in the wsgi handler
@protect('view_user') @protect('view_user')
@asyncio.coroutine async def user_handler(request):
def user_handler(request):
name = request.match_info.get('name', "Anonymous") name = request.match_info.get('name', "Anonymous")
text = "Hello, " + name text = "Hello, " + name
return web.Response(body=text.encode('utf-8')) return web.Response(body=text.encode('utf-8'))
@@ -27,14 +25,12 @@ Simple example::
# option 3: super low # option 3: super low
# wsgi doesn't do anything # wsgi doesn't do anything
@asyncio.coroutine async def user_update_handler(request):
def user_update_handler(request):
# identity, asked_permission # identity, asked_permission
user_id = yield from identity_policy.identify(request) user_id = await identity_policy.identify(request)
identity = yield from auth_policy.authorized_user_id(user_id) identity = await auth_policy.authorized_userid(user_id)
allowed = yield from request.auth_policy.permits( allowed = await request.auth_policy.permits(
identity, asked_permission identity, asked_permission)
)
if not allowed: if not allowed:
# how is this pluggable as well? # how is this pluggable as well?
# ? return NotAllowedStream() # ? return NotAllowedStream()
@@ -42,8 +38,7 @@ Simple example::
update_user() update_user()
@asyncio.coroutine async def init(loop):
def init(loop):
# set up identity and auth # set up identity and auth
auth_policy = DictionaryAuthorizationPolicy({'me': ('view_user',), auth_policy = DictionaryAuthorizationPolicy({'me': ('view_user',),
'you': ('view_user', 'you': ('view_user',
@@ -60,8 +55,8 @@ Simple example::
app.router.add_route('GET', '/{user}/edit', user_update_handler) app.router.add_route('GET', '/{user}/edit', user_update_handler)
# get it started # get it started
srv = yield from loop.create_server(app.make_handler(), srv = await loop.create_server(app.make_handler(),
'127.0.0.1', 8080) '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 return srv

View File

@@ -12,7 +12,7 @@ the `demo source`_.
https://github.com/aio-libs/aiohttp_security/tree/master/demo https://github.com/aio-libs/aiohttp_security/tree/master/demo
.. _passlib: .. _passlib:
https://pythonhosted.org/passlib/ https://passlib.readthedocs.io
Database Database
-------- --------
@@ -21,12 +21,14 @@ Launch these sql scripts to init database and fill it with sample data:
``psql template1 < demo/sql/init_db.sql`` ``psql template1 < demo/sql/init_db.sql``
and then and
``psql template1 < demo/sql/sample_data.sql`` ``psql template1 < demo/sql/sample_data.sql``
You will have two tables for storing users and their permissions Now you have two tables:
- for storing users
+--------------+ +--------------+
| users | | users |
@@ -42,7 +44,7 @@ You will have two tables for storing users and their permissions
| disabled | | disabled |
+--------------+ +--------------+
and second table is permissions table: - for storing their permissions
+-----------------+ +-----------------+
| permissions | | permissions |
@@ -63,48 +65,46 @@ First one should have these methods: *identify*, *remember* and *forget*.
For second one: *authorized_userid* and *permits*. We will use built-in For second one: *authorized_userid* and *permits*. We will use built-in
*SessionIdentityPolicy* and write our own database-based authorization policy. *SessionIdentityPolicy* and write our own database-based authorization policy.
In our example we will lookup database by user login and if present return In our example we will lookup database by user login and if presents then return
this identity:: this identity::
@asyncio.coroutine async def authorized_userid(self, identity):
def authorized_userid(self, identity): async with self.dbengine as conn:
with (yield from self.dbengine) as conn:
where = sa.and_(db.users.c.login == identity, where = sa.and_(db.users.c.login == identity,
sa.not_(db.users.c.disabled)) sa.not_(db.users.c.disabled))
query = db.users.count().where(where) query = db.users.count().where(where)
ret = yield from conn.scalar(query) ret = await conn.scalar(query)
if ret: if ret:
return identity return identity
else: else:
return None return None
For permission check we will fetch the user first, check if he is superuser For permission checking we will fetch the user first, check if he is superuser
(all permissions are allowed), otherwise check if permission is explicitly set (all permissions are allowed), otherwise check if permission is explicitly set
for that user:: for that user::
@asyncio.coroutine async def permits(self, identity, permission, context=None):
def permits(self, identity, permission, context=None):
if identity is None: if identity is None:
return False return False
with (yield from self.dbengine) as conn: async with self.dbengine as conn:
where = sa.and_(db.users.c.login == identity, where = sa.and_(db.users.c.login == identity,
sa.not_(db.users.c.disabled)) sa.not_(db.users.c.disabled))
query = db.users.select().where(where) query = db.users.select().where(where)
ret = yield from conn.execute(query) ret = await conn.execute(query)
user = yield from ret.fetchone() user = await ret.fetchone()
if user is not None: if user is not None:
user_id = user[0] user_id = user[0]
is_superuser = user[4] is_superuser = user[3]
if is_superuser: if is_superuser:
return True return True
where = db.permissions.c.user_id == user_id where = db.permissions.c.user_id == user_id
query = db.permissions.select().where(where) query = db.permissions.select().where(where)
ret = yield from conn.execute(query) ret = await conn.execute(query)
result = yield from ret.fetchall() result = await ret.fetchall()
if ret is not None: if ret is not None:
for record in result: for record in result:
if record.perm_name == permission: if record.perm_name == permission:
@@ -127,13 +127,12 @@ Once we have all the code in place we can install it for our application::
from .db_auth import DBAuthorizationPolicy from .db_auth import DBAuthorizationPolicy
@asyncio.coroutine async def init(loop):
def init(loop): redis_pool = await create_pool(('localhost', 6379))
redis_pool = yield from create_pool(('localhost', 6379)) dbengine = await create_engine(user='aiohttp_security',
dbengine = yield from create_engine(user='aiohttp_security', password='aiohttp_security',
password='aiohttp_security', database='aiohttp_security',
database='aiohttp_security', host='127.0.0.1')
host='127.0.0.1')
app = web.Application(loop=loop) app = web.Application(loop=loop)
setup_session(app, RedisStorage(redis_pool)) setup_session(app, RedisStorage(redis_pool))
setup_security(app, setup_security(app,
@@ -143,39 +142,33 @@ 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 Now we have authorization and can decorate every other view with access rights
based on permissions. This simple decorator (for class-based handlers) will based on permissions. There are already implemented two decorators::
help to do that::
def require(permission): from aiohttp_security import has_permission, login_required
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::
For each view you need to protect just apply the decorator on it::
class Web: class Web:
@require('protected') @has_permission('protected')
@asyncio.coroutine async def protected_page(self, request):
def protected_page(self, request):
response = web.Response(body=b'You are on protected page') response = web.Response(body=b'You are on protected page')
return response return response
or::
If someone will try to access this protected page he will see:: class Web:
@login_required
async def logout(self, request):
response = web.Response(body=b'You have been logged out')
await forget(request, response)
return response
403, User has no permission "protected" If someone try to access that protected page he will see::
403: Forbidden
The best part about it is that you can implement any logic you want until it The best part of it - you can implement any logic you want until it
follows the API conventions. follows the API conventions.
Launch application Launch application
@@ -183,18 +176,17 @@ Launch application
For working with passwords there is a good library passlib_. Once you've 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 created some users you want to check their credentials on login. Similar
function may do what you trying to accomplish:: function may do what you are trying to accomplish::
from passlib.hash import sha256_crypt from passlib.hash import sha256_crypt
@asyncio.coroutine async def check_credentials(db_engine, username, password):
def check_credentials(db_engine, username, password): async with db_engine as conn:
with (yield from db_engine) as conn:
where = sa.and_(db.users.c.login == username, where = sa.and_(db.users.c.login == username,
sa.not_(db.users.c.disabled)) sa.not_(db.users.c.disabled))
query = db.users.select().where(where) query = db.users.select().where(where)
ret = yield from conn.execute(query) ret = await conn.execute(query)
user = yield from ret.fetchone() user = await ret.fetchone()
if user is not None: if user is not None:
hash = user[2] hash = user[2]
return sha256_crypt.verify(password, hash) return sha256_crypt.verify(password, hash)
@@ -203,8 +195,8 @@ function may do what you trying to accomplish::
Final step is to launch your application:: Final step is to launch your application::
python demo/main.py python demo/database_auth/main.py
Try to login with admin/moderator/user accounts (with *password* password) Try to login with admin/moderator/user accounts (with **password** password)
and access **/public** or **/protected** endpoints. and access **/public** or **/protected** endpoints.

View File

@@ -3,16 +3,9 @@ aiohttp_security
The library provides security for :ref:`aiohttp.web<aiohttp-web>`. The library provides security for :ref:`aiohttp.web<aiohttp-web>`.
Usage
-----
Contents
License --------
-------
``aiohttp_security`` is offered under the Apache 2 license.
Contents:
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
@@ -23,7 +16,10 @@ Contents:
example_db_auth example_db_auth
glossary glossary
License
-------
``aiohttp_security`` is offered under the Apache 2 license.
Indices and tables Indices and tables
================== ==================

View File

@@ -21,7 +21,7 @@ Public API functions
The action is performed by registered The action is performed by registered
:meth:`AbstractIdentityPolicy.remember`. :meth:`AbstractIdentityPolicy.remember`.
Usually the *idenity* is stored in user cookies homehow for using by Usually the *identity* is stored in user cookies somehow for using by
:func:`authorized_userid` and :func:`permits`. :func:`authorized_userid` and :func:`permits`.
:param request: :class:`aiohttp.web.Request` object. :param request: :class:`aiohttp.web.Request` object.
@@ -78,7 +78,7 @@ Public API functions
:param request: :class:`aiohttp.web.Request` object. :param request: :class:`aiohttp.web.Request` object.
:param str permission: requested :term:`permission`. :param permission: Requested :term:`permission`. :class:`str` or :class:`enum.Enum` object.
:param context: additional object may be passed into :param context: additional object may be passed into
:meth:`AbstractAuthorizationPolicy.permission` :meth:`AbstractAuthorizationPolicy.permission`
@@ -88,6 +88,33 @@ Public API functions
``False`` otherwise. ``False`` otherwise.
.. coroutinefunction:: is_anonymous(request)
Checks if user is anonymous user.
Return ``True`` if user is not remembered in request, otherwise returns ``False``.
:param request: :class:`aiohttp.web.Request` object.
.. decorator:: login_required
Decorator for handlers that checks if user is authorized.
Raises :class:`aiohttp.web.HTTPUnauthorized` if user is not authorized.
.. 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) .. function:: setup(app, identity_policy, autz_policy)
Setup :mod:`aiohttp` application with security policies. Setup :mod:`aiohttp` application with security policies.

View File

@@ -13,6 +13,10 @@ First of all, what is *aiohttp_security* about?
It is a set of public API functions and standard for implementation details. It is a set of public API functions and standard for implementation details.
Public API
==========
API is implementation agnostic, all client code should not call policy API is implementation agnostic, all client code should not call policy
code (see below) directly but use API only. code (see below) directly but use API only.
@@ -27,9 +31,6 @@ base classes for both concepts as well as several implementations
shipped with the library. End user is free to build own implemetations shipped with the library. End user is free to build own implemetations
if needed. if needed.
Public API
==========
Authentication Authentication
============== ==============
@@ -43,11 +44,6 @@ knowledge is there the user still registered in DB.
If :class:`aiohttp.web.Request` has an :term:`identity` it means the user has If :class:`aiohttp.web.Request` has an :term:`identity` it means the user has
some ID that should be checked by :term:`authorization` policy. some ID that should be checked by :term:`authorization` policy.
:term:`identity` is a string shared between browser and server.
identity is a string shared between browser and server.
Thus it's not supposed to be database primary key, user login/email etc. Thus it's not supposed to be database primary key, user login/email etc.
Random string like uuid or hash is better choice. Random string like uuid or hash is better choice.

View File

@@ -1,14 +1,15 @@
-e . -e .
flake8 flake8==3.5.0
pytest pytest==3.4.2
pytest-cov pytest-cov==2.5.1
coverage coverage==4.5.1
sphinx sphinx==1.7.1
pep257 pep257==0.7.0
aiohttp_session aiohttp-session==2.3.0
aiopg[sa] aiopg[sa]==0.13.2
aioredis==0.2.8 aioredis==1.1.0
hiredis==0.2.0 hiredis==0.2.0
passlib==1.6.5 passlib==1.7.1
aiohttp cryptography==2.2
pytest-aiohttp aiohttp==3.0.9
pytest-aiohttp==0.3.0

View File

@@ -27,10 +27,12 @@ with codecs.open(os.path.join(os.path.abspath(os.path.dirname(
def read(f): def read(f):
return open(os.path.join(os.path.dirname(__file__), f)).read().strip() return open(os.path.join(os.path.dirname(__file__), f)).read().strip()
install_requires = ['aiohttp>=0.18'] install_requires = ['aiohttp>=0.18']
tests_require = install_requires + ['pytest'] tests_require = install_requires + ['pytest']
extras_require = {'session': 'aiohttp-session'} extras_require = {'session': 'aiohttp-session'}
setup(name='aiohttp-security', setup(name='aiohttp-security',
version=version, version=version,
description=("security for aiohttp.web"), description=("security for aiohttp.web"),
@@ -40,9 +42,11 @@ setup(name='aiohttp-security',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'Programming Language :: Python', 'Programming Language :: Python',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.6',
'Topic :: Internet :: WWW/HTTP'], 'Topic :: Internet :: WWW/HTTP',
'Framework :: AsyncIO',
],
author='Andrew Svetlov', author='Andrew Svetlov',
author_email='andrew.svetlov@gmail.com', author_email='andrew.svetlov@gmail.com',
url='https://github.com/aio-libs/aiohttp_security/', url='https://github.com/aio-libs/aiohttp_security/',

View File

@@ -1,5 +1,3 @@
import asyncio
from aiohttp import web from aiohttp import web
from aiohttp_security import (remember, forget, from aiohttp_security import (remember, forget,
AbstractAuthorizationPolicy) AbstractAuthorizationPolicy)
@@ -10,47 +8,39 @@ from aiohttp_security.api import IDENTITY_KEY
class Autz(AbstractAuthorizationPolicy): class Autz(AbstractAuthorizationPolicy):
@asyncio.coroutine async def permits(self, identity, permission, context=None):
def permits(self, identity, permission, context=None):
pass pass
@asyncio.coroutine async def authorized_userid(self, identity):
def authorized_userid(self, identity):
pass pass
@asyncio.coroutine async def test_remember(loop, test_client):
def test_remember(loop, test_client):
@asyncio.coroutine async def handler(request):
def handler(request):
response = web.Response() response = web.Response()
yield from remember(request, response, 'Andrew') await remember(request, response, 'Andrew')
return response return response
app = web.Application(loop=loop) app = web.Application(loop=loop)
_setup(app, CookiesIdentityPolicy(), Autz()) _setup(app, CookiesIdentityPolicy(), Autz())
app.router.add_route('GET', '/', handler) app.router.add_route('GET', '/', handler)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.get('/') resp = await client.get('/')
assert 200 == resp.status assert 200 == resp.status
assert 'Andrew' == resp.cookies['AIOHTTP_SECURITY'].value assert 'Andrew' == resp.cookies['AIOHTTP_SECURITY'].value
yield from resp.release()
@asyncio.coroutine async def test_identify(loop, test_client):
def test_identify(loop, test_client):
@asyncio.coroutine async def create(request):
def create(request):
response = web.Response() response = web.Response()
yield from remember(request, response, 'Andrew') await remember(request, response, 'Andrew')
return response return response
@asyncio.coroutine async def check(request):
def check(request):
policy = request.app[IDENTITY_KEY] policy = request.app[IDENTITY_KEY]
user_id = yield from policy.identify(request) user_id = await policy.identify(request)
assert 'Andrew' == user_id assert 'Andrew' == user_id
return web.Response() return web.Response()
@@ -58,32 +48,27 @@ def test_identify(loop, test_client):
_setup(app, CookiesIdentityPolicy(), Autz()) _setup(app, CookiesIdentityPolicy(), Autz())
app.router.add_route('GET', '/', check) app.router.add_route('GET', '/', check)
app.router.add_route('POST', '/', create) app.router.add_route('POST', '/', create)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.post('/') resp = await client.post('/')
assert 200 == resp.status assert 200 == resp.status
yield from resp.release() await resp.release()
resp = yield from client.get('/') resp = await client.get('/')
assert 200 == resp.status assert 200 == resp.status
yield from resp.release()
@asyncio.coroutine async def test_forget(loop, test_client):
def test_forget(loop, test_client):
@asyncio.coroutine async def index(request):
def index(request):
return web.Response() return web.Response()
@asyncio.coroutine async def login(request):
def login(request):
response = web.HTTPFound(location='/') response = web.HTTPFound(location='/')
yield from remember(request, response, 'Andrew') await remember(request, response, 'Andrew')
return response return response
@asyncio.coroutine async def logout(request):
def logout(request):
response = web.HTTPFound(location='/') response = web.HTTPFound(location='/')
yield from forget(request, response) await forget(request, response)
return response return response
app = web.Application(loop=loop) app = web.Application(loop=loop)
@@ -91,18 +76,17 @@ def test_forget(loop, test_client):
app.router.add_route('GET', '/', index) app.router.add_route('GET', '/', index)
app.router.add_route('POST', '/login', login) app.router.add_route('POST', '/login', login)
app.router.add_route('POST', '/logout', logout) app.router.add_route('POST', '/logout', logout)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.post('/login') resp = await client.post('/login')
assert 200 == resp.status assert 200 == resp.status
assert resp.url.endswith('/') assert str(resp.url).endswith('/')
cookies = client.session.cookie_jar.filter_cookies( cookies = client.session.cookie_jar.filter_cookies(
client.make_url('/')) client.make_url('/'))
assert 'Andrew' == cookies['AIOHTTP_SECURITY'].value assert 'Andrew' == cookies['AIOHTTP_SECURITY'].value
yield from resp.release()
resp = yield from client.post('/logout') resp = await client.post('/logout')
assert 200 == resp.status assert 200 == resp.status
assert resp.url.endswith('/') assert str(resp.url).endswith('/')
cookies = client.session.cookie_jar.filter_cookies( cookies = client.session.cookie_jar.filter_cookies(
client.make_url('/')) client.make_url('/'))
assert 'AIOHTTP_SECURITY' not in cookies assert 'AIOHTTP_SECURITY' not in cookies
yield from resp.release()

View File

@@ -1,42 +1,37 @@
import asyncio import enum
from aiohttp import web from aiohttp import web
from aiohttp_security import (remember,
authorized_userid, permits,
AbstractAuthorizationPolicy)
from aiohttp_security import setup as _setup from aiohttp_security import setup as _setup
from aiohttp_security import (AbstractAuthorizationPolicy, authorized_userid,
forget, has_permission, is_anonymous,
login_required, permits, remember)
from aiohttp_security.cookies_identity import CookiesIdentityPolicy from aiohttp_security.cookies_identity import CookiesIdentityPolicy
class Autz(AbstractAuthorizationPolicy): class Autz(AbstractAuthorizationPolicy):
@asyncio.coroutine async def permits(self, identity, permission, context=None):
def permits(self, identity, permission, context=None):
if identity == 'UserID': if identity == 'UserID':
return permission in {'read', 'write'} return permission in {'read', 'write'}
else: else:
return False return False
@asyncio.coroutine async def authorized_userid(self, identity):
def authorized_userid(self, identity):
if identity == 'UserID': if identity == 'UserID':
return 'Andrew' return 'Andrew'
else: else:
return None return None
@asyncio.coroutine async def test_authorized_userid(loop, test_client):
def test_authorized_userid(loop, test_client):
@asyncio.coroutine async def login(request):
def login(request):
response = web.HTTPFound(location='/') response = web.HTTPFound(location='/')
yield from remember(request, response, 'UserID') await remember(request, response, 'UserID')
return response return response
@asyncio.coroutine async def check(request):
def check(request): userid = await authorized_userid(request)
userid = yield from authorized_userid(request)
assert 'Andrew' == userid assert 'Andrew' == userid
return web.Response(text=userid) return web.Response(text=userid)
@@ -44,50 +39,61 @@ def test_authorized_userid(loop, test_client):
_setup(app, CookiesIdentityPolicy(), Autz()) _setup(app, CookiesIdentityPolicy(), Autz())
app.router.add_route('GET', '/', check) app.router.add_route('GET', '/', check)
app.router.add_route('POST', '/login', login) app.router.add_route('POST', '/login', login)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.post('/login') resp = await client.post('/login')
assert 200 == resp.status assert 200 == resp.status
txt = yield from resp.text() txt = await resp.text()
assert 'Andrew' == txt assert 'Andrew' == txt
yield from resp.release()
@asyncio.coroutine async def test_authorized_userid_not_authorized(loop, test_client):
def test_authorized_userid_not_authorized(loop, test_client):
@asyncio.coroutine async def check(request):
def check(request): userid = await authorized_userid(request)
userid = yield from authorized_userid(request)
assert userid is None assert userid is None
return web.Response() return web.Response()
app = web.Application(loop=loop) app = web.Application(loop=loop)
_setup(app, CookiesIdentityPolicy(), Autz()) _setup(app, CookiesIdentityPolicy(), Autz())
app.router.add_route('GET', '/', check) app.router.add_route('GET', '/', check)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.get('/') resp = await client.get('/')
assert 200 == resp.status assert 200 == resp.status
yield from resp.release()
@asyncio.coroutine async def test_permits_enum_permission(loop, test_client):
def test_permits(loop, test_client): class Permission(enum.Enum):
READ = '101'
WRITE = '102'
UNKNOWN = '103'
@asyncio.coroutine class Autz(AbstractAuthorizationPolicy):
def login(request):
async def permits(self, identity, permission, context=None):
if identity == 'UserID':
return permission in {Permission.READ, Permission.WRITE}
else:
return False
async def authorized_userid(self, identity):
if identity == 'UserID':
return 'Andrew'
else:
return None
async def login(request):
response = web.HTTPFound(location='/') response = web.HTTPFound(location='/')
yield from remember(request, response, 'UserID') await remember(request, response, 'UserID')
return response return response
@asyncio.coroutine async def check(request):
def check(request): ret = await permits(request, Permission.READ)
ret = yield from permits(request, 'read')
assert ret assert ret
ret = yield from permits(request, 'write') ret = await permits(request, Permission.WRITE)
assert ret assert ret
ret = yield from permits(request, 'unknown') ret = await permits(request, Permission.UNKNOWN)
assert not ret assert not ret
return web.Response() return web.Response()
@@ -95,29 +101,151 @@ def test_permits(loop, test_client):
_setup(app, CookiesIdentityPolicy(), Autz()) _setup(app, CookiesIdentityPolicy(), Autz())
app.router.add_route('GET', '/', check) app.router.add_route('GET', '/', check)
app.router.add_route('POST', '/login', login) app.router.add_route('POST', '/login', login)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.post('/login') resp = await client.post('/login')
assert 200 == resp.status assert 200 == resp.status
yield from resp.release()
@asyncio.coroutine async def test_permits_unauthorized(loop, test_client):
def test_permits_unauthorized(loop, test_client):
@asyncio.coroutine async def check(request):
def check(request): ret = await permits(request, 'read')
ret = yield from permits(request, 'read')
assert not ret assert not ret
ret = yield from permits(request, 'write') ret = await permits(request, 'write')
assert not ret assert not ret
ret = yield from permits(request, 'unknown') ret = await permits(request, 'unknown')
assert not ret assert not ret
return web.Response() return web.Response()
app = web.Application(loop=loop) app = web.Application(loop=loop)
_setup(app, CookiesIdentityPolicy(), Autz()) _setup(app, CookiesIdentityPolicy(), Autz())
app.router.add_route('GET', '/', check) app.router.add_route('GET', '/', check)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.get('/') resp = await client.get('/')
assert 200 == resp.status assert 200 == resp.status
yield from resp.release()
async def test_is_anonymous(loop, test_client):
async def index(request):
is_anon = await is_anonymous(request)
if is_anon:
return web.HTTPUnauthorized()
return web.HTTPOk()
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)
return response
app = web.Application(loop=loop)
_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)
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_login_required(loop, test_client):
@login_required
async def index(request):
return web.HTTPOk()
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)
return response
app = web.Application(loop=loop)
_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)
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_has_permission(loop, test_client):
@has_permission('read')
async def index_read(request):
return web.HTTPOk()
@has_permission('write')
async def index_write(request):
return web.HTTPOk()
@has_permission('forbid')
async def index_forbid(request):
return web.HTTPOk()
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)
return response
app = web.Application(loop=loop)
_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)
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

View File

@@ -1,42 +1,34 @@
import asyncio
from aiohttp import web from aiohttp import web
from aiohttp_security import authorized_userid, permits from aiohttp_security import authorized_userid, permits
@asyncio.coroutine async def test_authorized_userid(loop, test_client):
def test_authorized_userid(loop, test_client):
@asyncio.coroutine async def check(request):
def check(request): userid = await authorized_userid(request)
userid = yield from authorized_userid(request)
assert userid is None assert userid is None
return web.Response() return web.Response()
app = web.Application(loop=loop) app = web.Application(loop=loop)
app.router.add_route('GET', '/', check) app.router.add_route('GET', '/', check)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.get('/') resp = await client.get('/')
assert 200 == resp.status assert 200 == resp.status
yield from resp.release()
@asyncio.coroutine async def test_permits(loop, test_client):
def test_permits(loop, test_client):
@asyncio.coroutine async def check(request):
def check(request): ret = await permits(request, 'read')
ret = yield from permits(request, 'read')
assert ret assert ret
ret = yield from permits(request, 'write') ret = await permits(request, 'write')
assert ret assert ret
ret = yield from permits(request, 'unknown') ret = await permits(request, 'unknown')
assert ret assert ret
return web.Response() return web.Response()
app = web.Application(loop=loop) app = web.Application(loop=loop)
app.router.add_route('GET', '/', check) app.router.add_route('GET', '/', check)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.get('/') resp = await client.get('/')
assert 200 == resp.status assert 200 == resp.status
yield from resp.release()

View File

@@ -1,42 +1,34 @@
import asyncio
from aiohttp import web from aiohttp import web
from aiohttp_security import remember, forget from aiohttp_security import remember, forget
@asyncio.coroutine async def test_remember(loop, test_client):
def test_remember(loop, test_client):
@asyncio.coroutine async def do_remember(request):
def do_remember(request):
response = web.Response() response = web.Response()
yield from remember(request, response, 'Andrew') await remember(request, response, 'Andrew')
app = web.Application(loop=loop) app = web.Application(loop=loop)
app.router.add_route('POST', '/', do_remember) app.router.add_route('POST', '/', do_remember)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.post('/') resp = await client.post('/')
assert 500 == resp.status assert 500 == resp.status
assert (('Security subsystem is not initialized, ' assert (('Security subsystem is not initialized, '
'call aiohttp_security.setup(...) first') == 'call aiohttp_security.setup(...) first') ==
resp.reason) resp.reason)
yield from resp.release()
@asyncio.coroutine async def test_forget(loop, test_client):
def test_forget(loop, test_client):
@asyncio.coroutine async def do_forget(request):
def do_forget(request):
response = web.Response() response = web.Response()
yield from forget(request, response) await forget(request, response)
app = web.Application(loop=loop) app = web.Application(loop=loop)
app.router.add_route('POST', '/', do_forget) app.router.add_route('POST', '/', do_forget)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.post('/') resp = await client.post('/')
assert 500 == resp.status assert 500 == resp.status
assert (('Security subsystem is not initialized, ' assert (('Security subsystem is not initialized, '
'call aiohttp_security.setup(...) first') == 'call aiohttp_security.setup(...) first') ==
resp.reason) resp.reason)
yield from resp.release()

View File

@@ -1,4 +1,3 @@
import asyncio
import pytest import pytest
from aiohttp import web from aiohttp import web
@@ -13,12 +12,10 @@ from aiohttp_session import setup as setup_session
class Autz(AbstractAuthorizationPolicy): class Autz(AbstractAuthorizationPolicy):
@asyncio.coroutine async def permits(self, identity, permission, context=None):
def permits(self, identity, permission, context=None):
pass pass
@asyncio.coroutine async def authorized_userid(self, identity):
def authorized_userid(self, identity):
pass pass
@@ -30,81 +27,67 @@ def make_app(loop):
return app return app
@asyncio.coroutine async def test_remember(make_app, test_client):
def test_remember(make_app, test_client):
@asyncio.coroutine async def handler(request):
def handler(request):
response = web.Response() response = web.Response()
yield from remember(request, response, 'Andrew') await remember(request, response, 'Andrew')
return response return response
@asyncio.coroutine async def check(request):
def check(request): session = await get_session(request)
session = yield from get_session(request)
assert session['AIOHTTP_SECURITY'] == 'Andrew' assert session['AIOHTTP_SECURITY'] == 'Andrew'
return web.HTTPOk() return web.HTTPOk()
app = make_app() app = make_app()
app.router.add_route('GET', '/', handler) app.router.add_route('GET', '/', handler)
app.router.add_route('GET', '/check', check) app.router.add_route('GET', '/check', check)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.get('/') resp = await client.get('/')
assert 200 == resp.status assert 200 == resp.status
yield from resp.release()
resp = yield from client.get('/check') resp = await client.get('/check')
assert 200 == resp.status assert 200 == resp.status
yield from resp.release()
@asyncio.coroutine async def test_identify(make_app, test_client):
def test_identify(make_app, test_client):
@asyncio.coroutine async def create(request):
def create(request):
response = web.Response() response = web.Response()
yield from remember(request, response, 'Andrew') await remember(request, response, 'Andrew')
return response return response
@asyncio.coroutine async def check(request):
def check(request):
policy = request.app[IDENTITY_KEY] policy = request.app[IDENTITY_KEY]
user_id = yield from policy.identify(request) user_id = await policy.identify(request)
assert 'Andrew' == user_id assert 'Andrew' == user_id
return web.Response() return web.Response()
app = make_app() app = make_app()
app.router.add_route('GET', '/', check) app.router.add_route('GET', '/', check)
app.router.add_route('POST', '/', create) app.router.add_route('POST', '/', create)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.post('/') resp = await client.post('/')
assert 200 == resp.status assert 200 == resp.status
yield from resp.release()
resp = yield from client.get('/') resp = await client.get('/')
assert 200 == resp.status assert 200 == resp.status
yield from resp.release()
@asyncio.coroutine async def test_forget(make_app, test_client):
def test_forget(make_app, test_client):
@asyncio.coroutine async def index(request):
def index(request): session = await get_session(request)
session = yield from get_session(request)
return web.HTTPOk(text=session.get('AIOHTTP_SECURITY', '')) return web.HTTPOk(text=session.get('AIOHTTP_SECURITY', ''))
@asyncio.coroutine async def login(request):
def login(request):
response = web.HTTPFound(location='/') response = web.HTTPFound(location='/')
yield from remember(request, response, 'Andrew') await remember(request, response, 'Andrew')
return response return response
@asyncio.coroutine async def logout(request):
def logout(request):
response = web.HTTPFound('/') response = web.HTTPFound('/')
yield from forget(request, response) await forget(request, response)
return response return response
app = make_app() app = make_app()
@@ -112,18 +95,16 @@ def test_forget(make_app, test_client):
app.router.add_route('POST', '/login', login) app.router.add_route('POST', '/login', login)
app.router.add_route('POST', '/logout', logout) app.router.add_route('POST', '/logout', logout)
client = yield from test_client(app) client = await test_client(app)
resp = yield from client.post('/login') resp = await client.post('/login')
assert 200 == resp.status assert 200 == resp.status
assert resp.url.endswith('/') assert str(resp.url).endswith('/')
txt = yield from resp.text() txt = await resp.text()
assert 'Andrew' == txt assert 'Andrew' == txt
yield from resp.release()
resp = yield from client.post('/logout') resp = await client.post('/logout')
assert 200 == resp.status assert 200 == resp.status
assert resp.url.endswith('/') assert str(resp.url).endswith('/')
txt = yield from resp.text() txt = await resp.text()
assert '' == txt assert '' == txt
yield from resp.release()