支持 Manifest V2 动态 tid 验证、Tenant Context 同步和租户内 JIT 投影,并保留 Manifest V1 与旧 Session 兼容。\n\n增加 tenantHint、租户切换、普通注册关闭及 application/principal/tenant 两级 SSF 撤销;迁移、定向安全测试和本地双租户跨仓 E2E 已通过。\n\nrelease_required=true;未执行 Release、Staging 或真实链路。
77 lines
2.6 KiB
Go
77 lines
2.6 KiB
Go
package identityruntime
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/identity"
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
|
)
|
|
|
|
type tenantContextSyncRepository interface {
|
|
DueOIDCTenantBindingSyncs(context.Context, string, string, int) ([]store.OIDCTenantBindingSyncTarget, error)
|
|
ApplyOIDCTenantBindingSync(context.Context, string, identity.TenantContext, bool, time.Time) error
|
|
RejectOIDCTenantBinding(context.Context, string, string, time.Time) error
|
|
FailOIDCTenantBindingSync(context.Context, string, string, time.Time) error
|
|
}
|
|
|
|
type tenantContextSyncReader interface {
|
|
Get(context.Context, string, string) (identity.TenantContext, bool, error)
|
|
}
|
|
|
|
func runTenantContextSynchronizer(ctx context.Context, repository tenantContextSyncRepository, reader tenantContextSyncReader, issuer, applicationID string) {
|
|
ticker := time.NewTicker(15 * time.Second)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case now := <-ticker.C:
|
|
_ = synchronizeDueTenantContexts(ctx, repository, reader, issuer, applicationID, now.UTC())
|
|
}
|
|
}
|
|
}
|
|
|
|
func synchronizeDueTenantContexts(ctx context.Context, repository tenantContextSyncRepository, reader tenantContextSyncReader, issuer, applicationID string, now time.Time) error {
|
|
targets, err := repository.DueOIDCTenantBindingSyncs(ctx, issuer, applicationID, 50)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, target := range targets {
|
|
tenant, unchanged, readErr := reader.Get(ctx, target.ExternalTenantID, target.ETag)
|
|
switch {
|
|
case readErr == nil && unchanged:
|
|
err = repository.ApplyOIDCTenantBindingSync(ctx, target.ID, identity.TenantContext{}, true, now)
|
|
case readErr == nil && tenant.Active():
|
|
err = repository.ApplyOIDCTenantBindingSync(ctx, target.ID, tenant, false, now)
|
|
case readErr == nil:
|
|
err = repository.RejectOIDCTenantBinding(ctx, target.ID, "tenant_inactive", now)
|
|
case errors.Is(readErr, identity.ErrTenantContextNotFound):
|
|
err = repository.RejectOIDCTenantBinding(ctx, target.ID, "tenant_not_found", now)
|
|
default:
|
|
err = repository.FailOIDCTenantBindingSync(
|
|
ctx, target.ID, "tenant_context_unavailable", now.Add(tenantContextRetryDelay(target.FailureCount)),
|
|
)
|
|
}
|
|
if err != nil && !errors.Is(err, store.ErrOIDCTenantUnavailable) {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func tenantContextRetryDelay(failureCount int) time.Duration {
|
|
if failureCount < 0 {
|
|
failureCount = 0
|
|
}
|
|
delay := 30 * time.Second
|
|
for index := 0; index < failureCount && delay < 5*time.Minute; index++ {
|
|
delay *= 2
|
|
}
|
|
if delay > 5*time.Minute {
|
|
return 5 * time.Minute
|
|
}
|
|
return delay
|
|
}
|