package identityprovider import ( "context" "encoding/json" "io" "net/http" "strings" "testing" "vpn-platform/backend/internal/model" "vpn-platform/backend/internal/store" ) func TestDirectoryOnlyAcceptsMatchingOpenIDAndName(t *testing.T) { previous := upstreamClient t.Cleanup(func() { upstreamClient = previous }) response := `{"code":0,"data":{"users":[{"user_id":"ou_123","name":"Li Ge"}]}}` fallback := `{"code":99991672}` upstreamClient = &http.Client{Transport: transportFunc(func(req *http.Request) (*http.Response, error) { body := `{"code":0,"tenant_access_token":"temporary"}` switch req.URL.String() { case feishuTenantToken: case feishuBasicBatch: if req.Method != http.MethodPost || req.Header.Get("Authorization") != "Bearer temporary" || req.Header.Get("Content-Type") != "application/json" { t.Fatal("invalid basic batch request") } var payload struct { UserIDs []string `json:"user_ids"` } if json.NewDecoder(req.Body).Decode(&payload) != nil || len(payload.UserIDs) != 1 || payload.UserIDs[0] != "ou_123" { t.Fatal("wrong open_id in basic batch") } body = response case feishuContactUser + "ou_123?user_id_type=open_id": body = fallback default: t.Fatalf("unexpected directory request: %s", req.URL) } return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}, nil })} if name, source, err := fetchDirectoryNameWithSource(context.Background(), "cli_app", "secret", "ou_123"); err != nil || name != "Li Ge" || source != "basic_batch" { t.Fatalf("directory: %q %q %v", name, source, err) } for _, reply := range []string{`{"code":0,"data":{"users":[{"user_id":"ou_other","name":"Wrong"}]}}`, `{"code":0,"data":{"users":[{"name":"No ID"}]}}`, `{"code":99991672,"msg":"secret"}`} { response = reply if _, err := fetchDirectoryName(context.Background(), "cli_app", "secret", "ou_123"); err == nil { t.Fatal("accepted unverified directory identity") } } response = `{"code":99991672}` fallback = `{"code":0,"data":{"user":{"open_id":"ou_123","name":"Directory name"}}}` if name, source, err := fetchDirectoryNameWithSource(context.Background(), "cli_app", "secret", "ou_123"); err != nil || name != "Directory name" || source != "contact_user" { t.Fatalf("fallback: %q %q %v", name, source, err) } upstreamClient = &http.Client{Transport: transportFunc(func(req *http.Request) (*http.Response, error) { if req.URL.String() == feishuTenantToken { return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(`{"code":0,"tenant_access_token":"temporary"}`)), Header: make(http.Header)}, nil } return &http.Response{StatusCode: 403, Body: io.NopCloser(strings.NewReader(`{"code":99991672,"msg":"private upstream detail"}`)), Header: make(http.Header)}, nil })} if _, err := fetchDirectoryName(context.Background(), "cli_app", "secret", "ou_123"); err == nil || strings.Contains(err.Error(), "private upstream detail") { t.Fatal("directory permission failure leaked upstream details") } upstreamClient = &http.Client{Transport: transportFunc(func(req *http.Request) (*http.Response, error) { return nil, req.Context().Err() })} ctx, cancel := context.WithCancel(context.Background()) cancel() if _, err := fetchDirectoryName(ctx, "cli_app", "secret", "ou_123"); err == nil { t.Fatal("cancelled directory request succeeded") } } func TestAdmissionDefaultsToAllAndScopesGrants(t *testing.T) { previous := store.DB t.Cleanup(func() { if store.DB != nil { if db, err := store.DB.DB(); err == nil { _ = db.Close() } } store.DB = previous }) if err := store.Init(t.TempDir()); err != nil { t.Fatal(err) } base := "https://vpn.example.com/id" redirect := "https://vpn.example.com/oauth2/callback" idp := model.IdentityProvider{AppID: "cli_current", ClientID: "vpn_client", TenantKey: "tenant"} tunnel := model.Tunnel{Name: "feishu", Protocol: "openvpn", Config: `{"auth_mode":"oidc","oidc_provider":"builtin","oidc_issuer":"https://vpn.example.com/id","oidc_client_id":"vpn_client","oidc_base_url":"https://vpn.example.com"}`} if err := store.DB.Create(&tunnel).Error; err != nil { t.Fatal(err) } if ok, err := admitted(redirect, base, idp, "tenant:ou_1"); err != nil || !ok { t.Fatalf("default: %v %v", ok, err) } if err := store.DB.Model(&tunnel).Update("config", `{"auth_mode":"oidc","oidc_provider":"builtin","oidc_issuer":"https://vpn.example.com/id","oidc_client_id":"vpn_client","oidc_base_url":"https://vpn.example.com","feishu_access_mode":true}`).Error; err != nil { t.Fatal(err) } if ok, err := admitted(redirect, base, idp, "tenant:ou_1"); err == nil || ok { t.Fatal("invalid policy admitted") } if err := store.DB.Model(&tunnel).Update("config", tunnel.Config).Error; err != nil { t.Fatal(err) } if ok, _ := admitted("https://wrong.example.com/oauth2/callback", base, idp, "tenant:ou_1"); ok { t.Fatal("unknown callback admitted") } tunnel.Config = `{"auth_mode":"oidc","oidc_provider":"builtin","oidc_issuer":"https://vpn.example.com/id","oidc_client_id":"vpn_client","oidc_base_url":"https://vpn.example.com","feishu_access_mode":"allowlist"}` if err := store.DB.Save(&tunnel).Error; err != nil { t.Fatal(err) } if ok, err := admitted(redirect, base, idp, "tenant:ou_1"); err != nil || ok { t.Fatalf("unauthorized: %v %v", ok, err) } grant := model.FeishuTunnelGrant{TunnelID: tunnel.ID, Issuer: base, AppID: idp.AppID, Subject: "tenant:ou_1"} if err := store.DB.Create(&grant).Error; err != nil { t.Fatal(err) } if ok, err := admitted(redirect, base, idp, grant.Subject); err != nil || !ok { t.Fatalf("grant: %v %v", ok, err) } idp.AppID = "cli_other" if ok, _ := admitted(redirect, base, idp, grant.Subject); ok { t.Fatal("cross-app grant admitted") } idp.AppID = "cli_current" if err := store.DB.Delete(&grant).Error; err != nil { t.Fatal(err) } if ok, _ := admitted(redirect, base, idp, grant.Subject); ok { t.Fatal("revoked grant admitted") } if err := store.DB.Migrator().DropTable(&model.FeishuTunnelGrant{}); err != nil { t.Fatal(err) } if ok, err := admitted(redirect, base, idp, grant.Subject); ok || err == nil { t.Fatal("database failure admitted") } }