66 lines
2.7 KiB
TypeScript
66 lines
2.7 KiB
TypeScript
|
|
/**
|
|||
|
|
* 能力可见性(任务 A3.5):按身份与白名单决定谁能看到哪些能力。
|
|||
|
|
*
|
|||
|
|
* 两条边界(架构设计 5.1):
|
|||
|
|
* - 工具列表与能力清单必须来自同一次过滤,否则模型能看到用户看不到的能力
|
|||
|
|
* - 权限归业务系统(D9),这里只管「平台把哪些能力暴露给这个身份」,不管业务系统内部的角色
|
|||
|
|
*
|
|||
|
|
* 默认策略:开发期 defaultAllow=true(未配白名单时全部可见)。接入真实身份(A4.1)后应改成 false,
|
|||
|
|
* 由授权表逐条给出——这与治理模型里「未登记默认拒绝」是同一条原则。
|
|||
|
|
*/
|
|||
|
|
import type { Capability } from '@aps/provider-sdk';
|
|||
|
|
|
|||
|
|
export interface CapabilityGrant {
|
|||
|
|
/** 空表示不限用户 */
|
|||
|
|
userId?: string;
|
|||
|
|
/** 空表示不限租户 */
|
|||
|
|
tenantId?: string;
|
|||
|
|
/** 允许的能力 id 前缀,如 aps.schedule 或 fake. */
|
|||
|
|
allow: string[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface CapabilityVisibility {
|
|||
|
|
defaultAllow: boolean;
|
|||
|
|
denyWrite: boolean;
|
|||
|
|
grants: CapabilityGrant[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface VisibilitySubject {
|
|||
|
|
userId: string;
|
|||
|
|
tenantId: string;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const matchesPrefix = (capabilityId: string, prefixes: string[]): boolean =>
|
|||
|
|
prefixes.some((prefix) => (prefix.endsWith('*') ? capabilityId.startsWith(prefix.slice(0, -1)) : capabilityId === prefix));
|
|||
|
|
|
|||
|
|
const grantApplies = (grant: CapabilityGrant, subject: VisibilitySubject): boolean => {
|
|||
|
|
if (grant.userId !== undefined && grant.userId !== subject.userId) return false;
|
|||
|
|
if (grant.tenantId !== undefined && grant.tenantId !== subject.tenantId) return false;
|
|||
|
|
return true;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/** 这个身份能看到这个能力吗 */
|
|||
|
|
export function isCapabilityVisible(capability: Capability, visibility: CapabilityVisibility, subject: VisibilitySubject): boolean {
|
|||
|
|
// 写能力在只读模式下对所有人不可见(演示、审计或只读部署用)
|
|||
|
|
if (visibility.denyWrite && capability.effect === 'write') return false;
|
|||
|
|
|
|||
|
|
const granted = visibility.grants.some((grant) => grantApplies(grant, subject) && matchesPrefix(capability.id, grant.allow));
|
|||
|
|
if (granted) return true;
|
|||
|
|
if (!visibility.defaultAllow) return false;
|
|||
|
|
|
|||
|
|
// 默认允许模式下仍可用 denyWrite 收口;白名单里的 deny 规则暂不做,等 A4 的策略层
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function visibleCapabilities(
|
|||
|
|
capabilities: Capability[],
|
|||
|
|
visibility: CapabilityVisibility,
|
|||
|
|
subject: VisibilitySubject,
|
|||
|
|
): Capability[] {
|
|||
|
|
return capabilities.filter((capability) => isCapabilityVisible(capability, visibility, subject));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/** 能力集的指纹:用于判断运行时会话是否需要重建(工具集变了就得重建) */
|
|||
|
|
export function capabilityFingerprint(capabilities: Capability[]): string {
|
|||
|
|
return [...capabilities.map((capability) => capability.id)].sort().join(',');
|
|||
|
|
}
|