mirror of
https://github.com/veops/cmdb.git
synced 2025-08-07 06:09:48 +08:00
Modify code organization
This commit is contained in:
1
cmdb-api/api/lib/perm/__init__.py
Normal file
1
cmdb-api/api/lib/perm/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding:utf-8 -*-
|
23
cmdb-api/api/lib/perm/acl/__init__.py
Normal file
23
cmdb-api/api/lib/perm/acl/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from functools import wraps
|
||||
|
||||
from flask import request
|
||||
from flask import abort
|
||||
|
||||
from api.lib.perm.acl.cache import AppCache
|
||||
|
||||
|
||||
def validate_app(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
app_id = request.values.get('app_id')
|
||||
app = AppCache.get(app_id)
|
||||
if app is None:
|
||||
return abort(400, "App <{0}> does not exist".format(app_id))
|
||||
request.values['app_id'] = app.id
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
174
cmdb-api/api/lib/perm/acl/acl.py
Normal file
174
cmdb-api/api/lib/perm/acl/acl.py
Normal file
@@ -0,0 +1,174 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import functools
|
||||
|
||||
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.lib.perm.acl.cache import RoleCache
|
||||
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.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)
|
||||
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, 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 = self._get_resource(name, resource_type_name)
|
||||
if resource:
|
||||
ResourceCRUD.delete(resource.id)
|
||||
|
||||
def has_permission(self, resource_name, resource_type, perm):
|
||||
|
||||
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):
|
||||
if not resources:
|
||||
return
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
if g.user.username == "worker":
|
||||
return
|
||||
|
||||
resources = [resources] if isinstance(resources, six.string_types) else resources
|
||||
for resource in resources:
|
||||
if not ACLManager().has_permission(resource, resource_type, perm):
|
||||
return abort(403, "has no permission")
|
||||
|
||||
|
||||
def has_perm(resources, resource_type, perm):
|
||||
def decorator_has_perm(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_has_perm(*args, **kwargs):
|
||||
if not resources:
|
||||
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)
|
||||
|
||||
return wrapper_has_perm
|
||||
|
||||
return decorator_has_perm
|
||||
|
||||
|
||||
def is_app_admin(app=None):
|
||||
if RoleEnum.CONFIG in session.get("acl", {}).get("parentRoles", []):
|
||||
return True
|
||||
|
||||
app = app or 'cmdb'
|
||||
app_id = AppCache.get(app).id
|
||||
|
||||
for role in session.get("acl", {}).get("parentRoles", []):
|
||||
if RoleCache.get_by_name(app_id, role).is_app_admin:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def has_perm_from_args(arg_name, resource_type, perm, callback=None):
|
||||
def decorator_has_perm(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_has_perm(*args, **kwargs):
|
||||
if not arg_name:
|
||||
return
|
||||
resource = request.view_args.get(arg_name) or request.values.get(arg_name)
|
||||
if callback is not None and resource:
|
||||
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)
|
||||
|
||||
return wrapper_has_perm
|
||||
|
||||
return decorator_has_perm
|
||||
|
||||
|
||||
def role_required(role_name):
|
||||
def decorator_role_required(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper_role_required(*args, **kwargs):
|
||||
if not role_name:
|
||||
return
|
||||
|
||||
if current_app.config.get("USE_ACL"):
|
||||
if role_name not in session.get("acl", {}).get("parentRoles", []) and not is_app_admin():
|
||||
return abort(403, "Role {0} is required".format(role_name))
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper_role_required
|
||||
|
||||
return decorator_role_required
|
174
cmdb-api/api/lib/perm/acl/cache.py
Normal file
174
cmdb-api/api/lib/perm/acl/cache.py
Normal file
@@ -0,0 +1,174 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from api.extensions import cache
|
||||
from api.extensions import db
|
||||
from api.models.acl import App
|
||||
from api.models.acl import Permission
|
||||
from api.models.acl import Role
|
||||
from api.models.acl import User
|
||||
|
||||
|
||||
class AppCache(object):
|
||||
PREFIX_ID = "App::id::{0}"
|
||||
PREFIX_NAME = "App::name::{0}"
|
||||
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
app = cache.get(cls.PREFIX_ID.format(key)) or cache.get(cls.PREFIX_NAME.format(key))
|
||||
if app is None:
|
||||
app = App.get_by_id(key) or App.get_by(name=key, to_dict=False, first=True)
|
||||
if app is not None:
|
||||
cls.set(app)
|
||||
|
||||
return app
|
||||
|
||||
@classmethod
|
||||
def set(cls, app):
|
||||
cache.set(cls.PREFIX_ID.format(app.id), app)
|
||||
cache.set(cls.PREFIX_NAME.format(app.name), app)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, app):
|
||||
cache.delete(cls.PREFIX_ID.format(app.id))
|
||||
cache.delete(cls.PREFIX_NAME.format(app.name))
|
||||
|
||||
|
||||
class UserCache(object):
|
||||
PREFIX_ID = "User::uid::{0}"
|
||||
PREFIX_NAME = "User::username::{0}"
|
||||
PREFIX_NICK = "User::nickname::{0}"
|
||||
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
user = cache.get(cls.PREFIX_ID.format(key)) or \
|
||||
cache.get(cls.PREFIX_NAME.format(key)) or \
|
||||
cache.get(cls.PREFIX_NICK.format(key))
|
||||
if not user:
|
||||
user = User.query.get(key) or \
|
||||
User.query.get_by_username(key) or \
|
||||
User.query.get_by_nickname(key)
|
||||
if user:
|
||||
cls.set(user)
|
||||
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def set(cls, user):
|
||||
cache.set(cls.PREFIX_ID.format(user.uid), user)
|
||||
cache.set(cls.PREFIX_NAME.format(user.username), user)
|
||||
cache.set(cls.PREFIX_NICK.format(user.nickname), user)
|
||||
|
||||
@classmethod
|
||||
def clean(cls, user):
|
||||
cache.delete(cls.PREFIX_ID.format(user.uid))
|
||||
cache.delete(cls.PREFIX_NAME.format(user.username))
|
||||
cache.delete(cls.PREFIX_NICK.format(user.nickname))
|
||||
|
||||
|
||||
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 RoleRelationCRUD
|
||||
parent_ids = RoleRelationCRUD.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 RoleRelationCRUD
|
||||
child_ids = RoleRelationCRUD.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)
|
||||
db.session.close()
|
||||
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))
|
||||
|
||||
|
||||
class PermissionCache(object):
|
||||
PREFIX_ID = "Permission::id::{0}"
|
||||
PREFIX_NAME = "Permission::name::{0}"
|
||||
|
||||
@classmethod
|
||||
def get(cls, key):
|
||||
perm = cache.get(cls.PREFIX_ID.format(key))
|
||||
perm = perm or cache.get(cls.PREFIX_NAME.format(key))
|
||||
if perm is None:
|
||||
perm = Permission.get_by_id(key)
|
||||
perm = perm or Permission.get_by(name=key, first=True, to_dict=False)
|
||||
if perm is not None:
|
||||
cache.set(cls.PREFIX_ID.format(key), perm)
|
||||
|
||||
return perm
|
||||
|
||||
@classmethod
|
||||
def clean(cls, key):
|
||||
cache.delete(cls.PREFIX_ID.format(key))
|
||||
cache.delete(cls.PREFIX_NAME.format(key))
|
5
cmdb-api/api/lib/perm/acl/const.py
Normal file
5
cmdb-api/api/lib/perm/acl/const.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from api.lib.cmdb.const import CMDB_QUEUE
|
||||
|
||||
ACL_QUEUE = CMDB_QUEUE
|
48
cmdb-api/api/lib/perm/acl/permission.py
Normal file
48
cmdb-api/api/lib/perm/acl/permission.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
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):
|
||||
@staticmethod
|
||||
def get_all(resource_id=None, group_id=None):
|
||||
result = dict()
|
||||
if resource_id is not None:
|
||||
perms = RolePermission.get_by(resource_id=resource_id, to_dict=False)
|
||||
else:
|
||||
perms = RolePermission.get_by(group_id=group_id, to_dict=False)
|
||||
|
||||
for perm in perms:
|
||||
perm_dict = PermissionCache.get(perm.perm_id).to_dict()
|
||||
perm_dict.update(dict(rid=perm.rid))
|
||||
result.setdefault(RoleCache.get(perm.rid).name, []).append(perm_dict)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def grant(rid, perms, resource_id=None, group_id=None):
|
||||
for perm in perms:
|
||||
perm = PermissionCache.get(perm)
|
||||
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:
|
||||
perm = PermissionCache.get(perm)
|
||||
existed = RolePermission.get_by(rid=rid,
|
||||
perm_id=perm.id,
|
||||
group_id=group_id,
|
||||
resource_id=resource_id,
|
||||
first=True,
|
||||
to_dict=False)
|
||||
existed and existed.soft_delete()
|
||||
|
||||
role_rebuild.apply_async(args=(rid,), queue=ACL_QUEUE)
|
186
cmdb-api/api/lib/perm/acl/resource.py
Normal file
186
cmdb-api/api/lib/perm/acl/resource.py
Normal file
@@ -0,0 +1,186 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
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):
|
||||
@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()
|
||||
res = query.offset((page - 1) * page_size).limit(page_size)
|
||||
rt_ids = [i.id for i in res]
|
||||
perms = db.session.query(Permission).filter(Permission.deleted.is_(False)).filter(
|
||||
Permission.resource_type_id.in_(rt_ids))
|
||||
id2perms = dict()
|
||||
for perm in perms:
|
||||
id2perms.setdefault(perm.resource_type_id, []).append(perm.to_dict())
|
||||
|
||||
return numfound, res, id2perms
|
||||
|
||||
@staticmethod
|
||||
def get_perms(rt_id):
|
||||
perms = Permission.get_by(resource_type_id=rt_id, to_dict=False)
|
||||
return [i.to_dict() for i in perms]
|
||||
|
||||
@classmethod
|
||||
def add(cls, app_id, name, description, 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, description=description, app_id=app_id)
|
||||
|
||||
cls.update_perms(rt.id, perms, app_id)
|
||||
|
||||
return rt
|
||||
|
||||
@classmethod
|
||||
def update(cls, rt_id, **kwargs):
|
||||
kwargs.pop('app_id', None)
|
||||
|
||||
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 and other.id != rt_id:
|
||||
return abort(400, "ResourceType <{0}> is duplicated".format(kwargs['name']))
|
||||
|
||||
if 'perms' in kwargs:
|
||||
cls.update_perms(rt_id, kwargs.pop('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_by(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()
|
||||
|
||||
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
|
||||
def search(q, app_id, resource_type_id=None, 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)))
|
||||
|
||||
if resource_type_id:
|
||||
query = query.filter(Resource.resource_type_id == resource_type_id)
|
||||
|
||||
numfound = query.count()
|
||||
|
||||
return numfound, query.offset((page - 1) * page_size).limit(page_size)
|
||||
|
||||
@staticmethod
|
||||
def add(name, type_id, app_id):
|
||||
Resource.get_by(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 and 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()
|
||||
|
||||
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)
|
228
cmdb-api/api/lib/perm/acl/role.py
Normal file
228
cmdb-api/api/lib/perm/acl/role.py
Normal file
@@ -0,0 +1,228 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import six
|
||||
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 ResourceType
|
||||
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):
|
||||
@staticmethod
|
||||
def get_parents(rids=None, uids=None):
|
||||
rid2uid = dict()
|
||||
if uids is not None:
|
||||
uids = [uids] if isinstance(uids, six.integer_types) else uids
|
||||
rids = db.session.query(Role).filter(Role.deleted.is_(False)).filter(Role.uid.in_(uids))
|
||||
rid2uid = {i.id: i.uid for i in rids}
|
||||
rids = [i.id for i in rids]
|
||||
else:
|
||||
rids = [rids] if isinstance(rids, six.integer_types) else rids
|
||||
|
||||
res = db.session.query(RoleRelation).filter(
|
||||
RoleRelation.child_id.in_(rids)).filter(RoleRelation.deleted.is_(False))
|
||||
id2parents = {}
|
||||
for i in res:
|
||||
id2parents.setdefault(rid2uid.get(i.child_id, i.child_id), []).append(RoleCache.get(i.parent_id).to_dict())
|
||||
|
||||
return id2parents
|
||||
|
||||
@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(parent_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
|
||||
|
||||
@staticmethod
|
||||
def add(parent_id, child_id):
|
||||
RoleRelation.get_by(parent_id=parent_id, child_id=child_id) and abort(400, "It's already existed")
|
||||
|
||||
RoleRelationCache.clean(parent_id)
|
||||
RoleRelationCache.clean(child_id)
|
||||
|
||||
return RoleRelation.create(parent_id=parent_id, child_id=child_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)
|
||||
|
||||
RoleRelationCache.clean(existed.parent_id)
|
||||
RoleRelationCache.clean(existed.child_id)
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
@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)
|
||||
|
||||
RoleRelationCache.clean(existed.parent_id)
|
||||
RoleRelationCache.clean(existed.child_id)
|
||||
|
||||
existed.soft_delete()
|
||||
|
||||
|
||||
class RoleCRUD(object):
|
||||
@staticmethod
|
||||
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 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)))
|
||||
|
||||
numfound = query.count()
|
||||
|
||||
return numfound, query.offset((page - 1) * page_size).limit(page_size)
|
||||
|
||||
@staticmethod
|
||||
def add_role(name, app_id=None, 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):
|
||||
kwargs.pop('app_id', None)
|
||||
|
||||
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))
|
||||
|
||||
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=(list(RoleRelationCRUD.recursive_child_ids(rid)), ), 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=resource_id, to_dict=False)]
|
||||
|
||||
@classmethod
|
||||
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
|
||||
|
||||
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 = RoleRelationCRUD.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)
|
82
cmdb-api/api/lib/perm/acl/user.py
Normal file
82
cmdb-api/api/lib/perm/acl/user.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
import random
|
||||
import string
|
||||
import uuid
|
||||
|
||||
from flask import abort
|
||||
from flask import g
|
||||
|
||||
from api.extensions import db
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
from api.lib.perm.acl.role import RoleCRUD
|
||||
from api.models.acl import Role
|
||||
from api.models.acl import User
|
||||
|
||||
|
||||
class UserCRUD(object):
|
||||
@staticmethod
|
||||
def search(q, page=1, page_size=None):
|
||||
query = db.session.query(User).filter(User.deleted.is_(False))
|
||||
if q:
|
||||
query = query.filter(User.username.ilike('%{0}%'.format(q)))
|
||||
|
||||
numfound = query.count()
|
||||
|
||||
return numfound, query.offset((page - 1) * page_size).limit(page_size)
|
||||
|
||||
@staticmethod
|
||||
def _gen_key_secret():
|
||||
key = uuid.uuid4().hex
|
||||
secret = ''.join(random.sample(string.ascii_letters + string.digits + '~!@#$%^&*?', 32))
|
||||
|
||||
return key, secret
|
||||
|
||||
@classmethod
|
||||
def add(cls, **kwargs):
|
||||
existed = User.get_by(username=kwargs['username'], email=kwargs['email'])
|
||||
existed and abort(400, "User <{0}> is already existed".format(kwargs['username']))
|
||||
|
||||
kwargs['nickname'] = kwargs.get('nickname') or kwargs['username']
|
||||
kwargs['block'] = 0
|
||||
kwargs['key'], kwargs['secret'] = cls._gen_key_secret()
|
||||
|
||||
user = User.create(**kwargs)
|
||||
|
||||
RoleCRUD.add_role(user.username, uid=user.uid)
|
||||
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def update(uid, **kwargs):
|
||||
user = User.get_by(uid=uid, to_dict=False, first=True) or abort(404, "User <{0}> does not exist".format(uid))
|
||||
|
||||
if kwargs.get("username"):
|
||||
other = User.get_by(username=kwargs['username'], first=True, to_dict=False)
|
||||
if other is not None and other.uid != user.uid:
|
||||
return abort(400, "User <{0}> cannot be duplicated".format(kwargs['username']))
|
||||
|
||||
UserCache.clean(user)
|
||||
|
||||
if kwargs.get("username") and kwargs['username'] != user.username:
|
||||
role = Role.get_by(name=user.username, first=True, to_dict=False)
|
||||
if role is not None:
|
||||
RoleCRUD.update_role(role.id, **dict(name=kwargs['name']))
|
||||
|
||||
return user.update(**kwargs)
|
||||
|
||||
@classmethod
|
||||
def reset_key_secret(cls):
|
||||
key, secret = cls._gen_key_secret()
|
||||
g.user.update(key=key, secret=secret)
|
||||
|
||||
return key, secret
|
||||
|
||||
@classmethod
|
||||
def delete(cls, uid):
|
||||
user = User.get_by(uid=uid, to_dict=False, first=True) or abort(404, "User <{0}> does not exist".format(uid))
|
||||
|
||||
UserCache.clean(user)
|
||||
|
||||
user.soft_delete()
|
104
cmdb-api/api/lib/perm/auth.py
Normal file
104
cmdb-api/api/lib/perm/auth.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from functools import wraps
|
||||
|
||||
import jwt
|
||||
from flask import abort
|
||||
from flask import current_app
|
||||
from flask import g
|
||||
from flask import request
|
||||
from flask import session
|
||||
from flask_login import login_user
|
||||
|
||||
from api.models.acl import User
|
||||
from api.lib.perm.acl.cache import UserCache
|
||||
|
||||
|
||||
def _auth_with_key():
|
||||
key = request.values.get('_key')
|
||||
secret = request.values.get('_secret')
|
||||
path = request.path
|
||||
keys = sorted(request.values.keys())
|
||||
req_args = [request.values[k] for k in keys if k not in ("_key", "_secret")]
|
||||
user, authenticated = User.query.authenticate_with_key(key, secret, req_args, path)
|
||||
if user and authenticated:
|
||||
login_user(user)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _auth_with_session():
|
||||
if isinstance(getattr(g, 'user', None), User):
|
||||
login_user(g.user)
|
||||
return True
|
||||
if "acl" in session and "userName" in (session["acl"] or {}):
|
||||
login_user(UserCache.get(session["acl"]["userName"]))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _auth_with_token():
|
||||
auth_headers = request.headers.get('Access-Token', '').strip()
|
||||
if not auth_headers:
|
||||
return False
|
||||
|
||||
try:
|
||||
token = auth_headers
|
||||
data = jwt.decode(token, current_app.config['SECRET_KEY'])
|
||||
user = User.query.filter_by(email=data['sub']).first()
|
||||
if not user:
|
||||
return False
|
||||
|
||||
login_user(user)
|
||||
return True
|
||||
except jwt.ExpiredSignatureError:
|
||||
return False
|
||||
except (jwt.InvalidTokenError, Exception):
|
||||
return False
|
||||
|
||||
|
||||
def _auth_with_ip_white_list():
|
||||
ip = request.remote_addr
|
||||
key = request.values.get('_key')
|
||||
secret = request.values.get('_secret')
|
||||
|
||||
if not key and not secret and ip.strip() in current_app.config.get("WHITE_LIST", []): # TODO
|
||||
user = UserCache.get("worker")
|
||||
login_user(user)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def auth_required(func):
|
||||
if request.json is not None:
|
||||
setattr(request, 'values', request.json)
|
||||
else:
|
||||
setattr(request, 'values', request.values.to_dict())
|
||||
|
||||
current_app.logger.debug(request.values)
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
if not getattr(func, 'authenticated', True):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
if _auth_with_session() or _auth_with_key() or _auth_with_token() or _auth_with_ip_white_list():
|
||||
return func(*args, **kwargs)
|
||||
|
||||
abort(401)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def auth_abandoned(func):
|
||||
setattr(func, "authenticated", False)
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
Reference in New Issue
Block a user