Egg通过middleware实现数据权限

需求:

项目中的es有很多表,每个表有很多不同的数据。需求是把数据权限的粒度做到单条数据层面。
比如:用户查询10个ip,其中3个有权限,显示所有内容,7个无权限,只显示部分内容并且标识出来,便于前端展示。

方案:

  1. 通过egg的middleware中间件,在接口返回出对返回数据进行隐私处理。
  2. 建立数据权限组机制,数据导入时需要选择数据所属的权限组,并且在每条数据上标记权限组id
  3. 用户可以动态的增删数据权限组,拥有这个数据权限组,就拥有了该数据权限组下的所有数据
  4. 接口返回数据经过中间件时,根据用户所属的用户权限组,对数据进行处理。有权限的直接返回,无权限的返回最低限度的可展示数据。

直接上代码

这波代码结构清晰注释明了,不会看不懂吧=w=

import { Context } from 'egg';
import * as _ from 'lodash';

// 需要校验数据权限的接口,其他的直接过
const dataAuthUrl = [
  '/api/ip/port',
  '/api/ip/domain',
  // ...仅保留示例,多余接口删除
];

// 无权限的数据可以预览,预览数据所需要返回的通用字段
// data_permission就是数据所属的数据权限组id
const previewCommonKeys = ['id', '_id', 'data_permission'];

// 不同数据库所返回的预览数据字段
const previewKeyMap = {
  ip: ['ip'], // ip指纹库
  domain: ['domain'], // 域名库
  // ...仅保留示例,其余表所对应的预览字段删除
};

// 获取所需要展示的所有预览字段
// dataType keyof previewKeyMap
const getPreviewKeys = dataType => [...previewCommonKeys, ...previewKeyMap[dataType]];

// 根据数据权限过滤数据
// data 所需要处理的es数据
// dataAuth 用户拥有的数据权限组id数组
// previewKeys 预览数据所需要展示的字段
const dataTrans = (data: any, dataAuth: string[], previewKeys: string[]) => {
  // 该条数据 存在数据权限字段 且 数据权限字段不为空 且 值不在登录用户所包含的数据权限组中
  if (
    data &&
    data.data_permission &&
    data.data_permission !== '' &&
    !dataAuth.includes(data.data_permission)
  ) {
    // 处理数据,只返回可预览的部分数据
    const previewData = {};
    previewKeys.forEach(k => {
      if (data[k]) {
        previewData[k] = data[k];
      }
    });
    return { _dataAuthAccessDenied: true, ...previewData };
  }
  // 否则直接返回
  return data;
};

// 通用dataList结构处理: 对 data.list进行处理
const dataListTransformer = (dataType: string) => {
  return (ctx: Context, body: any, dataAuth: string[]) => {
    // 过滤数据
    const { data, ...rest } = body;
    const previewKeys = getPreviewKeys(dataType);
    return {
      ...rest,
      data: {
        ...data,
        // 对列表数据进行过滤
        list: data.list.map(item => dataTrans(item, dataAuth, previewKeys)),
      },
    };
  };
};
// 通用dataArray结构处理: 对data本身进行处理,data是数组
const dataArrayTransformer = (dataType: string) => {
  return (ctx: Context, body: any, dataAuth: string[]) => {
    const { data, ...rest } = body;
    const previewKeys = getPreviewKeys(dataType);
    return {
      ...rest,
      data: data.map(item => dataTrans(item, dataAuth, previewKeys)),
    };
  };
};
// 通用data结构处理,对data本身进行处理,data是单个数据对象
const dataTransformer = (dataType: string) => {
  return (ctx: Context, body: any, dataAuth: string[]) => {
    const { data, ...rest } = body;
    const previewKeys = getPreviewKeys(dataType);
    return {
      ...rest,
      data: dataTrans(data, dataAuth, previewKeys),
    };
  };
};

// 针对接口对其返回数据 根据 数据权限过滤
const url2Transformer = {
  '/api/domain/vul': dataListTransformer('vul'),
  '/api/domain/whois': dataTransformer('domainWhois'),
  '/api/ip/visit': dataArrayTransformer('netRecord'),
  // ...仅保留示例,其余接口对应的处理删除
};

export default () => async (ctx: Context, next) => {
  // 开发模式跳过权限检查
  const { config } = ctx.app;
  if (config.skipAuthentication) {
    await next();
    return true;
  }
  const reqUrl = ctx.request.url;
  // 不处理的直接结束
  if (!dataAuthUrl.includes(reqUrl)) {
    await next();
    return true;
  }
  // 获取用户的数据权限组,用户页面分配的永久权限
  const userDoc = await ctx.model.AuthUser.findById(ctx.user.id);
  if (!userDoc) {
    await next();
    return true;
  }
  const { dataAuth, account } = userDoc;
  // 获取用户申请的数据权限组,个人中心 我的数据权限 中申请的有期限的权限
  const dataPermission = await ctx.model.DataPermission.aggregate([
    {
      $match: {
        founder: account,
        approveStatus: 'adopt',
      },
    },
    {
      $lookup: {
        from: 'data_auth_groups',
        localField: 'dataPermissionName',
        foreignField: 'name',
        as: 'dataAuthGroup',
      },
    },
    {
      $unwind: '$dataAuthGroup',
    },
  ]);
  const applyDataAuth = dataPermission.map(item => item.dataAuthGroup._id);

  // 组合在一起才是完整的数据权限
  const allDataAuth = _.uniq(
    [...dataAuth, ...applyDataAuth].map(ObjectId => ObjectId.toString()),
  );

  await next();
  // 处理返回数据
  const { status, body } = ctx.response;
  if (status === 200) {
    const newData = url2Transformer[reqUrl](ctx, body, allDataAuth);
    ctx.response.body = newData;
  }
  return true;
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值