乐趣区

关于权限:PingCode-权限系统设计与实现

作者:龚林杰

本文次要介绍 PingCode 整个权限体系的设计以及实现。在介绍权限体系之前,咱们还须要先明确 PingCode 的产品体系、账户体系,这样能力把权限都给介绍分明。

子产品体系

跟  Worktile  一样,PingCode 是一些列产品的组合,他蕴含有很多个子产品:产品治理,项目管理,测试治理,常识治理,效力度量,合作空间,自动化,Access 等,还蕴含了利用市场的很多个插件,利用,甚至用户本人开发的自建利用等。

PingCode 的每个子产品都有一套本人的权限点的配置,用来管制团队中每个成员的操作行为,那么咱们会为每个子产品独立设计一套权限零碎吗?

账户体系

PingCode 同 Worktile 一样是一种多租户零碎,是以团队为外围,用户只属于团队成员。然而在 PingCode 权限体系分为两套零碎,第一套权限零碎是治理整个团队的要害操作的,第二套权限零碎则是治理每个子产品外部要害操作的。

  • 团队权限体系(左侧)
  • 产品权限体系(右侧)

    一. 团队权限体系

    这套零碎是用于管制团队治理的权限的,比方团队成员治理,订阅治理,平安治理,每个子产品的创立和配置的治理等。

    在这套零碎中,有两种配置形式

  • 权限组的设置:咱们能够本人定义权限组,同时给权限组设置相应的权限
  • 管理员权限:能够增加某位成员成为管理员,同时给其设置权限组或者独自设置权限

    通过以上增加管理员,配置权限组和权限的模式即实现了对于团队要害数据的权限管制。

    二. 产品权限体系

    这套权限零碎就是管制 PingCode 中每个子产品外部性能的操作。

  • 定义角色:在后盾治理中,咱们能够自定义若干个角色,其中有三个角色是内置的:管理员,一般成员,只读成员。子产品配置核心增加所须要的角色即可。
  • 权限点定义:具体的权限点配置是在每个子产品中的配置核心定义的,上面咱们以 产品治理 为事例。也就是说每个子产品定义本人的权限点,在其配置核心别离给产品角色进行调配权限。

    

    在产品治理的产品成员治理中能够给成员调配相应的角色,该成员在以后的产品中就领有了该角色相应的权限。

    以上就是 PingCode 的子产品体系和账户体系的介绍,那么技术上咱们是如何进行权限设计的呢?

    权限的设计与实现

    性能权限是齐全依照 RBAC 模型 设计的,关系为:

    无论是团队权限还是产品权限,咱们对立应用同样的设计形式,也就是说连数据库表都是雷同的,只是在操作的中央做了相应的辨别。

    数据库设计

    在传统关系型数据库的设计,根本都是三张表:角色表,权限表,角色权限关联表,如果校验一个或一组权限,是须要三表关联查问的。

然而 PingCode 采纳的是 Mongdb + 零碎常量配置的模式来解决的,所以这里咱们只介绍角色表和角色的权限存储表,而权限点则是通过零碎常量进行配置的。

class RoleEntity {
   _id: Id;
  name: string;
  application: Application; // 用于辨别哪个子产品的角色
  type: Type; // 辨别:团队角色,产品角色
  is_system: Is; // 是否为零碎内置
}
class RolePermissionEntity {
  _id: Id;
  role_id: Id;
  application: Application; // 用于标识哪个子产品的权限
  permissions: Dictionary<Is>; // 这是一个字典表,具体模式 {create_ticket: 1, edit_ticket: 0}
}

permissions 用户存储每个权限点是否具备操作权限的,0: 无权限,1:有权限

每个子产品权限点的常量配置

const permissionDictionary = {
    // product permissions
    product_basic_setting: {
        display_position: 1000,
        storage_position: 1000, 
        admin_default: Is.yes,
        normal_member_default: Is.no,
        readonly_member_default: Is.no,
        group: PERMISSION_GROUPS.product.key,
        key: `product_basic_setting`, // 在 RolePermissionEntity 表中 permissions 字段中的一个 key 值
        text: ` 根本设置 `
    },
    item1...
    item2...
}

只是以上的数据表还不足以撑持整个权限零碎的运行,成员是否有操作的权限最终还是要判断该成员领有什么角色。咱们还是别离介绍团队权限和子产品权限两个路线。

  • 团队权限
    对于这个权限体系来说很间接,就是在成员上给予其什么样的团队角色,这里阐明一下,咱们在技术实现上把权限组也作为角色来存储了,也就是说 role_ids 中存储有管理员角色 ID 和权限组 ID
class UserEntity {
  _id: Id;
  role_ids: Id[];
  ...
}

该成员领有角色之后,就能够进行判断其领有什么样的权限了,申请如下:

  • 产品权限
    对于每个子产品来说成员有没有权限操作子产品下的性能,也是依据成员领有什么样的角色,他存储在产品的成员治理中
