From a9fc1ca8f68ac592ef6cd7bdbd09cae6df132c03 Mon Sep 17 00:00:00 2001
From: pycook <pycook@126.com>
Date: Fri, 6 Dec 2019 22:33:31 +0800
Subject: [PATCH] acl done and bugfix

---
 api/commands/click_cmdb.py                  |  61 +++++++++++
 api/lib/cmdb/ci.py                          |  58 +++-------
 api/lib/cmdb/ci_type.py                     |   9 ++
 api/lib/cmdb/const.py                       |   4 +-
 api/lib/cmdb/preference.py                  |  15 +++
 api/lib/perm/acl/acl.py                     | 112 ++++++++++++--------
 api/lib/perm/acl/const.py                   |   3 +-
 api/lib/perm/acl/permission.py              |   6 ++
 api/lib/perm/acl/resource.py                |  11 ++
 api/lib/perm/acl/role.py                    |  45 ++++----
 api/models/acl.py                           |   9 --
 api/settings.py.example                     |   3 +
 api/tasks/acl.py                            |   2 +-
 api/tasks/cmdb.py                           |   8 +-
 api/views/account.py                        |  17 ++-
 api/views/acl/role.py                       |   4 +-
 api/views/acl/user.py                       |   6 +-
 api/views/cmdb/ci.py                        |  10 +-
 api/views/cmdb/ci_relation.py               |   9 +-
 api/views/cmdb/preference.py                |  10 +-
 ui/src/views/cmdb/ci/modules/SearchForm.vue |   4 +-
 ui/src/views/cmdb/relation_views/index.vue  |   2 +-
 22 files changed, 270 insertions(+), 138 deletions(-)

diff --git a/api/commands/click_cmdb.py b/api/commands/click_cmdb.py
index c53bd04..e8a9a88 100644
--- a/api/commands/click_cmdb.py
+++ b/api/commands/click_cmdb.py
@@ -6,15 +6,27 @@ import json
 import click
 from flask import current_app
 from flask.cli import with_appcontext
+from werkzeug.exceptions import BadRequest
 
 import api.lib.cmdb.ci
 from api.extensions import db
 from api.extensions import rd
+from api.lib.cmdb.const import PermEnum
 from api.lib.cmdb.const import REDIS_PREFIX_CI
 from api.lib.cmdb.const import REDIS_PREFIX_CI_RELATION
+from api.lib.cmdb.const import ResourceTypeEnum
+from api.lib.cmdb.const import RoleEnum
 from api.lib.cmdb.const import ValueTypeEnum
+from api.lib.perm.acl.acl import ACLManager
+from api.lib.perm.acl.cache import AppCache
+from api.lib.perm.acl.resource import ResourceCRUD
+from api.lib.perm.acl.resource import ResourceTypeCRUD
+from api.lib.perm.acl.role import RoleCRUD
+from api.models.acl import ResourceType
 from api.models.cmdb import CI
 from api.models.cmdb import CIRelation
+from api.models.cmdb import CIType
+from api.models.cmdb import PreferenceRelationView
 
 
 @click.command()
@@ -74,3 +86,52 @@ def init_cache():
         rd.create_or_update(relations, REDIS_PREFIX_CI_RELATION)
 
     db.session.remove()
