acl done and bugfix

This commit is contained in:
pycook
2019-12-06 22:33:31 +08:00
parent 16b724bd40
commit f424ad6864
22 changed files with 270 additions and 138 deletions

View File

@@ -12,7 +12,6 @@ from api.extensions import db
from api.extensions import rd
from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.cache import RelationTypeCache
from api.lib.cmdb.ci_type import CITypeAttributeManager
from api.lib.cmdb.ci_type import CITypeManager
from api.lib.cmdb.const import CMDB_QUEUE
@@ -32,6 +31,7 @@ from api.lib.utils import handle_arg_list
from api.models.cmdb import CI
from api.models.cmdb import CIRelation
from api.models.cmdb import CITypeAttribute
from api.models.cmdb import CITypeRelation
from api.tasks.cmdb import ci_cache
from api.tasks.cmdb import ci_delete
from api.tasks.cmdb import ci_relation_cache
@@ -52,7 +52,7 @@ class CIManager(object):
@staticmethod
def confirm_ci_existed(ci_id):
CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not existed".format(ci_id))
return CI.get_by_id(ci_id) or abort(404, "CI <{0}> is not existed".format(ci_id))
@classmethod
def get_ci_by_id(cls, ci_id, ret_key=RetKey.NAME, fields=None, need_children=True):
@@ -408,10 +408,6 @@ class CIRelationManager(object):
def __init__(self):
pass
@staticmethod
def _get_default_relation_type():
return RelationTypeCache.get("contain").id # FIXME
@classmethod
def get_children(cls, ci_id, ret_key=RetKey.NAME):
second_cis = CIRelation.get_by(first_ci_id=ci_id, to_dict=False)
@@ -428,7 +424,8 @@ class CIRelationManager(object):
res[ci_type.name] = children
return res
def get_second_cis(self, first_ci_id, relation_type_id=None, page=1, per_page=None, **kwargs):
@staticmethod
def get_second_cis(first_ci_id, relation_type_id=None, page=1, per_page=None):
second_cis = db.session.query(CI.id).filter(CI.deleted.is_(False)).join(
CIRelation, CIRelation.second_ci_id == CI.id).filter(
CIRelation.first_ci_id == first_ci_id).filter(CIRelation.deleted.is_(False))
@@ -436,9 +433,6 @@ class CIRelationManager(object):
if relation_type_id is not None:
second_cis = second_cis.filter(CIRelation.relation_type_id == relation_type_id)
if kwargs: # TODO: special for devices
second_cis = self._query_wrap_for_device(second_cis, **kwargs)
numfound = second_cis.count()
if per_page != "all":
second_cis = second_cis.offset((page - 1) * per_page).limit(per_page).all()
@@ -473,33 +467,6 @@ class CIRelationManager(object):
return query_sql
def _query_wrap_for_device(self, query_sql, **kwargs):
_type = kwargs.pop("_type", False) or kwargs.pop("type", False) or kwargs.pop("ci_type", False)
if _type:
ci_type = CITypeCache.get(_type)
if ci_type is None:
return
query_sql = query_sql.filter(CI.type_id == ci_type.id)
for k, v in kwargs.items():
attr = AttributeCache.get(k)
if attr is None:
continue
value_table = TableMap(attr_name=k).table
ci_table = query_sql.subquery()
query_sql = db.session.query(ci_table.c.id).join(
value_table, value_table.ci_id == ci_table.c.id).filter(
value_table.attr_id == attr.id).filter(ci_table.deleted.is_(False)).filter(
value_table.value.ilike(v.replace("*", "%")))
# current_app.logger.debug(query_sql)
sort_by = kwargs.pop("sort", "")
if sort_by:
query_sql = self._sort_handler(sort_by, query_sql)
return query_sql
@classmethod
def get_first_cis(cls, second_ci, relation_type_id=None, page=1, per_page=None):
first_cis = db.session.query(CIRelation.first_ci_id).filter(
@@ -519,10 +486,8 @@ class CIRelationManager(object):
@classmethod
def add(cls, first_ci_id, second_ci_id, more=None, relation_type_id=None):
relation_type_id = relation_type_id or cls._get_default_relation_type()
CIManager.confirm_ci_existed(first_ci_id)
CIManager.confirm_ci_existed(second_ci_id)
first_ci = CIManager.confirm_ci_existed(first_ci_id)
second_ci = CIManager.confirm_ci_existed(second_ci_id)
existed = CIRelation.get_by(first_ci_id=first_ci_id,
second_ci_id=second_ci_id,
@@ -531,11 +496,22 @@ class CIRelationManager(object):
if existed is not None:
if existed.relation_type_id != relation_type_id:
existed.update(relation_type_id=relation_type_id)
CIRelationHistoryManager().add(existed, OperateType.UPDATE)
else:
if relation_type_id is None:
type_relation = CITypeRelation.get_by(parent_id=first_ci.type_id,
child_id=second_ci.type_id,
first=True,
to_dict=False)
relation_type_id = type_relation and type_relation.relation_type_id
relation_type_id or abort(404, "Relation {0} <-> {1} is not found".format(
first_ci.ci_type.name, second_ci.ci_type.name))
existed = CIRelation.create(first_ci_id=first_ci_id,
second_ci_id=second_ci_id,
relation_type_id=relation_type_id)
CIRelationHistoryManager().add(existed, OperateType.ADD)
ci_relation_cache.apply_async(args=(first_ci_id, second_ci_id), queue=CMDB_QUEUE)

View File

@@ -69,6 +69,15 @@ class CITypeManager(object):
CITypeCache.clean(ci_type.name)
if current_app.config.get("USE_ACL"):
from api.lib.perm.acl.acl import ACLManager
from api.lib.cmdb.const import ResourceTypeEnum, RoleEnum, PermEnum
ACLManager().add_resource(ci_type.name, ResourceTypeEnum.CI)
ACLManager().grant_resource_to_role(ci_type.name,
RoleEnum.CMDB_READ_ALL,
ResourceTypeEnum.CI,
permissions=[PermEnum.READ])
return ci_type.id
@classmethod

View File

@@ -37,8 +37,9 @@ class RetKey(BaseEnum):
ALIAS = "alias"
class ResourceType(BaseEnum):
class ResourceTypeEnum(BaseEnum):
CI = "CIType"
RELATION_VIEW = "RelationView"
class PermEnum(BaseEnum):
@@ -50,6 +51,7 @@ class PermEnum(BaseEnum):
class RoleEnum(BaseEnum):
CONFIG = "admin"
CMDB_READ_ALL = "CMDB_READ_ALL"
CMDB_QUEUE = "cmdb_async"

View File

@@ -7,6 +7,7 @@ import json
import six
import toposort
from flask import abort
from flask import current_app
from flask import g
from api.extensions import db
@@ -19,6 +20,8 @@ from api.models.cmdb import CITypeRelation
from api.models.cmdb import PreferenceRelationView
from api.models.cmdb import PreferenceShowAttributes
from api.models.cmdb import PreferenceTreeView
from api.lib.perm.acl.acl import ACLManager
from api.lib.cmdb.const import ResourceTypeEnum, RoleEnum, PermEnum
class PreferenceManager(object):
@@ -116,6 +119,11 @@ class PreferenceManager(object):
@staticmethod
def get_relation_view():
views = PreferenceRelationView.get_by(to_dict=True)
if current_app.config.get("USE_ACL"):
views = [i for i in views if ACLManager().has_permission(i.get('name'),
ResourceTypeEnum.RELATION_VIEW,
PermEnum.READ)]
view2cr_ids = dict()
result = dict()
name2id = list()
@@ -170,6 +178,13 @@ class PreferenceManager(object):
if existed is None:
PreferenceRelationView.create(name=name, cr_ids=json.dumps(cr_ids))
if current_app.config.get("USE_ACL"):
ACLManager().add_resource(name, ResourceTypeEnum.RELATION_VIEW)
ACLManager().grant_resource_to_role(name,
RoleEnum.CMDB_READ_ALL,
ResourceTypeEnum.RELATION_VIEW,
permissions=[PermEnum.READ])
return cls.get_relation_view()
@staticmethod

View File

@@ -6,54 +6,83 @@ import six
from flask import current_app, g, request
from flask import session, abort
from api.lib.cmdb.const import ResourceTypeEnum as CmdbResourceType
from api.lib.cmdb.const import RoleEnum
from api.lib.perm.acl.cache import AppCache
from api.models.acl import ResourceType
from api.models.acl import Resource
from api.lib.perm.acl.cache import UserCache
from api.lib.perm.acl.permission import PermissionCRUD
from api.lib.perm.acl.resource import ResourceCRUD
from api.lib.perm.acl.role import RoleCRUD
from api.models.acl import Resource
from api.models.acl import ResourceGroup
from api.models.acl import ResourceType
from api.models.acl import Role
CMDB_RESOURCE_TYPES = CmdbResourceType.all()
class ACLManager(object):
def __init__(self):
self.user_info = session["acl"] if "acl" in session else {}
self.app_id = AppCache.get('cmdb')
if not self.app_id:
raise Exception("cmdb not in acl apps")
self.app_id = self.app_id.id
def _get_resource(self, name, resource_type_name):
resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
resource_type or abort(404, "ResourceType <{0}> cannot be found".format(resource_type_name))
return Resource.get_by(resource_type_id=resource_type.id,
app_id=self.app_id,
name=name,
first=True,
to_dict=False)
def _get_resource_group(self, name):
return ResourceGroup.get_by(
app_id=self.app_id,
name=name,
first=True,
to_dict=False
)
def _get_role(self, name):
user = UserCache.get(name)
if user:
return Role.get_by(name=name, uid=user.uid, first=True, to_dict=False)
return Role.get_by(name=name, app_id=self.app_id, first=True, to_dict=False)
def add_resource(self, name, resource_type_name=None):
resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
if resource_type:
return abort(400, "ResourceType <{0}> cannot be found".format(resource_type_name))
resource_type or abort(404, "ResourceType <{0}> cannot be found".format(resource_type_name))
ResourceCRUD.add(name, resource_type.id, self.app_id)
def grant_resource_to_role(self, name, role, resource_type_name=None):
resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
if resource_type:
return abort(400, "ResourceType <{0}> cannot be found".format(resource_type_name))
def grant_resource_to_role(self, name, role, resource_type_name=None, permissions=None):
resource = self._get_resource(name, resource_type_name)
role = self._get_role(role)
if resource:
PermissionCRUD.grant(role.id, permissions, resource_id=resource.id)
else:
group = self._get_resource_group(name)
if group:
PermissionCRUD.grant(role.id, permissions, group_id=group.id)
def del_resource(self, name, resource_type_name=None):
resource_type = ResourceType.get_by(name=resource_type_name, first=True, to_dict=False)
if resource_type:
return abort(400, "ResourceType <{0}> cannot be found".format(resource_type_name))
resource = Resource.get_by(resource_type_id=resource_type.id,
app_id=self.app_id,
name=name,
first=True,
to_dict=False)
resource = self._get_resource(name, resource_type_name)
if resource:
ResourceCRUD.delete(resource.id)
def get_resources(self, resource_type_name=None):
if "acl" not in session:
abort(405)
return []
def has_permission(self, resource_name, resource_type, perm):
if "acl" not in session:
abort(405)
return True
role = self._get_role(g.user.username)
role or abort(404, "Role <{0}> is not found".format(g.user.username))
return RoleCRUD.has_permission(role.id, resource_name, resource_type, self.app_id, perm)
def validate_permission(resources, resource_type, perm):
@@ -70,24 +99,6 @@ def validate_permission(resources, resource_type, perm):
return abort(403, "has no permission")
def can_access_resources(resource_type):
def decorator_can_access_resources(func):
@functools.wraps(func)
def wrapper_can_access_resources(*args, **kwargs):
if current_app.config.get("USE_ACL"):
res = ACLManager().get_resources(resource_type)
result = {i.get("name"): i.get("permissions") for i in res}
if hasattr(g, "resources"):
g.resources.update({resource_type: result})
else:
g.resources = {resource_type: result}
return func(*args, **kwargs)
return wrapper_can_access_resources
return decorator_can_access_resources
def has_perm(resources, resource_type, perm):
def decorator_has_perm(func):
@functools.wraps(func)
@@ -96,6 +107,9 @@ def has_perm(resources, resource_type, perm):
return
if current_app.config.get("USE_ACL"):
if is_app_admin():
return func(*args, **kwargs)
validate_permission(resources, resource_type, perm)
return func(*args, **kwargs)
@@ -105,6 +119,13 @@ def has_perm(resources, resource_type, perm):
return decorator_has_perm
def is_app_admin():
if RoleEnum.CONFIG in session.get("acl", {}).get("parentRoles", []):
return True
return False
def has_perm_from_args(arg_name, resource_type, perm, callback=None):
def decorator_has_perm(func):
@functools.wraps(func)
@@ -116,6 +137,9 @@ def has_perm_from_args(arg_name, resource_type, perm, callback=None):
resource = callback(resource)
if current_app.config.get("USE_ACL") and resource:
if is_app_admin():
return func(*args, **kwargs)
validate_permission(resource, resource_type, perm)
return func(*args, **kwargs)

View File

@@ -1,4 +1,5 @@
# -*- coding:utf-8 -*-
from api.lib.cmdb.const import CMDB_QUEUE
ACL_QUEUE = "acl_async"
ACL_QUEUE = CMDB_QUEUE

View File

@@ -3,7 +3,9 @@
from api.lib.perm.acl.cache import PermissionCache
from api.lib.perm.acl.cache import RoleCache
from api.lib.perm.acl.const import ACL_QUEUE
from api.models.acl import RolePermission
from api.tasks.acl import role_rebuild
class PermissionCRUD(object):
@@ -29,6 +31,8 @@ class PermissionCRUD(object):
existed = RolePermission.get_by(rid=rid, perm_id=perm.id, group_id=group_id, resource_id=resource_id)
existed or RolePermission.create(rid=rid, perm_id=perm.id, group_id=group_id, resource_id=resource_id)
role_rebuild.apply_async(args=(rid,), queue=ACL_QUEUE)
@staticmethod
def revoke(rid, perms, resource_id=None, group_id=None):
for perm in perms:
@@ -40,3 +44,5 @@ class PermissionCRUD(object):
first=True,
to_dict=False)
existed and existed.soft_delete()
role_rebuild.apply_async(args=(rid,), queue=ACL_QUEUE)

View File

@@ -4,11 +4,14 @@
from flask import abort
from api.extensions import db
from api.lib.perm.acl.const import ACL_QUEUE
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
from api.models.acl import RolePermission
from api.tasks.acl import role_rebuild
class ResourceTypeCRUD(object):
@@ -134,6 +137,10 @@ class ResourceGroupCRUD(object):
for item in items:
item.soft_delete()
for i in RolePermission.get_by(group_id=rg_id, to_dict=False):
i.soft_delete()
role_rebuild.apply_async(args=(i.rid,), queue=ACL_QUEUE)
class ResourceCRUD(object):
@staticmethod
@@ -173,3 +180,7 @@ class ResourceCRUD(object):
resource = Resource.get_by_id(_id) or abort(404, "Resource <{0}> is not found".format(_id))
resource.soft_delete()
for i in RolePermission.get_by(resource_id=_id, to_dict=False):
i.soft_delete()
role_rebuild.apply_async(args=(i.rid,), queue=ACL_QUEUE)

View File

@@ -10,6 +10,7 @@ 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 ResourceType
from api.models.acl import Role
from api.models.acl import RolePermission
from api.models.acl import RoleRelation
@@ -44,7 +45,7 @@ class RoleRelationCRUD(object):
@staticmethod
def get_child_ids(rid):
res = RoleRelation.get_by(child_id=rid, to_dict=False)
res = RoleRelation.get_by(parent_id=rid, to_dict=False)
return [i.parent_id for i in res]
@@ -82,27 +83,37 @@ class RoleRelationCRUD(object):
return RoleRelation.create(parent_id=parent_id, child_id=child_id)
@staticmethod
def delete(_id):
@classmethod
def delete(cls, _id):
existed = RoleRelation.get_by_id(_id) or abort(400, "RoleRelation <{0}> does not exist".format(_id))
child_ids = cls.recursive_child_ids(existed.child_id)
for child_id in child_ids:
role_rebuild.apply_async(args=(child_id,), queue=ACL_QUEUE)
existed.soft_delete()
@staticmethod
def delete2(parent_id, child_id):
@classmethod
def delete2(cls, parent_id, child_id):
existed = RoleRelation.get_by(parent_id=parent_id, child_id=child_id, first=True, to_dict=False)
existed or abort(400, "RoleRelation < {0} -> {1} > does not exist".format(parent_id, child_id))
child_ids = cls.recursive_child_ids(existed.child_id)
for child_id in child_ids:
role_rebuild.apply_async(args=(child_id,), queue=ACL_QUEUE)
existed.soft_delete()
class RoleCRUD(object):
@staticmethod
def search(q, app_id, page=1, page_size=None, user_role=False):
query = db.session.query(Role).filter(Role.deleted.is_(False)).filter(Role.app_id == app_id)
def search(q, app_id, page=1, page_size=None, user_role=True):
query = db.session.query(Role).filter(Role.deleted.is_(False))
query = query.filter(Role.app_id == app_id).filter(Role.uid.is_(None))
if not user_role:
query = query.filter(Role.uid.is_(None))
if user_role:
query1 = db.session.query(Role).filter(Role.deleted.is_(False)).filter(Role.uid.isnot(None))
query = query.union(query1)
if q:
query = query.filter(Role.name.ilike('%{0}%'.format(q)))
@@ -134,9 +145,6 @@ class RoleCRUD(object):
def delete_role(cls, rid):
role = Role.get_by_id(rid) or abort(404, "Role <{0}> does not exist".format(rid))
parent_ids = RoleRelationCRUD.get_parent_ids(rid)
child_ids = RoleRelationCRUD.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):
@@ -145,7 +153,7 @@ class RoleCRUD(object):
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)
role_rebuild.apply_async(args=(list(RoleRelationCRUD.recursive_child_ids(rid)), ), queue=ACL_QUEUE)
RoleCache.clean(rid)
RoleRelationCache.clean(rid)
@@ -166,20 +174,21 @@ class RoleCRUD(object):
@staticmethod
def get_group_ids(resource_id):
return [i.group_id for i in ResourceGroupItems.get_by(resource_id, to_dict=False)]
return [i.group_id for i in ResourceGroupItems.get_by(resource_id=resource_id, to_dict=False)]
@classmethod
def has_permission(cls, rid, resource_name, perm):
resource = Resource.get_by(name=resource_name, first=True, to_dict=False)
def has_permission(cls, rid, resource_name, resource_type, app_id, perm):
resource_type = ResourceType.get_by(app_id=app_id, name=resource_type, first=True, to_dict=False)
resource_type or abort(404, "ResourceType <{0}> is not found".format(resource_type))
type_id = resource_type.id
resource = Resource.get_by(name=resource_name, resource_type_id=type_id, first=True, to_dict=False)
resource = resource or abort(403, "Resource <{0}> is not in ACL".format(resource_name))
parent_ids = RoleRelationCRUD.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