mirror of https://github.com/veypi/OneAuth.git
test: Add comprehensive security and integration tests
- Add wildcard permission tests for RBAC hierarchy
- Add multi-tenant isolation tests for organization access
- Add OAuth2 security tests including client ownership and redirect URI
- Add race condition tests for concurrent operations
- Add edge case tests for SQL injection, XSS, input validation
- Add security test report documenting findings and fixes
master
parent
c588962485
commit
0b22d2c2c8
@ -0,0 +1,265 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestOAuthClientSecretLeak 测试 OAuth 客户端密钥泄漏
|
||||||
|
func TestOAuthClientSecretLeak(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
var clientID string
|
||||||
|
var clientSecret string
|
||||||
|
|
||||||
|
// Admin 创建 OAuth 客户端
|
||||||
|
t.Run("Admin creates OAuth client", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/oauth/clients", map[string]any{
|
||||||
|
"name": "Test Client",
|
||||||
|
"redirect_uris": []string{"https://example.com/callback"},
|
||||||
|
"allowed_scopes": "openid profile email",
|
||||||
|
}, AdminToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
clientID = data.ClientID
|
||||||
|
clientSecret = data.ClientSecret
|
||||||
|
})
|
||||||
|
|
||||||
|
if clientID == "" || clientSecret == "" {
|
||||||
|
t.Skip("Failed to create OAuth client")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通用户不应该看到 client_secret
|
||||||
|
t.Run("Regular user cannot see client secret", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "GET", "/api/oauth/clients/"+clientID, nil, User1Token)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
|
||||||
|
if data.ClientSecret != "" {
|
||||||
|
t.Errorf("Regular user should not see client_secret, got: %s", data.ClientSecret)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Admin 也不应该在 GET 请求中看到 client_secret
|
||||||
|
t.Run("Admin GET should not reveal client secret", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "GET", "/api/oauth/clients/"+clientID, nil, AdminToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
|
||||||
|
// client_secret 应该只在创建时返回
|
||||||
|
if data.ClientSecret != "" {
|
||||||
|
t.Logf("Warning: client_secret visible in GET response: %s", data.ClientSecret)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
t.Run("Cleanup", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "DELETE", "/api/oauth/clients/"+clientID, nil, AdminToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOAuthClientAccessControlSecurity 测试 OAuth 客户端访问控制安全
|
||||||
|
func TestOAuthClientAccessControlSecurity(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
var clientID string
|
||||||
|
|
||||||
|
// User1 创建 OAuth 客户端
|
||||||
|
t.Run("User1 creates OAuth client", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/oauth/clients", map[string]any{
|
||||||
|
"name": "User1 Client",
|
||||||
|
"redirect_uris": []string{"https://user1.com/callback"},
|
||||||
|
"allowed_scopes": "openid profile",
|
||||||
|
}, User1Token)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
clientID = data.ClientID
|
||||||
|
})
|
||||||
|
|
||||||
|
if clientID == "" {
|
||||||
|
t.Skip("Failed to create OAuth client")
|
||||||
|
}
|
||||||
|
|
||||||
|
// User2 不应该能修改 User1 的客户端
|
||||||
|
t.Run("User2 cannot modify User1's client", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "PATCH", "/api/oauth/clients/"+clientID, map[string]string{
|
||||||
|
"name": "Hacked by User2",
|
||||||
|
}, User2Token)
|
||||||
|
if resp.Code == 200 {
|
||||||
|
t.Errorf("User2 should not be able to modify User1's client, got 200")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// User2 不应该能删除 User1 的客户端
|
||||||
|
t.Run("User2 cannot delete User1's client", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "DELETE", "/api/oauth/clients/"+clientID, nil, User2Token)
|
||||||
|
if resp.Code == 200 {
|
||||||
|
t.Errorf("User2 should not be able to delete User1's client, got 200")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Admin 可以修改任何客户端
|
||||||
|
t.Run("Admin can modify any client", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "PATCH", "/api/oauth/clients/"+clientID, map[string]string{
|
||||||
|
"name": "Modified by Admin",
|
||||||
|
}, AdminToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
t.Run("Cleanup", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "DELETE", "/api/oauth/clients/"+clientID, nil, AdminToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOAuthAuthorizeEndpoint 测试 OAuth 授权端点安全
|
||||||
|
func TestOAuthAuthorizeEndpoint(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
// 测试缺少参数的授权请求
|
||||||
|
t.Run("Authorize without client_id", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "GET", "/api/oauth/authorize?response_type=code&redirect_uri=https://example.com/callback", nil, "")
|
||||||
|
// 应该返回错误,因为没有 client_id
|
||||||
|
if resp.Code == 200 {
|
||||||
|
t.Errorf("Expected error for missing client_id, got 200")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试无效的 response_type
|
||||||
|
t.Run("Authorize with invalid response_type", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "GET", "/api/oauth/authorize?client_id=test&response_type=invalid&redirect_uri=https://example.com/callback", nil, "")
|
||||||
|
if resp.Code == 200 {
|
||||||
|
t.Errorf("Expected error for invalid response_type, got 200")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOAuthTokenEndpoint 测试 OAuth Token 端点安全
|
||||||
|
func TestOAuthTokenEndpoint(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
// 测试缺少参数的 token 请求
|
||||||
|
t.Run("Token without grant_type", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/oauth/token", map[string]string{
|
||||||
|
"client_id": "test",
|
||||||
|
}, "")
|
||||||
|
// 应该返回错误
|
||||||
|
if resp.Code == 200 {
|
||||||
|
t.Errorf("Expected error for missing grant_type, got 200")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 测试无效的 grant_type
|
||||||
|
t.Run("Token with invalid grant_type", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/oauth/token", map[string]string{
|
||||||
|
"grant_type": "invalid_grant",
|
||||||
|
"client_id": "test",
|
||||||
|
}, "")
|
||||||
|
if resp.Code == 200 {
|
||||||
|
t.Errorf("Expected error for invalid grant_type, got 200")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOAuthScopeValidation 测试 OAuth Scope 验证
|
||||||
|
func TestOAuthScopeValidation(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
var clientID string
|
||||||
|
|
||||||
|
// 创建带有特定 scope 限制的客户端
|
||||||
|
t.Run("Create client with limited scopes", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/oauth/clients", map[string]any{
|
||||||
|
"name": "Limited Scope Client",
|
||||||
|
"redirect_uris": []string{"https://example.com/callback"},
|
||||||
|
"allowed_scopes": "openid profile", // 只允许 openid 和 profile
|
||||||
|
}, AdminToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
clientID = data.ClientID
|
||||||
|
})
|
||||||
|
|
||||||
|
if clientID == "" {
|
||||||
|
t.Skip("Failed to create OAuth client")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试:请求超出允许范围的 scope
|
||||||
|
t.Run("Request scope beyond allowed", func(t *testing.T) {
|
||||||
|
// 注意:这需要实际的授权流程,这里只是测试端点行为
|
||||||
|
resp := doRequest(t, "GET", "/api/oauth/authorize?client_id="+clientID+"&response_type=code&redirect_uri=https://example.com/callback&scope=openid profile email admin", nil, "")
|
||||||
|
// 应该返回错误或限制 scope
|
||||||
|
if resp.Code == 200 {
|
||||||
|
t.Logf("Warning: Request for excessive scope returned 200, scope validation may be missing")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
t.Run("Cleanup", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "DELETE", "/api/oauth/clients/"+clientID, nil, AdminToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestOAuthRedirectURISecurity 测试 OAuth Redirect URI 安全
|
||||||
|
func TestOAuthRedirectURISecurity(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
var clientID string
|
||||||
|
|
||||||
|
// 创建带有特定 redirect_uri 的客户端
|
||||||
|
t.Run("Create client with specific redirect_uri", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "POST", "/api/oauth/clients", map[string]any{
|
||||||
|
"name": "URI Test Client",
|
||||||
|
"redirect_uris": []string{"https://trusted.example.com/callback"},
|
||||||
|
"allowed_scopes": "openid",
|
||||||
|
}, AdminToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
clientID = data.ClientID
|
||||||
|
})
|
||||||
|
|
||||||
|
if clientID == "" {
|
||||||
|
t.Skip("Failed to create OAuth client")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试:使用不匹配的 redirect_uri
|
||||||
|
t.Run("Authorize with mismatched redirect_uri", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "GET", "/api/oauth/authorize?client_id="+clientID+"&response_type=code&redirect_uri=https://evil.com/callback", nil, "")
|
||||||
|
// 应该返回错误,因为 redirect_uri 不匹配
|
||||||
|
if resp.Code == 200 {
|
||||||
|
t.Errorf("Expected error for mismatched redirect_uri, got 200")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
t.Run("Cleanup", func(t *testing.T) {
|
||||||
|
resp := doRequest(t, "DELETE", "/api/oauth/clients/"+clientID, nil, AdminToken)
|
||||||
|
assertStatus(t, resp, 200)
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -0,0 +1,314 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestConcurrentOrgCreation 测试并发创建组织
|
||||||
|
func TestConcurrentOrgCreation(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
errors := make(chan error, 10)
|
||||||
|
|
||||||
|
// 并发创建多个组织
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(index int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
resp := doRequest(t, "POST", "/api/orgs", map[string]string{
|
||||||
|
"code": "concurrent_org_" + string(rune('a'+index)),
|
||||||
|
"name": "Concurrent Org " + string(rune('A'+index)),
|
||||||
|
}, User1Token)
|
||||||
|
|
||||||
|
if resp.Code != 200 && resp.Code != 400 {
|
||||||
|
// 400 可能是重复创建,其他错误码需要记录
|
||||||
|
errors <- nil
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(errors)
|
||||||
|
|
||||||
|
// 检查是否有错误
|
||||||
|
errorCount := 0
|
||||||
|
for range errors {
|
||||||
|
errorCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
if errorCount > 0 {
|
||||||
|
t.Errorf("Got %d errors during concurrent org creation", errorCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConcurrentMemberAddition 测试并发添加成员
|
||||||
|
func TestConcurrentMemberAddition(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
var orgID string
|
||||||
|
|
||||||
|
// User1 创建组织
|
||||||
|
resp := doRequest(t, "POST", "/api/orgs", map[string]string{
|
||||||
|
"code": "concurrent_member_test",
|
||||||
|
"name": "Concurrent Member 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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先添加 User2 为成员
|
||||||
|
resp = doRequest(t, "POST", "/api/orgs/"+orgID+"/members", map[string]string{
|
||||||
|
"user_id": User2ID,
|
||||||
|
"role": "member",
|
||||||
|
}, User1Token)
|
||||||
|
|
||||||
|
if resp.Code != 200 {
|
||||||
|
t.Skip("Failed to add initial member")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 并发获取组织详情
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
resp := doRequest(t, "GET", "/api/orgs/"+orgID, nil, User2Token)
|
||||||
|
if resp.Code != 200 {
|
||||||
|
t.Errorf("Concurrent access failed with code: %d", resp.Code)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
doRequest(t, "DELETE", "/api/orgs/"+orgID, nil, User1Token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConcurrentRoleUpdate 测试并发角色更新
|
||||||
|
func TestConcurrentRoleUpdate(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
// 创建测试角色
|
||||||
|
resp := doRequest(t, "POST", "/api/roles", map[string]string{
|
||||||
|
"code": "concurrent_role",
|
||||||
|
"name": "Concurrent Role",
|
||||||
|
"description": "Role for concurrent test",
|
||||||
|
}, AdminToken)
|
||||||
|
|
||||||
|
if resp.Code != 200 {
|
||||||
|
t.Skip("Failed to create role")
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
roleID := data.ID
|
||||||
|
|
||||||
|
// 并发更新角色权限
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(index int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
var perms []string
|
||||||
|
if index%2 == 0 {
|
||||||
|
perms = []string{"vb:org:read"}
|
||||||
|
} else {
|
||||||
|
perms = []string{"vb:org:create"}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := doRequest(t, "PUT", "/api/roles/"+roleID+"/permissions", map[string]any{
|
||||||
|
"permission_ids": perms,
|
||||||
|
}, AdminToken)
|
||||||
|
|
||||||
|
if resp.Code != 200 {
|
||||||
|
t.Errorf("Concurrent role update failed with code: %d", resp.Code)
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
doRequest(t, "DELETE", "/api/roles/"+roleID, nil, AdminToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConcurrentUserUpdate 测试并发用户更新
|
||||||
|
func TestConcurrentUserUpdate(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// User1 并发更新自己的信息
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(index int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
resp := doRequest(t, "PATCH", "/api/auth/me", map[string]string{
|
||||||
|
"nickname": "Concurrent Update " + string(rune('A'+index)),
|
||||||
|
}, User1Token)
|
||||||
|
|
||||||
|
if resp.Code != 200 {
|
||||||
|
t.Errorf("Concurrent user update failed with code: %d", resp.Code)
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConcurrentTokenRefresh 测试并发 Token 刷新
|
||||||
|
func TestConcurrentTokenRefresh(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
// 先获取 refresh token
|
||||||
|
resp := doRequest(t, "POST", "/api/auth/login", map[string]string{
|
||||||
|
"username": "user1_test",
|
||||||
|
"password": "password123",
|
||||||
|
}, "")
|
||||||
|
|
||||||
|
if resp.Code != 200 {
|
||||||
|
t.Skip("Failed to login")
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
|
||||||
|
if data.RefreshToken == "" {
|
||||||
|
t.Skip("No refresh token available")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 并发刷新 token
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
tokens := make(chan string, 5)
|
||||||
|
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
resp := doRequest(t, "POST", "/api/auth/refresh", map[string]string{
|
||||||
|
"refresh_token": data.RefreshToken,
|
||||||
|
}, "")
|
||||||
|
|
||||||
|
if resp.Code == 200 {
|
||||||
|
var refreshData struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &refreshData)
|
||||||
|
if refreshData.AccessToken != "" {
|
||||||
|
tokens <- refreshData.AccessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
close(tokens)
|
||||||
|
|
||||||
|
// 验证至少有一个成功
|
||||||
|
tokenCount := 0
|
||||||
|
for range tokens {
|
||||||
|
tokenCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenCount == 0 {
|
||||||
|
t.Errorf("All concurrent token refreshes failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConcurrentPermissionCheck 测试并发权限检查
|
||||||
|
func TestConcurrentPermissionCheck(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// 多个用户并发检查权限
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(index int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
var token string
|
||||||
|
switch index % 3 {
|
||||||
|
case 0:
|
||||||
|
token = AdminToken
|
||||||
|
case 1:
|
||||||
|
token = User1Token
|
||||||
|
case 2:
|
||||||
|
token = User2Token
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := doRequest(t, "GET", "/api/orgs", nil, token)
|
||||||
|
// 所有用户都应该能访问 org 列表
|
||||||
|
if resp.Code != 200 {
|
||||||
|
t.Errorf("Permission check failed with code: %d", resp.Code)
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestConcurrentOAuthClientOps 测试并发 OAuth 客户端操作
|
||||||
|
func TestConcurrentOAuthClientOps(t *testing.T) {
|
||||||
|
ensureUsers(t)
|
||||||
|
|
||||||
|
// 创建测试客户端
|
||||||
|
resp := doRequest(t, "POST", "/api/oauth/clients", map[string]any{
|
||||||
|
"name": "Concurrent Test Client",
|
||||||
|
"redirect_uris": []string{"https://example.com/callback"},
|
||||||
|
"allowed_scopes": "openid",
|
||||||
|
}, AdminToken)
|
||||||
|
|
||||||
|
if resp.Code != 200 {
|
||||||
|
t.Skip("Failed to create OAuth client")
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
}
|
||||||
|
decodeResponse(t, resp, &data)
|
||||||
|
clientID := data.ClientID
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
// 并发更新客户端
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(index int) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
resp := doRequest(t, "PATCH", "/api/oauth/clients/"+clientID, map[string]string{
|
||||||
|
"name": "Updated Name " + string(rune('A'+index)),
|
||||||
|
}, AdminToken)
|
||||||
|
|
||||||
|
if resp.Code != 200 {
|
||||||
|
t.Errorf("Concurrent OAuth client update failed with code: %d", resp.Code)
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
doRequest(t, "DELETE", "/api/oauth/clients/"+clientID, nil, AdminToken)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue