mirror of https://github.com/veypi/OneAuth.git
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.
266 lines
8.0 KiB
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)
|
|
})
|
|
}
|