首页
/ Rocket.Chat ABAC 分级横幅:用一份 JSON 配置为受管房间渲染美政府风格的机密标记横幅

Rocket.Chat ABAC 分级横幅:用一份 JSON 配置为受管房间渲染美政府风格的机密标记横幅

2026-09-07 17:20:00作者:范靓好Udolf

本文围绕 Rocket.Chat 中"ABAC 分级横幅(Classification Banners)"这一企业版特性展开:管理员只需在后台填写一份 JSON 配置,即可描述美政府风格的机密标记——机密等级(Clearance Level)、特殊访问计划(Special Access Programs)、可对外发布范围(Releasable To)以及对应的展示颜色;随后,所有由 ABAC(基于属性的访问控制)管理的房间会在房间标题上方为全体成员显示一条彩色横幅。读完本文,你将掌握两个新增设置的启用条件、JSON 配置 Schema 的完整字段语义、横幅渲染引擎的匹配与配色逻辑,以及如何在端到端测试中验证整条链路。

一、功能定位:为什么需要"分级横幅"

在启用 ABAC 的企业部署中,房间(room)会携带来自 IdP(SAML/LDAP)的属性,用来驱动访问决策。但属性本身是"隐性的":成员无法一眼看出当前房间属于什么密级、受哪些特殊访问计划约束、允许发布给谁。

分级横幅要解决的正是这个可视化问题。该特性面向 ABAC 管理的房间:

  • 管理员用一份 JSON 配置定义"属性键 → 可读标签 + 颜色"的映射(等级、特殊访问计划、可发布范围、颜色);
  • 客户端在渲染房间时,把房间实际的 ABAC 属性与配置做匹配,生成一段横幅文本和背景色;
  • 横幅显示在房间头部(room header)上方,对该房间所有成员可见;
  • 未匹配到任何属性时,显示管理员预设的兜底文案与颜色(fallbackText / fallbackColor)。

该特性属于企业版(EE)模块,依赖 abac 模块与 ABAC_Enabled 设置。

二、新增的两个后台设置及其启用条件

设置注册位于 abac.ts,全部包裹在 modules: ['abac']enterprise: true 的设置分组内,因此只有企业版且启用了 ABAC 模块的实例才会出现这些选项:

// apps/meteor/ee/server/settings/abac.ts(节选)
await this.add('ABAC_Classification_Banners_Enabled', false, {
	type: 'boolean',
	public: true,
	invalidValue: false,
	section: 'ABAC_Classification_Banners',
	enableQuery: abacEnabledQuery,               // 依赖 ABAC_Enabled = true
	i18nDescription: 'ABAC_Classification_Banners_Enabled_Description',
});
await this.add('ABAC_Classification_Banners_Config', '', {
	type: 'code',
	code: 'application/json',
	multiline: true,
	public: true,
	invalidValue: '',
	section: 'ABAC_Classification_Banners',
	enableQuery: [abacEnabledQuery, { _id: 'ABAC_Classification_Banners_Enabled', value: true }],
	schema: bannersConfigSchema,                   // 服务端即按 JSON Schema 校验
});

要点:

  1. 两级开关ABAC_Classification_Banners_Enabled(总开关)只有在 ABAC_Enabled = true 时才可用;ABAC_Classification_Banners_Config(JSON 配置本体)进一步依赖总开关为 true,形成 ABAC_Enabled → Banners_Enabled → Config 的启用链。
  2. 服务端强校验ABAC_Classification_Banners_Config 注册时直接挂了 schema: bannersConfigSchema,该 Schema 导入自 classification-banners.schema.json。也就是说,管理员提交的 JSON 若不满足 Schema,根本无法保存,而不是等到客户端渲染时才报错。
  3. 配置类型是 code + application/json 的多行代码编辑器,默认值为空字符串;同文件还注册了 ABAC_ShowAttributesInRooms 等相关设置,横幅与房间属性展示是两个独立开关,可以只开其一。

