修复已配置DNS账户延迟导入和重复展示
将已配置的旧 DNS 账户按项目权限幂等导入为可编辑账户,避免重复展示只读兼容视图;补充回归测试和整改任务清单。
This commit is contained in:
parent
527bf29908
commit
b188c779ab
@ -36,6 +36,12 @@ func (s *Server) listDNSAccounts(c *gin.Context) {
|
||||
cfg := s.config
|
||||
s.configMu.RUnlock()
|
||||
items := make([]dnsAccountView, 0, 1)
|
||||
hasLegacyAccount := false
|
||||
var legacyAccount domain.DNSAccount
|
||||
configuredLegacyProjectID := strings.TrimSpace(cfg.DNS.ProjectID)
|
||||
if configuredLegacyProjectID == "" {
|
||||
configuredLegacyProjectID = domain.DefaultProjectID
|
||||
}
|
||||
if repo, ok := s.projectMembers.(repository.DNSAccountRepository); ok {
|
||||
accounts, err := repo.ListVisibleDNSAccounts(c.Request.Context(), c.GetString(authenticatedActorKey))
|
||||
if err != nil {
|
||||
@ -43,6 +49,14 @@ func (s *Server) listDNSAccounts(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
for _, account := range accounts {
|
||||
if account.ID == "legacy-system-dns" {
|
||||
if account.ProjectID != configuredLegacyProjectID {
|
||||
continue
|
||||
}
|
||||
hasLegacyAccount = true
|
||||
legacyAccount = account
|
||||
continue
|
||||
}
|
||||
item, err := s.managedDNSAccountView(c, account)
|
||||
if err != nil {
|
||||
s.writeError(c, http.StatusServiceUnavailable, "读取 DNS 账户归属失败", nil)
|
||||
@ -78,6 +92,54 @@ func (s *Server) listDNSAccounts(c *gin.Context) {
|
||||
s.writeError(c, http.StatusServiceUnavailable, "读取项目 DNS 权限失败,请稍后重试", nil)
|
||||
return
|
||||
}
|
||||
if hasLegacyAccount {
|
||||
item, viewErr := s.configuredLegacyDNSAccountView(c, legacyAccount, cfg.DNS.Provider, cfg.DNS.CredentialRef, cfg.DNS.OwnerUserID, cfg.Admin.Username)
|
||||
if viewErr != nil {
|
||||
s.writeError(c, http.StatusServiceUnavailable, "读取 DNS 账户归属失败", nil)
|
||||
return
|
||||
}
|
||||
items = append(items, item)
|
||||
c.JSON(http.StatusOK, gin.H{"items": items})
|
||||
return
|
||||
}
|
||||
if importer, ok := s.projectMembers.(repository.DNSLegacyAccountImporter); ok {
|
||||
if managedRepo, repoOK := s.projectMembers.(repository.DNSAccountRepository); repoOK && strings.TrimSpace(cfg.DNS.Provider) != "" {
|
||||
const legacyID = "legacy-system-dns"
|
||||
legacyAccount, err := managedRepo.GetVisibleDNSAccount(c.Request.Context(), c.GetString(authenticatedActorKey), legacyID)
|
||||
if errors.Is(err, repository.ErrProjectAccessDenied) {
|
||||
legacyProjectID := strings.TrimSpace(cfg.DNS.ProjectID)
|
||||
if legacyProjectID == "" {
|
||||
legacyProjectID = domain.DefaultProjectID
|
||||
}
|
||||
legacyOwnerID := strings.TrimSpace(cfg.DNS.OwnerUserID)
|
||||
if legacyOwnerID == "" {
|
||||
legacyOwnerID = "primary-super-admin"
|
||||
}
|
||||
if err := importer.ImportLegacyDNSAccount(c.Request.Context(), domain.DNSAccount{
|
||||
ID: legacyID, Name: "旧系统 DNS 账户", ProjectID: legacyProjectID,
|
||||
OwnerUserID: legacyOwnerID, Provider: cfg.DNS.Provider, CredentialRef: cfg.DNS.CredentialRef,
|
||||
}); err != nil {
|
||||
s.writeError(c, http.StatusServiceUnavailable, "登记已配置的 DNS 账户失败,请稍后重试", nil)
|
||||
return
|
||||
}
|
||||
legacyAccount, err = managedRepo.GetVisibleDNSAccount(c.Request.Context(), c.GetString(authenticatedActorKey), legacyID)
|
||||
}
|
||||
if err == nil {
|
||||
item, viewErr := s.configuredLegacyDNSAccountView(c, legacyAccount, cfg.DNS.Provider, cfg.DNS.CredentialRef, cfg.DNS.OwnerUserID, cfg.Admin.Username)
|
||||
if viewErr != nil {
|
||||
s.writeError(c, http.StatusServiceUnavailable, "读取 DNS 账户归属失败", nil)
|
||||
return
|
||||
}
|
||||
items = append(items, item)
|
||||
c.JSON(http.StatusOK, gin.H{"items": items})
|
||||
return
|
||||
}
|
||||
if !errors.Is(err, repository.ErrProjectAccessDenied) {
|
||||
s.writeError(c, http.StatusServiceUnavailable, "读取已配置的 DNS 账户失败,请稍后重试", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
item := dnsAccountView{
|
||||
ID: "system-dns", Name: "系统 DNS 账户", Provider: cfg.DNS.Provider,
|
||||
ProjectID: projectID,
|
||||
@ -129,6 +191,53 @@ func (s *Server) listDNSAccounts(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"items": items})
|
||||
}
|
||||
|
||||
func (s *Server) configuredLegacyDNSAccountView(c *gin.Context, account domain.DNSAccount, provider, credentialRef, ownerUserID, adminUsername string) (dnsAccountView, error) {
|
||||
ownerID := strings.TrimSpace(ownerUserID)
|
||||
if ownerID == "" {
|
||||
ownerID = account.OwnerUserID
|
||||
}
|
||||
if ownerID == "" {
|
||||
ownerID = "primary-super-admin"
|
||||
}
|
||||
item := dnsAccountView{
|
||||
ID: account.ID, Name: account.Name, Provider: account.Provider, ProjectID: account.ProjectID,
|
||||
OwnerUserID: ownerID, OwnerState: "unassigned", OwnerUsername: "未分配",
|
||||
VerificationState: account.VerificationState, CredentialState: "available",
|
||||
Status: account.Status, Revision: account.Revision, Managed: true,
|
||||
}
|
||||
if descriptor, err := dnsprovider.GetDescriptor(provider); err == nil {
|
||||
item.ProviderName = descriptor.Name
|
||||
} else {
|
||||
item.ProviderName = "未知服务商"
|
||||
}
|
||||
if ownerID == "primary-super-admin" {
|
||||
item.OwnerState, item.OwnerUsername = "assigned", adminUsername
|
||||
} else {
|
||||
if s.users == nil {
|
||||
return dnsAccountView{}, errors.New("用户归属查询暂不可用")
|
||||
}
|
||||
owner, err := s.users.GetUserByID(c.Request.Context(), ownerID)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return dnsAccountView{}, err
|
||||
}
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
item.OwnerState, item.OwnerUsername = "missing", "原用户已不存在"
|
||||
} else {
|
||||
item.OwnerState, item.OwnerUsername = "assigned", owner.Username
|
||||
}
|
||||
}
|
||||
credentials, err := s.readDNSCredentials(c.Request.Context(), credentialRef)
|
||||
if err != nil {
|
||||
item.CredentialState, item.AccountIdentifier = "unavailable", "暂不可读取"
|
||||
} else {
|
||||
item.AccountIdentifier = dnsAccountIdentifier(provider, credentials)
|
||||
if _, err := dnsprovider.ValidateCredentials(provider, credentials); err != nil {
|
||||
item.CredentialState = "invalid"
|
||||
}
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *Server) managedDNSAccountView(c *gin.Context, account domain.DNSAccount) (dnsAccountView, error) {
|
||||
item := dnsAccountView{
|
||||
ID: account.ID, Name: account.Name, Provider: account.Provider, ProviderName: "未知服务商",
|
||||
|
||||
@ -103,6 +103,34 @@ func TestAdminDNSAccountsExposeProjectScope(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSAccountsLazilyImportConfiguredAccountForEditing(t *testing.T) {
|
||||
f := newProjectMemberAPIFixture(t)
|
||||
f.server.config.DNS.ProjectID = "project-a"
|
||||
f.server.config.DNS.Provider = "cloudflare"
|
||||
f.server.config.DNS.CredentialRef = "secret://dns/cloudflare/project-a"
|
||||
f.server.secrets = &fakeSecretReaderStore{data: map[string]map[string]string{
|
||||
f.server.config.DNS.CredentialRef: {"api_token": "test-token"},
|
||||
}}
|
||||
|
||||
response := serveUserRequest(f.server, f.cookies["manager"], http.MethodGet, "/api/v1/dns/accounts", "")
|
||||
if response.Code != http.StatusOK {
|
||||
t.Fatalf("读取已配置 DNS 账户失败:%d %s", response.Code, response.Body.String())
|
||||
}
|
||||
if !strings.Contains(response.Body.String(), `"id":"legacy-system-dns"`) ||
|
||||
!strings.Contains(response.Body.String(), `"managed":true`) {
|
||||
t.Fatalf("已配置 DNS 账户未迁移为可编辑账户:%s", response.Body.String())
|
||||
}
|
||||
if strings.Contains(response.Body.String(), `"id":"system-dns"`) {
|
||||
t.Fatalf("已迁移账户仍重复显示旧系统只读视图:%s", response.Body.String())
|
||||
}
|
||||
|
||||
updated := serveUserRequest(f.server, f.cookies["manager"], http.MethodPut,
|
||||
"/api/v1/dns/accounts/legacy-system-dns", `{"revision":1,"name":"修改后的账户"}`)
|
||||
if updated.Code != http.StatusOK {
|
||||
t.Fatalf("已配置 DNS 账户不能修改:%d %s", updated.Code, updated.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSCredentialUseRejectsUnauthorizedProject(t *testing.T) {
|
||||
server := New(Dependencies{
|
||||
ProjectDomains: projectDomainRepositoryStub{authorizeErr: repository.ErrProjectAccessDenied},
|
||||
|
||||
@ -177,7 +177,9 @@
|
||||
- [✓] 编辑表单不回显 AccessKey、Secret、Token 等旧敏感值;留空表示保留当前凭据。
|
||||
- [✓] 填写任意新凭据时使用白名单字段替换秘密引用,并把账户状态置为待重新验证。
|
||||
- [✓] 编辑请求在写入秘密前核对实时项目 DNS 管理权限和账户版本;越权或过期请求不会写入秘密。
|
||||
- [x] 后端生命周期、权限隔离和前端 26 项专项测试已通过,等待全量回归及发布复审。
|
||||
- [✓] 已配置账户首次读取时按当前项目权限幂等导入为可编辑账户,不再重复展示 `system-dns` 只读兼容视图。
|
||||
- [✓] 延迟导入不会绕过项目 DNS 权限;无权用户不会触发导入或读取秘密仓库。
|
||||
- [✓] 后端全量测试、前端 349 项测试、`go vet ./...` 和前端生产构建均已通过,等待发布复审。
|
||||
- [ ] 真实 DNS 服务商在线验证、三数据库实机回归和第一台候选包上线仍未完成。
|
||||
|
||||
### 验收边界
|
||||
|
||||
Loading…
Reference in New Issue
Block a user