Skip to content

Commit 1679f67

Browse files
enabokovasvetlov
authored andcommitted
Update docs and demo with login required, has permission (#128)
* Work on * Update docs with login_required and has_permission
1 parent f9628b0 commit 1679f67

File tree

6 files changed

+84
-108
lines changed

6 files changed

+84
-108
lines changed

demo/database_auth/handlers.py

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,31 @@
1-
import functools
1+
from textwrap import dedent
22

33
from aiohttp import web
44

5-
from aiohttp_security import remember, forget, authorized_userid, permits
5+
from aiohttp_security import (
6+
remember, forget, authorized_userid,
7+
has_permission, login_required,
8+
)
69

710
from .db_auth import check_credentials
811

912

10-
def require(permission):
11-
def wrapper(f):
12-
@functools.wraps(f)
13-
async def wrapped(self, request):
14-
has_perm = await permits(request, permission)
15-
if not has_perm:
16-
message = 'User has no permission {}'.format(permission)
17-
raise web.HTTPForbidden(body=message.encode())
18-
return await f(self, request)
19-
return wrapped
20-
return wrapper
21-
22-
2313
class Web(object):
24-
index_template = """
25-
<!doctype html>
26-
<head>
27-
</head>
28-
<body>
29-
<p>{message}</p>
30-
<form action="/login" method="post">
31-
Login:
32-
<input type="text" name="login">
33-
Password:
34-
<input type="password" name="password">
35-
<input type="submit" value="Login">
36-
</form>
37-
<a href="/logout">Logout</a>
38-
</body>
39-
"""
14+
index_template = dedent("""
15+
<!doctype html>
16+
<head></head>
17+
<body>
18+
<p>{message}</p>
19+
<form action="/login" method="post">
20+
Login:
21+
<input type="text" name="login">
22+
Password:
23+
<input type="password" name="password">
24+
<input type="submit" value="Login">
25+
</form>
26+
<a href="/logout">Logout</a>
27+
</body>
28+
""")
4029

4130
async def index(self, request):
4231
username = await authorized_userid(request)
@@ -61,19 +50,19 @@ async def login(self, request):
6150
return web.HTTPUnauthorized(
6251
body=b'Invalid username/password combination')
6352

64-
@require('public')
53+
@login_required
6554
async def logout(self, request):
6655
response = web.Response(body=b'You have been logged out')
6756
await forget(request, response)
6857
return response
6958

70-
@require('public')
59+
@has_permission('public')
7160
async def internal_page(self, request):
7261
response = web.Response(
7362
body=b'This page is visible for all registered users')
7463
return response
7564

76-
@require('protected')
65+
@has_permission('protected')
7766
async def protected_page(self, request):
7867
response = web.Response(body=b'You are on protected page')
7968
return response

demo/database_auth/main.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@
99
from aioredis import create_pool
1010

1111

12-
from demo.db_auth import DBAuthorizationPolicy
13-
from demo.handlers import Web
12+
from demo.database_auth.db_auth import DBAuthorizationPolicy
13+
from demo.database_auth.handlers import Web
1414

1515

1616
async def init(loop):
1717
redis_pool = await create_pool(('localhost', 6379))
1818
db_engine = await create_engine(user='aiohttp_security',
19-
password='aiohttp_security',
20-
database='aiohttp_security',
21-
host='127.0.0.1')
19+
password='aiohttp_security',
20+
database='aiohttp_security',
21+
host='127.0.0.1')
2222
app = web.Application(loop=loop)
2323
app.db_engine = db_engine
2424
setup_session(app, RedisStorage(redis_pool))

demo/dictionary_auth/handlers.py

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,30 @@
1-
import functools
21
from textwrap import dedent
32

43
from aiohttp import web
54

6-
from aiohttp_security import remember, forget, authorized_userid, permits
5+
from aiohttp_security import (
6+
remember, forget, authorized_userid,
7+
has_permission, login_required,
8+
)
79

810
from .authz import check_credentials
911

1012

