原子指令授权

原子指令授权主要解决这类问题:能对哪个范围内原子数据执行什么操作

- 授权策略

CabloyJS采用白名单策略,所有角色的授权都必须显式设置(superuser也不例外)

- 授权内容

针对原子类型原子指令授权,如以下授权记录:

角色 原子类型 原子指令
system party create

- 资源范围

在授权时可以指定权限的资源范围,在CabloyJS中资源范围也是角色,如以下授权记录

角色 原子类型 原子指令 资源范围
system party read 财务部

角色system仅能读取财务部创建的party数据

财务部也是角色

授权途径

授权途径有三种:人工授权初始授权测试授权

- 人工授权

如果某些权限只有在实际部署或运行时才能决定,可通过管理界面进行授权操作

- 初始授权

如果需要在系统运行时,预先初始化权限,可以在Bean组件version.manager的方法init中通过代码分配权限。比如,给模版角色system分配权限

初始授权测试环境开发环境生产环境均有效

src/module/test-party/backend/src/bean/version.manager.js

async init(options) {
  // only in test/local
  if (!app.meta.isTest && !app.meta.isLocal) return;

  // init
  if (options.version === 1) {
    // types
    for (const name of [ 'Birthday', 'Dance', 'Garden' ]) {
      await this.ctx.model.partyType.insert({ name });
    }
    // add role rights
    const roleRights = [
      // basic
      { roleName: 'system', action: 'create' },
      { roleName: 'system', action: 'read', scopeNames: 'authenticated' },
      { roleName: 'system', action: 'write', scopeNames: 0 },
      { roleName: 'system', action: 'delete', scopeNames: 0 },
      { roleName: 'system', action: 'clone', scopeNames: 0 },
      { roleName: 'system', action: 'deleteBulk' },
      { roleName: 'system', action: 'exportBulk' },
    ];
    await this.ctx.bean.role.addRoleRightBatch({ atomClassName: 'party', roleRights });
  }

}

- 测试授权

为了测试与开发的便利,还可以在开发阶段预先初始化权限,可以在Bean组件version.manager的方法test中通过代码分配权限。比如,给某些测试角色分配权限

测试授权仅在测试环境开发环境有效

src/module/test-party/backend/src/bean/version.manager.js

const VersionTestFn = require('./version/test.js');

async test() {
  const versionTest = new (VersionTestFn(this.ctx))();
  await versionTest.run();
}

授权API

- addRoleRight

a-base/backend/src/config/middleware/adapter/role.js

async addRoleRight({ roleId, atomClassId, action, scope })
名称 说明
roleId 需要被授权的角色Id
atomClassId 原子类型Id
action 需要授权的指令code
scope 资源范围

scope支持以下值:

  • 0: 操作自己创建的原子数据
  • roleId: 操作roleId及以下所有子角色的用户创建的原子数据
  • roleIds: roleId数组

- addRoleRightBatch

a-base/backend/src/config/middleware/adapter/role.js

async addRoleRightBatch({ module, atomClassName, atomClassIdParent = 0, roleRights })
名称 说明
module 模块名称,如果为空就使用当前模块名称
atomClassName 原子类型名称
roleRights 授权记录数组

授权判断方式

可以通过中间件Api进行授权的判断

- 中间件判断

CabloyJS使用中间件right封装了授权判断的逻辑,只需在后端路由上配置相应的中间件参数即可

src/module/test-party/backend/src/routes.js

{ method: 'post', path: 'test/atom/checkRightCreate', controller: 'testAtomRight', middlewares: 'test',
  meta: { right: { type: 'atom', action: 1 } },
},
{ method: 'post', path: 'test/atom/checkRightRead', controller: 'testAtomRight', middlewares: 'test',
  meta: { right: { type: 'atom', action: 2 } },
},
{ method: 'post', path: 'test/atom/checkRightWrite', controller: 'testAtomRight', middlewares: 'test',
  meta: { right: { type: 'atom', action: 3 } },
},
名称 说明
right 全局中间件right,默认处于开启状态,只需配置参数即可
type 授权类型,这里是原子授权
action 原子指令代码

- Api判断

src/module/test-party/backend/src/controller/test/atom/all.js

// atomClass
const atomClass = await this.ctx.meta.atomClass.get({ atomClassName: 'party' });
// userIds
const userIds = this.ctx.cache.mem.get('userIds');

1. 指令create

const checkRightCreates = [[ 'Tom', true ], [ 'Jimmy', true ], [ 'Smith', false ]];
for (const [ userName, right ] of checkRightCreates) {
  const res = await this.ctx.meta.atom.checkRightCreate({
    atomClass,
    user: { id: userIds[userName] },
  });
  assert.equal(!!res, right, userName);
}

2. 指令read

const checkRightReads = [[ 'Tom', partyKey.atomId, true ]];
for (const [ userName, atomId, right ] of checkRightReads) {
  const res = await this.ctx.meta.atom.checkRightRead({
    atom: { id: atomId },
    user: { id: userIds[userName] },
  });
  assert.equal(!!res, right, userName);
}

3. 指令write

const checkRightWrites = [[ 'Tom', partyKeyFormal.atomId, true ], [ 'Tomson', partyKeyFormal.atomId, false ]];
for (const [ userName, atomId, right ] of checkRightWrites) {
  const res = await this.ctx.bean.atom.checkRightAction({
    atom: { id: atomId },
    action: this.ctx.constant.module('a-base').atom.action.write,
    user: { id: userIds[userName] },
  });
  assert.equal(!!res, right, userName);
}

4. 指令delete

const checkRightDeletes = [[ 'Tom', partyKeyFormal.atomId, true ], [ 'Tomson', partyKeyFormal.atomId, false ]];
for (const [ userName, atomId, right ] of checkRightDeletes) {
  const res = await this.ctx.bean.atom.checkRightAction({
    atom: { id: atomId },
    action: this.ctx.constant.module('a-base').atom.action.delete,
    user: { id: userIds[userName] },
  });
  assert.equal(!!res, right, userName);
}