Skip to content

Commit 2a4357c

Browse files
committed
add permission support in apijson get(one)/head/delete, 8 test cases
1 parent 39a1c38 commit 2a4357c

File tree

8 files changed

+285
-61
lines changed

8 files changed

+285
-61
lines changed

tests/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
commands to run the tests:
2+
3+
```
4+
cd tests
5+
nosetests --with-doctest
6+
```

tests/demo/apps/apijson_demo/dbinit.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
User = models.user
88
Privacy = models.privacy
99
Comment = models.comment
10+
Comment2 = models.comment2
1011
Moment = models.moment
1112
PublicNotice = models.publicnotice
1213

@@ -95,21 +96,21 @@
9596
"to_username" : "userb",
9697
"moment_id" : 1,
9798
"date" : "2018-12-1",
98-
"content" : "comment haha",
99+
"content" : "comment from usera to userb",
99100
},
100101
{
101102
"username" : "userb",
102103
"to_username" : "usera",
103104
"moment_id" : 2,
104105
"date" : "2018-12-2",
105-
"content" : "comment xixi",
106+
"content" : "comment from userb to usera",
106107
},
107108
{
108109
"username" : "userc",
109110
"to_username" : "usera",
110111
"moment_id" : 3,
111112
"date" : "2018-12-9",
112-
"content" : "comment hoho",
113+
"content" : "comment from userc to usera",
113114
},
114115
]
115116

@@ -158,6 +159,7 @@
158159
d["to_id"] = User.get(User.c.username==d["to_username"]).id
159160
print("create comment record for user '%s'"%(d["username"]))
160161
Comment(**d).save()
162+
Comment2(**d).save()
161163
else:
162164
print("error: unknown user '%s'"%(d["username"]))
163165

tests/demo/apps/apijson_demo/models.py

+7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ class Comment(Model):
2828
date = Field(datetime.datetime, auto_now_add=True)
2929
content = Field(TEXT)
3030

31+
class Comment2(Model):
32+
user_id = Reference("user")
33+
to_id = Reference("user")
34+
moment_id = Reference("moment")
35+
date = Field(datetime.datetime, auto_now_add=True)
36+
content = Field(TEXT)
37+
3138
class PublicNotice(Model):
3239
date = Field(datetime.datetime, auto_now_add=True)
3340
content = Field(TEXT)

tests/demo/apps/apijson_demo/settings.ini

+29
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
[MODELS]
22
privacy = 'apijson_demo.models.Privacy'
33
comment = 'apijson_demo.models.Comment'
4+
comment2 = 'apijson_demo.models.Comment2'
45
moment = 'apijson_demo.models.Moment'
56
publicnotice = 'apijson_demo.models.PublicNotice'
67
norequesttag = 'apijson_demo.models.NoRequestTag'
78

