diff --git a/demo/apps/apijson_demo/dbinit.py b/demo/apps/apijson_demo/dbinit.py index bb34631..b235176 100644 --- a/demo/apps/apijson_demo/dbinit.py +++ b/demo/apps/apijson_demo/dbinit.py @@ -10,6 +10,11 @@ Moment = models.moment user_list = [ + { + "username": "admin", + "nickname": "Administrator", + "email": "admin@localhost", + }, { "username": "usera", "nickname": "User A", @@ -101,7 +106,10 @@ print("create user '%s'"%(d["username"])) u = User(**d) u.set_password("123") + if d["username"]=="admin": + u.is_superuser = True u.save() + for d in privacy_list: user = User.get(User.c.username==d["username"]) if user: diff --git a/demo/apps/apijson_demo/views.py b/demo/apps/apijson_demo/views.py index af9807b..6646283 100644 --- a/demo/apps/apijson_demo/views.py +++ b/demo/apps/apijson_demo/views.py @@ -5,22 +5,22 @@ @expose('/') def index(): if request.user: - user_info = "login as user '%s'"%(request.user) + user_info = "login as user '%s(%s)'"%(request.user.username,request.user) else: - user_info = "not login, you can login with username 'usera/userb/userc', and password '123'" + user_info = "not login, you can login with username 'admin/usera/userb/userc', and password '123'" request_get = [ { - "label":"Single record query: with id as parameter", + "label":"Single record query: no parameter", "value":'''{ "user":{ - "id":1 } }''', }, { - "label":"Single record query: no parameter", + "label":"Single record query: with id as parameter", "value":'''{ "user":{ + "id":1 } }''', }, diff --git a/demo/apps/settings.ini b/demo/apps/settings.ini index 72f041b..0794738 100644 --- a/demo/apps/settings.ini +++ b/demo/apps/settings.ini @@ -13,6 +13,7 @@ INSTALLED_APPS = [ 'uliweb.contrib.auth', 'uliweb.contrib.i18n', 'uliweb.contrib.flashmessage', + 'uliweb.contrib.rbac', 'uliweb_apps.site', 'uliweb_apps.login', 'uliweb_comui', @@ -27,14 +28,9 @@ MAINMENU = { ] } -[APIJSON_MODEL] -#overwrite user table to public for test -user = { - "user_id_field" : "id", - "secret_fields" : ["password"], - "default_filter_by_self" : True -} - [LAYOUT] logo_lg = "Uliweb" logo_mini = "U" + +[SESSION] +timeout = 36000 diff --git a/demo/doc/imgs/demo_screenshot.png b/demo/doc/imgs/demo_screenshot.png index ac484ff..5a8c248 100644 Binary files a/demo/doc/imgs/demo_screenshot.png and b/demo/doc/imgs/demo_screenshot.png differ diff --git a/uliweb_apijson/apijson/README.md b/uliweb_apijson/apijson/README.md index 77bad7e..ad0508d 100644 --- a/uliweb_apijson/apijson/README.md +++ b/uliweb_apijson/apijson/README.md @@ -7,12 +7,13 @@ uliweb-apijson is a subset and slightly different variation of [apijson](https:/ ## example ``` -[APIJSON_MODEL_CONFIG] +[APIJSON_MODELS] user = { - "public" : False, "user_id_field" : "id", "secret_fields" : ["password"], - "default_filter_by_self" : True + "rbac_get" : { + "roles" : ["ADMIN","OWNER"] + } } ``` @@ -20,163 +21,12 @@ user = { settings.APIJSON_MODEL_CONFIG.[MODEL_NAME] -| Field | Doc | -| ---------------------- | ------------------------------------------------------------ | -| public | Default to be "False".
If not public, should be **login user** and only can see **user own data**. | -| user_id_field | Field name of user id, related to query user own data. | -| secret_fields | Secret fields won't be exposed. | -| default_filter_by_self | If True, when no filter parameter, will filter by self user id | +| Field | Doc | +| ------------- | ---------------------------------------------------------- | +| user_id_field | Field name of user id, related to query user own data. | +| secret_fields | Secret fields won't be exposed. | +| rbac_get | Configure of roles or permissions for apijson 'get' method | # Supported API Examples -### Single record query: with id as parameter - -URL: apijson/get - -Method: POST - -Request: - -``` -{ - "user":{ - "id":1 - } -} -``` - -Response: - -``` -{ - "code": 200, - "msg": "success", - "user": { - "username": "usera", - "nickname": "User A", - "email": "usera@localhost", - "is_superuser": false, - "last_login": null, - "date_join": "2018-12-05 15:44:26", - "image": "", - "active": false, - "locked": false, - "deleted": false, - "auth_type": "default", - "id": 1 - } -} -``` - -### Single record query: no parameter - -URL: apijson/get - -Method: POST - -Request: - -``` -{ - "user":{ - } -} -``` - -Response: - -``` -{ - "code": 200, - "msg": "success", - "user": { - "username": "usera", - "nickname": "User A", - "email": "usera@localhost", - "is_superuser": false, - "last_login": null, - "date_join": "2018-12-05 15:44:26", - "image": "", - "active": false, - "locked": false, - "deleted": false, - "auth_type": "default", - "id": 1 - } -} -``` - -### Single record query: @column - -URL: apijson/get - -Method: POST - -Request: - -``` -{ - "user":{ - "@column": "id,username,email" - } -} -``` - -Response: - -``` -{ - "code": 200, - "msg": "success", - "user": { - "username": "usera", - "email": "usera@localhost", - "id": 1 - } -} -``` - -### Array query - -URL: apijson/get - -Method: POST - -Request: - -``` -{ - "[]":{ - "@count":2, - "@page":0, - "user":{ - "@column":"id,username,nickname,email", - "@order":"id-" - } - } -} -``` - -Response: - -``` -{ - "code": 200, - "msg": "success", - "[]": [ - { - "username": "userc", - "nickname": "User C", - "email": "userc@localhost", - "id": 3 - }, - { - "username": "userb", - "nickname": "User B", - "email": "userb@localhost", - "id": 2 - } - ] -} -``` - +Please run [demo](../../demo/README.md) project and try it. diff --git a/uliweb_apijson/apijson/settings.ini b/uliweb_apijson/apijson/settings.ini index bdd90bf..8cf9018 100644 --- a/uliweb_apijson/apijson/settings.ini +++ b/uliweb_apijson/apijson/settings.ini @@ -1,7 +1,16 @@ -[APIJSON_MODEL_CONFIG] +#apijson style role names +[ROLES] +ADMIN = _('APIJSON ADMIN'), 'uliweb.contrib.rbac.superuser', True +UNKNOWN = _('APIJSON UNKNOWN'), 'uliweb.contrib.rbac.anonymous', True +LOGIN = _('APIJSON LOGIN'), 'uliweb.contrib.rbac.trusted', True +#will do more when query in the database +OWNER = _('APIJSON OWNER'), 'uliweb.contrib.rbac.trusted', True + +[APIJSON_MODELS] user = { - "public" : False, "user_id_field" : "id", "secret_fields" : ["password"], - "default_filter_by_self" : True + "rbac_get" : { + "roles" : ["ADMIN","OWNER"] + } } diff --git a/uliweb_apijson/apijson/views.py b/uliweb_apijson/apijson/views.py index 548e85d..b5c6959 100644 --- a/uliweb_apijson/apijson/views.py +++ b/uliweb_apijson/apijson/views.py @@ -23,33 +23,69 @@ def __begin__(self): def get(self): for key in self.request_data: if key[-2:]=="[]": - rsp = self._query_array(key) + rsp = self._get_array(key) else: - rsp = self._query_one(key) + rsp = self._get_one(key) if rsp: return rsp return json(self.rdict) - def _query_one(self,key): + def _get_one(self,key): modelname = key + params = self.request_data[key] + try: model = getattr(models,modelname) - model_setting = settings.APIJSON_MODEL.get(modelname,{}) + model_setting = settings.APIJSON_MODELS.get(modelname,{}) except ModelNotFound as e: log.error("try to find model '%s' but not found: '%s'"%(modelname,e)) return json({"code":400,"msg":"model '%s' not found"%(modelname)}) model_column_set = None q = model.all() - public = model_setting.get("public",False) + + #rbac check begin + rbac_get = model_setting.get("rbac_get",{}) + if not rbac_get: + return json({"code":401,"msg":"'%s' not accessible by apijson"%(modelname)}) + + roles = rbac_get.get("roles") + perms = rbac_get.get("perms") + params_role = params.get("@role") + permission_check_ok = False + user_role = None + if params_role: + if params_role not in roles: + return json({"code":401,"msg":"'%s' not accessible by role '%s'"%(modelname,params_role)}) + if functions.has_role(request.user,params_role): + permission_check_ok = True + user_role = params_role + else: + return json({"code":401,"msg":"user doesn't have role '%s'"%(params_role)}) + if not permission_check_ok and roles: + for role in roles: + if functions.has_role(request.user,role): + permission_check_ok = True + user_role = role + break + + if not permission_check_ok and perms: + for perm in perms: + if functions.has_permission(request.user,perm): + permission_check_ok = True + break + + if not permission_check_ok: + return json({"code":401,"msg":"no permission"}) + #rbac check end + filtered = False - if not public: - if not request.user: - return json({"code":401,"msg":"'%s' not accessable for unauthorized request"%(modelname)}) + + if user_role == "OWNER": owner_filtered,q = self._filter_owner(model,model_setting,q) - if owner_filtered: - filtered = True - else: - return json({"code":401,"msg":"'%s' not accessable because not public"%(modelname)}) + if not owner_filtered: + return json({"code":401,"msg":"'%s' cannot filter with owner"%(modelname)}) + filtered = True + params = self.request_data[key] if isinstance(params,dict): for n in params: @@ -61,14 +97,9 @@ def _query_one(self,key): filtered = True else: return json({"code":400,"msg":"'%s' have no attribute '%s'"%(modelname,n)}) - #default filter + #default filter is trying to filter with owner if not filtered and request.user: - default_filter_by_self = model_setting.get("default_filter_by_self",False) - if default_filter_by_self: - user_id_field = model_setting.get("user_id_field") - if user_id_field: - q = q.filter(getattr(model.c,user_id_field)==request.user.id) - filtered = True + owner_filtered,q = self._filter_owner(model,model_setting,q) o = q.one() if o: o = o.to_dict() @@ -83,7 +114,7 @@ def _query_one(self,key): del o[k] self.rdict[key] = o - def _query_array(self,key): + def _get_array(self,key): params = self.request_data[key] query_count = None query_page = None @@ -118,9 +149,8 @@ def _query_array(self,key): return json({"code":400,"msg":"no model found in array query"}) #model settings - model_setting = settings.APIJSON_MODEL.get(modelname,{}) + model_setting = settings.APIJSON_MODELS.get(modelname,{}) secret_fields = model_setting["secret_fields"] - public = model_setting.get("public",False) #model params #column @@ -137,6 +167,47 @@ def _query_array(self,key): model_order = model_param.get("@order") q = model.all() + + #rbac check begin + rbac_get = model_setting.get("rbac_get",{}) + if not rbac_get: + return json({"code":401,"msg":"'%s' not accessible by apijson"%(modelname)}) + + roles = rbac_get.get("roles") + perms = rbac_get.get("perms") + params_role = params.get("@role") + permission_check_ok = False + user_role = None + if params_role: + if params_role not in roles: + return json({"code":401,"msg":"'%s' not accessible by role '%s'"%(modelname,params_role)}) + if functions.has_role(request.user,params_role): + permission_check_ok = True + user_role = params_role + else: + return json({"code":401,"msg":"user doesn't have role '%s'"%(params_role)}) + if not permission_check_ok and roles: + for role in roles: + if functions.has_role(request.user,role): + permission_check_ok = True + user_role = role + break + + if not permission_check_ok and perms: + for perm in perms: + if functions.has_permission(request.user,perm): + permission_check_ok = True + break + + if not permission_check_ok: + return json({"code":401,"msg":"no permission"}) + #rbac check end + + if user_role == "OWNER": + owner_filtered,q = self._filter_owner(model,model_setting,q) + if not owner_filtered: + return json({"code":401,"msg":"'%s' cannot filter with owner"%(modelname)}) + if query_count: if query_page: q = q.offset(query_page*query_count) @@ -155,13 +226,6 @@ def _query_array(self,key): column = getattr(model.c,sort_key) q = q.order_by(getattr(column,sort_order)()) - if not public: - if not request.user: - return json({"code":401,"msg":"'%s' not accessable for unauthorized request"%(modelname)}) - owner_filtered,q = self._filter_owner(model,model_setting,q) - if not owner_filtered: - return json({"code":401,"msg":"'%s' not accessable because not public"%(modelname)}) - def _get_info(i): d = i.to_dict() if secret_fields: