news 2026/9/16 15:49:14

Open edX 课程创作 AuthZ 迁移的双轨权限路径设计:以 ADR 0027 为例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Open edX 课程创作 AuthZ 迁移的双轨权限路径设计:以 ADR 0027 为例

Open edX 课程创作 AuthZ 迁移的双轨权限路径设计:以 ADR 0027 为例

【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform

本文围绕 Open edX 的架构决策记录docs/decisions/0027-dual-path-course-authoring-migration.rst展开,讲清AUTHZ_COURSE_AUTHORING_FLAG门控的"从 legacyCourseAccessRole迁移到 openedx-authz"过程中如何引入角色感知的双路径判断。读完后你能掌握:enable_authz_course_authoring(course_key, role)的设计动机与行为细节、course_creator_group等未迁移角色如何安全回退到 legacy 路径,以及调用方何时必须传入role参数以避免复发 500/403 故障。

背景:迁移中的角色覆盖不全问题

Open edX 正在把课程创作权限的授权引擎从 legacy 的CourseAccessRole表切换到 openedx-authz。这个切换由 waffle 开关AUTHZ_COURSE_AUTHORING_FLAGauthz.enable_course_authoring)整体门控,定义在 toggles.py 中:

# .. toggle_description: This toggle will enable the new openedx-authz authorization engine for course authoring. # .. toggle_use_cases: temporary # .. toggle_creation_date: 2026-02-05 # .. toggle_target_removal_date: 2027-06-09 AUTHZ_COURSE_AUTHORING_FLAG = CourseWaffleFlag('authz.enable_course_authoring', __name__)

注意几个关键属性:

  • toggle_use_cases: temporary:这是一个临时开关,迁移完成后会移除;
  • toggle_target_removal_date: 2027-06-09:计划移除时间,即本 ADR 所说的"stopgap";
  • CourseWaffleFlag:按course_key粒度启用,不是全局开关。

问题在于:迁移尚未完成,只有部分 legacy 角色在 openedx-authz 中有对应角色(由 openedx-authz 的LEGACY_COURSE_ROLE_EQUIVALENCES定义),而course_creator_grouporg_course_creator_group尚无对应角色。最初的实现只判断 waffle 开关,从不检查"正在处理的这个角色是否已经迁移",于是打开开关后出现两个故障(均记录在 openedx-authz issue #353、#354):

  • 通过 Django admin 授予 course creator 权限时报 500;
  • 已有 legacy course creator 授权的用户在创建课程时收到 403。

两者的共同根因是同一个:代码试图对一个在 openedx-authz 中尚无对应角色的 legacy 角色使用 authz 路径,导致授权解析失败(异常或权限被拒)。

决策:给enable_authz_course_authoring增加可选role参数

ADR 的核心决定是:enable_authz_course_authoring接受一个可选的role参数,以便基于具体角色做决策

在 roles.py 中的实现如下:

def enable_authz_course_authoring(course_key: CourseKey | None = None, role: str | None = None) -> bool: """ True only if the authz.enable_course_authoring waffle flag is enabled and, when `role` is given, that role has a migrated authz equivalent. ... """ if not AUTHZ_COURSE_AUTHORING_FLAG.is_enabled(course_key): return False if role is not None and get_authz_role_from_legacy_role(role) is None: return False return True

行为规则完全对应 ADR 的三条决策点:

条件返回值说明
开关关闭(对给定course_keyFalse函数提前返回,从不评估role,零额外开销
开关开启 + 未传roleTrue保持旧行为,跟随开关(通用权限检查调用方适用)
开关开启 + 传入role且该角色在LEGACY_COURSE_ROLE_EQUIVALENCES对应 authz 角色False回退 legacy 路径,避免 500/403
开关开启 + 传入role且存在对应 authz 角色True走 authz 路径

其中角色映射函数同样在 roles.py:

def get_authz_role_from_legacy_role(legacy_role: str) -> str: return authz_roles.LEGACY_COURSE_ROLE_EQUIVALENCES.get(legacy_role, None) def get_legacy_role_from_authz_role(authz_role: str) -> str: return next((k for k, v in authz_roles.LEGACY_COURSE_ROLE_EQUIVALENCES.items() if v == authz_role), None)

LEGACY_COURSE_ROLE_EQUIVALENCES来自openedx_authz.constants.roles(外部依赖 openedx-authz 包),本仓库只负责查表。从源码结构看role参数只有在开关已开启时才被求值(第一行if not AUTHZ_COURSE_AUTHORING_FLAG.is_enabled(course_key): return False),这印证了 ADR "role check only runs when the flag is on" 的表述。

调用方约定:操作具体角色时必须传role

ADR 的调用方规则很明确:操作具体角色的调用方(授予/撤销)应把该角色传入;不关心具体角色的调用方(通用权限检查)可省略

在源码中,RoleBase的所有双路径入口都遵循了这个约定,例如 roles.py:

def add_users(self, *users): if enable_authz_course_authoring(self.course_key, role=self._role_name): self._authz_add_users(users) else: self._legacy_add_users(users) def remove_users(self, *users): if enable_authz_course_authoring(self.course_key, role=self._role_name): self._authz_remove_users(users) else: self._legacy_remove_users(users)

类似的调用点还包括users_with_roleget_orgs_for_userhas_org_for_user(均在RoleBase上),以及CourseRole.course_group_already_exists(注意:这个方法role,因为它检查的是"该课程是否已存在任何角色授权"这种通用判断,符合"不关心具体角色则省略"的规则)。

相反,CourseCreatorRoleROLE = "course_creator_group")与OrgContentCreatorRoleROLE = "org_course_creator_group")目前没有 authz 等价角色。ADR 中提到的is_content_creator检查正是因此完全跳过开关判断:

# common/djangoapps/student/auth.py def is_content_creator(user, org): """ Neither CourseCreatorRole nor OrgContentCreatorRole has a migrated AuthZ equivalent yet (see ADR 0027), so this always checks the legacy role-based permission system. Once either role gets a migrated equivalent, this should also check AuthZ, gated on that role. """ return _has_legacy_content_creator_access(user, org) def _has_legacy_content_creator_access(user, org): return (user_has_role(user, CourseCreatorRole()) or user_has_role(user, OrgContentCreatorRole(org=org)))

见 auth.py。而 Studio 的课程创建入口(views/course.py 中的create_course)正是用is_content_creator(request.user, org)来放行:

has_course_creator_role = is_content_creator(request.user, org) if not has_course_creator_role: raise PermissionDenied()

这条链路解释了故障 #354 的成因与修复方式:由于is_content_creator始终走 legacy 判断,已有 legacycourse_creator_group授权的用户即使在开关开启后依然能通过创建权限校验。

未迁移角色的读取路径:RoleCache 双源合并

除了写路径(grant/revoke),ADR 隐含的第二个风险是读路径:开关开启后,若只从 openedx-authz 读角色,legacy 表中的未迁移授权会被"漏掉"。仓库中的实际解法是 roles.py 中的AuthzCompatCourseAccessRole兼容数据类与RoleCache

@dataclass(frozen=True) class AuthzCompatCourseAccessRole: """ Generic data class for storing CourseAccessRole-compatible data to be used inside BulkRoleCache and RoleCache. This allows the cache to store both legacy and openedx-authz compatible roles """ user_id: int username: str org: str course_id: str | None role: str

RoleCache.__init__在缓存未命中时会同时加载两个来源:

  1. openedx-authz 兼容角色get_authz_compat_course_access_roles_for_user(user)把用户在 authz 中的课程/组织/平台级授权转换为 legacy 兼容记录(无 legacy 映射的 authz 角色被跳过);
  2. legacy 角色CourseAccessRole.objects.filter(user=user)直接查表。

BulkRoleCache.prefetch(批量预取,roles.py)同样合并两个来源。这意味着RoleCache.has_role对未迁移角色(如course_creator_group)的判定始终基于 legacy 表数据,与is_content_creator的"始终走 legacy"策略一致,避免了"写走 legacy、读走 authz"导致读不到的不一致。

影响与后续清理计划(Consequences)

