- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
导读
CustomResourceManager 是 Windows-classic-samples 仓库中一个围绕ACL 编辑器(ACL Editor / ACLUI)相关接口编写的 C++ 控制台示例,核心目标是演示如何为自定义资源对象实现一套完整的 DACL(Discretionary Access Control List,自主访问控制列表)编辑与有效权限(Effective Access)评估流程。示例将资源建模为一个在线论坛(Forums / Sections / Topics 三层树),你既可以通过系统级 ACL 编辑器界面交互式修改任一资源的访问控制,又能在“有效访问(Effective Access)”页签中验证指定用户或组的实际权限。阅读本文后,你将掌握ISecurityInformation、ISecurityInformation3、IEffectivePermission2、ISecurityObjectTypeInfo四个接口的协作方式,理解自定义权限掩码、泛型权限映射、ACE 继承传播与 AuthZ 访问检查的完整实现路径。
示例概览:用论坛树模拟自定义资源
该样例直接复用了ISecurityInformation系列接口,让 Windows 自带的“安全属性页(ACL Editor)”能够编辑不属于文件系统或注册表的自定义对象的安全描述符(Security Descriptor)。其场景是一个三层论坛资源树(定义见 resource.h 中的ResourceType与ResourceIndices):
| 层级 | 资源 | 类型 |
|---|---|---|
| 顶层 | Contoso forums | FORUM(容器) |
| 二层 | Sports / Movies / Hobbies | SECTION(容器) |
| 三层 | Favorite team、Upcoming events(Sports 下);2012 releases、Classics(Movies 下);Learning to cook、Snowboarding(Hobbies 下) | TOPIC(非容器) |
每个Resource对象内部保存名称、类型、SDDL 字符串形式的安全描述符以及父/子索引,并通过childIndices列表维护层级关系(见 resource.cpp)。CSecInfo在构造函数(CSecInfo.cpp)中一次性创建全部 10 个资源,随后用一段默认 SDDL 对根对象执行SetSecurity(DACL_SECURITY_INFORMATION, ...),从而让整个树继承初始 DACL。
示例的默认安全描述符(SDDL)为:
O:WDG:BAD:AI(A;CIIO;FA;;;WD)(A;;FA;;;BA)S:AI(AU;SAFACIIO;FA;;;WD)即:所有者 Everyone、主组 Everyone;DACL 为自动继承(AI),带一个“容器/对象继承 + 仅继承(CIIO)”的对 Everyone 完全控制(FA)ACE 和一个对内置管理员(BA)完全控制的显式 ACE;SACL 同样为自动继承并带有对 Everyone 的成功审核项。
系统要求与构建方式
根据样例自带的 README.md,运行与编译环境如下:
- 客户端(Client):Windows 8.1
- 服务器(Server):Windows Server 2012 R2
- 开发工具:Visual Studio 2013(任意 SKU)及以上版本
构建步骤:
- 打开解决方案文件 CustomResourceManager.sln。
- 在 Visual Studio 中加载完成后,按
F7(Visual Studio 2013 也可用F6),或通过菜单Build → Build Solution编译。
工程配置(CustomResourceManager.vcxproj)中值得注意的两点:
- 平台工具集为
v110,仅提供Debug|Win32与Release|Win32两个配置,使用UNICODE预定义; - 链接阶段显式引入
authz.lib与aclui.lib,前者支撑 AuthZ 访问检查,后者支撑 ACL 编辑器 UI,这正是“编辑自定义 DACL + 计算有效权限”两条主线的直接依赖。
运行与交互:从菜单到安全属性页
构建完成后,按F5(调试运行)或Ctrl+F5(不带调试运行),或在Debug菜单选择对应选项即可启动。程序启动后打印一个编号菜单(见 main.cpp):
------------------------------------ 1. Contoso forums 2. Sports 3. Favorite team 4. Upcoming events 5. Movies 6. 2012 releases 7. Classics 8. Hobbies 9. Learning to cook 10. Snowboarding 11. Help ------------------------------------ Input a number to view/edit the security descriptor for one of the above:- 输入1~10:选中对应资源,随后调用
EditSecurity(nullptr, &cSecInfo)(AclUI.h中的入口函数)弹出标准安全属性页,可编辑该资源的 DACL; - 输入11:打印 Help 文本,说明资源层级与“可继承 ACE 沿继承层级传播”的行为;
- 输入其他值:退出程序。
在安全属性页的Effective Access(有效访问)页签中,可以输入或选择某个用户/组,直接验证其对当前资源实际拥有的权限——这一能力正是通过IEffectivePermission2::ComputeEffectivePermissionWithSecondarySecurity实现的(详见下文)。
若EditSecurity返回失败,程序会输出EditSecurity error %d及GetLastError()的错误码。
架构剖析:四个接口与五个源文件
CSecInfo是示例的核心类,它同时实现四个 COM 接口(见 CSecInfo.h):
| 接口 | 作用 |
|---|---|
ISecurityInformation | ACL 编辑器的主体回调:提供对象信息、读写安全描述符、枚举访问权限、映射泛型权限、提供继承类型等 |
ISecurityInformation3 | 提供资源全名(GetFullResourceName)与提权编辑入口(OpenElevatedEditor) |
IEffectivePermission2 | 计算有效权限,支持用户/设备 SID、组与声明(claims)的“假设(what-if)”组合 |
ISecurityObjectTypeInfo | 通过GetInheritSource报告每个继承 ACE 的来源(父级或祖父级) |
源码按职责拆分为五个文件:
- main.cpp:控制台菜单与
EditSecurity调用; - CSecInfo.h / CSecInfo.cpp:类声明、构造函数、继承传播(
SetSecurityOfChildren、OrderDacl)、GetInheritSource与有效权限计算; - SecurityInformation1.cpp:
ISecurityInformation全部方法的实现; - resource.h / resource.cpp:
Resource资源树模型; - utility.h / utility.cpp:ACL 增删、ACE 比较、安全描述符格式转换等底层工具函数。
自定义权限模型:访问掩码、泛型映射与继承类型
权限位定义
示例为“论坛”场景定义了 12 个具体权限位(CSecInfo.h):
#define CREATE_PERM 0x0001 #define READ_PERM 0x0002 #define VOTE_PERM 0x0004 #define UPDATE_OWN_PERM 0x0008 #define UPDATE_OTHERS_PERM 0x0010 #define HIDE_PERM 0x0020 #define SHOW_PERM 0x0040 #define LOCK_PERM 0x0080 #define UNLOCK_PERM 0x0100 #define DESTROY_PERM 0x0200 #define VIEW_PERMS_PERM 0x0400 #define CHANGE_PERMS_PERM 0x0800并在此基础上叠加出三级“泛型权限”,低层权限被高层权限包含:
#define GENERIC_POST_PERM (CREATE_PERM | READ_PERM | VOTE_PERM | UPDATE_OWN_PERM) #define GENERIC_MOD_PERM (GENERIC_POST_PERM | UPDATE_OTHERS_PERM | HIDE_PERM | SHOW_PERM | LOCK_PERM | UNLOCK_PERM) #define GENERIC_ADMIN_PERM (GENERIC_MOD_PERM | DESTROY_PERM | VIEW_PERMS_PERM | CHANGE_PERMS_PERM)这三者构成GENERIC_MAPPING ObjectMap(Post→GENERIC_POST_PERM、Moderate→GENERIC_MOD_PERM、Administer→GENERIC_ADMIN_PERM),在MapGeneric回调中通过MapGenericMask(pMask, &ObjectMap)将 ACE 中的泛型访问权展开为具体权限位。
权限列表:g_siForumsAccess
GetAccessRights返回的SI_ACCESS表(CSecInfo.h)是 ACL 编辑器在“编辑”与“添加”权限时展示的条目:
- 通用条目(显示在“基本权限”区):
Administer(GENERIC_ADMIN_PERM)、Moderate(GENERIC_MOD_PERM)、Post(GENERIC_POST_PERM); - 高级条目(勾选“显示高级权限”后显示):Create、Read、Vote、Update / edit own content、Update / edit others' content、Hide、Show、Lock、Unlock、Destroy / delete、View permissions、Change permissions,分别对应上述 12 个权限位。
每个条目均带SI_ACCESS_GENERAL/SI_ACCESS_SPECIFIC与INHERIT_FULL(CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE)标记,INHERIT_FULL表示这些 ACE 可同时继承给子容器与子对象。默认新增权限索引为m_DefaultAccess = 0,即默认授予Administer(完全控制)。
继承类型表
容器资源通过GetInheritTypes返回三个继承选项(CSecInfo.h):
| 显示文本 | 对应 ACE 标志 |
|---|---|
| This object only | 0(不继承) |
| This object and children (sections/topics) | CONTAINER_INHERIT_ACE |
| Children (sections/topics) only | INHERIT_ONLY_ACE | CONTAINER_INHERIT_ACE |
样例刻意只使用容器继承(CONTAINER_INHERIT_ACE),以贴合“论坛→版块→话题”的树形结构。非容器(TOPIC)的GetInheritTypes返回E_NOTIMPL,ACL 编辑器将不显示继承控件。
ISecurityInformation 实现要点
GetObjectInformation:控制安全属性页形态
该方法(SecurityInformation1.cpp)组装SI_OBJECT_INFO.dwFlags:
m_dwSIFlags = 0 | SI_ADVANCED // 显示“高级”按钮 | SI_EDIT_PERMS // 允许编辑 DACL | SI_EDIT_OWNER // 高级页允许修改所有者 | SI_PAGE_TITLE // 使用 pszPageTitle 作为基本页标题 | SI_VIEW_ONLY // 实现 ISecurityInformation3 所必需 | SI_EDIT_EFFECTIVE // 启用有效权限编辑 | SI_ENABLE_EDIT_ATTRIBUTE_CONDITION // 启用 ACE 条件编辑 ;当当前资源是容器(FORUM 或 SECTION)时,追加SI_CONTAINER以显示继承控件。页面标题固定为L"Forums Resource Manager",对象名取自当前资源名。
GetSecurity / SetSecurity:SDDL 与安全描述符的读写
GetSecurity(SecurityInformation1.cpp):当fDefault为真时返回默认 SDDL 字符串转换出的安全描述符,否则返回当前资源保存的 SDDL;仅当请求中包含 DACL / OWNER / GROUP 信息时才真正转换。SetSecurity(SecurityInformation1.cpp):ACL 编辑器保存修改时的核心回调,处理逻辑包括:- 将当前资源的 SDDL 转为绝对安全描述符,取出目标 DACL;
- 处理
SE_DACL_PROTECTED(DACL 上的P标志):若新 SD 带保护位则RemoveAllInheritedAces清掉全部继承 ACE;若此前受保护而用户重新开启继承,则调用SetSecurityOfChildren让父级重新下发可继承 ACE; AddAllAcesFromAcl(pSourceDacl, &pDestDacl, TRUE)合入编辑后的 ACE(去重),再RemoveExplicitUniqueAces移除已被用户删掉的显式 ACE;OrderDacl按规范顺序重排 ACE;- 写回绝对 SD 的 DACL/Group/Owner,并转回 SDDL 字符串保存;
- 若当前资源是容器,递归
SetSecurityOfChildren把新 DACL 传播给全部后代。
GetAccessRights / MapGeneric / GetInheritTypes
GetAccessRights:返回g_siForumsAccess表、条目数量与默认权限索引;MapGeneric:清除OBJECT_INHERIT_ACE位(样例不使用对象继承)后调用MapGenericMask展开泛型权限;GetInheritTypes:仅容器返回继承类型表,非容器返回E_NOTIMPL。
继承传播与 ACE 规范化:SetSecurityOfChildren 与 OrderDacl
递归下发可继承 ACE
SetSecurityOfChildren(parentIndex, si, pSD)(CSecInfo.cpp)实现自定义的“继承传播”:
- 遍历父资源的所有子资源;
- 若子资源 SD 带
SE_DACL_PROTECTED,跳过(子对象主动隔离继承); - 提取父 DACL,用
GetSizeOfAllInheritableAces统计可继承 ACE 的总大小; - 通过
RemoveAllInheritedAces清掉子 DACL 中所有带INHERITED_ACE(ID标志)的旧继承 ACE; - 若子 ACL 为空则按需分配并
InitializeAcl; AddInheritableAcesFromAcl把父级可继承 ACE 复制给子级——复制时置上INHERITED_ACE标志、清除INHERIT_ONLY标志,并在遇到NO_PROPAGATE_INHERIT_ACE时清除子级上的继承标志(见 utility.cpp);OrderDacl规范化后写回子 SD,并设置SE_DACL_AUTO_INHERITED;- 递归进入子资源,将传播继续延伸到孙级。
DACL 规范排序:OrderDacl
Windows 要求 DACL 中的 ACE 按规范顺序排列(拒绝 ACE 在允许 ACE 之前、显式 ACE 在继承 ACE 之前)。由于继承来源可能跨越两级(父 SECTION、祖父 FORUM),样例需要知道每条继承 ACE 的“代际差(GenerationGap)”,因此OrderDacl(CSecInfo.cpp)借助GetInheritSourceHelper获取PINHERITED_FROM数组,然后分 6 趟完成排序:
| 趟次 | 选取的 ACE |
|---|---|
| 0 | 显式拒绝(GenerationGap == 0,DENIED) |
| 1 | 显式允许(GenerationGap == 0,ALLOWED) |
| 2 | 来自父级的拒绝(GenerationGap == 1) |
| 3 | 来自父级的允许(GenerationGap == 1) |
| 4 | 来自祖父级的拒绝(GenerationGap == 2) |
| 5 | 来自祖父级的允许(GenerationGap == 2) |
根对象(FORUM)没有祖先,直接返回。排序完成后还会做两项健全性检查:ACE 总数一致、ACL 字节占用一致,确保没有 ACE 在重排中丢失。
GetInheritSource:追溯继承来源
ISecurityObjectTypeInfo::GetInheritSource(CSecInfo.cpp)委托给GetInheritSourceHelper(CSecInfo.cpp),对每条带INHERITED_ACE标志的 ACE:
- 若是 SECTION,检查其是否存在于父 FORUM 的 DACL(用
ACEAlreadyInACL比较,且忽略继承标志差异),存在则GenerationGap = 1、AncestorName填父资源名; - 若是 TOPIC,先查父 SECTION,不在父级再查祖父 FORUM,从而把
GenerationGap标为 1 或 2; - 若两边都找不到(成为“孤儿 ACE”),则视为
GenerationGap = 0(显式 ACE)。
ACEAlreadyInACL的“forInheritancePurposes”模式会剥离INHERIT_ONLY、NO_PROPAGATE_INHERIT_ACE等仅在父级出现的标志再做逐字节比对(类型、标志、长度、SID 与掩码),并兼容 callback ACE(见 utility.cpp)。
有效权限计算:IEffectivePermission2 与 AuthZ 调用链
安全属性页的Effective Access页签由ComputeEffectivePermissionWithSecondarySecurity(CSecInfo.cpp)驱动,其核心是 AuthZ API 调用链:
AuthzInitializeResourceManager(AUTHZ_RM_FLAG_NO_AUDIT, ..., L"SDK Sample Resource Manager", ...)创建资源管理器;AuthzInitializeContextFromSid分别为用户 SID 与设备 SID 创建 AuthZ 上下文;若存在设备 SID,则用AuthzInitializeCompoundContext合并成复合上下文;- 通过
AuthzModifyClaims/AuthzModifySids应用“what-if”的用户/设备声明(claims)与组 SID,支持在编辑器中临时添加组或声明来预测权限变化; - 以
MAXIMUM_ALLOWED作为期望访问掩码,构造AUTHZ_ACCESS_REQUEST与AUTHZ_ACCESS_REPLY,调用AuthzAccessCheck对当前资源的安全描述符执行最终访问检查; - 将结果写入
PEFFPERM_RESULT_LIST:fEvaluated = true,pGrantedAccessList指向授予的访问掩码,pObjectTypeList指向GUID_NULL的对象类型列表(样例不支持对象 ACE)。
函数开头还会校验参数约束:dwSecurityObjectCount必须为 1、安全对象 ID 必须是SECURITY_OBJECT_ID_OBJECT_SD(样例没有 share 或 CAP 的概念),pSid必须存在,服务器名恒为nullptr。所有句柄在退出前都会通过AuthzFreeResourceManager、AuthzFreeContext等释放,避免泄漏。
小结
CustomResourceManager 以约 1089 行的 C++ 源码完整示范了“自定义资源 + 系统 ACL 编辑器”的集成方式:资源树模型负责描述对象层级,ISecurityInformation家族接口负责把安全描述符、权限位、继承规则呈现给编辑器,SetSecurityOfChildren与OrderDacl负责在保存时完成多级继承传播与 DACL 规范化,而IEffectivePermission2+ AuthZ 则让管理员在不实际修改权限的前提下预览任意用户/组的有效访问。无论你要为业务对象、自定义存储还是管理控制台设计权限面板,这套“SDDL 字符串存储 → 绝对 SD 编辑 → 递归继承 → 有效权限校验”的流水线都极具参考价值。
- 示例工程
【免费下载链接】Windows-classic-samples
This repo contains samples that demonstrate the API used in Windows classic desktop applications.
相关推荐
Windows-classic-samples 之 BasicPlugins:用自定义授权与 PowerShell 会话配置构建 Management OData Web Service
Windows classic samples 之 BasicPlugins:用自定义授权与 PowerShell 会话配置构建 Management ODat
示例工程Windows-universal-samples 之 ResizeAppView:UWP 应用窗口尺寸自定义全解析
Windows universal samples 之 ResizeAppView:UWP 应用窗口尺寸自定义全解析 导读 ResizeAppView 是 Wi
示例工程XML Notepad 自定义编辑器与自定义构建器:用 vs:builder / vs:editor 注解扩展 XSD 编辑体验
XML Notepad 自定义编辑器与自定义构建器:用 vs:builder / vs:editor 注解扩展 XSD 编辑体验 导读 XML Notepad
桌面应用开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考