You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/tests/oauth_security_test.go

266 lines
8.0 KiB
Go

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)
})
}