class ProductEntity {
    _id: Id;
      name: string;
      members: Array<{uid: string, role_ids: Id[]}>
}

判断子产品的权限点就略微又些简单了,申请如下:

对立实现

PingCode 领有两套权限体系,每个子产品都有本人的权限判断,为了达到一致性,缩小谬误,以及代码的复用咱们必定是要对立实现的。

因为团队权限的配置是独立于所有产品之外的,而且绝对比较简单,所以上面咱们只介绍 PingCode 所有子产品的权限体系是如何搭建的。

  • 配置
    通过应用 PingCode 你会发现,在 PingCode 中所有子产品配置核心的权限配置中,产品状态都是一样的,惟一不同的是每个子产品的权限点不同。这样对于咱们对立配置的实现也带来的微小的不便。咱们从技术上是通过权限零碎提供 SDK 的形式,定义出对立的 API PATH,让每个子产品接入 SDK,让子产品依据要求传入特有的权限点配置。
import {ApplicationRoleService} from "../role/application-role.service";
import {RoleEntity} from "../../entities";
import {FindResponse} from "@atinc/eros/info";
import {RouterContext} from "@atinc/chaos/router";
import {PermissionGroup} from "../../constants";
import {TyphonOperationContext} from "../../info/operation-context";
export declare class ApplicationRoleConfigurationFacade<TRoleService extends ApplicationRoleService> {
    protected roleService: TRoleService;
    constructor(roleService: TRoleService);
    /**
     * @api     {get}   /apiPrefix/configuration/application-roles  获取角色列表
     * @apiName         getApplicationRoles
     * @apiGroup        SDK-Configuration-Role
     * @apiSuccess      {Number}    code    200
     * @apiSuccessExample {json} Success-Response:
     * {
     *    "oid": "893b67d6-244b-40d2-ae1e-6a5c3d7910a2",
     *    "code": 200,
     *    "data": {*      "value": RoleEntity[]
     *    }
     * }
     */
    protected getApplicationRoles(ctx: RouterContext<TyphonOperationContext>): Promise<FindResponse<RoleEntity[], void>>;
    /**
     * @api     {get}   /apiPrefix/configuration/application-roles/all-permissions  获取所有角色以及权限
     * @apiName         getAllApplicationRolesAndPermissions
     * @apiGroup        SDK-Configuration-Role
     * @apiSuccess      {Number}    code    200
     * @apiSuccessExample {json} Success-Response:
     * {
     *    "oid": "893b67d6-244b-40d2-ae1e-6a5c3d7910a2",
     *    "code": 200,
     *    "data": {
     *      "value":{*              roles: RoleEntity[],
     *              groups: any[]
     *          }
     *    }
     * }
     */
    protected getAllApplicationRolesAndPermissions(ctx: RouterContext<TyphonOperationContext>): Promise<FindResponse<{roles: RoleEntity[];
        groups: any[];}, void>>;
    /**
     * @api     {get}   /apiPrefix/configuration/application-roles/:roleId  获取角色详情
     * @apiParam    (param)     {string}        roleId
     * @apiName         getApplicationRole
     * @apiGroup        SDK-Configuration-Role
     * @apiSuccess      {Number}    code    200
     * @apiSuccessExample {json} Success-Response:
     * {
     *    "oid": "893b67d6-244b-40d2-ae1e-6a5c3d7910a2",
     *    "code": 200,
     *    "data": {
     *      "value": RoleEntity
     *    }
     * }
     */
    protected getApplicationRole(ctx: RouterContext<TyphonOperationContext>): Promise<FindResponse<RoleEntity, void>>;
    /**
     * @api     {get}   /apiPrefix/configuration/application-roles/:roleId/permissions  获取角色权限
     * @apiParam        (param)     {string}        roleId
     * @apiName         getApplicationRolePermissions
     * @apiGroup        SDK-Configuration-Role
     * @apiSuccess      {Number}    code    200
     * @apiSuccessExample {json} Success-Response:
     * {
     *    "oid": "893b67d6-244b-40d2-ae1e-6a5c3d7910a2",
     *    "code": 200,
     *    "data": {*      "value": PermissionGroup[]
     *    }
     * }
     */
    protected getApplicationRolePermissions(ctx: RouterContext<TyphonOperationContext>): Promise<FindResponse<PermissionGroup[], void>>;
    /**
     * @api     {put}   /apiPrefix/configuration/application-roles/:roleId/permissions  设置角色权限
     * @apiParam        (param)     {string}        roleId
     * @apiName         setApplicationRolePermissions
     * @apiGroup        SDK-Configuration-Role
     * @apiParamExample {json} Request-Example:
     * [{
     *          display_position: number;
     *          storage_position: number;
     *          admin_default?: Is;
     *          group?: string;
     *          key: string;
     *          text?: string;
     *  }]
     *
     * @apiSuccess      {Number}    code    200
     * @apiSuccessExample {json} Success-Response:
     * {
     *    "oid": "893b67d6-244b-40d2-ae1e-6a5c3d7910a2",
     *    "code": 200,
     *    "data": {
     *      "value": true
     *    }
     * }
     */
    protected setApplicationRolePermissions(ctx: RouterContext<TyphonOperationContext>): Promise<FindResponse<boolean, void>>;
    /**
     * @api     {put}   /apiPrefix/configuration/application-roles/:roleId/default 设置默认角色
     * @apiParam        (param)     {string}        roleId
     * @apiName         setDefaultRole
     * @apiGroup        SDK-Configuration-Role
     * @apiSuccess      {Number}    code    200
     * @apiSuccessExample {json} Success-Response:
     * {
     *    "oid": "893b67d6-244b-40d2-ae1e-6a5c3d7910a2",
     *    "code": 200,
     *    "data": {
     *      "value": true
     *    }
     * }
     */
    protected setDefaultRole(ctx: RouterContext<TyphonOperationContext>): Promise<FindResponse<boolean, void>>;
    /**
     * @api     {del}   /apiPrefix/configuration/application-roles/:roleId/default 勾销默认角色
     * @apiParam        (param)     {string}        roleId
     * @apiName         removeDefaultRole
     * @apiGroup        SDK-Configuration-Role
     * @apiSuccess      {Number}    code    200
     * @apiSuccessExample {json} Success-Response:
     * {
     *    "oid": "893b67d6-244b-40d2-ae1e-6a5c3d7910a2",
     *    "code": 200,
     *    "data": {
     *      "value": true
     *    }
     * }
     */
    protected removeDefaultRole(ctx: RouterContext<TyphonOperationContext>): Promise<FindResponse<boolean, void>>;
}

