初始提交:OCI 面板后端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:04 +08:00
commit b9a3e97e84
168 changed files with 31794 additions and 0 deletions
@@ -0,0 +1,242 @@
package oci
import (
"context"
"fmt"
"os"
"strings"
"testing"
"time"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/identitydomains"
)
// TestRealOCIFederation 验证 Federation 全链路:下载域元数据 → 创建 IdP
// (默认 JIT)→ 激活并上登录页 → 免 MFA 规则置顶 → 逐级回滚清理。
// 只操作本测试创建的资源,现有 IdP 与规则只做顺延断言。
func TestRealOCIFederation(t *testing.T) {
if os.Getenv("OCI_INTEGRATION_TEST") != "1" {
t.Skip("set OCI_INTEGRATION_TEST=1 to run real OCI tests")
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
cred := loadTestCredentials(t, "试用期")
client := NewClient()
region := cred.Region
metadata, err := client.DownloadDomainSamlMetadata(ctx, cred, region)
if err != nil {
t.Fatalf("DownloadDomainSamlMetadata: %v", err)
}
if !strings.Contains(string(metadata), "EntityDescriptor") {
t.Fatalf("metadata is not SAML XML: %s", string(metadata[:min(len(metadata), 120)]))
}
t.Logf("domain saml metadata: %d bytes", len(metadata))
before, err := client.ListConsoleSignOnRules(ctx, cred, region)
if err != nil {
t.Fatalf("ListConsoleSignOnRules: %v", err)
}
logRules(t, "before", before)
idp := createTestIdp(ctx, t, client, cred, region)
verifyIdpDefaults(ctx, t, client, cred, region, idp.ID)
activated, err := client.SetIdentityProviderEnabled(ctx, cred, region, idp.ID, true)
if err != nil {
t.Fatalf("activate idp: %v", err)
}
if !activated.Enabled {
t.Error("idp not enabled after activate")
}
assertLoginPage(ctx, t, client, cred, region, idp.ID, true)
exemptionRoundTrip(ctx, t, client, cred, region, idp.ID, len(before))
deactivated, err := client.SetIdentityProviderEnabled(ctx, cred, region, idp.ID, false)
if err != nil {
t.Fatalf("deactivate idp: %v", err)
}
if deactivated.Enabled {
t.Error("idp still enabled after deactivate")
}
assertLoginPage(ctx, t, client, cred, region, idp.ID, false)
}
func createTestIdp(ctx context.Context, t *testing.T, client *RealClient, cred Credentials, region string) IdentityProviderInfo {
t.Helper()
metadata, err := os.ReadFile("../../test-idp.xml")
if err != nil {
t.Fatalf("read test-idp.xml: %v", err)
}
name := fmt.Sprintf("oci-portal-e2e-idp-%d", time.Now().Unix())
idp, err := client.CreateSamlIdentityProvider(ctx, cred, region, CreateIdpInput{
Name: name,
Metadata: string(metadata),
Description: "oci-portal e2e temporary idp",
})
if err != nil {
t.Fatalf("CreateSamlIdentityProvider: %v", err)
}
t.Logf("idp created: %s (%s) partner=%s", idp.Name, idp.ID, idp.PartnerProviderID)
if idp.Enabled {
t.Error("new idp should be disabled")
}
t.Cleanup(func() {
cctx, ccancel := context.WithTimeout(context.Background(), 2*time.Minute)
defer ccancel()
if err := client.DeleteIdentityProvider(cctx, cred, region, idp.ID); err != nil {
t.Errorf("cleanup: delete idp: %v", err)
} else {
t.Log("cleanup: idp deleted")
}
})
return idp
}
// verifyIdpDefaults 回读 IdP 断言控制台默认值与 JIT 属性映射两条。
func verifyIdpDefaults(ctx context.Context, t *testing.T, client *RealClient, cred Credentials, region, idpID string) {
t.Helper()
dc, err := client.domainsClient(ctx, cred, region)
if err != nil {
t.Fatalf("domainsClient: %v", err)
}
got, err := dc.GetIdentityProvider(ctx, identitydomains.GetIdentityProviderRequest{IdentityProviderId: &idpID})
if err != nil {
t.Fatalf("get idp: %v", err)
}
if deref(got.NameIdFormat) != "saml-none" {
t.Errorf("nameIdFormat = %q, want saml-none", deref(got.NameIdFormat))
}
if got.UserMappingMethod != identitydomains.IdentityProviderUserMappingMethodNameidtouserattribute || deref(got.UserMappingStoreAttribute) != "userName" {
t.Errorf("user mapping = %s/%s, want NameIDToUserAttribute/userName", got.UserMappingMethod, deref(got.UserMappingStoreAttribute))
}
if got.JitUserProvEnabled == nil || !*got.JitUserProvEnabled || got.JitUserProvCreateUserEnabled == nil || !*got.JitUserProvCreateUserEnabled {
t.Error("jit enable/create should be true")
}
if got.JitUserProvAttributeUpdateEnabled != nil && *got.JitUserProvAttributeUpdateEnabled {
t.Error("jit attribute update should be false")
}
if len(got.JitUserProvAssignedGroups) != 1 || deref(got.JitUserProvAssignedGroups[0].Display) != "Administrators" {
t.Errorf("jit assigned groups = %+v, want [Administrators]", got.JitUserProvAssignedGroups)
}
verifyJitMappings(ctx, t, dc, got.JitUserProvAttributes)
}
func verifyJitMappings(ctx context.Context, t *testing.T, dc identitydomains.IdentityDomainsClient, ref *identitydomains.IdentityProviderJitUserProvAttributes) {
t.Helper()
if ref == nil || ref.Value == nil {
t.Error("idp has no jit attribute mapping resource")
return
}
ma, err := dc.GetMappedAttribute(ctx, identitydomains.GetMappedAttributeRequest{MappedAttributeId: ref.Value})
if err != nil {
t.Errorf("get mapped attribute: %v", err)
return
}
want := map[string]bool{"userName": false, "name.familyName": false}
for _, m := range ma.AttributeMappings {
if deref(m.ManagedObjectAttributeName) == "$(assertion.fed.nameidvalue)" {
want[deref(m.IdcsAttributeName)] = true
}
}
for attr, seen := range want {
if !seen {
t.Errorf("jit mapping NameID value → %s missing; got %+v", attr, ma.AttributeMappings)
}
}
}
// assertLoginPage 校验 DefaultIDPRule 的 SamlIDPs 是否包含目标 IdP。
func assertLoginPage(ctx context.Context, t *testing.T, client *RealClient, cred Credentials, region, idpID string, want bool) {
t.Helper()
dc, err := client.domainsClient(ctx, cred, region)
if err != nil {
t.Fatalf("domainsClient: %v", err)
}
rule, err := dc.GetRule(ctx, identitydomains.GetRuleRequest{RuleId: common.String(defaultIdpRuleID)})
if err != nil {
t.Fatalf("get default idp rule: %v", err)
}
for _, r := range rule.Return {
if deref(r.Name) != "SamlIDPs" {
continue
}
has := strings.Contains(deref(r.Value), idpID)
if has != want {
t.Errorf("login page SamlIDPs contains idp = %v, want %v (value=%s)", has, want, deref(r.Value))
}
return
}
t.Error("DefaultIDPRule has no SamlIDPs return")
}
// exemptionRoundTrip 创建免 MFA 规则断言置顶与顺延,删除后断言完全复位。
func exemptionRoundTrip(ctx context.Context, t *testing.T, client *RealClient, cred Credentials, region, idpID string, beforeCount int) {
t.Helper()
rule, err := client.CreateMfaExemptionRule(ctx, cred, region, idpID, "oci-portal-e2e-skip-mfa")
if err != nil {
t.Fatalf("CreateMfaExemptionRule: %v", err)
}
t.Logf("exemption rule created: %s (%s)", rule.Name, rule.ID)
deleted := false
defer func() {
if deleted {
return
}
if err := client.DeleteMfaExemptionRule(ctx, cred, region, rule.ID); err != nil {
t.Errorf("cleanup: delete exemption rule: %v", err)
}
}()
after, err := client.ListConsoleSignOnRules(ctx, cred, region)
if err != nil {
t.Fatalf("ListConsoleSignOnRules after create: %v", err)
}
logRules(t, "after-create", after)
if len(after) != beforeCount+1 {
t.Errorf("rule count = %d, want %d", len(after), beforeCount+1)
}
if after[0].ID != rule.ID || after[0].Sequence != 1 {
t.Errorf("top rule = %s seq=%d, want %s seq=1", after[0].ID, after[0].Sequence, rule.ID)
}
if after[0].AuthenticationFactor != "IDP" {
t.Errorf("authenticationFactor = %s, want IDP", after[0].AuthenticationFactor)
}
if !strings.Contains(after[0].ConditionValue, idpID) {
t.Errorf("condition value %q does not reference idp", after[0].ConditionValue)
}
if err := client.DeleteMfaExemptionRule(ctx, cred, region, rule.ID); err != nil {
t.Fatalf("DeleteMfaExemptionRule: %v", err)
}
deleted = true
restored, err := client.ListConsoleSignOnRules(ctx, cred, region)
if err != nil {
t.Fatalf("ListConsoleSignOnRules after delete: %v", err)
}
logRules(t, "after-delete", restored)
if len(restored) != beforeCount {
t.Errorf("rule count after delete = %d, want %d", len(restored), beforeCount)
}
for _, r := range restored {
if r.ID == rule.ID {
t.Error("exemption rule still present after delete")
}
}
}
func logRules(t *testing.T, tag string, rules []SignOnRuleInfo) {
t.Helper()
for _, r := range rules {
t.Logf("%s: seq=%d %s (%s) factor=%s", tag, r.Sequence, r.Name, r.ID, r.AuthenticationFactor)
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}