OpenProject 12.2.5 发布详解:LDAP 组同步缺陷修复与未同步成员排查实践
【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openproject
OpenProject 12.2.5 是 2022 年 10 月 4 日发布的补丁版本,核心任务是修复 LDAP 组同步链路中的两处缺陷,其中最值得关注的是:同一同步批次中新增用户会导致已在 LDAP 中被移除的用户无法从 OpenProject 组中正确删除。本文以该版本发布说明为骨架,结合 ldap_groups 模块的源码实现,完整讲解缺陷成因、官方提供的ldap_groups:print_unsynced_members排查命令,以及同步机制背后的原理,帮助管理员准确评估影响面并完成升级与数据核对。
版本概览
- 版本号:OpenProject 12.2.5
- 发布日期:2022-10-04
- 版本类型:补丁版本(bugfix release)
官方在该版本中明确建议所有用户升级到最新版本(升级方式包括打包安装的sudo openproject run系列命令与 Docker 部署的重建容器流程)。本版本不包含新增功能,全部变更聚焦于缺陷修复,其中 LDAP 组同步相关的两个修复项构成了本次发布的主体。
核心缺陷:LDAP 组同步批次内的成员误删与残留
缺陷现象
发布说明中描述的缺陷链路如下:
- 管理员通过 LDAP 组同步功能,将 LDAP 目录中的用户同步到 OpenProject 的内置用户组(Group);
- 在同一个同步步骤中,既有 LDAP 组内新增用户,又存在被 LDAP 移除的用户;
- 此时,本应从 OpenProject 组中删除的“被移除用户”没有按预期被清除,最终导致这些用户在 LDAP 中已经不存在,却仍然保留在 OpenProject 组内。
从触发条件看,该缺陷属于同步批次内的成员集合替换逻辑问题:新增成员与移除成员在同一次update_memberships!调用中发生,而移除判断依赖的成员快照被新增操作污染,从而误判了哪些用户“仍然存在”。
源码层面的成因分析
在 ldap_groups 模块中,同步的核心逻辑位于 synchronize_groups_service.rb。关键的update_memberships!方法(第 86-96 行)执行两阶段操作:
def update_memberships!(sync, users) # Remove group users no longer in ids no_longer_present = ::LdapGroups::Membership.where(group_id: sync.id).where.not(user_id: users.select(:id)) remove_memberships!(no_longer_present, sync) # Add all current users from LDAP as members add_memberships!(users, sync) end第一阶段根据“当前 LDAP 成员集合”反查本地成员记录,找出不再出现的用户并移除;第二阶段再将 LDAP 中的当前成员全量加入。缺陷场景下,同一步骤中新增用户会干扰成员快照的构建(尤其是基于用户登录名映射时的大小写、重复记录处理),使得remove_memberships!阶段拿到的“待移除列表”不完整,被 LDAP 删除的用户因此漏删。
模型层的增删实现位于 synchronized_group.rb:
add_members!(第 29-41 行):在事务内通过Membership.insert_all批量写入同步成员关系,并调用Groups::UpdateService将用户加入内部组;remove_members!(第 47-59 行):在事务内先删除同步成员关系,再从内部组移除用户,任一步失败都会触发ActiveRecord::Rollback回滚。
同步成员关系由 membership.rb 承载,validates_uniqueness_of :user_id, scope: :group_id约束保证同一同步组内成员唯一,这决定了同步必须依赖“全量比对、增量增删”的策略——也正是该缺陷暴露的位置。
影响评估
- 受影响的用户表现为:在 LDAP 中已被移除,但在 OpenProject 组中仍然存在;
- 该缺陷不会影响 LDAP 中仍然存在的用户,也不会导致 LDAP 成员被误删;
- 由于系统无法判断这些残留用户是否本应保留(例如管理员手动加入的成员),12.2.5 未提供自动清理机制,需要管理员借助诊断命令人工核对。
官方排查命令:ldap_groups:print_unsynced_members
为帮助管理员发现上述残留用户,12.2.5 引入了新的 rake 任务ldap_groups:print_unsynced_members,其作用为:遍历所有已同步的 LDAP 组,打印出组内存在但并非源自 LDAP 同步的成员。
打包安装(Packaged installation)
sudo openproject run bundle exec rake ldap_groups:print_unsynced_membersDocker 安装
docker exec -it <web container ID> bash -c "bundle exec rake ldap_groups:print_unsynced_members"其中<web container ID>需要替换为 OpenProject Web 容器的实际 ID(可通过docker ps查看)。
命令的源码实现
该任务定义于 ldap_groups.rake 的第 38-54 行:
task print_unsynced_members: :environment do LdapGroups::SynchronizedGroup .includes(:group) .find_each do |sync| group = sync.group unsynced_logins = User .where(id: group.user_ids) .where.not(id: sync.users.select(:user_id)) .pluck(:login) if unsynced_logins.any? puts "In group #{group}, #{unsynced_logins.count} user(s) exist that are not synced from LDAP:" puts unsynced_logins.join(", ") end end end核心比对逻辑清晰可读:
- 通过
LdapGroups::SynchronizedGroup.includes(:group).find_each遍历所有同步组(find_each分批加载,避免内存暴涨); - 对每个同步组,取出内部组全部用户 ID(
group.user_ids)与同步成员 ID(sync.users.select(:user_id)); - 用
where.not求差集,得到“在组内但不在同步记录中”的用户; - 打印组名、残留用户数量与登录名列表(逗号分隔)。
输出解读
- 若所有同步组状态健康,命令不输出任何组信息(仅静默完成);
- 若存在残留,输出格式为:
In group <组名>, <数量> user(s) exist that are not synced from LDAP: <login1>, <login2>, ...管理员应逐一核对输出的登录名:确认这些用户是否属于 LDAP 中已删除、但 OpenProject 侧未清理的情况,再决定手动从组中移除。
使用注意事项
- 只读诊断:该任务仅打印信息,不会修改任何数据,可安全在生产环境重复执行;
- 依赖环境:需要应用数据库可访问且 Rails 环境正常加载(
task ... :environment会引导 Rails 启动); - 适用范围:仅覆盖已配置同步的组(即存在
LdapGroups::SynchronizedGroup记录的组),手动创建的普通 OpenProject 组不在此列; - 手动清理:确认无误后,可在 OpenProject 管理界面中从对应组移除这些用户,或根据实际需要将其恢复为同步成员。
同步机制的完整链路(源码级)
为理解该缺陷的影响范围,有必要梳理 ldap_groups 模块的同步全貌。顶层入口为 synchronization_service.rb,执行顺序如下:
SynchronizationService.synchronize!以系统用户(User.system.run_given)身份执行同步,保证权限一致;- 遍历所有 LDAP 认证源(
LdapAuthSource.find_each); - 对每个认证源,先通过
SynchronizeFilterService依据同步过滤器(SynchronizedFilter)发现/创建目录中的组; - 再调用
SynchronizeGroupsService逐个组同步成员(即缺陷所在的synchronize_members链路); - 任一环节异常均记录
Rails.logger.error,不中断其他组的同步。
成员发现支持两种模式(见 synchronize_groups_service.rb 第 101-109 行):
- 反向查找(reverse lookup):默认模式,按
memberOf=<组DN>过滤 LDAP 用户(适用于 Active Directory 与启用 memberof overlay 的 OpenLDAP); - 正向查找(forward lookup):直接从组条目读取
member_lookup_attribute(如member、uniqueMember)指向的 DN 列表再逐个解析,适配groupOfNames/groupOfUniqueNames等模式。
成员落库后,同步关系(LdapGroups::Membership)与 OpenProject 内部用户组(Group)通过 synchronized_group.rb 的add_members!/remove_members!保持联动,事务保证两处状态的一致性。
本次修复的其他问题
#41256:LDAP 中移除的用户未从 OpenProject 组删除
即上述核心缺陷。修复后,同一同步批次内新增成员不再影响被移除成员的判定,LDAP 中删除的用户能够正确地在 OpenProject 组中被同步移除。验证依据可在模块测试中查找,例如 spec/services/synchronization_spec.rb 覆盖了同步服务的成员增删行为。
#43892:向组添加用户时,自动补全显示已有用户
该缺陷属于组管理界面的成员自动补全(autocompleter)问题:在向用户组添加成员时,候选列表中错误地显示了当前组已有的用户。修复后,自动补全会正确排除已是组成员的对象,避免管理员重复添加与界面误导。
升级建议与操作清单
- 升级版本:将 OpenProject 升级至 12.2.5(打包安装执行系统更新;Docker 部署拉取最新镜像并重建容器);
- 运行诊断:按部署方式执行
ldap_groups:print_unsynced_members,采集残留用户清单; - 核对数据:将输出与 LDAP 目录比对,确认残留用户是否已在 LDAP 中被删除;
- 手动清理:对确认应删除的成员,在 OpenProject 管理界面的对应组中手动移除;
- 回归验证:升级后再次触发一次 LDAP 组同步(管理界面操作或执行
bundle exec rake ldap_groups:synchronize),确认新同步周期不再产生残留。
总结
OpenProject 12.2.5 是一次聚焦 LDAP 组同步正确性的补丁发布。对运维与管理员而言,理解缺陷成因(同步批次内成员集合替换)与掌握ldap_groups:print_unsynced_members诊断命令,是在升级前后准确评估数据一致性的关键。结合 ldap_groups 模块源码(ldap_groups.rake、synchronize_groups_service.rb、synchronized_group.rb)可以确认:该版本未自动清理历史残留数据,而是提供了安全、只读的排查手段,把最终处置决策留给管理员,这是兼顾正确性与数据安全的设计取舍。
【免费下载链接】openprojectOpenProject is the leading open source project management software for product, project and portfolio management. A powerful Jira alternative with agile planning, issue tracking, roadmaps, Gantt charts, time tracking, collaboration features, and more. Available on premises or in the cloud. ⭐ Star us on GitHub项目地址: https://gitcode.com/GitHub_Trending/op/openproject
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考