11-
def require(permission):
12-
def wrapper(f):
13-
@functools.wraps(f)
14-
async def wrapped(request):
15-
has_perm = await permits(request, permission)
16-
if not has_perm:
17-
message = 'User has no permission {}'.format(permission)
18-
raise web.HTTPForbidden(body=message.encode())
19-
return await f(request)
20-
return wrapped
21-
return wrapper
22-
23-
2413
index_template = dedent("""
2514
<!doctype html>
26-
<head>
27-
</head>
28-
<body>
29-
<p>{message}</p>
30-
<form action="/login" method="post">
31-
Login:
32-
<input type="text" name="username">
33-
Password:
34-
<input type="password" name="password">
35-
<input type="submit" value="Login">
36-
</form>
37-
<a href="/logout">Logout</a>
38-
</body>
39-
""")
15+
<head></head>
16+
<body>
17+
<p>{message}</p>
18+
<form action="/login" method="post">
19+
Login:
20+
<input type="text" name="username">
21+
Password:
22+
<input type="password" name="password">
23+
<input type="submit" value="Login">
24+
</form>
25+
<a href="/logout">Logout</a>
26+
</body>
27+
""")
4028

4129

4230
async def index(request):
@@ -58,15 +46,16 @@ async def login(request):
5846
username = form.get('username')
5947
password = form.get('password')
6048

61-
verified = await check_credentials(request.app.user_map, username, password)
49+
verified = await check_credentials(
50+
request.app.user_map, username, password)
6251
if verified:
6352
await remember(request, response, username)
6453
return response
6554

6655
return web.HTTPUnauthorized(body='Invalid username / password combination')
6756

6857

69-
@require('public')
58+
@login_required
7059
async def logout(request):
7160
response = web.Response(
7261
text='You have been logged out',
@@ -76,7 +65,7 @@ async def logout(request):
7665
return response
7766

7867

79-
@require('public')
68+
@has_permission('public')
8069
async def internal_page(request):
8170
# pylint: disable=unused-argument
8271
response = web.Response(
@@ -86,7 +75,7 @@ async def internal_page(request):
8675
return response
8776

8877

89-
@require('protected')
78+
@has_permission('protected')
9079
async def protected_page(request):
9180
# pylint: disable=unused-argument
9281
response = web.Response(

demo/dictionary_auth/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
from aiohttp_security import setup as setup_security
77
from aiohttp_security import SessionIdentityPolicy
88

9-
from .authz import DictionaryAuthorizationPolicy
10-
from .handlers import configure_handlers
11-
from .users import user_map
9+
from demo.dictionary_auth.authz import DictionaryAuthorizationPolicy
10+
from demo.dictionary_auth.handlers import configure_handlers
11+
from demo.dictionary_auth.users import user_map
1212

1313

1414
def make_app():

docs/example.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ Simple example::
2828
async def user_update_handler(request):
2929
# identity, asked_permission
3030
user_id = await identity_policy.identify(request)
31-
identity = await auth_policy.authorized_user_id(user_id)
32-
allowed = await request.auth_policy.permits(identity,
33-
asked_permission)
31+
identity = await auth_policy.authorized_userid(user_id)
32+
allowed = await request.auth_policy.permits(
33+
identity, asked_permission)
3434
if not allowed:
3535
# how is this pluggable as well?
3636
# ? return NotAllowedStream()
@@ -56,7 +56,7 @@ Simple example::
5656

5757
# get it started
5858
srv = await loop.create_server(app.make_handler(),
59-
'127.0.0.1', 8080)
59+
'127.0.0.1', 8080)
6060
print("Server started at http://127.0.0.1:8080")
6161
return srv
6262

docs/example_db_auth.rst

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ Launch these sql scripts to init database and fill it with sample data:
2121

2222
``psql template1 < demo/sql/init_db.sql``
2323

24-
and then
24+
and
2525

2626
``psql template1 < demo/sql/sample_data.sql``
2727

2828

29-
You will have two tables for storing users and their permissions
29+
Now you have two tables:
30+
31+
- for storing users
3032

3133
+--------------+
3234
| users |
@@ -42,7 +44,7 @@ You will have two tables for storing users and their permissions
4244
| disabled |
4345
+--------------+
4446

45-
and second table is permissions table:
47+
- for storing their permissions
4648

4749
+-----------------+
4850
| permissions |
@@ -63,12 +65,12 @@ First one should have these methods: *identify*, *remember* and *forget*.
6365
For second one: *authorized_userid* and *permits*. We will use built-in
6466
*SessionIdentityPolicy* and write our own database-based authorization policy.
6567

66-
In our example we will lookup database by user login and if present return
68+
In our example we will lookup database by user login and if presents then return
6769
this identity::
6870

6971

7072
async def authorized_userid(self, identity):
71-
async with self.dbengine as conn:
73+
async with self.dbengine as conn:
7274
where = sa.and_(db.users.c.login == identity,
7375
sa.not_(db.users.c.disabled))
7476
query = db.users.count().where(where)
@@ -79,7 +81,7 @@ this identity::
7981
return None
8082

8183

82-
For permission check we will fetch the user first, check if he is superuser
84+
For permission checking we will fetch the user first, check if he is superuser
8385
(all permissions are allowed), otherwise check if permission is explicitly set
8486
for that user::
8587

@@ -95,7 +97,7 @@ for that user::
9597
user = await ret.fetchone()
9698
if user is not None:
9799
user_id = user[0]
98-
is_superuser = user[4]
100+
is_superuser = user[3]
99101
if is_superuser:
100102
return True
101103

@@ -140,45 +142,41 @@ Once we have all the code in place we can install it for our application::
140142

141143

142144
Now we have authorization and can decorate every other view with access rights
143-
based on permissions. This simple decorator (for class-based handlers) will
144-
help to do that::
145-
146-
def require(permission):
147-
def wrapper(f):
148-
@functools.wraps(f)
149-
async def wrapped(self, request):
150-
has_perm = await permits(request, permission)
151-
if not has_perm:
152-
message = 'User has no permission {}'.format(permission)
153-
raise web.HTTPForbidden(body=message.encode())
154-
return await f(self, request)
155-
return wrapped
156-
return wrapper
145+
based on permissions. There are already implemented two decorators::
157146

147+
from aiohttp_security import has_permission, login_required
158148

159-
For each view you need to protect just apply the decorator on it::
149+
For each view you need to protect - just apply the decorator on it::
160150

161151
class Web:
162-
@require('protected')
152+
@has_permission('protected')
163153
async def protected_page(self, request):
164154
response = web.Response(body=b'You are on protected page')
165155
return response
166156

157+
or::
158+
159+
class Web:
160+
@login_required
161+
async def logout(self, request):
162+
response = web.Response(body=b'You have been logged out')
163+
await forget(request, response)
164+
return response
167165

168-
If someone will try to access this protected page he will see::
166+
If someone try to access that protected page he will see::
169167

170-
403, User has no permission "protected"
168+
403: Forbidden
171169

172170

173-
The best part about it is that you can implement any logic you want until it
171+
The best part of it - you can implement any logic you want until it
174172
follows the API conventions.
175173

176174
Launch application
177175
------------------
178176

179177
For working with passwords there is a good library passlib_. Once you've
180178
created some users you want to check their credentials on login. Similar
181-
function may do what you trying to accomplish::
179+
function may do what you are trying to accomplish::
182180

183181
from passlib.hash import sha256_crypt
184182

@@ -197,8 +195,8 @@ function may do what you trying to accomplish::
197195

198196
Final step is to launch your application::
199197

200-
python demo/main.py
198+
python demo/database_auth/main.py
201199

202200

203-
Try to login with admin/moderator/user accounts (with *password* password)
201+
Try to login with admin/moderator/user accounts (with **password** password)
204202
and access **/public** or **/protected** endpoints.

0 commit comments

Comments
 (0)