+
+
+@click.command()
+@with_appcontext
+def init_acl():
+    app_id = AppCache.get('cmdb').id
+    # 1. add resource type
+    for resource_type in ResourceTypeEnum.all():
+        try:
+            ResourceTypeCRUD.add(app_id, resource_type, '', PermEnum.all())
+        except BadRequest:
+            pass
+
+    # 2. add role
+    try:
+        RoleCRUD.add_role(RoleEnum.CONFIG, app_id, True)
+    except BadRequest:
+        pass
+    try:
+        RoleCRUD.add_role(RoleEnum.CMDB_READ_ALL, app_id, False)
+    except BadRequest:
+        pass
+
+    # 3. add resource and grant
+    ci_types = CIType.get_by(to_dict=False)
+    type_id = ResourceType.get_by(name=ResourceTypeEnum.CI, first=True, to_dict=False).id
+    for ci_type in ci_types:
+        try:
+            ResourceCRUD.add(ci_type.name, type_id, app_id)
+        except BadRequest:
+            pass
+
+        ACLManager().grant_resource_to_role(ci_type.name,
+                                            RoleEnum.CMDB_READ_ALL,
+                                            ResourceTypeEnum.CI,
+                                            [PermEnum.READ])
+
+    relation_views = PreferenceRelationView.get_by(to_dict=False)
+    type_id = ResourceType.get_by(name=ResourceTypeEnum.RELATION_VIEW, first=True, to_dict=False).id
+    for view in relation_views:
+        try:
+            ResourceCRUD.add(view.name, type_id, app_id)
+        except BadRequest:
+            pass
+
+        ACLManager().grant_resource_to_role(view.name,
+                                            RoleEnum.CMDB_READ_ALL,
+                                            ResourceTypeEnum.RELATION_VIEW,
+                                            [PermEnum.READ])
diff --git a/api/lib/cmdb/ci.py b/api/lib/cmdb/ci.py
index ef51902..0c77bcc 100644
--- a/api/lib/cmdb/ci.py
+++ b/api/lib/cmdb/ci.py
@@ -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)
diff --git a/api/lib/cmdb/ci_type.py b/api/lib/cmdb/ci_type.py
index 158eece..8d9f634 100644
--- a/api/lib/cmdb/ci_type.py
+++ b/api/lib/cmdb/ci_type.py
@@ -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
diff --git a/api/lib/cmdb/const.py b/api/lib/cmdb/const.py
index 98263b9..e5bb8a7 100644
--- a/api/lib/cmdb/const.py
+++ b/api/lib/cmdb/const.py
@@ -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"
diff --git a/api/lib/cmdb/preference.py b/api/lib/cmdb/preference.py
index 6ac6d62..1da4b37 100644
--- a/api/lib/cmdb/preference.py
+++ b/api/lib/cmdb/preference.py
@@ -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
diff --git a/api/lib/perm/acl/acl.py b/api/lib/perm/acl/acl.py
index ea7c400..f8400fd 100644
--- a/api/lib/perm/acl/acl.py
+++ b/api/lib/perm/acl/acl.py
@@ -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)
diff --git a/api/lib/perm/acl/const.py b/api/lib/perm/acl/const.py
index 355ccbf..76cc8a2 100644
--- a/api/lib/perm/acl/const.py
+++ b/api/lib/perm/acl/const.py
@@ -1,4 +1,5 @@
 # -*- coding:utf-8 -*-
 
+from api.lib.cmdb.const import CMDB_QUEUE
 
-ACL_QUEUE = "acl_async"
+ACL_QUEUE = CMDB_QUEUE
diff --git a/api/lib/perm/acl/permission.py b/api/lib/perm/acl/permission.py
index 57abae3..3126b87 100644
--- a/api/lib/perm/acl/permission.py
+++ b/api/lib/perm/acl/permission.py
@@ -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)
diff --git a/api/lib/perm/acl/resource.py b/api/lib/perm/acl/resource.py
index 295ffbe..977824b 100644
--- a/api/lib/perm/acl/resource.py
+++ b/api/lib/perm/acl/resource.py
@@ -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)
diff --git a/api/lib/perm/acl/role.py b/api/lib/perm/acl/role.py
index ab128ab..f536bdf 100644
--- a/api/lib/perm/acl/role.py
+++ b/api/lib/perm/acl/role.py
@@ -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
diff --git a/api/models/acl.py b/api/models/acl.py
index ec374b2..3388945 100644
--- a/api/models/acl.py
+++ b/api/models/acl.py
@@ -136,9 +136,6 @@ class RoleRelation(Model):
     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"
@@ -156,8 +153,6 @@ class ResourceGroup(Model):
 
     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"
@@ -167,8 +162,6 @@ class Resource(Model):
 
     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"
@@ -185,8 +178,6 @@ class Permission(Model):
 
     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"
diff --git a/api/settings.py.example b/api/settings.py.example
index fec4f5b..593fbbc 100644
--- a/api/settings.py.example
+++ b/api/settings.py.example
@@ -79,3 +79,6 @@ USE_ACL = False
 # # elastic search
 ES_HOST = '127.0.0.1'
 USE_ES = False
+
+
+BOOL_TRUE = ['true', 'TRUE', 'True', True, '1', 1, "Yes", "YES", "yes", 'Y', 'y']
diff --git a/api/tasks/acl.py b/api/tasks/acl.py
index 9377014..8555559 100644
--- a/api/tasks/acl.py
+++ b/api/tasks/acl.py
@@ -13,4 +13,4 @@ def role_rebuild(rids):
     for rid in rids:
         RoleRelationCache.rebuild(rid)
 
