|
|
package tests
|
|
|
|
|
|
import (
|
|
|
"testing"
|
|
|
)
|
|
|
|
|
|
// TestMultiTenantIsolation 测试多租户数据隔离
|
|
|
func TestMultiTenantIsolation(t *testing.T) {
|
|
|
ensureUsers(t)
|
|
|
|
|
|
var org1ID, org2ID string
|
|
|
|
|
|
// User1 创建组织1
|
|
|
t.Run("User1 creates Organization 1", func(t *testing.T) {
|
|
|
resp := doRequest(t, "POST", "/api/orgs", map[string]string{
|
|
|
"code": "tenant_test_org1",
|
|
|
"name": "Tenant Test Org 1",
|
|
|
}, User1Token)
|
|
|
|
|
|
if resp.Code == 200 {
|
|
|
var data struct {
|
|
|
ID string `json:"id"`
|
|
|
}
|
|
|
decodeResponse(t, resp, &data)
|
|
|
org1ID = data.ID
|
|
|
} else {
|
|
|
// 可能已经存在,尝试获取
|
|
|
resp = doRequest(t, "GET", "/api/orgs", nil, User1Token)
|
|
|
assertStatus(t, resp, 200)
|
|
|
}
|
|
|
})
|
|
|
|
|
|
// User2 创建组织2
|
|
|
t.Run("User2 creates Organization 2", func(t *testing.T) {
|
|
|
resp := doRequest(t, "POST", "/api/orgs", map[string]string{
|
|
|
"code": "tenant_test_org2",
|
|
|
"name": "Tenant Test Org 2",
|
|
|
}, User2Token)
|
|
|
|
|
|
if resp.Code == 200 {
|
|
|
var data struct {
|
|
|
ID string `json:"id"`
|
|
|
}
|
|
|
decodeResponse(t, resp, &data)
|
|
|
org2ID = data.ID
|
|
|
}
|
|
|
})
|
|
|
|
|
|
if org1ID == "" || org2ID == "" {
|
|
|
t.Skip("Failed to create test orgs")
|
|
|
}
|
|
|
|
|
|
// 测试:User1 不能访问 User2 的组织
|
|
|
t.Run("User1 cannot access User2's organization", func(t *testing.T) {
|
|
|
resp := doRequest(t, "GET", "/api/orgs/"+org2ID, nil, User1Token)
|
|
|
if resp.Code == 200 {
|
|
|
t.Errorf("User1 should not be able to access User2's org, got 200")
|
|
|
}
|
|
|
})
|
|
|
|
|
|
// 测试:User2 不能访问 User1 的组织
|
|
|
t.Run("User2 cannot access User1's organization", func(t *testing.T) {
|
|
|
resp := doRequest(t, "GET", "/api/orgs/"+org1ID, nil, User2Token)
|
|
|
if resp.Code == 200 {
|
|
|
t.Errorf("User2 should not be able to access User1's org, got 200")
|
|
|
}
|
|
|
})
|
|
|
|
|
|
// 测试:User1 添加 User2 到组织1后,User2 可以访问
|
|
|
t.Run("User1 adds User2 to Org1", func(t *testing.T) {
|
|
|
resp := doRequest(t, "POST", "/api/orgs/"+org1ID+"/members", map[string]string{
|
|
|
"user_id": User2ID,
|
|
|
"role": "member",
|
|
|
}, User1Token)
|
|
|
assertStatus(t, resp, 200)
|
|
|
})
|
|
|
|
|
|
// User2 现在应该能访问组织1
|
|
|
t.Run("User2 can now access Org1 as member", func(t *testing.T) {
|
|
|
resp := doRequest(t, "GET", "/api/orgs/"+org1ID, nil, User2Token)
|
|
|
assertStatus(t, resp, 200)
|
|
|
})
|
|
|
|
|
|
// 但 User2 不应该能修改组织1(只有 owner 可以)
|
|
|
t.Run("User2 cannot update Org1 as member", func(t *testing.T) {
|
|
|
resp := doRequest(t, "PATCH", "/api/orgs/"+org1ID, map[string]string{
|
|
|
"name": "Hacked by User2",
|
|
|
}, User2Token)
|
|
|
// 应该失败,因为需要 org:update 权限
|
|
|
if resp.Code == 200 {
|
|
|
t.Errorf("User2 should not be able to update Org1, got 200")
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// TestOrgMemberPermission 测试组织成员权限
|
|
|
func TestOrgMemberPermission(t *testing.T) {
|
|
|
ensureUsers(t)
|
|
|
|
|
|
var orgID string
|
|
|
|
|
|
// User1 创建组织
|
|
|
t.Run("User1 creates org", func(t *testing.T) {
|
|
|
resp := doRequest(t, "POST", "/api/orgs", map[string]string{
|
|
|
"code": "member_perm_test",
|
|
|
"name": "Member Permission Test",
|
|
|
}, User1Token)
|
|
|
|
|
|
if resp.Code == 200 {
|
|
|
var data struct {
|
|
|
ID string `json:"id"`
|
|
|
}
|
|
|
decodeResponse(t, resp, &data)
|
|
|
orgID = data.ID
|
|
|
}
|
|
|
})
|
|
|
|
|
|
if orgID == "" {
|
|
|
t.Skip("Failed to create org")
|
|
|
}
|
|
|
|
|
|
// User1 添加 User2 为成员
|
|
|
t.Run("Add User2 as member", func(t *testing.T) {
|
|
|
resp := doRequest(t, "POST", "/api/orgs/"+orgID+"/members", map[string]string{
|
|
|
"user_id": User2ID,
|
|
|
"role": "member",
|
|
|
}, User1Token)
|
|
|
assertStatus(t, resp, 200)
|
|
|
})
|
|
|
|
|
|
// User2 可以查看成员列表
|
|
|
t.Run("Member can view member list", func(t *testing.T) {
|
|
|
resp := doRequest(t, "GET", "/api/orgs/"+orgID+"/members", nil, User2Token)
|
|
|
assertStatus(t, resp, 200)
|
|
|
})
|
|
|
|
|
|
// User2 不能添加新成员(需要 org:update 权限)
|
|
|
t.Run("Member cannot add new members", func(t *testing.T) {
|
|
|
resp := doRequest(t, "POST", "/api/orgs/"+orgID+"/members", map[string]string{
|
|
|
"user_id": AdminID,
|
|
|
"role": "member",
|
|
|
}, User2Token)
|
|
|
if resp.Code == 200 {
|
|
|
t.Errorf("Member should not be able to add new members, got 200")
|
|
|
}
|
|
|
})
|
|
|
|
|
|
// 清理
|
|
|
t.Run("Cleanup", func(t *testing.T) {
|
|
|
resp := doRequest(t, "DELETE", "/api/orgs/"+orgID, nil, User1Token)
|
|
|
assertStatus(t, resp, 200)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// TestCrossOrgDataLeak 测试跨组织数据泄漏
|
|
|
func TestCrossOrgDataLeak(t *testing.T) {
|
|
|
ensureUsers(t)
|
|
|
|
|
|
var orgID string
|
|
|
|
|
|
// User1 创建组织
|
|
|
t.Run("User1 creates org", func(t *testing.T) {
|
|
|
resp := doRequest(t, "POST", "/api/orgs", map[string]string{
|
|
|
"code": "data_leak_test",
|
|
|
"name": "Data Leak Test",
|
|
|
}, User1Token)
|
|
|
|
|
|
if resp.Code == 200 {
|
|
|
var data struct {
|
|
|
ID string `json:"id"`
|
|
|
}
|
|
|
decodeResponse(t, resp, &data)
|
|
|
orgID = data.ID
|
|
|
}
|
|
|
})
|
|
|
|
|
|
if orgID == "" {
|
|
|
t.Skip("Failed to create org")
|
|
|
}
|
|
|
|
|
|
// User1 添加 User2 为成员
|
|
|
t.Run("Add User2 as member", func(t *testing.T) {
|
|
|
resp := doRequest(t, "POST", "/api/orgs/"+orgID+"/members", map[string]string{
|
|
|
"user_id": User2ID,
|
|
|
"role": "member",
|
|
|
}, User1Token)
|
|
|
assertStatus(t, resp, 200)
|
|
|
})
|
|
|
|
|
|
// User2 获取组织详情
|
|
|
t.Run("User2 gets org details", func(t *testing.T) {
|
|
|
resp := doRequest(t, "GET", "/api/orgs/"+orgID, nil, User2Token)
|
|
|
assertStatus(t, resp, 200)
|
|
|
})
|
|
|
|
|
|
// User2 不应该能看到其他组织的成员
|
|
|
// 注意:这里假设 orgID+1 是另一个不存在的组织ID
|
|
|
t.Run("User2 cannot access non-existent org", func(t *testing.T) {
|
|
|
fakeOrgID := "non-existent-org-id-12345"
|
|
|
resp := doRequest(t, "GET", "/api/orgs/"+fakeOrgID, nil, User2Token)
|
|
|
if resp.Code == 200 {
|
|
|
t.Errorf("Should not be able to access non-existent org, got 200")
|
|
|
}
|
|
|
})
|
|
|
|
|
|
// 清理
|
|
|
t.Run("Cleanup", func(t *testing.T) {
|
|
|
resp := doRequest(t, "DELETE", "/api/orgs/"+orgID, nil, User1Token)
|
|
|
assertStatus(t, resp, 200)
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// TestOrgIDHeaderManipulation 测试 X-Org-ID 头部操作
|
|
|
func TestOrgIDHeaderManipulation(t *testing.T) {
|
|
|
ensureUsers(t)
|
|
|
|
|
|
var orgID string
|
|
|
|
|
|
// User1 创建组织
|
|
|
t.Run("User1 creates org", func(t *testing.T) {
|
|
|
resp := doRequest(t, "POST", "/api/orgs", map[string]string{
|
|
|
"code": "header_test_org",
|
|
|
"name": "Header Test Org",
|
|
|
}, User1Token)
|
|
|
|
|
|
if resp.Code == 200 {
|
|
|
var data struct {
|
|
|
ID string `json:"id"`
|
|
|
}
|
|
|
decodeResponse(t, resp, &data)
|
|
|
orgID = data.ID
|
|
|
}
|
|
|
})
|
|
|
|
|
|
if orgID == "" {
|
|
|
t.Skip("Failed to create org")
|
|
|
}
|
|
|
|
|
|
// 测试:User2 尝试通过伪造 X-Org-ID 访问组织
|
|
|
// 注意:这需要修改 doRequest 函数来支持自定义 header
|
|
|
// 目前测试表明 User2 无法访问
|
|
|
t.Run("User2 cannot access org without membership", func(t *testing.T) {
|
|
|
resp := doRequest(t, "GET", "/api/orgs/"+orgID, nil, User2Token)
|
|
|
if resp.Code == 200 {
|
|
|
t.Errorf("User2 should not be able to access org without membership, got 200")
|
|
|
}
|
|
|
})
|
|
|
|
|
|
// 清理
|
|
|
t.Run("Cleanup", func(t *testing.T) {
|
|
|
resp := doRequest(t, "DELETE", "/api/orgs/"+orgID, nil, User1Token)
|
|
|
assertStatus(t, resp, 200)
|
|
|
})
|
|
|
}
|