三、JSON 配置 Schema 逐字段解析

配置文件遵循 JSON Schema draft 2020-12,$idhttps://rocket.chat/schemas/classification-banners/v1.json,顶层强制 versionenabledbannerattributes 四个字段且不允许额外字段(additionalProperties: false)。

3.1 顶层与 banner 段

字段 类型/约束 含义
version 常量 1 配置格式版本,当前仅 v1
enabled boolean 配置内总开关;为 false 时客户端不渲染任何横幅
banner.style 枚举 ["classic"] 横幅样式,当前仅经典样式
banner.uppercase boolean 是否整段大写显示
banner.monospace boolean 是否使用等宽字体(fontFamily: 'mono'
banner.delimiter 字符串,1–8 字符 多个属性段之间的分隔符,如 " // "
banner.colorMode 枚举 highest / attribute 背景色取值模式(见第四节)
banner.fallbackText 非空字符串 无任何属性命中时显示的兜底文案
banner.fallbackColor #RRGGBB 十六进制 兜底背景色

3.2 attributes 数组:每个属性 13 个必填字段

attributes 至少 1 项且整体 uniqueItems;每项是一个"属性定义",把 IdP 的原始键值映射为横幅上的人类可读内容:

字段 约束 含义
id ^[a-z][a-z0-9_-]*$ 属性内部标识(小写字母开头)
source 非空字符串 IdP(SAML/LDAP)返回的原始属性键,用于与房间属性匹配
label 非空字符串 面向管理员的友好名称(不进横幅)
showInBanner boolean 是否出现在横幅中;为 false 的属性只参与配色、不参与文本
showLabel boolean 是否在横幅中显示 bannerLabel 前缀
bannerLabel 字符串 显示在值前面的前缀文本,如 "SAR""RELTO"
labelSeparator 枚举 - / 空格 / 空串 前缀与值之间的分隔符
valueSeparator 枚举 / / ", " / 空格 多个值之间的分隔符
sortAlpha boolean 是否按标签字母序排序后再拼接
groupThreshold 0220 值数量达到该阈值时折叠为 multipleLabel0 表示永不折叠(1 是非法值)
multipleLabel 字符串 折叠时显示的占位文案,如 "MULTIPLE PROGRAMS"
drivesColor boolean 该属性是否驱动横幅背景色
values 数组,uniqueItems,至少 1 项 原始值 → 标签 → 颜色的映射表

values 内每项为 { source, label, color }source 是 IdP 返回的原始值(如 "TS"),label 是横幅上显示的名称(如 "TOP SECRET"),color#RRGGBB

Schema 中两处注释值得注意:JSON Schema 无法表达"按某个字段去重"(如 attribute 按 id 唯一、value 按 source 唯一),因此这些约束"由工具校验"——在 Rocket.Chat 中即由设置保存时的 Schema 校验配合代码层行为共同保证。

四、横幅构建引擎:匹配、文本与配色逻辑

客户端渲染逻辑集中在 engine.ts,类型定义在 types.ts,核心是 buildClassificationBanner(config, roomAttributes)

1. 按属性分段(buildSegment)

  • 用属性的 source 去房间 abacAttributes 里找同键的属性值;
  • 房间值若能命中 values[].source,则替换为配置好的 label未命中映射表的值不会丢弃,而是以原始值形式追加在末尾,保证标记不"丢字";
  • sortAlpha 为真时,映射值与未映射值分别按 localeCompare 排序;
  • 命中数为 0 时该段整体不输出;
  • 文本拼装:命中值个数 ≥ groupThreshold(且阈值 > 0)时整段折叠为 multipleLabel,否则用 valueSeparator 连接;showLabel 为真时再前置 bannerLabel + labelSeparator

2. 配色(resolveColor)——colorMode 的两种模式

// apps/meteor/client/views/room/ClassificationBanner/lib/engine.ts(节选)
const driver = attributes.find(({ drivesColor }) => drivesColor) ?? attributes[0];
const match =
	banner.colorMode === 'highest'
		? driver.values.find(({ source }) => roomValues.includes(source))
		: roomValues.map((value) => driver.values.find(({ source }) => source === value)).find(isTruthy);
  • 驱动属性取第一个 drivesColor: true 的定义,若没人声明则回退到 attributes[0]
  • highest 模式:按配置中 values 的书写顺序找第一个命中的值——因此配置里应把限制性最强的等级写在最前(源码注释明确说明 values 按"most restrictive first"排序,highest 模式下第一个命中者胜出);
  • attribute 模式:改按房间属性自身的值顺序决定,尊重 IdP 给出的排列;
  • 两者都未命中时使用 banner.fallbackColor
  • 前景文字色由 readableTextColor 依据背景色自动换算,保证可读性(见 colors.ts)。

3. 兜底路径:所有 showInBanner 属性的分段都为空时,整条横幅退化为 fallbackText + fallbackColor,且 segments 为空数组——这正是配置里 fallbackText: "NO CLASSIFICATION DATA" 的用武之地。

五、一份可复制的完整配置示例

以下示例来自引擎单测 engine.spec.ts,覆盖等级、特殊访问计划、可发布范围三类典型属性,可直接改写后填入 ABAC_Classification_Banners_Config

{
	"$schema": "https://rocket.chat/schemas/classification-banners/v1.json",
	"version": 1,
	"enabled": true,
	"banner": {
		"style": "classic",
		"uppercase": true,
		"monospace": false,
		"delimiter": " // ",
		"colorMode": "highest",
		"fallbackText": "NO CLASSIFICATION DATA",
		"fallbackColor": "#6C727A"
	},
	"attributes": [
		{
			"id": "classification",
			"source": "clearance.level",
			"label": "Classification level",
			"showInBanner": true,
			"showLabel": false,
			"bannerLabel": "",
			"labelSeparator": "",
			"valueSeparator": "/",
			"sortAlpha": false,
			"groupThreshold": 0,
			"multipleLabel": "",
			"drivesColor": true,
			"values": [
				{ "source": "TS-SCI", "label": "TOP SECRET//SCI", "color": "#fce100" },
				{ "source": "TS", "label": "TOP SECRET", "color": "#ff8c00" },
				{ "source": "S", "label": "SECRET", "color": "#c8102e" },
				{ "source": "C", "label": "CONFIDENTIAL", "color": "#0033a0" },
				{ "source": "CUI", "label": "CUI", "color": "#502b85" },
				{ "source": "U", "label": "UNCLASSIFIED", "color": "#007a33" }
			]
		},
		{
			"id": "sar",
			"source": "access.programs",
			"label": "Special access programs",
			"showInBanner": true,
			"showLabel": true,
			"bannerLabel": "SAR",
			"labelSeparator": "-",
			"valueSeparator": "/",
			"sortAlpha": true,
			"groupThreshold": 4,
			"multipleLabel": "MULTIPLE PROGRAMS",
			"drivesColor": false,
			"values": [
				{ "source": "SAP-1042", "label": "APPLES", "color": "#c8102e" },
				{ "source": "SAP-2271", "label": "BANANAS", "color": "#ff8c00" },
				{ "source": "SAP-3380", "label": "ORANGES", "color": "#0033a0" }
			]
		},
		{
			"id": "relto",
			"source": "dissem.relto",
			"label": "Releasable to",
			"showInBanner": true,
			"showLabel": true,
			"bannerLabel": "RELTO",
			"labelSeparator": " ",
			"valueSeparator": "/",
			"sortAlpha": false,
			"groupThreshold": 0,
			"multipleLabel": "",
			"drivesColor": false,
			"values": [
				{ "source": "USA", "label": "USA", "color": "#0033a0" },
				{ "source": "FVEY", "label": "FVEY", "color": "#007a33" },
				{ "source": "NATO", "label": "NATO", "color": "#502b85" }
			]
		}
	]
}

示例中 classification 属性驱动颜色且 values 按 TS-SCI → U 的强度顺序书写(配合 highest 模式);sar 属性在值数 ≥ 4 时折叠为 "MULTIPLE PROGRAMS" 并按字母序排列;relto 属性展示 bannerLabel + labelSeparator(" ") + 值 的前缀形态(如 RELTO USA/NATO)。

六、客户端渲染链路:从设置到房间头部

横幅组件位于 ClassificationBanner.tsx,挂在房间视图内(房间视图 Room.tsx 与布局 RoomLayout.tsx 中可检索到分级横幅相关引用),其判定链为:

const isABACRoom = useIsABACManagedRoom(room);
const bannersEnabled = useSetting('ABAC_Classification_Banners_Enabled', false);
const rawConfig = useSetting('ABAC_Classification_Banners_Config', '');
const enabled = bannersEnabled && isABACRoom;

const banner = useMemo(() => {
	if (!enabled) return null;
	const config = parseClassificationBannersConfig(rawConfig);
	return config?.enabled ? buildClassificationBanner(config, room.abacAttributes ?? []) : null;
}, [enabled, rawConfig, room.abacAttributes]);

三个前置条件缺一不渲染:总开关开启、当前房间是 ABAC 管理的房间(useIsABACManagedRoom)、配置 JSON 可解析且 enabled: true。配置解析 parseClassificationBannersConfig 只做了 JSON.parse 的容错(失败返回 null),因为格式合法性已在设置保存时由 Schema 拦截。渲染结果是一个 role='region'aria-live='polite' 的横向条:背景色取 backgroundColor、文字色取引擎换算的 colormonospace 时切等宽字体、uppercase 时整段大写,高度固定 x20、文本水平居中并在超长时截断——对应"显示在房间头部上方、对所有成员可见"的产品定义。

七、Schema 强约束契约与测试验证

这份配置不是"尽量遵守"的软规范,而是带测试契约的强约束:

  • Schema 契约测试 schema.spec.ts 用 AJV 编译 v1 Schema,断言一份完整配置必须通过,并逐一验证 20 种非法输入必须被拒,包括:version: 2、缺 colorMode、空/超长 delimiter、空 fallbackTextfallbackColor: "red"(必须 6 位十六进制)、未知顶层字段、空 attributes、缺失 drivesColor、大写 id、非法 labelSeparator: "::"valueSeparator: "|"groupThreshold121、空 values、值颜色写成 3 位 #fff 等。这与设置项上挂载的 schema 字段共同构成"服务端存不进去 + 客户端测不出漏洞"的双重防线。
  • 引擎单测 engine.ts 同目录的 spec 覆盖分段折叠、字母排序、highest/attribute 两种配色与兜底路径。
  • 端到端测试 abac-classification-banner.spec.ts(Playwright,标注 Enterprise Only)用管理员身份通过设置 API 写入一份与上文同构的配置(clearance 属性、TS/U 两个取值),随后验证 ABAC 房间内横幅的实际渲染与颜色换算(含 convertHexToRGB 工具),是"设置 → 房间属性 → 横幅"整条链路的最权威回归依据。

八、小结

ABAC 分级横幅把一个企业级安全诉求压缩成了三件事:打开两个设置、填一份受 Schema 强校验的 JSON、让 ABAC 管理的房间自动显示彩色横幅。配置层(classification-banners.schema.json + abac.ts 的设置注册)、引擎层(engine.ts 的分段与配色)、渲染层(ClassificationBanner.tsx)各自独立且均有测试覆盖,是"配置驱动 UI"在 Rocket.Chat 企业模块中一个典型的完整样本。适用前提:企业版实例、abac 模块启用且 ABAC_Enabled = true;若你的 IdP 属性命名与本示例不同,只需修改各属性的 source 键即可,映射规则、阈值折叠与配色逻辑保持不变。

登录后查看全文
热门项目推荐
相关项目推荐