-    current_app.logger.info("%d rebuild.........." % rids)
+    current_app.logger.info("Role {0} rebuild..........".format(rids))
diff --git a/api/tasks/cmdb.py b/api/tasks/cmdb.py
index 1cdb2f8..357ddc0 100644
--- a/api/tasks/cmdb.py
+++ b/api/tasks/cmdb.py
@@ -50,8 +50,8 @@ def ci_relation_cache(parent_id, child_id):
     children = json.loads(children) if children is not None else {}
 
     cr = CIRelation.get_by(first_ci_id=parent_id, second_ci_id=child_id, first=True, to_dict=False)
-    if child_id not in children:
-        children[child_id] = cr.second_ci.type_id
+    if str(child_id) not in children:
+        children[str(child_id)] = cr.second_ci.type_id
 
     rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
 
@@ -63,8 +63,8 @@ def ci_relation_delete(parent_id, child_id):
     children = rd.get([parent_id], REDIS_PREFIX_CI_RELATION)[0]
     children = json.loads(children) if children is not None else {}
 
-    if child_id in children:
-        children.pop(child_id)
+    if str(child_id) in children:
+        children.pop(str(child_id))
 
     rd.create_or_update({parent_id: json.dumps(children)}, REDIS_PREFIX_CI_RELATION)
 
diff --git a/api/views/account.py b/api/views/account.py
index cd133a8..df7c22a 100644
--- a/api/views/account.py
+++ b/api/views/account.py
@@ -6,12 +6,15 @@ import jwt
 from flask import abort
 from flask import current_app
 from flask import request
+from flask import session
 from flask_login import login_user, logout_user
 
 from api.lib.decorator import args_required
 from api.lib.perm.auth import auth_abandoned
-from api.models.acl import User
+from api.models.acl import User, Role
 from api.resource import APIView
+from api.lib.perm.acl.role import RoleRelationCRUD
+from api.lib.perm.acl.cache import RoleCache
 
 
 class LoginView(APIView):
@@ -37,6 +40,18 @@ class LoginView(APIView):
             'exp': datetime.datetime.now() + datetime.timedelta(minutes=24 * 60 * 7)},
             current_app.config['SECRET_KEY'])
 
+        role = Role.get_by(uid=user.uid, first=True, to_dict=False)
+        if role:
+            parent_ids = RoleRelationCRUD.recursive_parent_ids(role.id)
+            parent_roles = [RoleCache.get(i).name for i in parent_ids]
+        else:
+            parent_roles = []
+        session["acl"] = dict(uid=user.uid,
+                              avatar=user.avatar,
+                              userName=user.username,
+                              nickName=user.nickname,
+                              parentRoles=parent_roles)
+
         return self.jsonify(token=token.decode())
 
 
diff --git a/api/views/acl/role.py b/api/views/acl/role.py
index b896778..f649304 100644
--- a/api/views/acl/role.py
+++ b/api/views/acl/role.py
@@ -1,5 +1,6 @@
 # -*- coding:utf-8 -*-
 
+from flask import current_app
 from flask import request
 
 from api.lib.decorator import args_required
@@ -21,7 +22,8 @@ class RoleView(APIView):
         page_size = get_page_size(request.values.get("page_size"))
         q = request.values.get('q')
         app_id = request.values.get('app_id')
-        user_role = request.values.get('user_role', False)
+        user_role = request.values.get('user_role', True)
+        user_role = True if user_role in current_app.config.get("BOOL_TRUE") else False
 
         numfound, roles = RoleCRUD.search(q, app_id, page, page_size, user_role)
 
diff --git a/api/views/acl/user.py b/api/views/acl/user.py
index 82b6f17..bf3cb9a 100644
--- a/api/views/acl/user.py
+++ b/api/views/acl/user.py
@@ -17,9 +17,9 @@ class GetUserInfoView(APIView):
     url_prefix = "/users/info"
 
     def get(self):
-        name = session.get("acl", {}).get("nickName") or session.get("CAS_USERNAME") or current_user.nickname
-        role = dict(permissions=session.get("acl", {}).get("parentRoles", []) or ["admin"])
-        avatar = session.get("acl", {}).get("avatar") or current_user.avatar
+        name = session.get("CAS_USERNAME") or current_user.nickname
+        role = dict(permissions=session.get("acl", {}).get("parentRoles", []))
+        avatar = current_user.avatar
         return self.jsonify(result=dict(name=name,
                                         role=role,
                                         avatar=avatar))
