// gm api 权限验证 import Cache from "@App/app/cache"; import { Script } from "@App/app/repo/scripts"; import { v4 as uuidv4 } from "uuid"; import { Api, Request } from "./gm_api"; import Queue from "@App/pkg/utils/queue"; import CacheKey from "@App/app/cache_key"; import { Permission, PermissionDAO } from "@App/app/repo/permission"; export interface ConfirmParam { // 权限名 permission: string; // 权限值 permissionValue?: string; // 确认权限标题 title?: string; // 权限详情内容 metadata?: { [key: string]: string }; // 权限描述 describe?: string; // 是否通配 wildcard?: boolean; // 权限内容 permissionContent?: string; } export interface UserConfirm { allow: boolean; type: number; // 1: 允许一次 2: 临时允许全部 3: 临时允许此 4: 永久允许全部 5: 永久允许此 } export interface ApiParam { // 默认提供的函数 default?: boolean; // 是否只有后台环境中才能执行 background?: boolean; // 是否需要弹出页面让用户进行确认 confirm?: (request: Request) => Promise; // 监听方法 listener?: () => void; // 别名 alias?: string[]; // 关联 link?: string; } export interface ApiValue { api: Api; param: ApiParam; } export interface IPermissionVerify { verify(request: Request, api: ApiValue): Promise; } export default class PermissionVerify { static apis: Map = new Map(); public static API(param: ApiParam = {}) { return (target: any, propertyName: string, descriptor: PropertyDescriptor) => { const key = propertyName; if (param.listener) { param.listener(); } PermissionVerify.apis.set(key, { api: descriptor.value, param, }); // 兼容GM.* const dot = key.replace("_", "."); if (dot !== key) { PermissionVerify.apis.set(dot, { api: descriptor.value, param, }); if (param.alias) { param.alias.push(dot); } else { param.alias = [dot]; } } // 处理别名 if (param.alias) { param.alias.forEach((alias) => { PermissionVerify.apis.set(alias, { api: descriptor.value, param, }); }); } }; } // 确认队列 confirmQueue: Queue<{ request: Request; confirm: ConfirmParam | boolean; resolve: (value: boolean) => void; reject: (reason: any) => void; }> = new Queue(); async removePermissionCache(scriptId: number) { // 先删除缓存 (await Cache.getInstance().list()).forEach((key) => { if (key.startsWith(`permission:${scriptId.toString()}:`)) { Cache.getInstance().del(key); } }); } private permissionDAO: PermissionDAO; constructor() { this.permissionDAO = new PermissionDAO(); this.dealConfirmQueue(); } // 验证是否有权限 verify(request: Request, api: ApiValue): Promise { if (api.param.default) { return Promise.resolve(true); } // 没有其它条件,从metadata.grant中判断 const { grant } = request.script.metadata; if (!grant) { return Promise.reject(new Error("grant is undefined")); } for (let i = 0; i < grant.length; i += 1) { if ( // 名称相等 grant[i] === request.api || // 别名相等 (api.param.alias && api.param.alias.includes(grant[i])) || // 有关联的 grant[i] === api.param.link ) { // 需要用户确认 if (api.param.confirm) { return this.pushConfirmQueue(request, api); } return Promise.resolve(true); } } return Promise.reject(new Error("permission not requested")); } async dealConfirmQueue() { // 处理确认队列 const data = await this.confirmQueue.pop(); if (!data) { this.dealConfirmQueue(); return; } try { const ret = await this.confirm(data.request, data.confirm); data.resolve(ret); } catch (e) { data.reject(e); } this.dealConfirmQueue(); } // 确认队列,为了防止一次性打开过多的窗口 async pushConfirmQueue(request: Request, api: ApiValue): Promise { const confirm = await api.param.confirm!(request); if (confirm === true) { return Promise.resolve(true); } return new Promise((resolve, reject) => { this.confirmQueue.push({ request, confirm, resolve, reject }); }); } async confirm(request: Request, confirm: boolean | ConfirmParam): Promise { if (typeof confirm === "boolean") { return confirm; } const cacheKey = CacheKey.permissionConfirm(request.script.uuid, confirm); // 从数据库中查询是否有此权限 const ret = await Cache.getInstance().getOrSet(cacheKey, async () => { let model = await this.permissionDAO.findByKey(request.uuid, confirm.permission, confirm.permissionValue || ""); if (!model) { // 允许通配 if (confirm.wildcard) { model = await this.permissionDAO.findByKey(request.uuid, confirm.permission, confirm.permissionValue || ""); } } return Promise.resolve(model); }); // 有查询到结果,进入判断,不再需要用户确认 if (ret) { if (ret.allow) { return Promise.resolve(true); } // 权限拒绝 return Promise.reject(new Error("permission denied")); } // 没有权限,则弹出页面让用户进行确认 const userConfirm = await this.confirmWindow(request.script, confirm); // 成功存入数据库 const model: Permission = { uuid: request.uuid, permission: confirm.permission, permissionValue: "", allow: userConfirm.allow, createtime: new Date().getTime(), updatetime: 0, }; switch (userConfirm.type) { case 4: case 2: { // 通配 model.permissionValue = "*"; break; } case 5: case 3: { model.permissionValue = confirm.permissionValue || ""; break; } default: break; } // 临时 放入缓存 if (userConfirm.type >= 2) { Cache.getInstance().set(cacheKey, model); } // 总是 放入数据库 if (userConfirm.type >= 4) { const oldConfirm = await this.permissionDAO.findByKey(request.uuid, model.permission, model.permissionValue); if (!oldConfirm) { await this.permissionDAO.save(model); } else { await this.permissionDAO.update(this.permissionDAO.key(model), model); } } if (userConfirm.allow) { return Promise.resolve(true); } return Promise.reject(new Error("permission not allowed")); } // 确认map confirmMap: Map< string, { confirm: ConfirmParam; script: Script; resolve: (value: UserConfirm) => void; reject: (reason: any) => void; } > = new Map(); // 弹出窗口让用户进行确认 async confirmWindow(script: Script, confirm: ConfirmParam): Promise { return Promise.resolve({ allow: true, type: 1, }); } }