diff --git a/demo/apps/apijson_demo/dbinit.py b/demo/apps/apijson_demo/dbinit.py index b235176..4fdcb5f 100644 --- a/demo/apps/apijson_demo/dbinit.py +++ b/demo/apps/apijson_demo/dbinit.py @@ -75,6 +75,11 @@ "date" : "2018-11-6", "content" : "test moment from c", }, + { + "username" : "admin", + "date" : "2018-11-7", + "content" : "test moment from admin", + }, ] comment_list = [ @@ -99,6 +104,13 @@ "date" : "2018-12-9", "content" : "comment hoho", }, + { + "username" : "admin", + "to_username" : "usera", + "moment_id" : 4, + "date" : "2018-12-10", + "content" : "comment kaka", + }, ] for d in user_list: diff --git a/demo/apps/settings.ini b/demo/apps/settings.ini index bd4ea4f..8a4d3c9 100644 --- a/demo/apps/settings.ini +++ b/demo/apps/settings.ini @@ -17,6 +17,7 @@ INSTALLED_APPS = [ 'uliweb_comui', 'uliweb_comapps.auth.login', 'uliweb_comapps.auth.user_admin', + 'uliweb_comapps.auth.rbac_admin', 'uliweb_apijson.apijson', 'apijson_demo', 'tables', diff --git a/demo/apps/tables/config.ini b/demo/apps/tables/config.ini new file mode 100644 index 0000000..f5dbe0e --- /dev/null +++ b/demo/apps/tables/config.ini @@ -0,0 +1,4 @@ +[DEPENDS] +REQUIRED_APPS = [ + 'uliweb_apijson.tables', +] diff --git a/demo/apps/tables/settings.ini b/demo/apps/tables/settings.ini index 4712b17..98ddebc 100644 --- a/demo/apps/tables/settings.ini +++ b/demo/apps/tables/settings.ini @@ -1,7 +1,11 @@ [APIJSON_TABLES] -user = { - "editable" : "auto", -} +user = {"model_name":"user", "tableui_name":"users"} +role = {"model_name":"role", "tableui_name":"roles", "role":"ADMIN"} +permission = {"model_name":"permission", "tableui_name":"permissions", "role":"ADMIN"} +moment = {"role":"OWNER"} +comment = {"role":"OWNER"} + +[APIJSON_TABLE_UI] moment = { "editable" : "auto", "table_fields" : [ @@ -20,7 +24,6 @@ moment = { {"title":"Content","key":"content","type":"textarea"}, ], } - comment = { "editable" : "auto", "table_fields" : [ diff --git a/demo/apps/tables/templates/Tables/list.html b/demo/apps/tables/templates/Tables/list.html deleted file mode 100644 index f6dc9c2..0000000 --- a/demo/apps/tables/templates/Tables/list.html +++ /dev/null @@ -1,37 +0,0 @@ -{{extend "Tables/layout.html"}} - -{{block title}}uliweb-apijson demo: tables{{end title}} - -{{block content_wrapper}} -{{include "vue/inc_apijson_table.html"}} - -
- {{if role!="ADMIN":}} - - {{pass #if}} -
-
- - - - - -
-
-
-
-{{end content_wrapper}} - -{{block mainapp_vue}} - -{{end mainapp_vue}} diff --git a/demo/apps/tables/views.py b/demo/apps/tables/views.py deleted file mode 100644 index 095b181..0000000 --- a/demo/apps/tables/views.py +++ /dev/null @@ -1,21 +0,0 @@ -#coding=utf-8 -from uliweb import expose, functions - -@expose('/tables') -class Tables(object): - @expose('') - def list(self): - table_keys = settings.APIJSON_MODELS.keys() - if request.user: - if functions.has_role(request.user,"ADMIN"): - role = "ADMIN" - else: - role = "OWNER" - else: - role = "UNKNOWN" - apijson_tables = functions.get_apijson_tables(role) - return { - "table_keys_json":json_dumps(table_keys), - "apijson_tables_json":json_dumps(apijson_tables), - "role":role, - } diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..007b525 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,6 @@ +commands to run the tests: + +``` +cd tests +nosetests --with-doctest +``` diff --git a/tests/demo/apps/apijson_demo/dbinit.py b/tests/demo/apps/apijson_demo/dbinit.py index f66a7b2..a679a70 100644 --- a/tests/demo/apps/apijson_demo/dbinit.py +++ b/tests/demo/apps/apijson_demo/dbinit.py @@ -7,6 +7,7 @@ User = models.user Privacy = models.privacy Comment = models.comment +Comment2 = models.comment2 Moment = models.moment PublicNotice = models.publicnotice @@ -95,21 +96,21 @@ "to_username" : "userb", "moment_id" : 1, "date" : "2018-12-1", - "content" : "comment haha", + "content" : "comment from usera to userb", }, { "username" : "userb", "to_username" : "usera", "moment_id" : 2, "date" : "2018-12-2", - "content" : "comment xixi", + "content" : "comment from userb to usera", }, { "username" : "userc", "to_username" : "usera", "moment_id" : 3, "date" : "2018-12-9", - "content" : "comment hoho", + "content" : "comment from userc to usera", }, ] @@ -158,6 +159,7 @@ d["to_id"] = User.get(User.c.username==d["to_username"]).id print("create comment record for user '%s'"%(d["username"])) Comment(**d).save() + Comment2(**d).save() else: print("error: unknown user '%s'"%(d["username"])) diff --git a/tests/demo/apps/apijson_demo/models.py b/tests/demo/apps/apijson_demo/models.py index ef92a94..56e0b9c 100644 --- a/tests/demo/apps/apijson_demo/models.py +++ b/tests/demo/apps/apijson_demo/models.py @@ -28,6 +28,13 @@ class Comment(Model): date = Field(datetime.datetime, auto_now_add=True) content = Field(TEXT) +class Comment2(Model): + user_id = Reference("user") + to_id = Reference("user") + moment_id = Reference("moment") + date = Field(datetime.datetime, auto_now_add=True) + content = Field(TEXT) + class PublicNotice(Model): date = Field(datetime.datetime, auto_now_add=True) content = Field(TEXT) diff --git a/tests/demo/apps/apijson_demo/settings.ini b/tests/demo/apps/apijson_demo/settings.ini index 906cd61..d2a97a8 100644 --- a/tests/demo/apps/apijson_demo/settings.ini +++ b/tests/demo/apps/apijson_demo/settings.ini @@ -1,10 +1,18 @@ [MODELS] privacy = 'apijson_demo.models.Privacy' comment = 'apijson_demo.models.Comment' +comment2 = 'apijson_demo.models.Comment2' moment = 'apijson_demo.models.Moment' publicnotice = 'apijson_demo.models.PublicNotice' norequesttag = 'apijson_demo.models.NoRequestTag' +[PERMISSIONS] +get_comment2 = "get comment2", ["OWNER", "ADMIN"], "" +head_comment2 = "head comment2", ["OWNER", "ADMIN"], "" +post_comment2 = "post comment2", ["OWNER", "ADMIN"], "" +put_comment2 = "put comment2", ["OWNER", "ADMIN"], "" +delete_comment2 = "delete comment2", ["OWNER", "ADMIN"], "" + [APIJSON_MODELS] user = { "user_id_field" : "id", @@ -39,6 +47,15 @@ comment = { "PUT" : { "roles" : ["OWNER","ADMIN"] }, "DELETE" : { "roles" : ["OWNER","ADMIN"] }, } +# only define permissions, no roles +comment2 = { + "user_id_field" : "user_id", + "GET" : { "permissions":["get_comment2"] }, + "HEAD" : { "permissions":["head_comment2"] }, + "POST" : { "permissions":["post_comment2"] }, + "PUT" : { "permissions":["put_comment2"]}, + "DELETE" : {"permissions":["delete_comment2"]}, +} publicnotice = { "GET" : { "roles" : ["OWNER","LOGIN","ADMIN","UNKNOWN"] }, "HEAD" : { "roles" : ["OWNER","LOGIN","ADMIN","UNKNOWN"] }, @@ -73,6 +90,19 @@ comment = { }, } +comment2 = { + "POST" :{ + "ADD" :{"@role": "OWNER"}, + "DISALLOW" : ["id"], + "NECESSARY" : ["moment_id","content"] + }, + "PUT" :{ + "ADD":{"@role": "OWNER"}, + "NECESSARY" : ["id","content"], + "DISALLOW" : ["user_id","to_id"], + }, +} + publicnotice = { "PUT" :{ "NECESSARY" : ["id","content"], diff --git a/tests/test.py b/tests/test.py index dd246b6..c6e8458 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1153,7 +1153,7 @@ def test_apijson_head(): >>> r = handler.post('/apijson/head', data=data, middlewares=[]) >>> d = json_loads(r.data) >>> print(d) - {'code': 400, 'msg': "no login user for role 'ADMIN'"} + {'code': 400, 'msg': "user doesn't have role 'ADMIN'"} >>> #apijson head, without user and @role >>> data ='''{ @@ -1581,7 +1581,7 @@ def test_apijson_delete(): >>> print(d) {'code': 400, 'msg': "model 'nonexist' not found"} - >>> #apijson delete, default to OWNER and delete other's record + >>> #apijson delete, try to delete other's moment >>> data ='''{ ... "moment": { ... "id": 2 @@ -1591,7 +1591,7 @@ def test_apijson_delete(): >>> r = handler.post('/apijson/delete', data=data, pre_call=pre_call_as("usera"), middlewares=[]) >>> d = json_loads(r.data) >>> print(d) - {'code': 400, 'msg': 'no permission'} + {'code': 400, 'msg': 'no role to access the data'} >>> #apijson delete, without id >>> data ='''{ @@ -1647,7 +1647,7 @@ def test_apijson_delete(): >>> r = handler.post('/apijson/delete', data=data, pre_call=pre_call_as("usera"), middlewares=[]) >>> d = json_loads(r.data) >>> print(d) - {'code': 400, 'msg': "'moment' not accessible by role 'UNKNOWN'"} + {'code': 400, 'msg': "role 'UNKNOWN' has no permission to access the data"} >>> #apijson delete, with OWNER but not login >>> data ='''{ @@ -1667,7 +1667,7 @@ def test_apijson_delete(): >>> r = handler.post('/apijson/delete', data=data, middlewares=[]) >>> d = json_loads(r.data) >>> print(d) - {'code': 400, 'msg': 'need login user'} + {'code': 400, 'msg': 'no role to access the data'} >>> #apijson delete, with UNKNOWN role >>> data ='''{ @@ -1701,5 +1701,112 @@ def test_apijson_delete(): >>> r = handler.post('/apijson/delete', data=data, pre_call=pre_call_as("admin"), middlewares=[]) >>> d = json_loads(r.data) >>> print(d) - {'code': 400, 'msg': "'moment' not accessible by role 'superuser'"} + {'code': 400, 'msg': "role 'superuser' has no permission to access the data"} + """ + +def test_apijson_permission(): + """ + >>> application = make_simple_application(project_dir='.') + >>> handler = application.handler() + + >>> #apijson get, query with id, access with owner + >>> data ='''{ + ... "comment2":{ + ... "id": 1 + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'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}} + + >>> #apijson get, query with id, access other's comment, expect empty result + >>> data ='''{ + ... "comment2":{ + ... "id": 1 + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("userb"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', 'comment2': None} + + >>> #apijson get, query array + >>> data ='''{ + ... "comment2":{ + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("usera"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'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}} + + >>> #apijson get, query one with admin as OWNER + >>> data ='''{ + ... "comment2":{ + ... "@role":"OWNER" + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'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}} + + >>> #apijson get, query one with admin as ADMIN + >>> data ='''{ + ... "comment2":{ + ... "@role":"ADMIN", + ... "user_id": 2 + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'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}} + + >>> #apijson get, query array + >>> data ='''{ + ... "[]":{ + ... "comment2": {"@role":"ADMIN"} + ... } + ... }''' + >>> r = handler.post('/apijson/get', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'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}}, {'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}}, {'comment2': {'user_id': 3, 'to_id': 2, 'moment_id': 2, 'date': '2018-12-02 00:00:00', 'content': 'comment from userb to usera', 'id': 3}}, {'comment2': {'user_id': 4, 'to_id': 2, 'moment_id': 3, 'date': '2018-12-09 00:00:00', 'content': 'comment from userc to usera', 'id': 4}}]} + + >>> #apijson head + >>> data ='''{ + ... "comment2": { + ... "user_id": 1 + ... } + ... }''' + >>> r = handler.post('/apijson/head', data=data, pre_call=pre_call_as("userc"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', 'comment2': {'code': 200, 'msg': 'success', 'count': 0}} + + >>> #apijson delete with a user which have no permission + >>> data ='''{ + ... "comment2": { + ... "id": 1 + ... }, + ... "@tag": "comment2" + ... }''' + >>> r = handler.post('/apijson/delete', data=data, pre_call=pre_call_as("userc"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 400, 'msg': 'no permission'} + + >>> #apijson delete with permission, ADMIN + >>> data ='''{ + ... "comment2": { + ... "id": 1 + ... }, + ... "@tag": "comment2" + ... }''' + >>> r = handler.post('/apijson/delete', data=data, pre_call=pre_call_as("admin"), middlewares=[]) + >>> d = json_loads(r.data) + >>> print(d) + {'code': 200, 'msg': 'success', 'comment2': {'id': 1, 'code': 200, 'message': 'success', 'count': 1}} """ diff --git a/uliweb_apijson/__init__.py b/uliweb_apijson/__init__.py index 7ea7bfe..522163a 100644 --- a/uliweb_apijson/__init__.py +++ b/uliweb_apijson/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.2.2' +__version__ = '0.3.0' __url__ = 'https://github.com/zhangchunlin/uliweb-apijson' __author__ = 'Chunlin Zhang' __email__ = 'zhangchunlin@gmail.com' diff --git a/uliweb_apijson/apijson/__init__.py b/uliweb_apijson/apijson/__init__.py index 596d9c9..9a9474f 100644 --- a/uliweb_apijson/apijson/__init__.py +++ b/uliweb_apijson/apijson/__init__.py @@ -2,57 +2,60 @@ from uliweb import settings, models, request, functions, UliwebError from uliweb.orm import ModelNotFound +from json import dumps as json_dumps import logging log = logging.getLogger('apijson') -def get_apijson_tables(role="UNKNOWN"): - from uliweb import settings - s = settings.APIJSON_TABLES - if s: - apijson_tables = dict(s.iteritems()) - else: - return {} - for n in apijson_tables: - c = apijson_tables[n] - editable = c.get("editable",False) - _model_name = c.get("@model_name") or n - if editable=="auto": +class ApijsonTable(object): + def __init__(self, model_name, request_tag=None, role=None, tableui_name=None): + self.model_name = model_name + self.request_tag = request_tag or self.model_name + self.role = role + self.tableui_name = tableui_name or self.model_name + self._get_tableui() + self._apply_auto() + + def _get_tableui(self): + self.tableui = settings.APIJSON_TABLE_UI.get(self.tableui_name, {}) + if not self.tableui: + log.warn("cannot find setting for {} in settings.APIJSON_TABLE_UI".format(self.tableui_name)) + + def _apply_auto(self): + editable = self.tableui.get("editable", False) + if editable == "auto": editable = False - POST = settings.APIJSON_MODELS.get(_model_name,{}).get("POST") + POST = settings.APIJSON_MODELS.get(self.model_name, {}).get("POST") if POST: roles = POST["roles"] if roles: - editable = role in roles - c["editable"] = editable - return apijson_tables - -def get_apijson_table(role="UNKNOWN",name=None): - from uliweb import settings - - if not name: - return {} - s = settings.APIJSON_TABLES - if s: - apijson_tables = dict(s.iteritems()) - else: - return {} - - c = apijson_tables.get(name) - if not c: - return {} - editable = c.get("editable",False) - _model_name = c.get("@model_name") or n - if editable=="auto": - editable = False - POST = settings.APIJSON_MODELS.get(_model_name,{}).get("POST") - if POST: - roles = POST["roles"] - if roles: - editable = role in roles - c["editable"] = editable - return c + editable = self.role in roles + self.tableui["editable"] = editable + + def to_dict(self): + return dict(model_name=self.model_name, + request_tag=self.request_tag, + role=self.role, + tableui_name=self.tableui_name, + tableui=self.tableui) + + +def get_apijson_tables(): + def iter_table(): + apison_table_ui = dict( + settings.APIJSON_TABLE_UI.iteritems()) + for tableui_name in apison_table_ui: + tableui = apison_table_ui[tableui_name] + model_name = tableui.get("@model_name") or tableui_name + request_tag = model_name + role = None + yield(ApijsonTable(model_name=model_name, request_tag=request_tag, role=role, tableui_name=tableui_name)) + return list(iter_table()) + + +def get_apijson_table(*args, **kwargs): + return ApijsonTable(*args, **kwargs) class ApiJsonModelQuery(object): def __init__(self,name,params,parent,key): @@ -88,23 +91,39 @@ def _check_GET_permission(self): roles = GET.get("roles") params_role = self.params.get("@role") - - if not params_role: - if hasattr(request,"user"): - params_role = "LOGIN" + user = getattr(request, "user", None) + + if roles: + if not params_role: + if user: + params_role = "LOGIN" + else: + params_role = "UNKNOWN" + elif params_role != "UNKNOWN": + if not user: + raise UliwebError("no login user for role '%s'" % (params_role)) + if params_role not in roles: + raise UliwebError("'%s' not accessible by role '%s'" % (self.name, params_role)) + if params_role == "UNKNOWN": + self.permission_check_ok = True + elif functions.has_role(user, params_role): + self.permission_check_ok = True else: - params_role = "UNKNOWN" - elif params_role != "UNKNOWN": - if not hasattr(request,"user"): - raise UliwebError("no login user for role '%s'"%(params_role)) - if params_role not in roles: - raise UliwebError("'%s' not accessible by role '%s'"%(self.name,params_role)) - if params_role == "UNKNOWN": - self.permission_check_ok = True - elif functions.has_role(request.user,params_role): - self.permission_check_ok = True - else: - raise UliwebError("user doesn't have role '%s'"%(params_role)) + raise UliwebError("user doesn't have role '%s'" % (params_role)) + if not self.permission_check_ok: + perms = GET.get("permissions") + if perms: + if params_role: + role, msg = functions.has_permission_as_role(user, params_role, *perms) + if role: + self.permission_check_ok = True + else: + role = functions.has_permission(user, *perms) + if role: + role_name = getattr(role, "name") + if role_name: + self.permission_check_ok = True + params_role = role_name if not self.permission_check_ok: raise UliwebError("no permission") @@ -260,3 +279,71 @@ def associated_query_array(self): params.update(refs) q = self._get_array_q(params) item[self.name] = self._get_info(q.one()) + +def is_obj_owner(user, obj, user_id_field): + if user and user_id_field: + return obj.to_dict().get(user_id_field)==user.id + return False + +def has_obj_role(user, obj, user_id_field, as_role, *roles): + from uliweb import functions + if as_role: + if as_role not in roles: + return False, "role '%s' has no permission to access the data"%(as_role) + if not functions.has_role(user, as_role): + return False, "user has no role '%s'"%(as_role) + if as_role == "OWNER": + if not is_obj_owner(user, obj, user_id_field): + return False, "user is not the owner of data" + return True, None + else: + for role in roles: + if functions.has_role(user, role): + if isinstance(role,str): + role_name = role + elif hasattr(role, "name"): + role_name = role.name + else: + continue + if role_name == "OWNER": + if is_obj_owner(user, obj, user_id_field): + return True, None + else: + continue + else: + return True, None + return False, "no role to access the data" + +def has_obj_permission(user, obj, user_id_field, *perms): + from uliweb import functions, models + + Role = models.role + Perm = models.permission + + for name in perms: + perm = Perm.get(Perm.c.name == name) + if not perm: + continue + has, msg = functions.has_obj_role(user, obj, user_id_field, None, *list(perm.perm_roles.with_relation().all())) + if has: + return has, None + return False, "no permission" + +def has_permission_as_role(user, as_role, *perms): + from uliweb import functions, models + + Role = models.role + Perm = models.permission + + flag = functions.has_role(user, as_role) + if not flag: + return False, "user has no role '%s'"%(as_role) + + for name in perms: + perm = Perm.get(Perm.c.name==name) + if not perm: + continue + for role in perm.perm_roles.with_relation().all(): + if role.name == as_role: + return role, None + return False, "no permission" diff --git a/uliweb_apijson/apijson/settings.ini b/uliweb_apijson/apijson/settings.ini index c713bf3..dd6e035 100644 --- a/uliweb_apijson/apijson/settings.ini +++ b/uliweb_apijson/apijson/settings.ini @@ -19,4 +19,7 @@ user = { [FUNCTIONS] get_apijson_tables = "uliweb_apijson.apijson.get_apijson_tables" -get_apijson_table = "uliweb_apijson.apijson.get_apijson_table" \ No newline at end of file +get_apijson_table = "uliweb_apijson.apijson.get_apijson_table" +has_obj_role = "uliweb_apijson.apijson.has_obj_role" +has_obj_permission = "uliweb_apijson.apijson.has_obj_permission" +has_permission_as_role = "uliweb_apijson.apijson.has_permission_as_role" diff --git a/uliweb_apijson/apijson/templates/vue/inc_apijson_table.html b/uliweb_apijson/apijson/templates/vue/inc_apijson_table.html index 361ba21..4920866 100644 --- a/uliweb_apijson/apijson/templates/vue/inc_apijson_table.html +++ b/uliweb_apijson/apijson/templates/vue/inc_apijson_table.html @@ -2,9 +2,7 @@ Vue.component('apijson-table', { delimiters: ['{', '}'], props: [ - "model_name", //apijson model name - "request_tag", //apijson request tag, default will be same with model name - "config", + "table", "custom_tcolumns_render_generator", "hook_init", //hook_init(vm), will invoke in mounted() "hook_ajax_params", //hook_ajax_params(method,action,params), will invoke before ajax action @@ -15,7 +13,7 @@
Add

- + @@ -47,15 +45,12 @@ -

Confirm to delete #{delete_params.row&&delete_params.row.id} in table '{model_name}'?

+

Confirm to delete #{delete_params.row&&delete_params.row.id} in table '{table.model_name}'?

`, data: function(){ var thisp = this return { - l_request_tag: null, - role: "{{=role or ''}}", - loading: false, modal_view: false, viewedit_items: [], @@ -105,7 +100,7 @@ if (thisp.config_editable) { buttons.push(delete_button) } - return h('div', buttons); + return h('div', buttons) } } }, @@ -126,7 +121,7 @@ }, tcolumns_init: false, tlist:[], - query_count: thisp.config ? (thisp.config.default_page_size || 10) : 10, + query_count: thisp.table.tableui ? (thisp.table.tableui.default_page_size || 10) : 10, current_page: 1, total: 0, sort_key: "id", @@ -181,10 +176,10 @@ var model_params = { "@order":thisp.sort_key+thisp.sort_order } - if (thisp.role!="") { - model_params["@role"] = thisp.role + if (thisp.table.role!=null && thisp.table.role!="") { + model_params["@role"] = thisp.table.role } - arr_params[thisp.model_name] = model_params + arr_params[thisp.table.model_name] = model_params var params = { "[]":arr_params, "total@":"/[]/total" @@ -201,7 +196,9 @@ if (data.code==200) { var arr = data["[]"] for (var i=0;i @@ -25,8 +25,6 @@ `, data: function(){ return { - l_request_tag: null, - role: "{{=role or ''}}", loading: false, row: {}, row_saved: {}, @@ -43,10 +41,11 @@ var model_params = { "id":this.id } - if (this.role!='') { - model_params["@role"] = this.role + var role = this.table.role + if (role!=null && role!='') { + model_params["@role"] = role } - params[this.model_name] = model_params + params[this.table.model_name] = model_params var thisp = this $.ajax({ type: "POST", @@ -56,7 +55,7 @@ success: function (data) { thisp.loading = false if (data.code==200) { - thisp.row = data[thisp.model_name] + thisp.row = data[thisp.table.model_name] thisp.row_saved = thisp.row thisp.viewedit_items = [] if (thisp.config_viewedit_fields!=null) { @@ -70,7 +69,7 @@ } else { thisp.$Notice.error({ - title: 'error when get table '+thisp.table_name, + title: 'error when get table '+thisp.tableui_name, desc: data.msg }) } @@ -84,7 +83,7 @@ }, save: function(){ var params = { - "@tag": this.l_request_tag + "@tag": this.table.request_tag } var record_params = {} //only save modified fields @@ -95,10 +94,11 @@ this.row[d.key] = d.value } } - if (this.role!='') { - record_params["@role"] = this.role + var role = this.table.role + if (role!=null && role!='') { + record_params["@role"] = role } - params[this.l_request_tag] = record_params + params[this.table.request_tag] = record_params params = this.ajax_hook("apijson_put","update",params) var thisp = this $.ajax({ @@ -110,15 +110,22 @@ if (data.code==200){ thisp.row_saved = thisp.row thisp.$Notice.success({ - title: 'success update #'+thisp.row.id+' in table '+thisp.model_name, + title: 'success update #'+thisp.row.id+' in table '+thisp.table.model_name, desc: data.msg }) + if(thisp.hook_ajax_post_result){ + thisp.hook_ajax_post_result(true, params, thisp.row_saved) + } } else { thisp.$Notice.error({ - title: 'error when update #'+thisp.row.id+' in table '+thisp.model_name, + title: 'error when update #'+thisp.row.id+' in table '+thisp.table.model_name, desc: data.msg }) + if(thisp.hook_ajax_post_result){ + thisp.hook_ajax_post_result(false, params, thisp.row_saved) + } + } } }) @@ -137,20 +144,14 @@ } }, mounted: function(){ - if (this.request_tag==null) { - this.l_request_tag = this.model_name - } - else { - this.l_request_tag = this.request_tag - } //if not do this, the first notice will hide behind the navigation bar in uliweb apps this.$Notice.config({top: 100,duration: 8}); if (this.hook_init!=null) { this.hook_init(this) } - if (this.config!=null){ - this.config_editable = this.config.editable || false - this.config_viewedit_fields = this.config.viewedit_fields || null + if (this.table.tableui!=null){ + this.config_editable = this.table.tableui.editable || false + this.config_viewedit_fields = this.table.tableui.viewedit_fields || null } this.init_viewedit() } diff --git a/uliweb_apijson/apijson/views.py b/uliweb_apijson/apijson/views.py index 5dc8a7e..e10b944 100644 --- a/uliweb_apijson/apijson/views.py +++ b/uliweb_apijson/apijson/views.py @@ -111,26 +111,39 @@ def _get_one(self,key): if not GET: return json({"code":400,"msg":"'%s' not accessible"%(model_name)}) + user = getattr(request,"user", None) roles = GET.get("roles") permission_check_ok = False - if not params_role: - if hasattr(request,"user") and request.user: - params_role = "LOGIN" + if roles: + if not params_role: + params_role = "LOGIN" if user else "UNKNOWN" + elif params_role != "UNKNOWN": + if not user: + return json({"code":400,"msg":"no login user for role '%s'"%(params_role)}) + if params_role not in roles: + return json({"code":400,"msg":"'%s' not accessible by role '%s'"%(model_name,params_role)}) + if params_role == "UNKNOWN": + permission_check_ok = True + elif functions.has_role(user,params_role): + permission_check_ok = True else: - params_role = "UNKNOWN" - elif params_role != "UNKNOWN": - if not (hasattr(request,"user") and request.user): - return json({"code":400,"msg":"no login user for role '%s'"%(params_role)}) - if params_role not in roles: - return json({"code":400,"msg":"'%s' not accessible by role '%s'"%(model_name,params_role)}) - if params_role == "UNKNOWN": - permission_check_ok = True - elif functions.has_role(request.user,params_role): - permission_check_ok = True - else: - return json({"code":400,"msg":"user doesn't has role '%s'"%(params_role)}) + return json({"code":400,"msg":"user doesn't has role '%s'"%(params_role)}) if not permission_check_ok: - return json({"code":400,"msg":"no permission"}) + perms = GET.get("permissions") + if perms: + if params_role: + role, msg = functions.has_permission_as_role(user, params_role, *perms) + if role: + permission_check_ok = True + else: + role = functions.has_permission(user, *perms) + if role: + role_name = getattr(role, "name") + if role_name: + permission_check_ok = True + params_role = role_name + if not permission_check_ok: + return json({"code":400,"msg":"no permission to access the data"}) if params_role=="OWNER": owner_filtered,q = self._filter_owner(model,model_setting,q) @@ -364,25 +377,35 @@ def _head(self,key): roles = HEAD.get("roles") permission_check_ok = False - if not params_role: - if hasattr(request,"user") and request.user: - params_role = "LOGIN" + user = getattr(request, "user", None) + if roles: + if not params_role: + params_role = "LOGIN" if user else "UNKNOWN" + if params_role not in roles: + return json({"code":400,"msg":"role '%s' not have permission HEAD for '%s'"%(params_role,model_name)}) + if functions.has_role(user, params_role): + permission_check_ok = True else: - params_role = "UNKNOWN" - if params_role not in roles: - return json({"code":400,"msg":"role '%s' not have permission HEAD for '%s'"%(params_role,model_name)}) - if params_role == "UNKNOWN": - permission_check_ok = True - elif not (hasattr(request,"user") and request.user): - return json({"code":400,"msg":"no login user for role '%s'"%(params_role)}) - elif functions.has_role(request.user,params_role): - permission_check_ok = True + return json({"code":400,"msg":"user doesn't have role '%s'"%(params_role)}) else: - return json({"code":400,"msg":"user doesn't have role '%s'"%(params_role)}) + perms = HEAD.get("permissions") + if perms: + if params_role: + role, msg = functions.has_permission_as_role(user, params_role, *perms) + if role: + permission_check_ok = True + else: + role = functions.has_permission(user, *perms) + if role: + role_name = getattr(role, "name") + if role_name: + permission_check_ok = True + params_role = role_name + #current implementation won't run here, but keep for safe if not permission_check_ok: return json({"code":400,"msg":"no permission"}) - + if params_role=="OWNER": owner_filtered,q = self._filter_owner(model,model_setting,q) if not owner_filtered: @@ -394,7 +417,7 @@ def _head(self,key): param = params[n] if not hasattr(model.c,n): return json({"code":400,"msg":"'%s' don't have field '%s'"%(model_name,n)}) - q = model.filter(getattr(model.c,n)==param) + q = q.filter(getattr(model.c,n)==param) rdict = { "code":200, "msg":"success", @@ -695,33 +718,23 @@ def _delete_one(self,key,tag): return json({"code":400,"msg":"cannot find record id = '%s'"%(id_)}) permission_check_ok = False + msg = "'%s' not accessible by user"%(model_name) DELETE = model_setting.get("DELETE") if DELETE: roles = DELETE.get("roles") - if params_role: - if not params_role in roles: - return json({"code":400,"msg":"'%s' not accessible by role '%s'"%(model_name,params_role)}) - roles = [params_role] if roles: - for role in roles: - if role == "OWNER": - if hasattr(request,"user") and request.user: - if user_id_field: - if obj.to_dict().get(user_id_field)==request.user.id: - permission_check_ok = True - break - else: - return json({"code":400,"msg":"need login user"}) - elif role == "UNKNOWN": + has, msg = functions.has_obj_role(getattr(request,"user",None), obj, user_id_field, params_role, *roles) + if has: + permission_check_ok = True + if not permission_check_ok: + perms = DELETE.get("permissions") + if perms: + has, msg = functions.has_obj_permission(getattr(request,"user",None), obj, user_id_field, *perms) + if has: permission_check_ok = True - break - else: - if functions.has_role(request.user,role): - permission_check_ok = True - break if not permission_check_ok: - return json({"code":400,"msg":"no permission"}) + return json({"code":400,"msg":msg}) try: obj.delete() diff --git a/uliweb_apijson/tables/README.md b/uliweb_apijson/tables/README.md new file mode 100644 index 0000000..e69de29 diff --git a/uliweb_apijson/tables/__init__.py b/uliweb_apijson/tables/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/uliweb_apijson/tables/settings.ini b/uliweb_apijson/tables/settings.ini new file mode 100644 index 0000000..e69de29 diff --git a/demo/apps/tables/static/readme.txt b/uliweb_apijson/tables/static/readme.txt similarity index 100% rename from demo/apps/tables/static/readme.txt rename to uliweb_apijson/tables/static/readme.txt diff --git a/uliweb_apijson/tables/templates/Tables/inc/table_config.html b/uliweb_apijson/tables/templates/Tables/inc/table_config.html new file mode 100644 index 0000000..1eb0526 --- /dev/null +++ b/uliweb_apijson/tables/templates/Tables/inc/table_config.html @@ -0,0 +1,94 @@ + diff --git a/demo/apps/tables/templates/Tables/layout.html b/uliweb_apijson/tables/templates/Tables/layout.html similarity index 100% rename from demo/apps/tables/templates/Tables/layout.html rename to uliweb_apijson/tables/templates/Tables/layout.html diff --git a/uliweb_apijson/tables/templates/Tables/list.html b/uliweb_apijson/tables/templates/Tables/list.html new file mode 100644 index 0000000..fa9fc65 --- /dev/null +++ b/uliweb_apijson/tables/templates/Tables/list.html @@ -0,0 +1,54 @@ +{{extend "Tables/layout.html"}} + +{{block title}}uliweb-apijson: tables{{end title}} + +{{block other_use}} +{{include "vue/inc_apijson_table.html"}} +{{include "Tables/inc/table_config.html"}} +{{end other_use}} + +{{block content_wrapper}} + +
+ {{if role!="ADMIN":}} + + {{pass #if}} +
+
+ + + + + + + +
+
+
+
+{{end content_wrapper}} + +{{block mainapp_vue}} + +{{end mainapp_vue}} diff --git a/demo/apps/tables/templates/readme.txt b/uliweb_apijson/tables/templates/readme.txt similarity index 100% rename from demo/apps/tables/templates/readme.txt rename to uliweb_apijson/tables/templates/readme.txt diff --git a/uliweb_apijson/tables/views.py b/uliweb_apijson/tables/views.py new file mode 100644 index 0000000..a2c8d70 --- /dev/null +++ b/uliweb_apijson/tables/views.py @@ -0,0 +1,30 @@ +#coding=utf-8 +from uliweb import expose, functions, settings + +@expose('/tables') +class Tables(object): + @expose('') + def list(self): + if request.user: + role = "ADMIN" if functions.has_role(request.user,"ADMIN") else "OWNER" + else: + role = "UNKNOWN" + apijson_tables = functions.get_apijson_tables() + def _get_model(i): + model_name = i.model_name + model = settings.APIJSON_MODELS.get(model_name,{}) + if not i.role: + roles = model.get("GET",{}).get("roles") + i.role = roles[0] if isinstance(roles, list) else roles + return model + models = [_get_model(i) for i in apijson_tables] + def _get_request(i): + request_tag = i.request_tag or i.model_name + return settings.APIJSON_REQUESTS.get(request_tag,{}) + requests = [_get_request(i) for i in apijson_tables] + return { + "apijson_tables_json":json_dumps([d.to_dict() for d in apijson_tables]), + "models_json": json_dumps(models), + "requests_json": json_dumps(requests), + "role":role, + }