feat(identity): 增加统一认证管理接口
提供接入码配对、配置查询、本地策略修改、验证、激活、回滚、禁用和公开运行时状态接口。写操作强制 Idempotency-Key 与 If-Match,后台 Exchange 支持重启恢复和过期收敛,并记录脱敏 Trace 与审计。\n\n验证:go test 相关 identity、store、identityruntime、httpapi 包;go vet ./apps/api/...
This commit is contained in:
@@ -18,8 +18,8 @@ type Repository interface {
|
||||
ActiveIdentityConfigurationRevision(context.Context) (identity.Revision, error)
|
||||
MarkIdentityRevisionValidated(context.Context, string, int64, string, string) (identity.Revision, error)
|
||||
MarkIdentityRevisionFailed(context.Context, string, int64, string, string, string) (identity.Revision, error)
|
||||
ActivateIdentityRevision(context.Context, string, int64) (identity.Revision, bool, error)
|
||||
DisableActiveIdentityRevision(context.Context, int64) (identity.Revision, error)
|
||||
ActivateIdentityRevision(context.Context, string, int64, string, string) (identity.Revision, bool, error)
|
||||
DisableActiveIdentityRevision(context.Context, int64, string, string) (identity.Revision, error)
|
||||
}
|
||||
|
||||
type Builder interface {
|
||||
@@ -96,7 +96,7 @@ func (manager *Manager) Validate(ctx context.Context, id string, expectedVersion
|
||||
return manager.repository.MarkIdentityRevisionValidated(ctx, id, expectedVersion, traceID, auditID)
|
||||
}
|
||||
|
||||
func (manager *Manager) Activate(ctx context.Context, id string, expectedVersion int64) (identity.Revision, error) {
|
||||
func (manager *Manager) Activate(ctx context.Context, id string, expectedVersion int64, traceID, auditID string) (identity.Revision, error) {
|
||||
manager.operation.Lock()
|
||||
defer manager.operation.Unlock()
|
||||
revision, err := manager.repository.IdentityConfigurationRevision(ctx, id)
|
||||
@@ -110,7 +110,7 @@ func (manager *Manager) Activate(ctx context.Context, id string, expectedVersion
|
||||
if err != nil {
|
||||
return identity.Revision{}, err
|
||||
}
|
||||
activated, _, err := manager.repository.ActivateIdentityRevision(ctx, id, expectedVersion)
|
||||
activated, _, err := manager.repository.ActivateIdentityRevision(ctx, id, expectedVersion, traceID, auditID)
|
||||
if err != nil {
|
||||
candidate.Close()
|
||||
return identity.Revision{}, err
|
||||
@@ -121,10 +121,40 @@ func (manager *Manager) Activate(ctx context.Context, id string, expectedVersion
|
||||
return activated, nil
|
||||
}
|
||||
|
||||
func (manager *Manager) Disable(ctx context.Context, expectedVersion int64) (identity.Revision, error) {
|
||||
func (manager *Manager) Rollback(ctx context.Context, id string, expectedVersion int64, traceID, auditID string) (identity.Revision, error) {
|
||||
manager.operation.Lock()
|
||||
defer manager.operation.Unlock()
|
||||
disabled, err := manager.repository.DisableActiveIdentityRevision(ctx, expectedVersion)
|
||||
revision, err := manager.repository.IdentityConfigurationRevision(ctx, id)
|
||||
if err != nil {
|
||||
return identity.Revision{}, err
|
||||
}
|
||||
if revision.Version != expectedVersion || revision.State != identity.RevisionSuperseded {
|
||||
return identity.Revision{}, identity.ErrRevisionConflict
|
||||
}
|
||||
candidate, err := manager.builder.Build(ctx, revision)
|
||||
if err != nil {
|
||||
return identity.Revision{}, err
|
||||
}
|
||||
validated, err := manager.repository.MarkIdentityRevisionValidated(ctx, id, expectedVersion, traceID, auditID)
|
||||
if err != nil {
|
||||
candidate.Close()
|
||||
return identity.Revision{}, err
|
||||
}
|
||||
activated, _, err := manager.repository.ActivateIdentityRevision(ctx, id, validated.Version, traceID, auditID)
|
||||
if err != nil {
|
||||
candidate.Close()
|
||||
return identity.Revision{}, err
|
||||
}
|
||||
candidate.Revision = activated
|
||||
old := manager.current.Swap(candidate)
|
||||
retireRuntime(old)
|
||||
return activated, nil
|
||||
}
|
||||
|
||||
func (manager *Manager) Disable(ctx context.Context, expectedVersion int64, traceID, auditID string) (identity.Revision, error) {
|
||||
manager.operation.Lock()
|
||||
defer manager.operation.Unlock()
|
||||
disabled, err := manager.repository.DisableActiveIdentityRevision(ctx, expectedVersion, traceID, auditID)
|
||||
if err != nil {
|
||||
return identity.Revision{}, err
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (f *runtimeRepositoryFake) MarkIdentityRevisionFailed(_ context.Context, id
|
||||
f.revisions[id] = revision
|
||||
return revision, nil
|
||||
}
|
||||
func (f *runtimeRepositoryFake) ActivateIdentityRevision(_ context.Context, id string, expected int64) (identity.Revision, bool, error) {
|
||||
func (f *runtimeRepositoryFake) ActivateIdentityRevision(_ context.Context, id string, expected int64, traceID, auditID string) (identity.Revision, bool, error) {
|
||||
f.activateCalled = true
|
||||
revision := f.revisions[id]
|
||||
if revision.Version != expected || revision.State != identity.RevisionValidated {
|
||||
@@ -56,16 +56,16 @@ func (f *runtimeRepositoryFake) ActivateIdentityRevision(_ context.Context, id s
|
||||
old.State = identity.RevisionSuperseded
|
||||
f.revisions[old.ID] = old
|
||||
}
|
||||
revision.State, revision.Version = identity.RevisionActive, revision.Version+1
|
||||
revision.State, revision.Version, revision.LastTraceID, revision.LastAuditID = identity.RevisionActive, revision.Version+1, traceID, auditID
|
||||
f.active, f.revisions[id] = revision, revision
|
||||
return revision, true, nil
|
||||
}
|
||||
func (f *runtimeRepositoryFake) DisableActiveIdentityRevision(_ context.Context, expected int64) (identity.Revision, error) {
|
||||
func (f *runtimeRepositoryFake) DisableActiveIdentityRevision(_ context.Context, expected int64, traceID, auditID string) (identity.Revision, error) {
|
||||
if f.active.Version != expected {
|
||||
return identity.Revision{}, identity.ErrRevisionConflict
|
||||
}
|
||||
disabled := f.active
|
||||
disabled.State, disabled.Version = identity.RevisionSuperseded, disabled.Version+1
|
||||
disabled.State, disabled.Version, disabled.LastTraceID, disabled.LastAuditID = identity.RevisionSuperseded, disabled.Version+1, traceID, auditID
|
||||
f.revisions[disabled.ID] = disabled
|
||||
f.active = identity.Revision{}
|
||||
return disabled, nil
|
||||
@@ -110,7 +110,7 @@ func TestActivationSwapsRuntimeOnlyAfterRepositoryActivation(t *testing.T) {
|
||||
manager := NewManager(repository, &runtimeBuilderFake{})
|
||||
manager.current.Store(&Runtime{Revision: active})
|
||||
|
||||
activated, err := manager.Activate(context.Background(), candidate.ID, candidate.Version)
|
||||
activated, err := manager.Activate(context.Background(), candidate.ID, candidate.Version, "trace", "audit")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -118,3 +118,21 @@ func TestActivationSwapsRuntimeOnlyAfterRepositoryActivation(t *testing.T) {
|
||||
t.Fatalf("activation order failed: activated=%#v current=%#v", activated, manager.Current())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRollbackValidatesAndAtomicallyReplacesActiveRuntime(t *testing.T) {
|
||||
active := identity.Revision{ID: "current", State: identity.RevisionActive, Version: 5}
|
||||
previous := identity.Revision{ID: "previous", State: identity.RevisionSuperseded, Version: 3}
|
||||
repository := &runtimeRepositoryFake{
|
||||
revisions: map[string]identity.Revision{"current": active, "previous": previous}, active: active,
|
||||
}
|
||||
manager := NewManager(repository, &runtimeBuilderFake{})
|
||||
manager.current.Store(&Runtime{Revision: active})
|
||||
|
||||
rolledBack, err := manager.Rollback(context.Background(), previous.ID, previous.Version, "trace", "audit")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rolledBack.State != identity.RevisionActive || manager.Current().Revision.ID != previous.ID || repository.active.ID != previous.ID {
|
||||
t.Fatalf("rollback did not replace active runtime: revision=%#v current=%#v", rolledBack, manager.Current())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user