Skip to content

<feature>[header]: add AccountVO.source#4018

Closed
ZStack-Robot wants to merge 1 commit into
zsv_5.1.0from
sync/wenhao.zhang/zsv-ldap@@2
Closed

<feature>[header]: add AccountVO.source#4018
ZStack-Robot wants to merge 1 commit into
zsv_5.1.0from
sync/wenhao.zhang/zsv-ldap@@2

Conversation

@ZStack-Robot

Copy link
Copy Markdown
Collaborator

Resolves: ZSV-12269

Change-Id: I7a6c61667a6874657376736b70767a68767a7862

sync from gitlab !9912

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

本 PR 引入 AccountSource 枚举并在模型、数据库迁移、账户创建/导入/LDAP 同步和 RBAC 初始化中加入或回填 source 字段,同时弃用 AccountType.ThirdParty,统一将其转为 Normal 并以 source 区分来源类型。

变更内容

Account Source 数据模型与暴露层

Layer / File(s) Summary
数据模型、枚举与 SDK/文档暴露
header/src/main/java/org/zstack/header/identity/AccountSource.java, header/src/main/java/org/zstack/header/identity/AccountType.java, header/src/main/java/org/zstack/header/identity/AccountVO.java, header/src/main/java/org/zstack/header/identity/AccountVO_.java, header/src/main/java/org/zstack/header/identity/CreateAccountMsg.java, header/src/main/java/org/zstack/header/identity/AccountInventory.java, header/src/main/java/org/zstack/header/identity/AccountInventoryDoc_zh_cn.groovy, sdk/src/main/java/org/zstack/sdk/AccountInventory.java
新增 AccountSource 枚举(Local、OpenLdap、WindowsAD、CAS、OAuth2、ZCenter)及 LDAP 服务器类型映射方法;AccountVO 与 CreateAccountMsg 新增 source 字段(持久化为枚举列);AccountInventory header 与 SDK 版本新增 source 字段并暴露访问方法;AccountType.ThirdParty 标注弃用;Inventory 文档补充 source 字段描述。
数据库迁移与约束/回填
conf/db/zsv/V5.1.0__schema.sql
新增 TpmKeyBackupVO 表;清理 EncryptedResourceKeyRefVO 孤立引用并添加外键约束;为 AccountVO 新增 source 列(varchar(32),NOT NULL,默认 Local);通过与 LDAP/第三方源引用表联表,按服务器类型回填 source(LDAP→OpenLdap/WindowsAD,CAS/OAuth2→对应类型),同时将原 ThirdParty 类型账户统一更新为 Normal。
账户创建、校验与初始化
identity/src/main/java/org/zstack/identity/AccountManagerImpl.java
创建流程解析 CreateAccountMsg 中的 type 与 source 参数,拒绝已弃用的 ThirdParty 类型、强制校验 source 必填且合法(否则抛出包含允许值列表的错误);初始化系统管理员时显式设置 source=Local;账户类型变更校验禁止变更为 ThirdParty。

账户导入与第三方源集成

Layer / File(s) Summary
导入规范与上下文模型
plugin/account-import/src/main/java/org/zstack/identity/imports/header/ImportAccountSpec.java, plugin/account-import/src/main/java/org/zstack/identity/imports/source/ImportThirdPartyAccountContext.java
ImportAccountSpec 新增 accountSource 字段及 getter/setter,用于携带源类型信息;ImportThirdPartyAccountContext 新增 accountSource 字段,在导入流程中传递和维护。
账户导入与销毁逻辑
plugin/account-import/src/main/java/org/zstack/identity/imports/source/AbstractAccountSourceBase.java
导入流程补全 ImportThirdPartyAccountContext 的 accountSource;在 createAccount 中规范化 accountType(空→Normal,ThirdParty→Normal)并校验 accountSource 非空(缺失则标记错误并移除 context);创建 CreateAccountMsg 时改用规范化的 type 并设置 source;销毁流程从按 type=ThirdParty 查询改为按 source≠Local 查询,新增专属方法 gatherAccountsForDestroySource 基于 AccountThirdPartyAccountSourceRefVO 关联查询待销毁的导入账户。
LDAP 同步生成 AccountSource
plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java
LDAP 同步中新增 AccountSource 依赖并在构造 ImportAccountSpec 时通过私有方法 toAccountSource 将 LdapServerType 转换为对应的 AccountSource;生成账户时改用 AccountType.Normal;在批次拆分时继续携带同一 accountSource。
RBAC 初始化查询调整
identity/src/main/java/org/zstack/identity/rbac/RBACManagerImpl.java
预置角色初始化时只查询 AccountType.Normal 的账户进行关联,排除已弃用的 ThirdParty 类型。

🎯 4 (Complex) | ⏱️ ~45 minutes

