tachybase_todo/packages/plugin-permissions-v04/src/AccessController.ts
2021-08-11 00:02:37 +08:00

437 lines
12 KiB
TypeScript

import { Op } from 'sequelize';
import { ROLE_TYPE_ANONYMOUS, ROLE_TYPE_ROOT, ROLE_TYPE_USER } from './constants';
function getPermissions(roles) {
return roles.reduce((permissions, role) => permissions.concat(role.permissions), []);
}
function getActionPermissions(permissions) {
const actionsMap = new Map<string, any>();
permissions.forEach(permission => {
permission.get('actions').forEach(action => {
// 如果没有同名 action
if (!actionsMap.has(action.name)) {
actionsMap.set(action.name, action);
return;
}
const existedScope = actionsMap.get(action.name).get('scope');
// 如果之前的同名 action 没有 scope 或 filter 为空
if (!existedScope || !existedScope.get('filter') || !Object.keys(existedScope.get('filter')).length) {
actionsMap.set(action.name, action);
return;
}
const newScope = action.get('scope');
// 如果新 action 没有 scope 或 filter 为空
if (!newScope || !newScope.get('filter') || !Object.keys(newScope.get('filter')).length) {
return;
}
// 以 or 关系合并两个 scope 中的 filter
existedScope.set('filter', { 'or': [existedScope.get('filter'), newScope.get('filter')] });
});
});
return Array.from(actionsMap.values());
}
function getFieldPermissions(permissions) {
const fieldsMap = new Map<string, any>();
permissions.forEach(permission => {
permission.get('fields_permissions').forEach(field => {
if (!fieldsMap.has(field.field_id)) {
fieldsMap.set(field.field_id, field);
return;
}
const existedActions = fieldsMap.get(field.field_id).get('actions');
if (!existedActions || !existedActions.length) {
fieldsMap.set(field.field_id, field);
return;
}
const newActions = field.get('actions');
if (!newActions || !newActions.length) {
return;
}
const actions = new Set(existedActions);
newActions.forEach(item => actions.add(item));
fieldsMap.get(field.field_id).set('actions', Array.from(actions));
});
});
return Array.from(fieldsMap.values());
}
function getTabPemissions(permissions) {
const tabs = new Set();
permissions.forEach(permission => {
permission.get('tabs_permissions').forEach(tabPermission => tabs.add(tabPermission.tab_id));
});
return Array.from(tabs);
}
export type CollectionPermissions = {
actions: any[];
fields: any[];
tabs: any[]
};
export type PermissionParams = boolean | {
filter: any,
fields: any[]
};
// ctx.can = new AccessController(ctx).can;
// ctx.can('collection').permissions()
// => [{ name: 'list', scope: { id: 1, filter: {} } }]
// ctx.can('collection').act('list').any()
// => { filter: {}, fields: [] }
// ctx.can('collection').act('update').any()
// ctx.can('collection').act('update').one(resourceKey)
// ctx.can('collection').act('get').one(resourceKey)
// ctx.as(roles).can('collection').permissions()
// TODO(optimize): 需要优化链式调用结构和上下文,以避免顺序或重置等问题
export default class AccessController<T extends typeof AccessController = typeof AccessController> {
static isRoot(roles): boolean {
return (Array.isArray(roles) ? roles : [roles]).some(role => role.type === ROLE_TYPE_ROOT);
}
context;
roles;
resourceName: string | null = null;
actionName: string | null = null;
constructor(ctx) {
this.context = ctx;
}
/**
* 用于临时创建新的身份进行相关操作
* @param roles Array
*/
as(roles) {
const instance = new (this.constructor as T)(this.context);
instance.roles = Array.isArray(roles) ? roles : [roles];
return instance;
}
can(resourceName: string | null) {
this.resourceName = resourceName;
this.actionName = null;
return this;
}
act(name: string | null) {
this.actionName = name;
return this;
}
async permissions(): Promise<CollectionPermissions> {
const roles = await this.getRolesWithPermissions();
if ((this.constructor as T).isRoot(roles)) {
return this.getRootPermissions();
}
const permissions = getPermissions(roles);
return {
actions: getActionPermissions(permissions),
fields: getFieldPermissions(permissions),
tabs: getTabPemissions(permissions)
};
}
async any(): Promise<PermissionParams> {
const roles = await this.getRolesWithPermissions();
if ((this.constructor as T).isRoot(roles)) {
return true;
}
if (this.resourceName === 'action_logs' && this.actionName === 'list') {
console.log(`skip ${this.resourceName}:${this.actionName}`);
return true;
}
if (this.resourceName === 'china_regions' && this.actionName === 'list') {
console.log(`skip ${this.resourceName}:${this.actionName}`);
return true;
}
// 只处理 actions 表里的权限,其余跳过
const getActionNames = await this.getActionNames();
if (!getActionNames.includes(this.actionName)) {
console.log(`skip ${this.resourceName}:${this.actionName}`);
return true;
}
const permissions = getPermissions(roles);
const actionPermissions = getActionPermissions(permissions);
if (!actionPermissions.length) {
// 如果找不到可用的 action 记录
// 则认为没有权限
return false;
}
const filters = actionPermissions
.filter(item => Boolean(item.scope) && Object.keys(item.scope.filter).length)
.map(item => item.scope.filter);
const fields = getFieldPermissions(permissions);
return {
filter: filters.length
? filters.length > 1 ? { or: filters } : filters[0]
: {},
fields
};
}
async one(resourceKey): Promise<PermissionParams> {
const any = await this.any();
if (typeof any === 'boolean') {
return any;
}
const { filter } = any;
const Collection = this.context.db.getModel(this.resourceName);
const existed = await Collection.count({
where: {
...Collection.parseApiJson({ filter, context: this.context }).where,
[Collection.primaryKeyAttribute]: resourceKey
}
});
return existed ? any : false;
}
async getPermissions(): Promise<any> {
const [Collection, Permission] = this.context.db.getModels(['collections', 'permissions']);
const roles = await this.getRoles();
const permissions = await Permission.findAll({
where: {
role_id: roles.map(role => role.id),
}
});
return permissions;
}
async getCollections(): Promise<any> {
const [Collection, Permission] = this.context.db.getModels(['collections', 'permissions']);
const isRoot = await this.isRoot();
if (isRoot) {
const collections = await Collection.findAll();
return collections.map(collection => collection.name);
}
const roles = await this.getRoles();
const permissions = await Permission.findAll({
where: {
role_id: roles.map(role => role.id),
}
});
return permissions.map(permission => permission.collection_name);
}
async isRoot(): Promise<boolean> {
const { context } = this;
const { currentUser } = context.state;
if (!currentUser) {
return false;
}
const count = await currentUser.countRoles({
where: {
type: ROLE_TYPE_ROOT
}
});
return !!count;
}
async getRoles() {
if (this.roles) {
return this.roles;
}
const { context } = this;
let userRoles = [];
const { currentUser } = context.state;
if (currentUser) {
const rootRoles = await currentUser.getRoles({
where: {
type: ROLE_TYPE_ROOT
}
});
if (rootRoles.length) {
return rootRoles;
}
userRoles = await currentUser.getRoles({
where: {
type: ROLE_TYPE_USER
}
});
}
const Role = context.db.getModel('roles');
const anonymousRoles = await Role.findAll({
where: {
type: ROLE_TYPE_ANONYMOUS
}
});
return [...userRoles, ...anonymousRoles];
}
async getRolesWithPermissions() {
const { context, resourceName, actionName = null } = this;
if (!resourceName) {
throw new Error('resource name must be set first by `can(resourceName)`');
}
const permissionOptions = {
where: {
collection_name: resourceName
},
include: [
{
association: 'actions',
...(actionName ? {
where: {
name: `${resourceName}:${actionName}`
},
required: true,
} : {}),
// 对 hasMany 关系可以进行拆分查询,避免联表过多标识符超过 PG 的 64 字符限制
separate: true,
include: [
{
association: 'scope'
}
]
},
{
association: 'fields_permissions',
include: [
{
association: 'field'
}
],
separate: true,
},
{
association: 'tabs_permissions',
separate: true,
}
]
};
const permissionInclusion = {
...permissionOptions,
association: 'permissions',
required: true
};
if (this.roles) {
if (!(this.constructor as T).isRoot(this.roles)) {
for (const role of this.roles) {
role.permissions = await role.getPermissions(permissionOptions);
role.set('permissions', role.permissions);
}
}
return this.roles;
}
let userRoles = [];
// 获取登入用户的角色及权限
const { currentUser } = context.state;
if (currentUser) {
const rootRoles = await currentUser.getRoles({
where: {
type: ROLE_TYPE_ROOT
}
});
if (rootRoles.length) {
return rootRoles;
}
userRoles = await currentUser.getRoles({
where: {
type: ROLE_TYPE_USER
},
include: [
permissionInclusion
]
});
}
// 获取匿名用户的角色及权限
const Role = context.db.getModel('roles');
const anonymousRoles = await Role.findAll({
where: {
type: ROLE_TYPE_ANONYMOUS
},
include: [
permissionInclusion
]
});
return [...anonymousRoles, ...userRoles];
}
async getActionNames() {
const { context, resourceName = null } = this;
const Action = context.db.getModel('actions');
const actions = await Action.findAll({
where: {
collection_name: { [Op.or]: [resourceName, null] }
}
});
return actions.map(action => action.name);
}
async getRootPermissions(): Promise<CollectionPermissions> {
const { context, resourceName = null } = this;
const Action = context.db.getModel('actions');
const actions = await Action.findAll({
where: {
collection_name: { [Op.or]: [resourceName, null] }
}
});
const Field = context.db.getModel('fields');
const fields = await Field.findAll({
where: {
collection_name: resourceName
}
});
const Tab = context.db.getModel('tabs');
const tabs = await Tab.findAll({
where: {
collection_name: resourceName
},
attribute: [Tab.primaryKeyAttribute]
});
const actionsNames = actions.map(action => ({
name: `${resourceName}:${action.name}`
}));
return {
actions: actionsNames,
fields: fields.map(field => ({
field,
field_id: field.id,
actions: actionsNames.map(({ name }) => name)
})),
tabs: tabs.map(tab => tab[Tab.primaryKeyAttribute])
}
}
}