Cerbos + Mongoose ORM 适应器
一个适配器库 赛博斯 查询计划( E)计划资源 API)反应并将其转换为 乌鸦 过滤器。 它旨在与一个已经使用该软件的项目同时运行 Cerbos Java 脚本 SDK 以获取查询计划,从而将授权逻辑推倒到 MongoDB .
如何运作
- 使用 Cerbos 客户端(
@cerbos/http或者说@cerbos/grpc打电话planResources并获得PlanResourcesResponse。 。 。 . - 提供
queryPlanToMongoose以及一个可选的映射器 来描述 Cerbos 属性路径与您的文档计划的关系. - 适配器走 Cerbos 表达式树,将支持的运算符翻译为 MongoDB 语法,然后返回
{ kind, filters? }。 。 。 . - 检查
result.kind数字 :ALWAYS_ALLOWED:呼叫者可以查询,无需额外的过滤.ALWAYS_DENIED:短路并返回空结果集.CONDITIONAL: 用result.filters。 。 。 .
您可以将适配器输出与现有的应用程序过滤器合并(例如,通过 $and)在发布Mongoose查询之前.
支持的运算符
| 类别 | 运算符 | 行为 |
|---|---|---|
| 逻辑 | and, (中文(简体) ), or, (中文(简体) ), not | 构建 $and, (中文(简体) ), $or,以及 $nor 组合. |
| 比较 | eq, (中文(简体) ), ne, (中文(简体) ), lt, (中文(简体) ), le, (中文(简体) ), gt, (中文(简体) ), ge | 输出 $eq, (中文(简体) ), $ne, (中文(简体) ), $lt, (中文(简体) ), $lte, (中文(简体) ), $gt, (中文(简体) ), $gte 检查与绘图的字段. |
| 成员 | in, (中文(简体) ), hasIntersection | $in 在简单的列表中,或 $elemMatch 瞄准数组关系时; hasIntersection 支持一个直接阵列字段或一个 map 计划内投影. |
| 字符串助手 | contains, (中文(简体) ), startsWith, (中文(简体) ), endsWith | 生成从正则表达式中逃出,这些表达式的目标为子字符串,前缀,或后缀. |
| 存在助手 | isSet, (中文(简体) ), exists | 用途 $exists编号 :$ne: null 用于平板和 $elemMatch 用于收藏. |
| 收藏助手 | filter, (中文(简体) ), lambda, (中文(简体) ), map, (中文(简体) ), all | 将 Cerbos 收藏表达式翻译为范围化 $elemMatch 过滤器和羊肉达变量映射到正确的嵌入路径. all 需要存储字段为数组. |
| 算术和价值 | add, (中文(简体) ), sub, (中文(简体) ), mult, (中文(简体) ), div, (中文(简体) ), mod, (中文(简体) ), if, (中文(简体) ), size, (中文(简体) ), index, (中文(简体) ), get-field | 使用文档级别 MongoDB $expr 表达. division需要非零常分母;索引需要非负整数常数并添加每文档边框检查. |
| 转换和匹配 | string, (中文(简体) ), double, (中文(简体) ), int, (中文(简体) ), timestamp, (中文(简体) ), matches | 使用被守护的MongoDB转换和正则表达式. Timerstamps在CEL即时范围内接受BSON日期或毫秒精确的RFC 3339字符串. Regex匹配接受一个经验证的通用的RE2/PCRE2子集. |
| 等级 | hierarchy, (中文(简体) ), ancestorOf, (中文(简体) ), descendentOf, (中文(简体) ), overlaps | 使用字面前缀和祖先列表过滤器来翻译一个已绘制的scalar分级路径. |
未列入上述原因的操作人员 queryPlanToMongoose 要掷出 Unsupported operator: <name>。 。 。 .
exists_one 响亮失败, 因为普通匹配过滤器无法为已失效元素保存精确匹配的红外线和 CEL 错误语义 .
当存储的 BSON 类型在 CEL 兼容源类型之外或解析失败时,转换失败关闭. double 财务报告和审定财务报表 int 接受字符串和数字 BSON 值,但不使用布尔; int 还拒绝BSON日期,而不是将其解释为毫秒. string 接受字符串、布尔值和数值。 失败的转换仍然被拒绝.
时间戳值必须在协调世界时的即时范围内下降(0001-01-01T00:00:00Z 结束 9999-12-31T23:59:59.999Z) (中文(简体) ). 字符串必须使用不超过三个分数-秒数的RFC 3339语法,与BSON Date的毫秒精度相匹配. 高精度或外程的字符串和 BSON 日期失败而关闭,而不是被默默地切入到不同的CEL瞬间.
matches 支持字节, .,则 *, (中文(简体) ), +,以及 ? 参数,领先 ^终端 $,并逃脱了regex元特征。 其他的构造失败关闭 。 一个终端 $ 被翻译为 PCRE2 的绝对文本端主播,因此MongoDB在最终新线之前无法匹配,保留了 RE2 语义.
履约合同
适配器对 Cerbos PDP 0.54.0 checkResource 使用20个敌对种子文件和真正的MongoDB 7和8个查询。 Spring Data适配器为这个相容快照定义了参考语义.
| 分类 | 覆盖范围 |
|---|---|
| 甲骨文测试 | 85 参考符合性动作外加regex,订购索引/get-field,和时间戳探测器(88个动作) |
| 未关闭 | 29项参考行动 |
| 已知的规划者差异 | has() cerbos计划员将缺失的属性折叠到 ALWAYS_ALLOWED时 checkResource 否认缺少属性文件 。 在计划员修好之前,请使用 R.attr.x != null 用于数据库支持的属性,而不是 has(R.attr.x) |
失败的闭合套件包括内部精确的一要素、汇总表达式或外文件参考 $elemMatch,嵌入式收集计数,相关可变可变成员,不安全的分数/非无限算法,并否定了无法保存CEL三值误差语义的无效收集上游. 这些计划不是无声地贬低MongoDB过滤器.
所需资源
- Cerbos > v0.16 + 两者之一
@cerbos/http或者说@cerbos/grpc客户端
系统要求
- 节点.js 22.0.0
- Mong 9.x
- MongoDB 7.0 或更新
安装
npm install @cerbos/orm-mongoose
API 密码
import {
queryPlanToMongoose,
PlanKind,
type Mapper,
} from "@cerbos/orm-mongoose";
const result = queryPlanToMongoose({
queryPlan, // PlanResourcesResponse from Cerbos
mapper, // optional Mapper - see below
});
if (result.kind === PlanKind.CONDITIONAL) {
await MyModel.find(result.filters);
}
PlanKind 从 @cerbos/core数字 :
export enum PlanKind {
ALWAYS_ALLOWED = "KIND_ALWAYS_ALLOWED",
ALWAYS_DENIED = "KIND_ALWAYS_DENIED",
CONDITIONAL = "KIND_CONDITIONAL",
}
映射器配置
Cerbos 查询计划引用字段, 使用路径如 request.resource.attr.title。使用映射器将这些名称翻译为您 Mongoose 模型中的路径,并描述关系/收集,以便适配器生成 $elemMatch 需要时过滤器.
export type MapperConfig = {
field?: string;
nullable?: boolean;
valueParser?: (value: any) => any;
relation?: {
name: string;
type: "one" | "many";
field?: string;
fields?: Record<string, MapperConfig>;
};
};
export type Mapper =
| Record<string, MapperConfig>
| ((key: string) => MapperConfig);
field将单一 Cerbos 路径重写到 MongoDB 中不同的字段 .nullable声明存储null代表缺少 Cerbos 属性。 比较会添加一个非null守护符,因此MongoDB不会将CEL评价错误变成授权匹配. 不要设置为字段null是一个明确的 Cerbos 值.valueParser在过滤器构建过程中会转换叶子值。 当 Cerbos 计划中包含需要转换为 MongoDB 特定类型的字符串表达式时(例如将字符串转换为ObjectId) (中文(简体) ). 解析器应用到每个值eq, (中文(简体) ),ne, (中文(简体) ),lt, (中文(简体) ),le, (中文(简体) ),gt, (中文(简体) ),ge,以及in操作员。 也通过fields地图.relation描述嵌入式文档(type: "one")或数组(type: "many") (中文(简体) ). 何时field用于比较(例如匹配)createdBy.id无$elemMatch) (中文(简体) ).fields补给层覆盖,所以 lambda 表达式,例如tag.name可以被映射到正确的属性.
如果您省略了映射器, 适配器会逐字使用查询计划路径, 只有当您的 Mongo 文档遵循 Cerbos 命名惯例时才有效 .
直接字段
const mapper: Mapper = {
"request.resource.attr.aBool": { field: "aBool" },
"request.resource.attr.aString": { field: "title" },
"request.principal.attr.department": { field: "principalDepartment" },
};
关系和收藏
使用 relation 当绘制嵌入对象或阵列时. type: "one" 映射到嵌入/单一关系和结果的点化字段路径,同时 type: "many" 映射到数组并让适配器放出 $elemMatch 条件。 可选 fields 映射让您重命名 lambda 表达式中引用的嵌入属性 .
const mapper: Mapper = {
"request.resource.attr.createdBy": {
relation: {
name: "createdBy",
type: "one",
field: "id",
},
},
"request.resource.attr.tags": {
relation: {
name: "tags",
type: "many",
fields: {
id: { field: "id" },
name: { field: "name" },
},
},
},
};
实际收集操作员
了解收集的操作员(filter, (中文(简体) ), exists, (中文(简体) ), hasIntersection, (中文(简体) ), map,以及 all)要求映射器声明与 type: "many"。适配器自动瞄准羊肉达变量并使用 fields 翻译表达式时的映射 tag.name数字 :
const mapper: Mapper = {
"request.resource.attr.tags": {
relation: {
name: "tags",
type: "many",
fields: {
name: { field: "name", nullable: true },
},
},
},
};
exists财务报告和审定财务报表filter将翻译条件包入$elemMatch。 。 。 .hasIntersection既适用于平面阵列,也适用于对象阵列;当计划使用时map(lambda(tag.name))适配器项目tag.name改为tags.$elemMatch.name。 。 。 .all将 lambda 条件转换为否定条件$elemMatch因此,所有要素必须满足上游.- 一个赤裸的
map表达式验证每个元素中引用的嵌入路径.
映射函数
如果您的映射遵循了可预测的模式, 您也可以提供函数 :
const mapper: Mapper = (path) => {
if (path.startsWith("request.resource.attr.")) {
return { field: path.replace("request.resource.attr.", "") };
}
if (path.startsWith("request.principal.attr.")) {
return { field: `principal.${path.replace("request.principal.attr.", "")}` };
}
return { field: path };
};
数值解析
使用 valueParser 将 Cerbos 计划中的值转换成 MongoDB 所期望的类型。 一个常用的大小写是将字符串ID转换为 ObjectId数字 :
import { Types } from "mongoose";
const mapper: Mapper = {
"request.resource.attr.id": {
field: "_id",
valueParser: (value) => new Types.ObjectId(value),
},
};
valueParser 并且通过 fields 地图 :
const mapper: Mapper = {
"request.resource.attr.createdBy": {
relation: {
name: "createdBy",
type: "one",
field: "id",
fields: {
id: {
field: "id",
valueParser: (value) => new Types.ObjectId(value),
},
},
},
},
};
用法实例
import { GRPC as Cerbos } from "@cerbos/grpc";
import mongoose from "mongoose";
import {
queryPlanToMongoose,
PlanKind,
type Mapper,
} from "@cerbos/orm-mongoose";
await mongoose.connect("mongodb://127.0.0.1:27017/test");
const cerbos = new Cerbos("localhost:3592", { tls: false });
const MyModel = mongoose.model("MyModel", /* ... schema ... */);
const mapper: Mapper = {
"request.resource.attr.title": { field: "title" },
"request.resource.attr.owner": {
relation: { name: "owner", type: "one", field: "id" },
},
"request.resource.attr.tags": {
relation: {
name: "tags",
type: "many",
fields: { name: { field: "name" } },
},
},
};
const queryPlan = await cerbos.planResources({
principal: { id: "user1", roles: ["USER"] },
resource: { kind: "document" },
action: "view",
});
const result = queryPlanToMongoose({ queryPlan, mapper });
if (result.kind === PlanKind.ALWAYS_DENIED) {
return [];
}
const filters = result.kind === PlanKind.CONDITIONAL ? result.filters : {};
const records = await MyModel.find(filters);
如果您已经拥有应用程序特定标准,您可以使用 $and数字 :
const filters = result.kind === PlanKind.CONDITIONAL ? result.filters : {};
await MyModel.find({ $and: [filters ?? {}, { archived: false }] });
处理错误
queryPlanToMongoose 在以下情景中抛出描述性错误 :
- 计划不是Cerbos的
PlanKind数值(Invalid query plan.) (中文(简体) ). - 有条件的计划省略
operator编号 :operands结构Invalid Cerbos expression structure) (中文(简体) ). - 计划中列出的操作人员未执行(
Unsupported operator: <name>) (中文(简体) ). - 面向收集的操作员(
map, (中文(简体) ),filter, (中文(简体) ),exists, (中文(简体) ),all使用时没有relation映射器,或带有 映射器宣布type: "one"地点type: "many"需要(例如:map operator requires a relation mapping) (中文(简体) ). - 计划中的Lambda表达式有误(例如缺少一个可变的操作结果)
Lambda variable must have a name) (中文(简体) ). - 值操作不符合预期类型,例如,
hasIntersection提供非阵列值.
及早弥补这些错误有助于使适配器和你的Cerbos政策保持同步.
限制
exists_one而不是无声地贬低exists。 。 。 .- 收集上游中的集合表达式、来自羊肉的外文件引用以及否定无效的收集宏失败,因为
$elemMatch无法保存其 CEL 范围界定和三值错误语义. - 未列出的运算符 支持的运算符 (例如搜索、模式、平面数学帮助器、原子数操作、复合键等)没有执行,将投掷
Unsupported operator。 。 。 . - 翻译可能使用文档级别
$expr,但从不需要多阶段聚合管道.