🐰 来源缘变秋意生,AccountType 消亡身化轻,
Local 本地竖新旗,LDAP·CAS·OAuth2 齐心驱,
数库回填一刀切,导入同步守本心。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题简洁准确地概括了本次PR的主要变更:向AccountVO类中添加source字段。
Description check ✅ Passed PR描述虽然简洁,但与变更集相关,包含了issue追踪号(ZSV-12269)和同步来源信息,足以表明这是一个关于账户来源功能的同步更新。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/wenhao.zhang/zsv-ldap@@2

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@conf/db/zsv/V5.1.0__schema.sql`:
- Line 4: The column createDate currently uses a hard-coded default ('1999-12-31
23:59:59'), which breaks time semantics; update the column definition for
createDate in the migration (the `createDate` timestamp NOT NULL line) to use a
dynamic default by replacing the literal with DEFAULT CURRENT_TIMESTAMP while
preserving NOT NULL, and ensure any subsequent DDL in this migration that relies
on that column uses the new CURRENT_TIMESTAMP behavior.

In `@identity/src/main/java/org/zstack/identity/AccountManagerImpl.java`:
- Around line 543-545: 在 AccountManagerImpl 中不要直接调用
AccountSource.valueOf(msg.getSource()),而应对 msg.getSource() 做显式校验并将非法值转换为业务错误:在设置
vo.setSource(...) 的位置使用安全解析(try-catch IllegalArgumentException
或先做枚举包含检查),如果解析失败抛出一个可读的参数错误(包含字段名 "source" 和传入值),例如将异常转换为项目中统一的
API/参数错误异常,从而避免运行时崩溃并返回可理解的错误给调用方。

In `@identity/src/main/java/org/zstack/identity/rbac/RBACManagerImpl.java`:
- Around line 179-182: The current query in RBACManagerImpl that builds
accountUuidList uses only AccountVO_.type == AccountType.Normal, which will
include migrated third‑party accounts now marked as Normal; update the query to
also filter by account source to include only local/native accounts (e.g., add a
condition on AccountVO_.source == <local-source-constant> or the enum/constant
used for local sources) so third‑party-sourced accounts are excluded from preset
role initialization; reference AccountVO, AccountVO_.type, AccountVO_.source,
and AccountType.Normal when making this change.

In `@plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java`:
- Around line 376-379: findGlobalUuidKey() currently calls switch
(taskSpec.getServerType()) without handling null which can throw NPE; mirror
toAccountSource()'s null-safe behavior by first normalizing the server type
(e.g., treat null as LdapServerType.Unknown) or explicitly check
taskSpec.getServerType() == null before the switch and handle that branch,
ensuring findGlobalUuidKey() uses LdapServerType.Unknown (or the same fallback
used by toAccountSource()) when server type is null.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9d788275-26c1-453c-a9eb-6bac5eba3718

📥 Commits

Reviewing files that changed from the base of the PR and between ad7aae2 and a7eaf69.

📒 Files selected for processing (13)
  • conf/db/zsv/V5.1.0__schema.sql
  • header/src/main/java/org/zstack/header/identity/AccountInventory.java
  • header/src/main/java/org/zstack/header/identity/AccountInventoryDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/identity/AccountSource.java
  • header/src/main/java/org/zstack/header/identity/AccountType.java
  • header/src/main/java/org/zstack/header/identity/AccountVO.java
  • header/src/main/java/org/zstack/header/identity/AccountVO_.java
  • header/src/main/java/org/zstack/header/identity/CreateAccountMsg.java
  • identity/src/main/java/org/zstack/identity/AccountManagerImpl.java
  • identity/src/main/java/org/zstack/identity/rbac/RBACManagerImpl.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/header/ImportAccountItem.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/source/AbstractAccountSourceBase.java
  • plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java

CREATE TABLE IF NOT EXISTS `zstack`.`TpmKeyBackupVO` (
`uuid` char(32) NOT NULL UNIQUE,
`lastOpDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

createDate 使用固定历史时间会导致数据时间语义失真。

这里把 createDate 默认值写死为 1999-12-31 23:59:59,会让新插入记录的创建时间不准确,影响审计、排序和排障。建议改为 DEFAULT CURRENT_TIMESTAMP

💡建议修改
-    `createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59',
+    `createDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,

