From 7810ee3974e8d5e450d379fa7dc7e5943b7acd01 Mon Sep 17 00:00:00 2001 From: pycook Date: Fri, 8 Nov 2019 17:42:13 +0800 Subject: [PATCH] Partially completed backend development of permissions management --- api/lib/perm/acl/__init__.py | 1 + api/lib/perm/acl/cache.py | 91 +++++++++++++++++++ api/lib/perm/acl/const.py | 4 + api/lib/perm/acl/resource.py | 158 ++++++++++++++++++++++++++++++++ api/lib/perm/acl/role.py | 168 +++++++++++++++++++++++++++++++++++ api/models/__init__.py | 1 + api/models/acl.py | 91 +++++++++++++++++++ api/tasks/acl.py | 16 ++++ api/views/__init__.py | 6 ++ api/views/acl/__init__.py | 3 + api/views/acl/resource.py | 107 ++++++++++++++++++++++ api/views/acl/role.py | 48 ++++++++++ 12 files changed, 694 insertions(+) create mode 100644 api/lib/perm/acl/cache.py create mode 100644 api/lib/perm/acl/const.py create mode 100644 api/lib/perm/acl/resource.py create mode 100644 api/lib/perm/acl/role.py create mode 100644 api/models/acl.py create mode 100644 api/tasks/acl.py create mode 100644 api/views/acl/__init__.py create mode 100644 api/views/acl/resource.py create mode 100644 api/views/acl/role.py diff --git a/api/lib/perm/acl/__init__.py b/api/lib/perm/acl/__init__.py index e69de29..380474e 100644 --- a/api/lib/perm/acl/__init__.py +++ b/api/lib/perm/acl/__init__.py @@ -0,0 +1 @@ +# -*- coding:utf-8 -*- diff --git a/api/lib/perm/acl/cache.py b/api/lib/perm/acl/cache.py new file mode 100644 index 0000000..0de541f --- /dev/null +++ b/api/lib/perm/acl/cache.py @@ -0,0 +1,91 @@ +# -*- coding:utf-8 -*- + +from api.extensions import cache +from api.models.acl import Role + + +class RoleCache(object): + PREFIX_ID = "Role::id::{0}" + PREFIX_NAME = "Role::app_id::{0}::name::{1}" + + @classmethod + def get_by_name(cls, app_id, name): + role = cache.get(cls.PREFIX_NAME.format(app_id, name)) + if role is None: + role = Role.get_by(app_id=app_id, name=name, first=True, to_dict=False) + if role is not None: + cache.set(cls.PREFIX_NAME.format(app_id, name), role) + + return role + + @classmethod + def get(cls, rid): + role = cache.get(cls.PREFIX_ID.format(rid)) + if role is None: + role = Role.get_by_id(rid) + if role is not None: + cache.set(cls.PREFIX_ID.format(rid), role) + + return role + + @classmethod + def clean(cls, rid): + cache.delete(cls.PREFIX_ID.format(rid)) + + @classmethod + def clean_by_name(cls, app_id, name): + cache.delete(cls.PREFIX_NAME.format(app_id, name)) + + +class RoleRelationCache(object): + PREFIX_PARENT = "RoleRelationParent::id::{0}" + PREFIX_CHILDREN = "RoleRelationChildren::id::{0}" + PREFIX_RESOURCES = "RoleRelationResources::id::{0}" + + @classmethod + def get_parent_ids(cls, rid): + parent_ids = cache.get(cls.PREFIX_PARENT.format(rid)) + if not parent_ids: + from api.lib.perm.acl.role import RoleCRUD + parent_ids = RoleCRUD.get_parent_ids(rid) + cache.set(cls.PREFIX_PARENT.format(rid), parent_ids, timeout=0) + + return parent_ids + + @classmethod + def get_child_ids(cls, rid): + child_ids = cache.get(cls.PREFIX_CHILDREN.format(rid)) + if not child_ids: + from api.lib.perm.acl.role import RoleCRUD + child_ids = RoleCRUD.get_child_ids(rid) + cache.set(cls.PREFIX_CHILDREN.format(rid), child_ids, timeout=0) + + return child_ids + + @classmethod + def get_resources(cls, rid): + """ + :param rid: + :return: {id2perms: {resource_id: [perm,]}, group2perms: {group_id: [perm, ]}} + """ + resources = cache.get(cls.PREFIX_RESOURCES.format(rid)) + if not resources: + from api.lib.perm.acl.role import RoleCRUD + resources = RoleCRUD.get_resources(rid) + cache.set(cls.PREFIX_RESOURCES.format(rid), resources, timeout=0) + + return resources or {} + + @classmethod + def rebuild(cls, rid): + cls.clean(rid) + + cls.get_parent_ids(rid) + cls.get_child_ids(rid) + cls.get_resources(rid) + + @classmethod + def clean(cls, rid): + cache.delete(cls.PREFIX_PARENT.format(rid)) + cache.delete(cls.PREFIX_CHILDREN.format(rid)) + cache.delete(cls.PREFIX_RESOURCES.format(rid)) diff --git a/api/lib/perm/acl/const.py b/api/lib/perm/acl/const.py new file mode 100644 index 0000000..355ccbf --- /dev/null +++ b/api/lib/perm/acl/const.py @@ -0,0 +1,4 @@ +# -*- coding:utf-8 -*- + + +ACL_QUEUE = "acl_async" diff --git a/api/lib/perm/acl/resource.py b/api/lib/perm/acl/resource.py new file mode 100644 index 0000000..6800820 --- /dev/null +++ b/api/lib/perm/acl/resource.py @@ -0,0 +1,158 @@ +# -*- coding:utf-8 -*- + + +from flask import abort + +from api.extensions import db +from api.models.acl import Permission +from api.models.acl import Resource +from api.models.acl import ResourceGroup +from api.models.acl import ResourceGroupItems +from api.models.acl import ResourceType + + +class ResourceTypeCRUD(object): + @staticmethod + def search(q, app_id, page=1, page_size=None): + query = db.session.query(ResourceType).filter( + ResourceType.deleted.is_(False)).filter(ResourceType.app_id == app_id) + if q: + query = query.filter(ResourceType.name.ilike('%{0}%'.format(q))) + + numfound = query.count() + + return numfound, query.offset((page - 1) * page_size).limit(page_size) + + @classmethod + def add(cls, app_id, name, perms): + ResourceType.get_by(name=name, app_id=app_id) and abort( + 400, "ResourceType <{0}> is already existed".format(name)) + + rt = ResourceType.create(name=name, app_id=app_id) + + cls.update_perms(rt.id, perms, app_id) + + return rt + + @classmethod + def update(cls, rt_id, **kwargs): + rt = ResourceType.get_by_id(rt_id) or abort(404, "ResourceType <{0}> is not found".format(rt_id)) + if 'name' in kwargs: + other = ResourceType.get_by(name=kwargs['name'], app_id=rt.app_id, to_dict=False, first=True) + if other.id != rt_id: + return abort(400, "ResourceType <{0}> is duplicated".format(kwargs['name'])) + + if 'perms' in kwargs: + cls.update_perms(rt_id, kwargs['perms'], rt.app_id) + + return rt.update(**kwargs) + + @classmethod + def delete(cls, rt_id): + rt = ResourceType.get_by_id(rt_id) or abort(404, "ResourceType <{0}> is not found".format(rt_id)) + + cls.update_perms(rt_id, [], rt.app_id) + + rt.soft_delete() + + @classmethod + def update_perms(cls, rt_id, perms, app_id): + existed = Permission.get_by(resource_type_id=rt_id, to_dict=False) + existed_names = [i.name for i in existed] + + for i in existed: + if i.name not in perms: + i.soft_delete() + + for i in perms: + if i not in existed_names: + Permission.create(resource_type_id=rt_id, + name=i, + app_id=app_id) + + +class ResourceGroupCRUD(object): + @staticmethod + def search(q, app_id, page=1, page_size=None): + query = db.session.query(ResourceGroup).filter( + ResourceGroup.deleted.is_(False)).filter(ResourceGroup.app_id == app_id) + + if q: + query = query.filter(ResourceGroup.name.ilike("%{0}%".format(q))) + + numfound = query.count() + + return numfound, query.offset((page - 1) * page_size).limit(page_size) + + @staticmethod + def get_items(rg_id): + items = ResourceGroupItems.get_by(group_id=rg_id, to_dict=False) + + return [i.resource.to_dict() for i in items] + + @staticmethod + def add(name, type_id, app_id): + ResourceGroup.get(name=name, resource_type_id=type_id, app_id=app_id) and abort( + 400, "ResourceGroup <{0}> is already existed".format(name)) + + return ResourceGroup.create(name=name, resource_type_id=type_id, app_id=app_id) + + @staticmethod + def update(rg_id, items): + existed = ResourceGroupItems.get_by(group_id=rg_id, to_dict=False) + existed_ids = [i.resource_id for i in existed] + + for i in existed: + if i.resource_id not in items: + i.soft_delete() + + for _id in items: + if _id not in existed_ids: + ResourceGroupItems.create(group_id=rg_id, resource_id=_id) + + @staticmethod + def delete(rg_id): + rg = ResourceGroup.get_by_id(rg_id) or abort(404, "ResourceGroup <{0}> is not found".format(rg_id)) + + rg.soft_delete() + + items = ResourceGroupItems.get_by(group_id=rg_id, to_dict=False) + for item in items: + item.soft_delete() + + +class ResourceCRUD(object): + @staticmethod + def search(q, app_id, page=1, page_size=None): + query = db.session.query(Resource).filter( + Resource.deleted.is_(False)).filter(Resource.app_id == app_id) + + if q: + query = query.filter(Resource.name.ilike("%{0}%".format(q))) + + numfound = query.count() + + return numfound, query.offset((page - 1) * page_size).limit(page_size) + + @staticmethod + def add(name, type_id, app_id): + Resource.get(name=name, resource_type_id=type_id, app_id=app_id) and abort( + 400, "Resource <{0}> is already existed".format(name)) + + return Resource.create(name=name, resource_type_id=type_id, app_id=app_id) + + @staticmethod + def update(_id, name): + resource = Resource.get_by_id(_id) or abort(404, "Resource <{0}> is not found".format(_id)) + + other = Resource.get_by(name=name, resource_type_id=resource.resource_type_id, to_dict=False, first=True) + if other.id != _id: + return abort(400, "Resource <{0}> is duplicated".format(name)) + + return resource.update(name=name) + + @staticmethod + def delete(_id): + resource = Resource.get_by_id(_id) or abort(404, "Resource <{0}> is not found".format(_id)) + + resource.soft_delete() diff --git a/api/lib/perm/acl/role.py b/api/lib/perm/acl/role.py new file mode 100644 index 0000000..76f4462 --- /dev/null +++ b/api/lib/perm/acl/role.py @@ -0,0 +1,168 @@ +# -*- coding:utf-8 -*- + + +from flask import abort + +from api.extensions import db +from api.lib.perm.acl.cache import RoleCache +from api.lib.perm.acl.cache import RoleRelationCache +from api.lib.perm.acl.const import ACL_QUEUE +from api.models.acl import Resource +from api.models.acl import ResourceGroupItems +from api.models.acl import Role +from api.models.acl import RolePermission +from api.models.acl import RoleRelation +from api.tasks.acl import role_rebuild + + +class RoleRelationCRUD(object): + pass + + +class RoleCRUD(object): + @staticmethod + def search(q, app_id, page=1, page_size=None): + query = db.session.query(Role).filter(Role.deleted.is_(False)).filter( + Role.app_id == app_id).filter(Role.uid.is_(None)) + if q: + query = query.filter(Role.name.ilike('%{0}%'.format(q))) + + numfound = query.count() + + return numfound, query.offset((page - 1) * page_size).limit(page_size) + + @staticmethod + def add_role(name, app_id, is_app_admin=False, uid=None): + Role.get_by(name=name, app_id=app_id) and abort(400, "Role <{0}> is already existed".format(name)) + + return Role.create(name=name, + app_id=app_id, + is_app_admin=is_app_admin, + uid=uid) + + @staticmethod + def update_role(rid, **kwargs): + role = Role.get_by_id(rid) or abort(404, "Role <{0}> does not exist".format(rid)) + + RoleCache.clean(rid) + + return role.update(**kwargs) + + @classmethod + def delete_role(cls, rid): + role = Role.get_by_id(rid) or abort(404, "Role <{0}> does not exist".format(rid)) + + parent_ids = cls.get_parent_ids(rid) + child_ids = cls.get_child_ids(rid) + + for i in RoleRelation.get_by(parent_id=rid, to_dict=False): + i.soft_delete() + for i in RoleRelation.get_by(child_id=rid, to_dict=False): + i.soft_delete() + + for i in RolePermission.get_by(rid=rid, to_dict=False): + i.soft_delete() + + role_rebuild.apply_async(args=(parent_ids + child_ids,), queue=ACL_QUEUE) + + RoleCache.clean(rid) + RoleRelationCache.clean(rid) + + role.soft_delete() + + @staticmethod + def get_resources(rid): + res = RolePermission.get_by(rid=rid, to_dict=False) + id2perms = dict(id2perms={}, group2perms={}) + for i in res: + if i.resource_id: + id2perms['id2perms'].setdefault(i.resource_id, []).append(i.perm.name) + elif i.group_id: + id2perms['group2perms'].setdefault(i.group_id, []).append(i.perm.name) + + return id2perms + + @staticmethod + def get_group_ids(resource_id): + return [i.group_id for i in ResourceGroupItems.get_by(resource_id, to_dict=False)] + + @staticmethod + def get_parent_ids(rid): + res = RoleRelation.get_by(child_id=rid, to_dict=False) + + return [i.parent_id for i in res] + + @staticmethod + def get_child_ids(rid): + res = RoleRelation.get_by(child_id=rid, to_dict=False) + + return [i.parent_id for i in res] + + @classmethod + def recursive_parent_ids(cls, rid): + all_parent_ids = set() + + def _get_parent(_id): + all_parent_ids.add(_id) + parent_ids = RoleRelationCache.get_parent_ids(_id) + for parent_id in parent_ids: + _get_parent(parent_id) + + _get_parent(rid) + + return all_parent_ids + + @classmethod + def recursive_child_ids(cls, rid): + all_child_ids = set() + + def _get_children(_id): + all_child_ids.add(_id) + child_ids = RoleRelationCache.get_child_ids(_id) + for child_id in child_ids: + _get_children(child_id) + + _get_children(rid) + + return all_child_ids + + @classmethod + def has_permission(cls, rid, resource_name, perm): + resource = Resource.get_by(name=resource_name, first=True, to_dict=False) + resource = resource or abort(403, "Resource <{0}> is not in ACL".format(resource_name)) + + parent_ids = cls.recursive_parent_ids(rid) + + group_ids = cls.get_group_ids(resource.id) + + for parent_id in parent_ids: + id2perms = RoleRelationCache.get_resources(parent_id) + + perms = id2perms['id2perms'].get(resource.id, []) + if perms and {perm}.issubset(set(perms)): + return True + + for group_id in group_ids: + perms = id2perms['group2perms'].get(group_id, []) + if perms and {perm}.issubset(set(perms)): + return True + + return False + + @classmethod + def get_permissions(cls, rid, resource_name): + resource = Resource.get_by(name=resource_name, first=True, to_dict=False) + resource = resource or abort(403, "Resource <{0}> is not in ACL".format(resource_name)) + + parent_ids = cls.recursive_parent_ids(rid) + group_ids = cls.get_group_ids(resource.id) + + perms = [] + for parent_id in parent_ids: + id2perms = RoleRelationCache.get_resources(parent_id) + perms += id2perms['id2perms'].get(parent_id, []) + + for group_id in group_ids: + perms += id2perms['group2perms'].get(group_id, []) + + return set(perms) diff --git a/api/models/__init__.py b/api/models/__init__.py index fabbe66..c10c68e 100644 --- a/api/models/__init__.py +++ b/api/models/__init__.py @@ -3,3 +3,4 @@ from .account import User from .cmdb import * +from .acl import * diff --git a/api/models/acl.py b/api/models/acl.py new file mode 100644 index 0000000..fea5845 --- /dev/null +++ b/api/models/acl.py @@ -0,0 +1,91 @@ +# -*- coding:utf-8 -*- + +from api.extensions import db +from api.lib.database import Model + + +class App(Model): + __tablename__ = "acl_apps" + + name = db.Column(db.String(64), index=True) + description = db.Column(db.Text) + app_id = db.Column(db.Text) + secret_key = db.Column(db.Text) + + +class Role(Model): + __tablename__ = "acl_roles" + + name = db.Column(db.Text, nullable=False) + is_app_admin = db.Column(db.Boolean, default=False) + app_id = db.Column(db.Integer, db.ForeignKey("acl_apps.id")) + uid = db.Column(db.Integer, db.ForeignKey("users.uid")) + + +class RoleRelation(Model): + __tablename__ = "acl_role_relations" + + parent_id = db.Column(db.Integer, db.ForeignKey('acl_roles.id')) + child_id = db.Column(db.Integer, db.ForeignKey('acl_roles.id')) + + __table_args__ = ( + db.UniqueConstraint("parent_id", "child_id", name="role_relation_unique"),) + + +class ResourceType(Model): + __tablename__ = "acl_resource_types" + + name = db.Column(db.String(64), index=True) + description = db.Column(db.Text) + app_id = db.Column(db.Integer, db.ForeignKey('acl_apps.id')) + + +class ResourceGroup(Model): + __tablename__ = "acl_resource_groups" + + name = db.Column(db.String(64), index=True, nullable=False) + resource_type_id = db.Column(db.Integer, db.ForeignKey("acl_resource_types.id")) + + app_id = db.Column(db.Integer, db.ForeignKey('acl_apps.id')) + + __table_args__ = (db.UniqueConstraint("name", "resource_type_id", "app_id", name="resource_group_app_unique"),) + + +class Resource(Model): + __tablename__ = "acl_resources" + + name = db.Column(db.String(128), nullable=False) + resource_type_id = db.Column(db.Integer, db.ForeignKey("acl_resource_types.id")) + + app_id = db.Column(db.Integer, db.ForeignKey("acl_apps.id")) + + __table_args__ = (db.UniqueConstraint("name", "resource_type_id", "app_id", name="resource_name_app_unique"),) + + +class ResourceGroupItems(Model): + __tablename__ = "acl_resource_group_items" + + group_id = db.Column(db.Integer, db.ForeignKey('acl_resource_groups.id'), nullable=False) + resource_id = db.Column(db.Integer, db.ForeignKey('acl_resources.id'), nullable=False) + + +class Permission(Model): + __tablename__ = "acl_permissions" + + name = db.Column(db.String(64), nullable=False) + resource_type_id = db.Column(db.Integer, db.ForeignKey("acl_resource_types.id")) + + app_id = db.Column(db.Integer, db.ForeignKey("acl_apps.id")) + + __table_args__ = (db.UniqueConstraint("name", "resource_type_id", "app_id", name="perm_name_app_unique"),) + + +class RolePermission(Model): + __tablename__ = "acl_role_permissions" + + rid = db.Column(db.Integer, db.ForeignKey('acl_roles.id')) + resource_id = db.Column(db.Integer, db.ForeignKey('acl_resources.id')) + group_id = db.Column(db.Integer, db.ForeignKey('acl_resource_groups.id')) + perm_id = db.Column(db.Integer, db.ForeignKey('acl_permissions.id')) + + perm = db.relationship("Permission", backref='acl_role_permissions.perm_id') diff --git a/api/tasks/acl.py b/api/tasks/acl.py new file mode 100644 index 0000000..9377014 --- /dev/null +++ b/api/tasks/acl.py @@ -0,0 +1,16 @@ +# -*- coding:utf-8 -*- + +from flask import current_app + +from api.extensions import celery +from api.lib.perm.acl.cache import RoleRelationCache +from api.lib.perm.acl.const import ACL_QUEUE + + +@celery.task(name="acl.role_rebuild", queue=ACL_QUEUE) +def role_rebuild(rids): + rids = rids if isinstance(rids, list) else [rids] + for rid in rids: + RoleRelationCache.rebuild(rid) + + current_app.logger.info("%d rebuild.........." % rids) diff --git a/api/views/__init__.py b/api/views/__init__.py index 3db020b..4d8a2ee 100644 --- a/api/views/__init__.py +++ b/api/views/__init__.py @@ -29,3 +29,9 @@ perm_rest.add_resource(GetUserInfoView, GetUserInfoView.url_prefix) blueprint_cmdb_v01 = Blueprint('cmdb_api_v01', __name__, url_prefix='/api/v0.1') rest = Api(blueprint_cmdb_v01) register_resources(os.path.join(HERE, "cmdb"), rest) + + +# acl +blueprint_acl_v1 = Blueprint('cmdb_acl_v1', __name__, url_prefix='/api/v1/acl') +rest = Api(blueprint_acl_v1) +register_resources(os.path.join(HERE, "acl"), rest) diff --git a/api/views/acl/__init__.py b/api/views/acl/__init__.py new file mode 100644 index 0000000..e3c651d --- /dev/null +++ b/api/views/acl/__init__.py @@ -0,0 +1,3 @@ +# -*- coding:utf-8 -*- + +__author__ = 'pycook' diff --git a/api/views/acl/resource.py b/api/views/acl/resource.py new file mode 100644 index 0000000..91f9c84 --- /dev/null +++ b/api/views/acl/resource.py @@ -0,0 +1,107 @@ +# -*- coding:utf-8 -*- + +from flask import request + +from api.lib.decorator import args_required +from api.lib.perm.acl.resource import ResourceCRUD +from api.lib.perm.acl.resource import ResourceGroupCRUD +from api.lib.utils import get_page +from api.lib.utils import get_page_size +from api.lib.utils import handle_arg_list +from api.resource import APIView + + +class ResourceView(APIView): + url_prefix = ("/resources", "/resources/") + + @args_required('app_id') + def get(self): + page = get_page(request.values.get("page", 1)) + page_size = get_page_size(request.values.get("page_size")) + q = request.values.get('q') + app_id = request.values.get('app_id') + + numfound, res = ResourceCRUD.search(q, app_id, page, page_size) + + return self.jsonify(numfound=numfound, + page=page, + page_size=page_size, + resources=[i.to_dict() for i in res]) + + @args_required('name') + @args_required('type_id') + @args_required('app_id') + def post(self): + name = request.values.get('name') + type_id = request.values.get('type_id') + app_id = request.values.get('app_id') + + resource = ResourceCRUD.add(name, type_id, app_id) + + return self.jsonify(resource.to_dict()) + + @args_required('name') + def put(self, resource_id): + name = request.values.get('name') + + resource = ResourceCRUD.update(resource_id, name) + + return self.jsonify(resource.to_dict()) + + def delete(self, resource_id): + ResourceCRUD.delete(resource_id) + + return self.jsonify(resource_id=resource_id) + + +class ResourceGroupView(APIView): + url_prefix = ("/resource_groups", "/resource_groups/") + + def get(self): + page = get_page(request.values.get("page", 1)) + page_size = get_page_size(request.values.get("page_size")) + q = request.values.get('q') + app_id = request.values.get('app_id') + + numfound, res = ResourceGroupCRUD.search(q, app_id, page, page_size) + + return self.jsonify(numfound=numfound, + page=page, + page_size=page_size, + groups=[i.to_dict() for i in res]) + + @args_required('name') + @args_required('type_id') + @args_required('app_id') + def post(self): + name = request.values.get('name') + type_id = request.values.get('type_id') + app_id = request.values.get('app_id') + + group = ResourceGroupCRUD.add(name, type_id, app_id) + + return self.jsonify(group.to_dict()) + + @args_required('items') + def put(self, group_id): + items = handle_arg_list(request.values.get("items")) + + ResourceGroupCRUD.update(group_id, items) + + items = ResourceGroupCRUD.get_items(group_id) + + return self.jsonify(items) + + def delete(self, group_id): + ResourceGroupCRUD.delete(group_id) + + return self.jsonify(group_id=group_id) + + +class ResourceGroupItemsView(APIView): + url_prefix = "/resource_groups//items" + + def get(self, group_id): + items = ResourceGroupCRUD.get_items(group_id) + + return self.jsonify(items) diff --git a/api/views/acl/role.py b/api/views/acl/role.py new file mode 100644 index 0000000..9a2b5cc --- /dev/null +++ b/api/views/acl/role.py @@ -0,0 +1,48 @@ +# -*- coding:utf-8 -*- + +from flask import request + +from api.lib.decorator import args_required +from api.lib.perm.acl.role import RoleCRUD +from api.lib.utils import get_page +from api.lib.utils import get_page_size +from api.resource import APIView + + +class RoleView(APIView): + url_prefix = ("/roles", "/roles/") + + @args_required('app_id') + def get(self): + page = get_page(request.values.get("page", 1)) + page_size = get_page_size(request.values.get("page_size")) + q = request.values.get('q') + app_id = request.values.get('app_id') + + numfound, roles = RoleCRUD.search(q, app_id, page, page_size) + + return self.jsonify(numfound=numfound, + page=page, + page_size=page_size, + roles=[i.to_dict() for i in roles]) + + @args_required('name') + @args_required('app_id') + def post(self): + name = request.values.get('name') + app_id = request.values.get('app_id') + is_app_admin = request.values.get('is_app_admin', False) + + role = RoleCRUD.add_role(name, app_id, is_app_admin=is_app_admin) + + return self.jsonify(role.to_dict()) + + def put(self, rid): + role = RoleCRUD.update_role(rid, **request.values) + + return self.jsonify(role.to_dict()) + + def delete(self, rid): + RoleCRUD.delete_role(rid) + + return self.jsonify(rid=rid)