140 lines
6.1 KiB
Go
140 lines
6.1 KiB
Go
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")
|
|
}
|
|
}
|