diff --git a/api/views/cmdb/ci.py b/api/views/cmdb/ci.py
index a45c4bb..0e9ed9b 100644
--- a/api/views/cmdb/ci.py
+++ b/api/views/cmdb/ci.py
@@ -10,7 +10,7 @@ from flask import request
 from api.lib.cmdb.cache import CITypeCache
 from api.lib.cmdb.ci import CIManager
 from api.lib.cmdb.const import ExistPolicy
-from api.lib.cmdb.const import ResourceType, PermEnum
+from api.lib.cmdb.const import ResourceTypeEnum, PermEnum
 from api.lib.cmdb.const import RetKey
 from api.lib.cmdb.search import SearchError
 from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
@@ -73,7 +73,7 @@ class CIView(APIView):
                 ci_dict[k] = v.strip() if isinstance(v, six.string_types) else v
         return ci_dict
 
-    @has_perm_from_args("ci_type", ResourceType.CI, PermEnum.ADD)
+    @has_perm_from_args("ci_type", ResourceTypeEnum.CI, PermEnum.ADD)
     def post(self):
         ci_type = request.values.get("ci_type")
         _no_attribute_policy = request.values.get("_no_attribute_policy", ExistPolicy.IGNORE)
@@ -87,7 +87,7 @@ class CIView(APIView):
                             _no_attribute_policy=_no_attribute_policy, **ci_dict)
         return self.jsonify(ci_id=ci_id)
 
-    @has_perm_from_args("ci_id", ResourceType.CI, PermEnum.UPDATE, CIManager.get_type_name)
+    @has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.UPDATE, CIManager.get_type_name)
     def put(self, ci_id=None):
         args = request.values
         ci_type = args.get("ci_type")
@@ -104,7 +104,7 @@ class CIView(APIView):
                                 **ci_dict)
         return self.jsonify(ci_id=ci_id)
 
-    @has_perm_from_args("ci_id", ResourceType.CI, PermEnum.DELETE, CIManager.get_type_name)
+    @has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.DELETE, CIManager.get_type_name)
     def delete(self, ci_id):
         manager = CIManager()
         manager.delete(ci_id)
@@ -163,7 +163,7 @@ class CISearchView(APIView):
 class CIUnique(APIView):
     url_prefix = "/ci/<int:ci_id>/unique"
 
-    @has_perm_from_args("ci_id", ResourceType.CI, PermEnum.UPDATE, CIManager.get_type_name)
+    @has_perm_from_args("ci_id", ResourceTypeEnum.CI, PermEnum.UPDATE, CIManager.get_type_name)
     def put(self, ci_id):
         params = request.values
         unique_name = params.keys()[0]
diff --git a/api/views/cmdb/ci_relation.py b/api/views/cmdb/ci_relation.py
index 13e05c2..38ed735 100644
--- a/api/views/cmdb/ci_relation.py
+++ b/api/views/cmdb/ci_relation.py
@@ -7,6 +7,7 @@ from flask import abort
 from flask import current_app
 from flask import request
 
+from api.lib.cmdb.cache import RelationTypeCache
 from api.lib.cmdb.ci import CIRelationManager
 from api.lib.cmdb.search import SearchError
 from api.lib.cmdb.search.ci_relation.search import Search
@@ -83,11 +84,15 @@ class GetSecondCIsView(APIView):
     def get(self, first_ci_id):
         page = get_page(request.values.get("page", 1))
         count = get_page_size(request.values.get("count"))
