cmdb/cmdb-api/api/lib/perm/acl/audit.py

405 lines
15 KiB
Python

# -*- coding:utf-8 -*-
import datetime
import itertools
import json
from enum import Enum
from typing import List
from flask import has_request_context
from flask import request
from flask_login import current_user
from sqlalchemy import func
from api.extensions import db
from api.lib.perm.acl import AppCache
from api.models.acl import AuditLoginLog
from api.models.acl import AuditPermissionLog
from api.models.acl import AuditResourceLog
from api.models.acl import AuditRoleLog
from api.models.acl import AuditTriggerLog
from api.models.acl import Permission
from api.models.acl import Resource
from api.models.acl import ResourceGroup
from api.models.acl import ResourceType
from api.models.acl import Role
from api.models.acl import RolePermission
class AuditScope(str, Enum):
app = 'app'
resource = 'resource'
resource_type = 'resource_type'
resource_group = 'resource_group'
user = 'user'
role = 'role'
role_relation = 'role_relation'
class AuditOperateType(str, Enum):
read = 'read'
create = 'create'
update = 'update'
delete = 'delete'
user_login = 'user_login'
role_relation_add = 'role_relation_add'
role_relation_delete = 'role_relation_delete'
grant = 'grant'
revoke = 'revoke'
trigger_apply = 'trigger_apply'
trigger_cancel = 'trigger_cancel'
class AuditOperateSource(str, Enum):
api = 'api'
acl = 'acl'
trigger = 'trigger'
class AuditCRUD(object):
@staticmethod
def get_current_operate_uid(uid=None):
user_id = uid or (getattr(current_user, 'uid', None)) or getattr(current_user, 'user_id', None)
if has_request_context() and request.headers.get('X-User-Id'):
_user_id = request.headers['X-User-Id']
user_id = int(_user_id) if _user_id.isdigit() else uid
return user_id
@staticmethod
def get_operate_source(source):
if has_request_context() and request.headers.get('App-Access-Token'):
source = AuditOperateSource.api
return source
@staticmethod
def search_permission(app_id, q=None, page=1, page_size=10, start=None, end=None):
criterion = []
if app_id:
app = AppCache.get(app_id)
criterion.append(AuditPermissionLog.app_id == app.id)
if start:
criterion.append(AuditPermissionLog.created_at >= start)
if end:
criterion.append(AuditPermissionLog.created_at <= end)
kwargs = {expr.split(':')[0]: expr.split(':')[1] for expr in q.split(',')} if q else {}
for k, v in kwargs.items():
if k == 'resource_type_id':
criterion.append(AuditPermissionLog.resource_type_id == int(v))
elif k == 'rid':
criterion.append(AuditPermissionLog.rid == int(v))
elif k == 'resource_id':
criterion.append(func.json_contains(AuditPermissionLog.resource_ids, v) == 1)
elif k == 'operate_uid':
criterion.append(AuditPermissionLog.operate_uid == v)
elif k == 'operate_type':
criterion.append(AuditPermissionLog.operate_type == v)
records = AuditPermissionLog.query.filter(
AuditPermissionLog.deleted == 0, *criterion).order_by(
AuditPermissionLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
data = {
'data': [r.to_dict() for r in records],
'id2resources': {},
'id2roles': {},
'id2groups': {},
'id2perms': {},
'id2resource_types': {},
}
resource_ids = set(itertools.chain(*[r.resource_ids for r in records]))
group_ids = set(itertools.chain(*[r.group_ids for r in records]))
permission_ids = set(itertools.chain(*[r.permission_ids for r in records]))
resource_type_ids = {r.resource_type_id for r in records}
rids = {r.rid for r in records}
if rids:
roles = Role.query.filter(Role.id.in_(rids)).all()
data['id2roles'] = {r.id: r.to_dict() for r in roles}
if resource_type_ids:
resource_types = ResourceType.query.filter(ResourceType.id.in_(resource_type_ids)).all()
data['id2resource_types'] = {r.id: r.to_dict() for r in resource_types}
if resource_ids:
resources = Resource.query.filter(Resource.id.in_(resource_ids)).all()
data['id2resources'] = {r.id: r.to_dict() for r in resources}
if group_ids:
groups = ResourceGroup.query.filter(ResourceGroup.id.in_(group_ids)).all()
data['id2groups'] = {_g.id: _g.to_dict() for _g in groups}
if permission_ids:
perms = Permission.query.filter(Permission.id.in_(permission_ids)).all()
data['id2perms'] = {_p.id: _p.to_dict() for _p in perms}
return data
@staticmethod
def search_role(app_id, q=None, page=1, page_size=10, start=None, end=None):
criterion = []
if app_id:
app = AppCache.get(app_id)
criterion.append(AuditRoleLog.app_id == app.id)
if start:
criterion.append(AuditRoleLog.created_at >= start)
if end:
criterion.append(AuditRoleLog.created_at <= end)
kwargs = {expr.split(':')[0]: expr.split(':')[1] for expr in q.split(',')} if q else {}
for k, v in kwargs.items():
if k == 'scope':
criterion.append(AuditRoleLog.scope == v)
elif k == 'link_id':
criterion.append(AuditRoleLog.link_id == int(v))
elif k == 'operate_uid':
criterion.append(AuditRoleLog.operate_uid == v)
elif k == 'operate_type':
criterion.append(AuditRoleLog.operate_type == v)
records = AuditRoleLog.query.filter(AuditRoleLog.deleted == 0, *criterion).order_by(
AuditRoleLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
data = {
'data': [r.to_dict() for r in records],
'id2roles': {}
}
role_permissions = list(itertools.chain(*[r.extra.get('role_permissions', []) for r in records]))
_rids = set()
if role_permissions:
resource_ids = set([r['resource_id'] for r in role_permissions])
group_ids = set([r['group_id'] for r in role_permissions])
perm_ids = set([r['perm_id'] for r in role_permissions])
_rids.update(set([r['rid'] for r in role_permissions]))
if resource_ids:
resources = Resource.query.filter(Resource.id.in_(resource_ids)).all()
data['id2resources'] = {r.id: r.to_dict() for r in resources}
if group_ids:
groups = ResourceGroup.query.filter(ResourceGroup.id.in_(group_ids)).all()
data['id2groups'] = {_g.id: _g.to_dict() for _g in groups}
if perm_ids:
perms = Permission.query.filter(Permission.id.in_(perm_ids)).all()
data['id2perms'] = {_p.id: _p.to_dict() for _p in perms}
rids = set(itertools.chain(*[r.extra.get('child_ids', []) + r.extra.get('parent_ids', [])
for r in records]))
rids.update(_rids)
if rids:
roles = Role.query.filter(Role.id.in_(rids)).all()
data['id2roles'].update({r.id: r.to_dict() for r in roles})
return data
@staticmethod
def search_resource(app_id, q=None, page=1, page_size=10, start=None, end=None):
criterion = []
if app_id:
app = AppCache.get(app_id)
criterion.append(AuditResourceLog.app_id == app.id)
if start:
criterion.append(AuditResourceLog.created_at >= start)
if end:
criterion.append(AuditResourceLog.created_at <= end)
kwargs = {expr.split(':')[0]: expr.split(':')[1] for expr in q.split(',')} if q else {}
for k, v in kwargs.items():
if k == 'scope':
criterion.append(AuditResourceLog.scope == v)
elif k == 'link_id':
criterion.append(AuditResourceLog.link_id == int(v))
elif k == 'operate_uid':
criterion.append(AuditResourceLog.operate_uid == v)
elif k == 'operate_type':
criterion.append(AuditResourceLog.operate_type == v)
records = AuditResourceLog.query.filter(
AuditResourceLog.deleted == 0, *criterion).order_by(
AuditResourceLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
data = {
'data': [r.to_dict() for r in records],
}
return data
@staticmethod
def search_trigger(app_id, q=None, page=1, page_size=10, start=None, end=None):
criterion = []
if app_id:
app = AppCache.get(app_id)
criterion.append(AuditTriggerLog.app_id == app.id)
if start:
criterion.append(AuditTriggerLog.created_at >= start)
if end:
criterion.append(AuditTriggerLog.created_at <= end)
kwargs = {expr.split(':')[0]: expr.split(':')[1] for expr in q.split(',')} if q else {}
for k, v in kwargs.items():
if k == 'trigger_id':
criterion.append(AuditTriggerLog.trigger_id == int(v))
elif k == 'operate_uid':
criterion.append(AuditTriggerLog.operate_uid == v)
elif k == 'operate_type':
criterion.append(AuditTriggerLog.operate_type == v)
records = AuditTriggerLog.query.filter(
AuditTriggerLog.deleted == 0, *criterion).order_by(
AuditTriggerLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
data = {
'data': [r.to_dict() for r in records],
'id2roles': {},
'id2resource_types': {},
}
rids = set(itertools.chain(*[json.loads(r.origin.get('roles', "[]")) +
json.loads(r.current.get('roles', "[]"))
for r in records]))
resource_type_ids = set([r.origin.get('resource_type_id') for r in records
if r.origin.get('resource_type_id')] +
[r.current.get('resource_type_id') for r in records
if r.current.get('resource_type_id')])
if rids:
roles = Role.query.filter(Role.id.in_(rids)).all()
data['id2roles'] = {r.id: r.to_dict() for r in roles}
if resource_type_ids:
resource_types = ResourceType.query.filter(ResourceType.id.in_(resource_type_ids)).all()
data['id2resource_types'] = {r.id: r.to_dict() for r in resource_types}
return data
@staticmethod
def search_login(_, q=None, page=1, page_size=10, start=None, end=None):
query = db.session.query(AuditLoginLog)
if start:
query = query.filter(AuditLoginLog.login_at >= start)
if end:
query = query.filter(AuditLoginLog.login_at <= end)
if q:
query = query.filter(AuditLoginLog.username == q)
records = query.order_by(
AuditLoginLog.id.desc()).offset((page - 1) * page_size).limit(page_size).all()
data = {
'data': [r.to_dict() for r in records],
}
return data
@classmethod
def add_role_log(cls, app_id, operate_type: AuditOperateType,
scope: AuditScope, link_id: int, origin: dict, current: dict, extra: dict,
uid=None, source=AuditOperateSource.acl):
user_id = cls.get_current_operate_uid(uid)
AuditRoleLog.create(app_id=app_id, operate_uid=user_id, operate_type=operate_type.value,
scope=scope.value,
link_id=link_id,
origin=origin,
current=current,
extra=extra,
source=source.value)
@classmethod
def add_resource_log(cls, app_id, operate_type: AuditOperateType,
scope: AuditScope, link_id: int, origin: dict, current: dict, extra: dict,
uid=None, source=AuditOperateSource.acl):
user_id = cls.get_current_operate_uid(uid)
source = cls.get_operate_source(source)
AuditResourceLog.create(app_id=app_id, operate_uid=user_id, operate_type=operate_type.value,
scope=scope.value,
link_id=link_id,
origin=origin,
current=current,
extra=extra,
source=source.value)
@classmethod
def add_permission_log(cls, app_id, operate_type: AuditOperateType,
rid: int, rt_id: int, role_permissions: List[RolePermission],
uid=None, source=AuditOperateSource.acl):
if not role_permissions:
return
user_id = cls.get_current_operate_uid(uid)
source = cls.get_operate_source(source)
resource_ids = list({r.resource_id for r in role_permissions if r.resource_id})
permission_ids = list({r.perm_id for r in role_permissions if r.perm_id})
group_ids = list({r.group_id for r in role_permissions if r.group_id})
AuditPermissionLog.create(app_id=app_id, operate_uid=user_id,
operate_type=operate_type.value,
rid=rid,
resource_type_id=rt_id,
resource_ids=resource_ids,
permission_ids=permission_ids,
group_ids=group_ids,
source=source.value)
@classmethod
def add_trigger_log(cls, app_id, trigger_id, operate_type: AuditOperateType,
origin: dict, current: dict, extra: dict,
uid=None, source=AuditOperateSource.acl):
user_id = cls.get_current_operate_uid(uid)
source = cls.get_operate_source(source)
AuditTriggerLog.create(app_id=app_id, trigger_id=trigger_id, operate_uid=user_id,
operate_type=operate_type.value,
origin=origin, current=current, extra=extra, source=source.value)
@classmethod
def add_login_log(cls, username, is_ok, description, _id=None, logout_at=None):
if _id is not None:
existed = AuditLoginLog.get_by_id(_id)
if existed is not None:
existed.update(logout_at=logout_at)
return
payload = dict(username=username,
is_ok=is_ok,
description=description,
logout_at=logout_at,
ip=request.headers.get('X-Real-IP') or request.remote_addr,
browser=request.headers.get('User-Agent'),
channel=request.values.get('channel', 'web'),
)
if logout_at is None:
payload['login_at'] = datetime.datetime.now()
try:
from api.lib.common_setting.employee import EmployeeCRUD
EmployeeCRUD.update_last_login_by_uid(current_user.uid)
except:
pass
return AuditLoginLog.create(**payload).id