ADR 的"Consequences"一节列出了四条影响,结合源码可逐条对应验证:

  1. 未迁移角色在开关开启后继续正常工作RoleBase.add_users/remove_users/users_with_role传入role后自动回退 legacy,is_content_creator始终走 legacy——对应修复 #353(Django admin 授予 500)与 #354(已有授权用户 403)。
  2. 不传role的调用方零成本role是可选参数,缺省时函数只在开关开启时直接返回True,无任何额外查表。
  3. 主要风险是未来调用方忘传role:ADR 明确指出,若某调用方操作具体角色却忘记传入,"会静默地复现本决策修复的同一个 bug"。这是给后续开发者的硬性约束——凡是对某个具体 legacy 角色做 grant/revoke/list 的操作,调用enable_authz_course_authoring时必须带上role=self._role_name
  4. 这是过渡方案,不是最终形态:ADR 强调"stopgap, not a permanent shape"。当所有 legacy 角色都有 authz 等价角色后,角色检查以及开关本身都会移除——与 toggles.py 中toggle_target_removal_date: 2027-06-09的标注一致。届时is_content_creator的 docstring 也已预埋了待办:"Once either role gets a migrated equivalent, this should also check AuthZ, gated on that role."

适用前提与限制

  • 本 ADR 描述的行为适用于当前仓库版本:AUTHZ_COURSE_AUTHORING_FLAGcourse key 粒度生效(CourseWaffleFlag),同一平台内可以部分课程开启、部分关闭;
  • 角色映射表LEGACY_COURSE_ROLE_EQUIVALENCES维护在外部依赖 openedx-authz 中(见 openedx-authz ADR 0011 的角色映射表),本仓库 roles.py 只是查表入口;
  • 开关的toggle_warning提示:启用该开关会触发在 legacy 与 openedx-authz 之间的角色授权数据迁移,生产环境开启前需评估数据迁移影响;
  • 文中提到的 openedx-authz issue #353 / #354 属于外部仓库(openedx-authz),本仓库内无法直接验证其细节,事实依据以 ADR 原文陈述为准。

关键文件索引

内容路径
本 ADR 原文0027-dual-path-course-authoring-migration.rst
开关定义toggles.py
enable_authz_course_authoring/ 角色映射 / 双路径角色类roles.py
is_content_creator始终走 legacyauth.py
Studio 课程创建权限入口views/course.py
基于开关的权限装饰器(不关心具体角色的调用方示例)decorators.py

【免费下载链接】openedx-platformThe Open edX LMS & Studio, powering education sites around the world!项目地址: https://gitcode.com/GitHub_Trending/ed/openedx-platform

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 15:46:54

贪心算法实战:分发糖果与区间问题解析

1. 贪心算法核心思想回顾在进入具体问题之前,我们先明确贪心算法的基本特征。这种算法在每一步选择中都采取当前状态下最优的决策,希望通过局部最优解的累积达到全局最优。与动态规划不同,贪心算法不会回退,这也决定了它并非适用于…

作者头像 李华
网站建设 2026/9/16 15:46:51

IEEE33节点系统为何首选前推回代潮流算法

简介:本资源是一份面向电力系统专业本科生、研究生及工程实践者的IEEE 33节点辐射状配电网潮流计算教学与实操资料,聚焦前推回代法这一经典解析算法的MATLAB实现。资源包含3个核心文件:1个MATLAB主程序(DG_powerflow.m&#xff09…

作者头像 李华
网站建设 2026/9/16 15:41:37

2026数据智能体选型决策地图:四类厂商本质差异与落地标尺

1. 这不是又一份“厂商对比表”,而是一张数据智能体落地的决策地图2026年,数据智能体(Data Agent)已不再是PPT里的概念名词,它正批量嵌入企业BI看板、供应链预警系统、客户成功工单流、甚至财务月结流程中。我去年帮三…

作者头像 李华
网站建设 2026/9/16 15:41:30

抖音批量下载:3 步完成无水印视频与作者作品收集

抖音批量下载:3 步完成无水印视频与作者作品收集 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support. 抖…

作者头像 李华
网站建设 2026/9/16 15:40:38

微信ipad协议,wechatapi.net

一、企业级应用的特殊要求与挑战当微信机器人从个人工具升级为企业级系统时,面临的需求复杂度呈指数级增长。一个成熟的企业级微信机器人系统需要满足以下核心要求:可用性要求:99.9%的系统可用性(全年停机时间不超过8.76小时&…

作者头像 李华