-        relation_type = request.values.get("relation_type", "contain")
+        relation_type = request.values.get("relation_type")
+        try:
+            relation_type_id = RelationTypeCache.get(relation_type).id if relation_type else None
+        except AttributeError:
+            return abort(400, "invalid relation type <{0}>".format(relation_type))
 
         manager = CIRelationManager()
         numfound, total, second_cis = manager.get_second_cis(
-            first_ci_id, page=page, per_page=count, relation_type=relation_type)
+            first_ci_id, page=page, per_page=count, relation_type_id=relation_type_id)
 
         return self.jsonify(numfound=numfound,
                             total=total,
diff --git a/api/views/cmdb/preference.py b/api/views/cmdb/preference.py
index be839e0..8747cf0 100644
--- a/api/views/cmdb/preference.py
+++ b/api/views/cmdb/preference.py
@@ -4,7 +4,7 @@
 from flask import request
 
 from api.lib.cmdb.ci_type import CITypeManager
-from api.lib.cmdb.const import ResourceType, PermEnum, RoleEnum
+from api.lib.cmdb.const import ResourceTypeEnum, PermEnum, RoleEnum
 from api.lib.cmdb.preference import PreferenceManager
 from api.lib.decorator import args_required
 from api.lib.perm.acl.acl import has_perm_from_args
@@ -31,7 +31,7 @@ class PreferenceShowAttributesView(APIView):
 
         return self.jsonify(attributes=attributes, is_subscribed=is_subscribed)
 
-    @has_perm_from_args("id_or_name", ResourceType.CI, PermEnum.READ, CITypeManager.get_name_by_id)
+    @has_perm_from_args("id_or_name", ResourceTypeEnum.CI, PermEnum.READ, CITypeManager.get_name_by_id)
     @args_required("attr")
     def post(self, id_or_name):
         id_or_name = int(id_or_name)
@@ -42,7 +42,7 @@ class PreferenceShowAttributesView(APIView):
         return self.jsonify(type_id=id_or_name,
                             attr_order=list(zip(attr_list, orders)))
 
-    @has_perm_from_args("id_or_name", ResourceType.CI, PermEnum.READ, CITypeManager.get_name_by_id)
+    @has_perm_from_args("id_or_name", ResourceTypeEnum.CI, PermEnum.READ, CITypeManager.get_name_by_id)
     def put(self, id_or_name):
         return self.post(id_or_name)
 
@@ -53,7 +53,7 @@ class PreferenceTreeApiView(APIView):
     def get(self):
         return self.jsonify(PreferenceManager.get_tree_view())
 
-    @has_perm_from_args("type_id", ResourceType.CI, PermEnum.READ, CITypeManager.get_name_by_id)
+    @has_perm_from_args("type_id", ResourceTypeEnum.CI, PermEnum.READ, CITypeManager.get_name_by_id)
     @args_required("type_id")
     @args_required("levels")
     def post(self):
@@ -85,9 +85,11 @@ class PreferenceRelationApiView(APIView):
 
         return self.jsonify(views=views, id2type=id2type, name2id=name2id)
 
+    @role_required(RoleEnum.CONFIG)
     def put(self):
         return self.post()
 
+    @role_required(RoleEnum.CONFIG)
     @args_required("name")
     def delete(self):
         name = request.values.get("name")
diff --git a/ui/src/views/cmdb/ci/modules/SearchForm.vue b/ui/src/views/cmdb/ci/modules/SearchForm.vue
index 2bf7f5a..c780476 100644
--- a/ui/src/views/cmdb/ci/modules/SearchForm.vue
+++ b/ui/src/views/cmdb/ci/modules/SearchForm.vue
@@ -73,8 +73,8 @@
             class="table-page-search-submitButtons"
             :style="advanced && { float: 'right', overflow: 'hidden' } || {} "
           >
-            <a-button type="primary" @click="$emit('refresh', true)">查询</a-button>
-            <a-button style="margin-left: 8px" @click="() => queryParam = {}">重置</a-button>
+            <a-button type="primary" @click="$emit('refresh', true)" v-if="preferenceAttrList.length">查询</a-button>
+            <a-button style="margin-left: 8px" @click="() => queryParam = {}" v-if="preferenceAttrList.length">重置</a-button>
             <a
               @click="toggleAdvanced"
               style="margin-left: 8px"
diff --git a/ui/src/views/cmdb/relation_views/index.vue b/ui/src/views/cmdb/relation_views/index.vue
index cf2fe57..9a3479f 100644
--- a/ui/src/views/cmdb/relation_views/index.vue
+++ b/ui/src/views/cmdb/relation_views/index.vue
@@ -7,7 +7,7 @@
         >{{ item[0] }}</router-link>
       </a-menu-item>
     </a-menu>
-    <a-alert message="管理员 还未配置关系视图!" banner v-else-if="relationViews.name2id && !relationViews.name2id.length"></a-alert>
+    <a-alert message="管理员 还未配置关系视图, 或者你无权限访问!" banner v-else-if="relationViews.name2id && !relationViews.name2id.length"></a-alert>
     <div style="clear: both; margin-top: 20px"></div>
     <template>
       <a-row :gutter="8">