这样每个子产品就能够获取角色和权限,并且给角色设置权限,设置默认角色等。

  • 权限判断
    同配置一样,权限判断也是通过 SDK 的形式进行,权限零碎提供权限管制的基类,用户获取其所在产品的所有权限,通过权限点判断是否有操作的权限。权限判断的基类中提供了如下几个办法。
export declare class PermissionBase {
    // 验证团队权限
    authenticateGlobalPermissionsByUser(operationContext: TyphonOperationContext, expectedPermissionPoint: string): Promise<boolean>;
    // 获取用户的团队权限
    getCombinedGlobalPermissionByUser(operationContext: TyphonOperationContext): Promise<string>;
       // 验证产品权限
    authenticateApplicationPermissionsByUser(operationContext: TyphonOperationContext, roleIds: Id[], expectedPermissionPoint: string): Promise<boolean>;
    // 获取用户的产品权限
    getCombinedApplicationPermissionByUser(operationContext: TyphonOperationContext, roleIds: Id[]): Promise<string>;
}

咱们调用 API 时,API 的中间件来判断权限,调用如下

    public authenticateOperationCustomerMiddleware(expectedPermissionPoint: PermissionDefinitionItem) {return async (ctx: RouterContext<CompanyOperationContext>, next: INextFunction) => {
            let product = ctx.operationContext.product;
            if (!product) {const productId = this.resolveProductId(ctx);
                product = await this.service.getProductById(ctx.operationContext, productId);
                ctx.operationContext.product = product;
            }
            const me = product.members.find(member => member.uid === ctx.operationContext.uid);
            if (!me) {throw new WTError(WTCode.invalidInput, `member not in the product(${product._id})`);
            }
            await this.authenticateApplicationPermissionsByUser(ctx.operationContext, me.role_ids || [], expectedPermissionPoint.key);
            return await next();};
    }

PingCode 所有子产品只须要集成权限零碎的 SDK 实现相应的 API 和 权限判断的基类,咱们就实现了权限零碎的统一性了。以上就是对于 PingCode 权限零碎的介绍以及如何设计和实现的。

结尾

当咱们实现产品性能的时候,尽量做到对立,可复用,可扩大,这样无论产品状态怎么变,咱们只须要做小的批改就能适配产品的变动,这也是咱们技术上要做到的规范。

最初,举荐咱们的智能化研发管理工具 PingCode 给大家。

PingCode 官网

对于 PingCode

PingCode 是由国内老牌 SaaS 厂商 Worktile 打造的智能化研发管理工具,围绕企业研发治理需要推出了 Agile(麻利开发)、Testhub(测试治理)、Wiki(知识库)、Plan(我的项目集)、Goals(指标治理)、Flow(自动化治理)、Access(目录治理)七大子产品以及利用市场,实现了对我的项目、工作、需要、缺点、迭代布局、测试、指标治理等研发治理全流程的笼罩以及代码托管工具、CI/CD 流水线、自动化测试等泛滥支流开发工具的买通。

自正式公布以来,以酷狗音乐、商汤科技、电银信息、51 社保、万国数据、金鹰卡通、用友、国汽智控、智齿客服、易快报等知名企业为代表,曾经有超过 13 个行业的泛滥企业抉择 PingCode 落地研发治理。

退出移动版