9+
[PERMISSIONS]
10+
get_comment2 = "get comment2", ["OWNER", "ADMIN"], ""
11+
head_comment2 = "head comment2", ["OWNER", "ADMIN"], ""
12+
post_comment2 = "post comment2", ["OWNER", "ADMIN"], ""
13+
put_comment2 = "put comment2", ["OWNER", "ADMIN"], ""
14+
delete_comment2 = "delete comment2", ["OWNER", "ADMIN"], ""
15+
816
[APIJSON_MODELS]
917
user = {
1018
"user_id_field" : "id",
@@ -39,6 +47,14 @@ comment = {
3947
"PUT" : { "roles" : ["OWNER","ADMIN"] },
4048
"DELETE" : { "roles" : ["OWNER","ADMIN"] },
4149
}
50+
comment2 = {
51+
"user_id_field" : "user_id",
52+
"GET" : { "permissions":["get_comment2"] },
53+
"HEAD" : { "permissions":["head_comment2"] },
54+
"POST" : { "permissions":["post_comment2"] },
55+
"PUT" : { "permissions":["put_comment2"]},
56+
"DELETE" : {"permissions":["delete_comment2"]},
57+
}
4258
publicnotice = {
4359
"GET" : { "roles" : ["OWNER","LOGIN","ADMIN","UNKNOWN"] },
4460
"HEAD" : { "roles" : ["OWNER","LOGIN","ADMIN","UNKNOWN"] },
@@ -73,6 +89,19 @@ comment = {
7389
},
7490
}
7591

92+
comment2 = {
93+
"POST" :{
94+
"ADD" :{"@role": "OWNER"},
95+
"DISALLOW" : ["id"],
96+
"NECESSARY" : ["moment_id","content"]
97+
},
98+
"PUT" :{
99+
"ADD":{"@role": "OWNER"},
100+
"NECESSARY" : ["id","content"],
101+
"DISALLOW" : ["user_id","to_id"],
102+
},
103+
}
104+
76105
publicnotice = {
77106
"PUT" :{
78107
"NECESSARY" : ["id","content"],

tests/test.py

+102-6
Original file line numberDiff line numberDiff line change
@@ -1153,7 +1153,7 @@ def test_apijson_head():
11531153
>>> r = handler.post('/apijson/head', data=data, middlewares=[])
11541154
>>> d = json_loads(r.data)
11551155
>>> print(d)
1156-
{'code': 400, 'msg': "no login user for role 'ADMIN'"}
1156+
{'code': 400, 'msg': "user doesn't have role 'ADMIN'"}
11571157
11581158
>>> #apijson head, without user and @role
11591159
>>> data ='''{
@@ -1581,7 +1581,7 @@ def test_apijson_delete():
15811581
>>> print(d)
15821582
{'code': 400, 'msg': "model 'nonexist' not found"}
15831583
1584-
>>> #apijson delete, default to OWNER and delete other's record
1584+
>>> #apijson delete, try to delete other's moment
15851585
>>> data ='''{
15861586
... "moment": {
15871587
... "id": 2
@@ -1591,7 +1591,7 @@ def test_apijson_delete():
15911591
>>> r = handler.post('/apijson/delete', data=data, pre_call=pre_call_as("usera"), middlewares=[])
15921592
>>> d = json_loads(r.data)
15931593
>>> print(d)
1594-
{'code': 400, 'msg': 'no permission'}
1594+
{'code': 400, 'msg': 'no role to access the data'}
15951595
15961596
>>> #apijson delete, without id
15971597
>>> data ='''{
@@ -1647,7 +1647,7 @@ def test_apijson_delete():
16471647
>>> r = handler.post('/apijson/delete', data=data, pre_call=pre_call_as("usera"), middlewares=[])
16481648
>>> d = json_loads(r.data)
16491649
>>> print(d)
1650-
{'code': 400, 'msg': "'moment' not accessible by role 'UNKNOWN'"}
1650+
{'code': 400, 'msg': "role 'UNKNOWN' has no permission to access the data"}
16511651
16521652
>>> #apijson delete, with OWNER but not login
16531653
>>> data ='''{
@@ -1667,7 +1667,7 @@ def test_apijson_delete():
16671667
>>> r = handler.post('/apijson/delete', data=data, middlewares=[])
16681668
>>> d = json_loads(r.data)
16691669
>>> print(d)
1670-
{'code': 400, 'msg': 'need login user'}
1670+
{'code': 400, 'msg': 'no role to access the data'}
16711671
16721672
>>> #apijson delete, with UNKNOWN role
16731673
>>> data ='''{
@@ -1701,5 +1701,101 @@ def test_apijson_delete():
17011701
>>> r = handler.post('/apijson/delete', data=data, pre_call=pre_call_as("admin"), middlewares=[])
17021702
>>> d = json_loads(r.data)
17031703
>>> print(d)
1704-
{'code': 400, 'msg': "'moment' not accessible by role 'superuser'"}
1704+
{'code': 400, 'msg': "role 'superuser' has no permission to access the data"}
1705+
"""
1706+
1707+
def test_apijson_permission():
1708+
"""
1709+
>>> application = make_simple_application(project_dir='.')
1710+
>>> handler = application.handler()
1711+
1712+
>>> #apijson get, query with id, access with owner
1713+
>>> data ='''{
1714+
... "comment2":{
1715+
... "id": 1
1716+
... }
1717+
... }'''
1718+
>>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[])
1719+
>>> d = json_loads(r.data)
1720+
>>> print(d)
1721+
{'code': 200, 'msg': 'success', 'comment2': {'user_id': 1, 'to_id': 3, 'moment_id': 1, 'date': '2018-11-01 00:00:00', 'content': 'comment from admin', 'id': 1}}
1722+
1723+
>>> #apijson get, query with id, access other's comment, expect empty result
1724+
>>> data ='''{
1725+
... "comment2":{
1726+
... "id": 1
1727+
... }
1728+
... }'''
1729+
>>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("userb"), middlewares=[])
1730+
>>> d = json_loads(r.data)
1731+
>>> print(d)
1732+
{'code': 200, 'msg': 'success', 'comment2': None}
1733+
1734+
>>> #apijson get, query array
1735+
>>> data ='''{
1736+
... "comment2":{
1737+
... }
1738+
... }'''
1739+
>>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[])
1740+
>>> d = json_loads(r.data)
1741+
>>> print(d)
1742+
{'code': 200, 'msg': 'success', 'comment2': {'user_id': 2, 'to_id': 3, 'moment_id': 1, 'date': '2018-12-01 00:00:00', 'content': 'comment from usera to userb', 'id': 2}}
1743+
1744+
>>> #apijson get, query one with admin as OWNER
1745+
>>> data ='''{
1746+
... "comment2":{
1747+
... "@role":"OWNER"
1748+
... }
1749+
... }'''
1750+
>>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[])
1751+
>>> d = json_loads(r.data)
1752+
>>> print(d)
1753+
{'code': 200, 'msg': 'success', 'comment2': {'user_id': 1, 'to_id': 3, 'moment_id': 1, 'date': '2018-11-01 00:00:00', 'content': 'comment from admin', 'id': 1}}
1754+
1755+
>>> #apijson get, query one with admin as ADMIN
1756+
>>> data ='''{
1757+
... "comment2":{
1758+
... "@role":"ADMIN",
1759+
... "user_id": 2
1760+
... }
1761+
... }'''
1762+
>>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[])
1763+
>>> d = json_loads(r.data)
1764+
>>> print(d)
1765+
{'code': 200, 'msg': 'success', 'comment2': {'user_id': 2, 'to_id': 3, 'moment_id': 1, 'date': '2018-12-01 00:00:00', 'content': 'comment from usera to userb', 'id': 2}}
1766+
1767+
>>> #apijson head
1768+
>>> data ='''{
1769+
... "comment2": {
1770+
... "user_id": 1
1771+
... }
1772+
... }'''
1773+
>>> r = handler.post('/apijson/head', data=data, pre_call=pre_call_as("userc"), middlewares=[])
1774+
>>> d = json_loads(r.data)
1775+
>>> print(d)
1776+
{'code': 200, 'msg': 'success', 'comment2': {'code': 200, 'msg': 'success', 'count': 0}}
1777+
1778+
>>> #apijson delete with a user which have no permission
1779+
>>> data ='''{
1780+
... "comment2": {
1781+
... "id": 1
1782+
... },
1783+
... "@tag": "comment2"
1784+
... }'''
1785+
>>> r = handler.post('/apijson/delete', data=data, pre_call=pre_call_as("userc"), middlewares=[])
1786+
>>> d = json_loads(r.data)
1787+
>>> print(d)
1788+
{'code': 400, 'msg': 'no permission'}
1789+
1790+
>>> #apijson delete with permission, ADMIN
1791+
>>> data ='''{
1792+
... "comment2": {
1793+
... "id": 1
1794+
... },
1795+
... "@tag": "comment2"
1796+
... }'''
1797+
>>> r = handler.post('/apijson/delete', data=data, pre_call=pre_call_as("admin"), middlewares=[])
1798+
>>> d = json_loads(r.data)
1799+
>>> print(d)
1800+
{'code': 200, 'msg': 'success', 'comment2': {'id': 1, 'code': 200, 'message': 'success', 'count': 1}}
17051801
"""

uliweb_apijson/apijson/__init__.py

+68
Original file line numberDiff line numberDiff line change
@@ -260,3 +260,71 @@ def associated_query_array(self):
260260
params.update(refs)
261261
q = self._get_array_q(params)
262262
item[self.name] = self._get_info(q.one())
263+
264+
def is_obj_owner(user, obj, user_id_field):
265+
if user and user_id_field:
266+
return obj.to_dict().get(user_id_field)==user.id
267+
return False
268+
269+
def has_obj_role(user, obj, user_id_field, as_role, *roles):
270+
from uliweb import functions
271+
if as_role:
272+
if as_role not in roles:
273+
return False, "role '%s' has no permission to access the data"%(as_role)
274+
if not functions.has_role(user, as_role):
275+
return False, "user has no role '%s'"%(as_role)
276+
if as_role == "OWNER":
277+
if not is_obj_owner(user, obj, user_id_field):
278+
return False, "user is not the owner of data"
279+
return True, None
280+
else:
281+
for role in roles:
282+
if functions.has_role(user, role):
283+
if isinstance(role,str):
284+
role_name = role
285+
elif hasattr(role, "name"):
286+
role_name = role.name
287+
else:
288+
continue
289+
if role_name == "OWNER":
290+
if is_obj_owner(user, obj, user_id_field):
291+
return True, None
292+
else:
293+
continue
294+
else:
295+
return True, None
296+
return False, "no role to access the data"
297+
298+
def has_obj_permission(user, obj, user_id_field, *perms):
299+
from uliweb import functions, models
300+
301+
Role = models.role
302+
Perm = models.permission
303+
304+
for name in perms:
305+
perm = Perm.get(Perm.c.name == name)
306+
if not perm:
307+
continue
308+
has, msg = functions.has_obj_role(user, obj, user_id_field, None, *list(perm.perm_roles.with_relation().all()))
309+
if has:
310+
return has, None
311+
return False, "no permission"
312+
313+
def has_permission_as_role(user, as_role, *perms):
314+
from uliweb import functions, models
315+
316+
Role = models.role
317+
Perm = models.permission
318+
319+
flag = functions.has_role(user, as_role)
320+
if not flag:
321+
return False, "user has no role '%s'"%(as_role)
322+
323+
for name in perms:
324+
perm = Perm.get(Perm.c.name==name)
325+
if not perm:
326+
continue
327+
for role in perm.perm_roles.with_relation().all():
328+
if role.name == as_role:
329+
return role, None
330+
return False, "no permission"

uliweb_apijson/apijson/settings.ini

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,7 @@ user = {
1919

2020
[FUNCTIONS]
2121
get_apijson_tables = "uliweb_apijson.apijson.get_apijson_tables"
22-
get_apijson_table = "uliweb_apijson.apijson.get_apijson_table"
22+
get_apijson_table = "uliweb_apijson.apijson.get_apijson_table"
23+
has_obj_role = "uliweb_apijson.apijson.has_obj_role"
24+
has_obj_permission = "uliweb_apijson.apijson.has_obj_permission"
25+
has_permission_as_role = "uliweb_apijson.apijson.has_permission_as_role"

0 commit comments

Comments
 (0)