As per coding guidelines **/*.sql: “Do not use DEFAULT 0000-00-00 00:00:00 , use DEFAULT CURRENT_TIMESTAMP instead”.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`createDate` timestamp NOT NULL DEFAULT '1999-12-31 23:59:59',
`createDate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@conf/db/zsv/V5.1.0__schema.sql` at line 4, The column createDate currently
uses a hard-coded default ('1999-12-31 23:59:59'), which breaks time semantics;
update the column definition for createDate in the migration (the `createDate`
timestamp NOT NULL line) to use a dynamic default by replacing the literal with
DEFAULT CURRENT_TIMESTAMP while preserving NOT NULL, and ensure any subsequent
DDL in this migration that relies on that column uses the new CURRENT_TIMESTAMP
behavior.

Comment thread identity/src/main/java/org/zstack/identity/AccountManagerImpl.java Outdated
Comment on lines 179 to 182
List<String> accountUuidList = Q.New(AccountVO.class)
.in(AccountVO_.type, list(AccountType.Normal, AccountType.ThirdParty))
.in(AccountVO_.type, AccountType.Normal)
.select(AccountVO_.uuid)
.listValues();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

type=Normal 过滤在迁移后会误纳入第三方来源账户

Line 180 现在只按 AccountType.Normal 过滤;但本次迁移会把历史第三方账户也转成 Normal,这会让这些账户重新被纳入预置角色初始化,和“排除第三方账户”的目标冲突。建议在这里增加 source 条件做排除(例如仅纳入本地来源账户)。

建议修复(示意)
                     List<String> accountUuidList = Q.New(AccountVO.class)
-                            .in(AccountVO_.type, AccountType.Normal)
+                            .eq(AccountVO_.type, AccountType.Normal)
+                            .eq(AccountVO_.source, AccountSource.Local)
                             .select(AccountVO_.uuid)
                             .listValues();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
List<String> accountUuidList = Q.New(AccountVO.class)
.in(AccountVO_.type, list(AccountType.Normal, AccountType.ThirdParty))
.in(AccountVO_.type, AccountType.Normal)
.select(AccountVO_.uuid)
.listValues();
List<String> accountUuidList = Q.New(AccountVO.class)
.eq(AccountVO_.type, AccountType.Normal)
.eq(AccountVO_.source, AccountSource.Local)
.select(AccountVO_.uuid)
.listValues();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@identity/src/main/java/org/zstack/identity/rbac/RBACManagerImpl.java` around
lines 179 - 182, The current query in RBACManagerImpl that builds
accountUuidList uses only AccountVO_.type == AccountType.Normal, which will
include migrated third‑party accounts now marked as Normal; update the query to
also filter by account source to include only local/native accounts (e.g., add a
condition on AccountVO_.source == <local-source-constant> or the enum/constant
used for local sources) so third‑party-sourced accounts are excluded from preset
role initialization; reference AccountVO, AccountVO_.type, AccountVO_.source,
and AccountType.Normal when making this change.

Comment on lines +376 to +379
private AccountSource toAccountSource(LdapServerType serverType) {
return AccountSource.fromLdapServerTypeName(
serverType == null ? LdapServerType.Unknown.name() : serverType.name());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="$(fd -i 'LdapSyncHelper.java' | head -n 1)"
echo "Inspect file: $file"
nl -ba "$file" | sed -n '372,410p'

python - <<'PY'
from pathlib import Path
p = next(Path('.').rglob('LdapSyncHelper.java'))
text = p.read_text(encoding='utf-8')

print("contains toAccountSource null fallback:",
      "serverType == null ? LdapServerType.Unknown.name() : serverType.name()" in text)

anchor = "public String findGlobalUuidKey()"
idx = text.find(anchor)
snippet = text[idx: idx + 320] if idx != -1 else ""
print("findGlobalUuidKey has explicit null guard:",
      "taskSpec.getServerType() == null" in snippet)
print("findGlobalUuidKey uses switch(taskSpec.getServerType()):",
      "switch (taskSpec.getServerType())" in snippet)
PY

Repository: MatheMatrix/zstack

Length of output: 207


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java"
echo "=== Examining file: $file ==="
echo ""
echo "=== Lines 372-410 (code context) ==="
sed -n '372,410p' "$file" | cat -n

echo ""
echo "=== Python verification ==="
python3 <<'PY'
from pathlib import Path

p = Path("plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java")
text = p.read_text(encoding='utf-8')
lines = text.split('\n')

# Check toAccountSource null fallback
print("1. Checking toAccountSource() method:")
for i, line in enumerate(lines[375:380], start=376):
    print(f"   Line {i}: {line}")

# Check findGlobalUuidKey null guard
print("\n2. Checking findGlobalUuidKey() method:")
for i, line in enumerate(lines):
    if "public String findGlobalUuidKey()" in line:
        print(f"   Found at line {i+1}")
        # Print 20 lines from this point
        for j in range(20):
            if i+j < len(lines):
                print(f"   Line {i+j+1}: {lines[i+j]}")
        break

# Verify the specific conditions
print("\n3. Verification results:")
print(f"   - toAccountSource has null fallback: {'serverType == null ?' in text}")
print(f"   - findGlobalUuidKey has null guard: {'findGlobalUuidKey' in text and 'taskSpec.getServerType() == null' in text[text.find('findGlobalUuidKey'):text.find('findGlobalUuidKey')+1000] if 'findGlobalUuidKey' in text else False}")

# Find the actual switch statement context
idx = text.find("public String findGlobalUuidKey()")
if idx != -1:
    snippet = text[idx:idx+800]
    print(f"   - Uses switch(taskSpec.getServerType()): {'switch (taskSpec.getServerType())' in snippet}")
    print(f"   - Has null guard before switch: {'== null' in snippet[:300]}")
PY

Repository: MatheMatrix/zstack

Length of output: 3800


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java"

echo "=== Looking for taskSpec field definition ==="
grep -n "taskSpec" "$file" | head -20

echo ""
echo "=== Looking for TaskSpec class and getServerType method ==="
fd "TaskSpec.java" -t f

echo ""
echo "=== Checking taskSpec initialization/assignment in the file ==="
grep -n "taskSpec\s*=" "$file" | head -10

echo ""
echo "=== Searching for getServerType() definition in codebase ==="
rg "getServerType\(\)" -A 2 -B 2 --max-count 10

Repository: MatheMatrix/zstack

Length of output: 13375


补齐 findGlobalUuidKey() 中的空值检查,与 toAccountSource() 保持一致的 null 兜底处理。

toAccountSource() 已对 serverType == null 进行兜底处理,但 findGlobalUuidKey() 在第 399 行直接使用 switch (taskSpec.getServerType()) 而无空值检查。由于 LdapServerType 在更新时可被设置为 null(见 LdapManagerImpl 的 spec.setServerType(msg.getServerType() == null ? null : ...)),当 getServerType() 返回 null 时会直接触发 NullPointerException,导致 LDAP 同步提前失败。

建议在 switch 前添加 null 检查,与 toAccountSource() 保持一致的兜底处理策略。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java` around
lines 376 - 379, findGlobalUuidKey() currently calls switch
(taskSpec.getServerType()) without handling null which can throw NPE; mirror
toAccountSource()'s null-safe behavior by first normalizing the server type
(e.g., treat null as LdapServerType.Unknown) or explicitly check
taskSpec.getServerType() == null before the switch and handle that branch,
ensuring findGlobalUuidKey() uses LdapServerType.Unknown (or the same fallback
used by toAccountSource()) when server type is null.

@MatheMatrix MatheMatrix force-pushed the sync/wenhao.zhang/zsv-ldap@@2 branch from a7eaf69 to 00088b2 Compare May 19, 2026 08:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@header/src/main/java/org/zstack/header/identity/AccountSource.java`:
- Around line 19-24: The method AccountSource.fromLdapServerTypeName currently
maps any null or unknown serverType to WindowsAD causing silent mislabeling;
change it to explicitly handle known values only by comparing serverType to
OpenLdap.name() and WindowsAD.name(), and throw an IllegalArgumentException (or
return null if preferred by project convention) when serverType is null or does
not match either OpenLdap or WindowsAD so unknown inputs fail fast instead of
being mapped to WindowsAD; update the method
AccountSource.fromLdapServerTypeName to perform those explicit checks and raise
the error using clear messaging that includes the original serverType.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5b2ae346-48ac-40a6-873e-8abc27fe96f1

📥 Commits

Reviewing files that changed from the base of the PR and between a7eaf69 and 00088b2.

📒 Files selected for processing (13)
  • conf/db/zsv/V5.1.0__schema.sql
  • header/src/main/java/org/zstack/header/identity/AccountInventory.java
  • header/src/main/java/org/zstack/header/identity/AccountInventoryDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/identity/AccountSource.java
  • header/src/main/java/org/zstack/header/identity/AccountType.java
  • header/src/main/java/org/zstack/header/identity/AccountVO.java
  • header/src/main/java/org/zstack/header/identity/AccountVO_.java
  • header/src/main/java/org/zstack/header/identity/CreateAccountMsg.java
  • identity/src/main/java/org/zstack/identity/AccountManagerImpl.java
  • identity/src/main/java/org/zstack/identity/rbac/RBACManagerImpl.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/header/ImportAccountItem.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/source/AbstractAccountSourceBase.java
  • plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java
🚧 Files skipped from review as they are similar to previous changes (12)
  • header/src/main/java/org/zstack/header/identity/AccountVO_.java
  • header/src/main/java/org/zstack/header/identity/AccountInventory.java
  • identity/src/main/java/org/zstack/identity/rbac/RBACManagerImpl.java
  • header/src/main/java/org/zstack/header/identity/CreateAccountMsg.java
  • plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/header/ImportAccountItem.java
  • header/src/main/java/org/zstack/header/identity/AccountInventoryDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/identity/AccountType.java
  • conf/db/zsv/V5.1.0__schema.sql
  • header/src/main/java/org/zstack/header/identity/AccountVO.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/source/AbstractAccountSourceBase.java
  • identity/src/main/java/org/zstack/identity/AccountManagerImpl.java

Comment on lines +19 to +24
public static AccountSource fromLdapServerTypeName(@Nullable String serverType) {
if (OpenLdap.name().equals(serverType)) {
return OpenLdap;
}
return WindowsAD;
}

@coderabbitai coderabbitai Bot May 19, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

避免将未知/空 LDAP 类型静默映射为 WindowsAD

当前逻辑会把 null 或任意未知 serverType 都写成 WindowsAD,会造成来源字段误标。建议显式匹配 WindowsAD,其余输入直接失败快。

建议修改
 public static AccountSource fromLdapServerTypeName(`@Nullable` String serverType) {
-    if (OpenLdap.name().equals(serverType)) {
-        return OpenLdap;
-    }
-    return WindowsAD;
+    if (OpenLdap.name().equals(serverType)) {
+        return OpenLdap;
+    }
+    if (WindowsAD.name().equals(serverType)) {
+        return WindowsAD;
+    }
+    throw new IllegalArgumentException(String.format("Unsupported LDAP server type: %s", serverType));
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static AccountSource fromLdapServerTypeName(@Nullable String serverType) {
if (OpenLdap.name().equals(serverType)) {
return OpenLdap;
}
return WindowsAD;
}
public static AccountSource fromLdapServerTypeName(`@Nullable` String serverType) {
if (OpenLdap.name().equals(serverType)) {
return OpenLdap;
}
if (WindowsAD.name().equals(serverType)) {
return WindowsAD;
}
throw new IllegalArgumentException(String.format("Unsupported LDAP server type: %s", serverType));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@header/src/main/java/org/zstack/header/identity/AccountSource.java` around
lines 19 - 24, The method AccountSource.fromLdapServerTypeName currently maps
any null or unknown serverType to WindowsAD causing silent mislabeling; change
it to explicitly handle known values only by comparing serverType to
OpenLdap.name() and WindowsAD.name(), and throw an IllegalArgumentException (or
return null if preferred by project convention) when serverType is null or does
not match either OpenLdap or WindowsAD so unknown inputs fail fast instead of
being mapped to WindowsAD; update the method
AccountSource.fromLdapServerTypeName to perform those explicit checks and raise
the error using clear messaging that includes the original serverType.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zstack-robot-1,感谢 wenhao.zhang 的说明。了解了——由于 LDAP 侧实际只会传入 null"Unknown",回退到 WindowsAD 是对齐现有默认值的有意设计,不撤回之前的建议,但不再坚持。

为了让后续维护者更容易理解这一回退语义,建议在 return WindowsAD; 前加一行简短注释,例如:

// null or "Unknown" serverType defaults to WindowsAD to align with legacy behavior
return WindowsAD;

这不影响任何逻辑,只是让意图更自解释。采纳与否由团队决定。

(´• ω •`) 🐇


✏️ Learnings added
Learnt from: zstack-robot-1
Repo: MatheMatrix/zstack PR: 4018
File: header/src/main/java/org/zstack/header/identity/AccountSource.java:19-24
Timestamp: 2026-05-19T09:46:14.609Z
Learning: 在 `AccountSource.fromLdapServerTypeName(Nullable String serverType)` 方法中(`header/src/main/java/org/zstack/header/identity/AccountSource.java`),将 `null` 或 `"Unknown"` 的 LDAP serverType 回退映射到 `WindowsAD` 是有意为之的行为,与项目现有默认值保持一致。代码审查中不应将此视为静默误标问题。

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: ZStack-Robot
Repo: MatheMatrix/zstack PR: 3826
File: compute/src/main/java/org/zstack/compute/allocator/HostAllocatorChain.java:261-265
Timestamp: 2026-04-22T04:38:00.864Z
Learning: 在 ZStack 的 `Platform.err()` / `Platform.toRawString()` 调用中,传入用于格式化的 `%s` 参数若实现了 `org.zstack.header.core.I18nMessage`,框架会自动通过 `getDetails()` 取得原始文本、并通过 `getI18nDetails()` 取得国际化文本。因此,在代码审查中不应将“直接把实现了 `I18nMessage` 的对象作为 `%s` 参数传入而可能打印出对象地址/不安全”当作问题;此用法是正确且应当被视为安全的(例如将 `HostCandidate.RejectedCandidate` 这类实现了 `I18nMessage` 的对象作为 `%s` 参数传入)。

@MatheMatrix MatheMatrix force-pushed the sync/wenhao.zhang/zsv-ldap@@2 branch 4 times, most recently from 3e72a5b to a0d38d0 Compare May 19, 2026 16:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
identity/src/main/java/org/zstack/identity/rbac/RBACManagerImpl.java (1)

179-182: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

这里只按 type=Normal 过滤会把迁移后的第三方账户重新纳入预置角色初始化。

本次迁移会把历史 ThirdParty 账户统一改成 Normal,这里只看 type 已经无法排除 LDAP/其他外部来源账户。建议补上 source == AccountSource.Local(或等价的本地来源条件),否则这两个预置角色会错误关联到非本地账户。

建议修复
                     List<String> accountUuidList = Q.New(AccountVO.class)
                             .eq(AccountVO_.type, AccountType.Normal)
+                            .eq(AccountVO_.source, AccountSource.Local)
                             .select(AccountVO_.uuid)
                             .listValues();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@identity/src/main/java/org/zstack/identity/rbac/RBACManagerImpl.java` around
lines 179 - 182, The current Account UUID query in RBACManagerImpl (the
accountUuidList built with Q.New(AccountVO.class) and filters on
AccountVO_.type) only filters by AccountType.Normal and will include migrated
third-party accounts; update the query to also restrict to local accounts by
adding a condition on AccountVO_.source == AccountSource.Local (or equivalent
local-source check) so only truly local accounts are returned when initializing
the preset roles.
plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java (1)

377-379: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

serverType 的空值语义还没有闭环。

这里已经把 null 作为 toAccountSource() 的合法输入处理了,但后续 buildReturnAttribute() 仍会走到 Line 399 的 switch (taskSpec.getServerType())。一旦 serverType 为空,LDAP 同步会在真正发起查询前直接触发 NullPointerException。建议先把 serverType 统一归一化,再同时用于 source 映射和 globalUuidKey 选择。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java` around
lines 377 - 379, Normalize null serverType early in the flow (before calling
toAccountSource and before buildReturnAttribute) so the same non-null value is
used for both AccountSource.fromLdapServerTypeName(...) and the globalUuidKey
selection; specifically, ensure taskSpec.getServerType() is replaced or assigned
to a normalized LdapServerType variable (e.g., resolvedServerType) and use that
variable when calling toAccountSource(...) and inside the switch in
buildReturnAttribute (the switch that currently uses taskSpec.getServerType()),
guaranteeing no NullPointerException and consistent mapping between source and
globalUuidKey.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@identity/src/main/java/org/zstack/identity/rbac/RBACManagerImpl.java`:
- Around line 179-182: The current Account UUID query in RBACManagerImpl (the
accountUuidList built with Q.New(AccountVO.class) and filters on
AccountVO_.type) only filters by AccountType.Normal and will include migrated
third-party accounts; update the query to also restrict to local accounts by
adding a condition on AccountVO_.source == AccountSource.Local (or equivalent
local-source check) so only truly local accounts are returned when initializing
the preset roles.

In `@plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java`:
- Around line 377-379: Normalize null serverType early in the flow (before
calling toAccountSource and before buildReturnAttribute) so the same non-null
value is used for both AccountSource.fromLdapServerTypeName(...) and the
globalUuidKey selection; specifically, ensure taskSpec.getServerType() is
replaced or assigned to a normalized LdapServerType variable (e.g.,
resolvedServerType) and use that variable when calling toAccountSource(...) and
inside the switch in buildReturnAttribute (the switch that currently uses
taskSpec.getServerType()), guaranteeing no NullPointerException and consistent
mapping between source and globalUuidKey.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cae5c4e0-9d57-44c1-a123-7404b9ed8e71

📥 Commits

Reviewing files that changed from the base of the PR and between 3e72a5b and a0d38d0.

📒 Files selected for processing (15)
  • conf/db/zsv/V5.1.0__schema.sql
  • header/src/main/java/org/zstack/header/identity/AccountInventory.java
  • header/src/main/java/org/zstack/header/identity/AccountInventoryDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/identity/AccountSource.java
  • header/src/main/java/org/zstack/header/identity/AccountType.java
  • header/src/main/java/org/zstack/header/identity/AccountVO.java
  • header/src/main/java/org/zstack/header/identity/AccountVO_.java
  • header/src/main/java/org/zstack/header/identity/CreateAccountMsg.java
  • identity/src/main/java/org/zstack/identity/AccountManagerImpl.java
  • identity/src/main/java/org/zstack/identity/rbac/RBACManagerImpl.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/header/ImportAccountSpec.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/source/AbstractAccountSourceBase.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/source/ImportThirdPartyAccountContext.java
  • plugin/ldap/src/main/java/org/zstack/ldap/compute/LdapSyncHelper.java
  • sdk/src/main/java/org/zstack/sdk/AccountInventory.java
✅ Files skipped from review due to trivial changes (1)
  • header/src/main/java/org/zstack/header/identity/AccountType.java
🚧 Files skipped from review as they are similar to previous changes (12)
  • sdk/src/main/java/org/zstack/sdk/AccountInventory.java
  • header/src/main/java/org/zstack/header/identity/CreateAccountMsg.java
  • header/src/main/java/org/zstack/header/identity/AccountVO_.java
  • header/src/main/java/org/zstack/header/identity/AccountVO.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/header/ImportAccountSpec.java
  • header/src/main/java/org/zstack/header/identity/AccountInventoryDoc_zh_cn.groovy
  • header/src/main/java/org/zstack/header/identity/AccountSource.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/source/ImportThirdPartyAccountContext.java
  • identity/src/main/java/org/zstack/identity/AccountManagerImpl.java
  • plugin/account-import/src/main/java/org/zstack/identity/imports/source/AbstractAccountSourceBase.java
  • conf/db/zsv/V5.1.0__schema.sql
  • header/src/main/java/org/zstack/header/identity/AccountInventory.java

@zstack-robot-2

Copy link
Copy Markdown
Collaborator

Comment from yaohua.wu:

Review: MR !9912 — ZSV-12269

总体评价

此 MR 是 ZCenter Account 重构第一轮的基础设施层,整体方向与 Jira 设计文档一致:

  • 拆分 AccountVO.type(角色)与新增 AccountVO.source(来源,不可变)
  • 升级脚本将历史 ThirdParty 账号迁移为 Normal + source(LDAP/SSO)
  • LDAP/导入路径同步切换写入 Normal + source
  • 保留 AccountType.ThirdParty 枚举并标记 @Deprecated,避免旧 SDK 立即破坏

设计实现紧贴方案,无明显架构偏离。下面列出需要修复或确认的几处问题。

关联 MR

  • premium !13960 — 下游消费层(IAM1 入组、CAS/OAuth2 登录、OAuth2 token 刷新、所有相关测试用例)。两个 MR 共用 zsv-ldap@@2 源分支,必须同时合入 zsv_5.1.0。premium 侧的 OAuth2LoginIAM1OAuth2ManagerImpl.checkAuth 强依赖本 MR 的 V5.1.0__schema.sql 回填,否则历史 OAuth2 账号将查不到。

🔴 Critical

# File:Line Issue Fix
1 identity/.../AccountManagerImpl.java:540-548 AccountSource.valueOf(msg.getSource())msg.getSource()null(cloudbus 反序列化未填字段时可能发生),抛出 NPE,而不是被下面的 catch (IllegalArgumentException) 捕获并转成友好的 operr。日志会变成裸 NPE,且不会带 allowed.values 提示。 valueOf 之前显式判空,缺省回退到 AccountSource.LocalString src = msg.getSource() == null ? AccountSource.Local.name() : msg.getSource(); 再 valueOf;或者把 catch 改为 catch (IllegalArgumentException | NullPointerException e)

🟡 Warning

# File:Line Issue Fix
2 identity/.../AccountManagerImpl.java:1020-1024 只在 API 拦截层拒绝 type=ThirdParty未拦截 APICreateAccountMsg.source != Local。普通管理员可以通过 API 创建出 source=CAS / OAuth2 / OpenLdap / WindowsAD / ZCenter 的账号,伪装成 SSO/LDAP 同步来的账号,进而通过下游按 source 判定的逻辑(如 premium OAuth2ManagerImpl.checkAuth)走 token 刷新等特殊路径。 API 层增加约束:API 创建账号仅允许 source 为空或 Local;非 Local 来源只能由内部 sync 流通过 CreateAccountMsg 设置。
3 header/.../AccountVO.java:31-35 Jira 设计明确 "source 创建后不可修改",但 @Column 缺少 updatable = false。当前任何代码路径调用 vo.setSource(...) 再 persist 都能改 source,没有 JPA / 数据库层兜底。 改为 @Column(nullable = false, updatable = false);或在 AccountVO.setSource 加注释说明仅供创建路径使用,并在 update 流加 assertion。
4 header/.../AccountSource.java:19-24 fromLdapServerTypeName任何非 OpenLdap 的字符串(包括 null"Unknown"、未来新增的枚举)静默归为 WindowsAD。Jira 设计中 "Unknown → WindowsAD" 仅用于历史迁移,但 LdapSyncHelper.toAccountSource运行时同步路径也调用了此函数(line 379-381),意味着任何 LDAP 服务器类型未知都被打成 WindowsAD,且无日志告警。 拆开两种语义:迁移用 fromLdapServerTypeNameOrDefault(默认 WindowsAD),运行时用严格 fromLdapServerTypeNameIllegalArgumentException 或返回 Optional,由调用方决定。最低限度:当输入非 OpenLdap 且非 WindowsADlogger.warn 记录原始值。
5 plugin/account-import/.../AbstractAccountSourceBase.java:923-934 gatherAccountsForDestroySource 的谓词由 account.type = 'ThirdParty' 改为 account.source != 'Local'语义变宽。原来只删 ThirdParty 账号,现在删任何 source != Local 的账号。若某个 LDAP server 的 ref 表里历史上误绑过 source=CAS/OAuth2/ZCenter 的账号(跨来源 ref),删除该 LDAP server 时会连带删除别处来源的账号。 改为按本 source 类型精确匹配:account.source = :sourceTypeOfThis,由调用方传入对应的 source(如 LDAP 实现传 OpenLdap/WindowsAD,CAS 传 CAS 等)。或至少在删除前 join 一次校验,确认 ref 对应的 source 类型与当前 source 自身一致。
6 plugin/account-import/.../AbstractAccountSourceBase.java:350-362 if (context.accountSource == null) { ... validContexts.remove(context); whileCompletion.done(); return; }While.each 回调里移除当前迭代对象While.each 是串行,但具体 While 实现是否允许回调中改原列表(迭代器/索引)需要确认;若实现是基于索引推进,移除当前元素会让后续元素跳过一个。 用收集模式替代原地修改:先记录所有 error 项到一个临时 list,循环结束后统一从 validContexts 移除;或不动 validContexts,仅在后续 step 跳过 errorForAccountExecution != null 的 context。
7 conf/db/zsv/V5.1.0__schema.sql:13-32 升级脚本是幂等的吗?UPDATE ... SET a.source = ldap.serverType 第一次升级正确;如果脚本被重复执行(如部署回滚后再升级),第二次执行不会出错但也不会有保护——可以接受。但 ALTER TABLE ... ADD COLUMN source 没有 IF NOT EXISTS 防护(取决于 MySQL 版本,5.7 不支持,8.0 才支持),如果升级器对已存在列没有自跳过逻辑,二次执行会失败。 确认 zstack 升级框架已对 ADD COLUMN 做幂等校验;若没有,使用项目里其他 V*.sql 已有的 IF NOT EXISTS 等价 stored procedure 写法。
8 conf/db/zsv/V5.1.0__schema.sql:19-29 两条 UPDATE 都用 INNER JOIN 到 AccountThirdPartyAccountSourceRefVO只覆盖了通过 ref 绑定的历史账号。如果存在历史 AccountVO.type='ThirdParty' 但 ref 表中无对应行的"孤儿"账号(数据异常但有可能),它们的 source 仍是默认 Local,但 type 被第三条 UPDATE 改成 Normal,会变成"看起来是本地账号但其实是失效的第三方账号"。 升级脚本最后追加一次校验查询,统计 type='ThirdParty' 但无 ref 的孤儿账号数量,写日志或 alert;至少在 release note 里说明如何手工排查这类账号。
9 conf/db/zsv/V5.1.0__schema.sql:1-12 整个文件的行尾从 CRLF 改为 LF,导致整文件 diff(远远超出实际修改)。审查时干扰大,且后续 commit 会反复触发 git 注意行尾。 让 git/编辑器统一行尾后再提交;或单独一个 commit 处理换行符规范化,业务变更隔离开。

🟢 Suggestion

# File:Line Issue Fix
10 header/.../AccountInventory.java:36 inv.setSource(vo.getSource().toString())vo.getSource() 因某些升级残留为 null 会 NPE。 inv.setSource(Optional.ofNullable(vo.getSource()).map(AccountSource::toString).orElse(AccountSource.Local.toString()));
11 header/.../CreateAccountMsg.java:16 字段初始化 private String source = AccountSource.Local.toString(); 在 cloudbus 反序列化场景下可能被覆写为 null(取决于序列化框架是否区分缺省与显式 null)。配合 Critical #1 一起处理。 见 Critical #1
12 header/.../AccountSource.java 缺少 nullUnknown 单元测试 fromLdapServerTypeName 无测试覆盖 null / "Unknown" / 大小写边界 加 unit test 至少覆盖 OpenLdap / WindowsAD / null / "Unknown" / "openldap"(小写)四种情形,提示预期行为。
13 identity/rbac/RBACManagerImpl.java:180 eq(AccountVO_.type, AccountType.Normal) 强依赖升级脚本已经把所有 ThirdParty 改成 Normal。若 RBAC 初始化先于升级脚本(理论上不应该,但需确认),仍可能漏处理历史 ThirdParty 账号。 升级流程的 ordering 确认无问题即可;或在初始化前 assert count(type=ThirdParty) == 0
14 plugin/account-import/.../AbstractAccountSourceBase.java:347-349 if (accountType == AccountType.ThirdParty) { accountType = AccountType.Normal; } 静默吞掉调用方传入的 ThirdParty 值,但没有日志说明本次发生了类型回退。如果有调用方仍按旧 API 传 ThirdParty,外部完全无感知。 logger.warn(...) 或直接拒绝(抛 operr),让所有调用方在第一轮里就强制改完。
15 plugin/ldap/.../LdapSyncHelper.java:379-381 serverType == null ? null : serverType.name() 配合 AccountSource.fromLdapServerTypeName 的静默回退(见 Warning #4),让 null 静默变 WindowsAD。 配合 Warning #4 修复;最低限度此处加日志 logger.warn("ldap serverType is null, defaulting AccountSource to WindowsAD")

Coverage

  • 逻辑文件:12 个(header / identity / plugin/account-import / plugin/ldap / sdk)
  • 配置文件:1 个(V5.1.0__schema.sql
  • 跨仓库依赖:premium MR !13960 依赖本 MR 的 AccountSource 类与升级脚本
  • 第二轮遗留:作者在 Jira ZSV-12269 评论中已明确 OAuth2ManagerImpl#checkAuth 应改为按 AccountThirdPartyAccountSourceRefVO 联查(不靠 source),下轮处理。本轮不强求修复,但需建立 ticket 追踪。

Verdict: REVISION_REQUIRED


🤖 Robot Reviewer

@MatheMatrix

Copy link
Copy Markdown
Owner

Comment from wenhao.zhang:

上面第二点:APICreateAccountMsg 根本没有 source 字段,不存在如下行为:

普通管理员可以通过 API 创建出 source=CAS / OAuth2 / OpenLdap / WindowsAD / ZCenter 的账号,伪装成 SSO/LDAP 同步来的账号

Resolves: ZSV-12269

DBImpact

Change-Id: I7a6c61667a6874657376736b70767a68767a7862
@MatheMatrix MatheMatrix force-pushed the sync/wenhao.zhang/zsv-ldap@@2 branch from a0d38d0 to 1620b5f Compare May 20, 2026 02:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants