xfy 2734b04d8f refactor: remove 16.8k lines of dead code across all internal packages
- Delete unused files: tempfile subsystem, matcher variants, server/internal
- Remove 200+ unused functions across proxy, ssl, lua, http2/3, stream, variable
- Fix proxy test type errors (backgroundRefresh ctx→Request)
- Move bench/tools mock backend into internal/testutil
- Remove corresponding test functions for all deleted code
2026-06-03 16:15:43 +08:00

650 lines
15 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package security 提供基本认证功能的测试。
//
// 该文件测试基本认证模块的各项功能,包括:
// - 基本认证创建和配置
// - 用户认证验证
// - 密码哈希bcrypt/argon2id
// - 用户添加和删除
// - 凭据提取
//
// 作者xfy
package security
import (
"testing"
"github.com/valyala/fasthttp"
"golang.org/x/crypto/bcrypt"
"rua.plus/lolly/internal/config"
)
func TestNewBasicAuth(t *testing.T) {
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)
tests := []struct {
cfg *config.AuthConfig
name string
wantErr bool
}{
{
name: "nil config",
cfg: nil,
wantErr: true,
},
{
name: "invalid type",
cfg: &config.AuthConfig{
Type: "digest",
},
wantErr: true,
},
{
name: "no users",
cfg: &config.AuthConfig{
Type: "basic",
},
wantErr: true,
},
{
name: "empty username",
cfg: &config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "", Password: string(hashedPassword)},
},
},
wantErr: true,
},
{
name: "empty password",
cfg: &config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: ""},
},
},
wantErr: true,
},
{
name: "valid config",
cfg: &config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
},
},
{
name: "valid with bcrypt",
cfg: &config.AuthConfig{
Type: "basic",
Algorithm: "bcrypt",
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
},
},
{
name: "valid with argon2id format",
cfg: &config.AuthConfig{
Type: "basic",
Algorithm: "argon2id",
Users: []config.User{
{Name: "admin", Password: "$argon2id$v=19$m=65536,t=3,p=4$c2FsdABoYXNo"},
},
},
},
{
name: "invalid algorithm",
cfg: &config.AuthConfig{
Type: "basic",
Algorithm: "md5",
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
auth, err := NewBasicAuth(tt.cfg)
if (err != nil) != tt.wantErr {
t.Errorf("NewBasicAuth() error = %v, wantErr %v", err, tt.wantErr)
}
if !tt.wantErr && auth == nil {
t.Error("Expected non-nil BasicAuth")
}
})
}
}
func TestBasicAuthAuthenticate(t *testing.T) {
password := "testpassword"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
tests := []struct {
name string
username string
password string
expected bool
}{
{
name: "valid credentials",
username: "admin",
password: password,
expected: true,
},
{
name: "wrong password",
username: "admin",
password: "wrongpassword",
expected: false,
},
{
name: "unknown user",
username: "unknown",
password: password,
expected: false,
},
{
name: "empty username",
username: "",
password: password,
expected: false,
},
{
name: "empty password",
username: "admin",
password: "",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := auth.Authenticate(tt.username, tt.password)
if result != tt.expected {
t.Errorf("Authenticate(%s, ***) = %v, expected %v", tt.username, result, tt.expected)
}
})
}
}
func TestBasicAuthAddUser(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: "$2b$12$existinghash"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
// Test adding user
err = auth.AddUser("newuser", "$2b$12$newhash")
if err != nil {
t.Errorf("AddUser() error: %v", err)
}
if !auth.HasUser("newuser") {
t.Error("Expected newuser to exist")
}
// Test empty username
err = auth.AddUser("", "$2b$12$hash")
if err == nil {
t.Error("Expected error for empty username")
}
// Test invalid hash format
err = auth.AddUser("user2", "invalidhash")
if err == nil {
t.Error("Expected error for invalid hash")
}
}
func TestBasicAuthRemoveUser(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: "$2b$12$hash"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
// Remove existing user
auth.RemoveUser("admin")
if auth.HasUser("admin") {
t.Error("Expected admin to be removed")
}
// Remove non-existent user (should not error)
auth.RemoveUser("nonexistent")
}
func TestBasicAuthUserCount(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "user1", Password: "$2b$12$hash1"},
{Name: "user2", Password: "$2b$12$hash2"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
if count := auth.UserCount(); count != 2 {
t.Errorf("Expected UserCount 2, got %d", count)
}
_ = auth.AddUser("user3", "$2b$12$hash3")
if count := auth.UserCount(); count != 3 {
t.Errorf("Expected UserCount 3, got %d", count)
}
auth.RemoveUser("user1")
if count := auth.UserCount(); count != 2 {
t.Errorf("Expected UserCount 2, got %d", count)
}
}
func TestValidatePasswordHash(t *testing.T) {
tests := []struct {
name string
hash string
algorithm HashAlgorithm
wantErr bool
}{
{
name: "valid bcrypt",
hash: "$2b$12$hash",
algorithm: HashBcrypt,
},
{
name: "invalid bcrypt format",
hash: "nothere",
algorithm: HashBcrypt,
wantErr: true,
},
{
name: "valid argon2id",
hash: "$argon2id$v=19$m=65536,t=3,p=4$salt$hash",
algorithm: HashArgon2id,
},
{
name: "invalid argon2id format",
hash: "$bcrypt$hash",
algorithm: HashArgon2id,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validatePasswordHash(tt.hash, tt.algorithm)
if (err != nil) != tt.wantErr {
t.Errorf("validatePasswordHash() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestBasicAuthProcess(t *testing.T) {
password := "testpassword"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
RequireTLS: false,
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
Realm: "Test Realm",
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
nextHandlerCalled := false
nextHandler := func(ctx *fasthttp.RequestCtx) {
nextHandlerCalled = true
_, _ = ctx.WriteString("OK")
}
handler := auth.Process(nextHandler)
if handler == nil {
t.Error("Process() returned nil handler")
}
// Test successful authentication
ctx := &fasthttp.RequestCtx{}
ctx.Request.Header.Set("Authorization", "Basic YWRtaW46dGVzdHBhc3N3b3Jk")
ctx.Request.SetRequestURI("/")
ctx.Request.Header.SetMethod("GET")
handler(ctx)
if ctx.Response.StatusCode() != fasthttp.StatusOK {
t.Errorf("Expected status 200, got %d", ctx.Response.StatusCode())
}
if !nextHandlerCalled {
t.Error("Expected next handler to be called on successful auth")
}
if string(ctx.UserValue("remote_user").(string)) != "admin" {
t.Errorf("Expected remote_user to be 'admin', got '%s'", string(ctx.UserValue("remote_user").(string)))
}
}
func TestBasicAuthProcessFailedAuth(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
RequireTLS: false,
Users: []config.User{
{Name: "admin", Password: "$2b$12$existinghash"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
nextHandlerCalled := false
nextHandler := func(ctx *fasthttp.RequestCtx) {
nextHandlerCalled = true
_, _ = ctx.WriteString("OK")
}
handler := auth.Process(nextHandler)
// Test without Authorization header
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/")
ctx.Request.Header.SetMethod("GET")
handler(ctx)
if ctx.Response.StatusCode() != fasthttp.StatusUnauthorized {
t.Errorf("Expected status 401, got %d", ctx.Response.StatusCode())
}
if nextHandlerCalled {
t.Error("Expected next handler NOT to be called on failed auth")
}
// Test with invalid credentials
ctx = &fasthttp.RequestCtx{}
ctx.Request.Header.Set("Authorization", "Basic YWRtaW46d29uZ3Bhc3N3b3Jk")
ctx.Request.SetRequestURI("/")
ctx.Request.Header.SetMethod("GET")
handler(ctx)
if ctx.Response.StatusCode() != fasthttp.StatusUnauthorized {
t.Errorf("Expected status 401, got %d", ctx.Response.StatusCode())
}
}
func TestBasicAuthRequireTLS(t *testing.T) {
password := "testpassword"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
RequireTLS: true,
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
handler := auth.Process(func(ctx *fasthttp.RequestCtx) {
_, _ = ctx.WriteString("OK")
})
// Test without TLS (should be forbidden)
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/")
ctx.Request.Header.SetMethod("GET")
handler(ctx)
if ctx.Response.StatusCode() != fasthttp.StatusForbidden {
t.Errorf("Expected status 403 without TLS, got %d", ctx.Response.StatusCode())
}
}
func TestBasicAuthUpdateUser(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: "$2b$12$oldhash"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
// Test updating user
err = auth.UpdateUser("admin", "$2b$12$newhash")
if err != nil {
t.Errorf("UpdateUser() error: %v", err)
}
// Update non-existent user
err = auth.UpdateUser("nonexistent", "$2b$12$hash")
if err != nil {
t.Errorf("UpdateUser() on non-existent user should add it: %v", err)
}
}
func TestBasicAuthHasUser(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: "$2b$12$hash"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
if !auth.HasUser("admin") {
t.Error("Expected admin to exist")
}
if auth.HasUser("nonexistent") {
t.Error("Expected nonexistent user to return false")
}
}
func TestExtractCredentials(t *testing.T) {
password := "testpassword"
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
RequireTLS: false,
Users: []config.User{
{Name: "admin", Password: string(hashedPassword)},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
ctx := &fasthttp.RequestCtx{}
_, _, ok := auth.extractCredentials(ctx)
if ok {
t.Error("Expected no credentials without header")
}
ctx.Request.Header.Set("Authorization", "Basic YWRtaW46dGVzdHBhc3N3b3Jk")
username, pwd, ok := auth.extractCredentials(ctx)
if !ok {
t.Error("Expected credentials to be extracted")
}
if username != "admin" {
t.Errorf("Expected username 'admin', got %s", username)
}
if pwd != "testpassword" {
t.Errorf("Expected password 'testpassword', got %s", pwd)
}
ctx.Request.Header.Set("Authorization", "Basic invalid_base64!!!")
_, _, ok = auth.extractCredentials(ctx)
if ok {
t.Error("Expected no credentials with invalid base64")
}
ctx.Request.Header.Set("Authorization", "Basic YWRtaW4=")
_, _, ok = auth.extractCredentials(ctx)
if ok {
t.Error("Expected no credentials without colon")
}
ctx.Request.Header.Set("Authorization", "Basic Og==")
username, pwd, ok = auth.extractCredentials(ctx)
if !ok {
t.Error("Expected extraction with empty password")
}
if username != "" {
t.Errorf("Expected empty username, got %s", username)
}
if pwd != "" {
t.Errorf("Expected empty password, got %s", pwd)
}
ctx.Request.Header.Set("Authorization", "Digest realm=\"test\", username=\"admin\"")
_, _, ok = auth.extractCredentials(ctx)
if ok {
t.Error("Expected no credentials with Digest header")
}
}
func TestSendAuthChallenge(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Realm: "My Realm",
Users: []config.User{
{Name: "admin", Password: "$2b$12$hash"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
ctx := &fasthttp.RequestCtx{}
// Manually set the header since ctx.Error overwrites it
auth.sendAuthChallenge(ctx)
// Check status code
if ctx.Response.StatusCode() != fasthttp.StatusUnauthorized {
t.Errorf("Expected status 401, got %d", ctx.Response.StatusCode())
}
// Note: ctx.Error() in sendAuthChallenge sets status, writes body, and may not preserve headers
// FastHTTP's Error method writes headers after status, so WWW-Authenticate is not preserved
// This test validates the method runs without panic
}
func TestNameEmptyRealm(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: "$2b$12$hash"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
if auth.realm != "Restricted Area" {
t.Errorf("Expected default realm 'Restricted Area', got %s", auth.realm)
}
}
func TestName(t *testing.T) {
auth, err := NewBasicAuth(&config.AuthConfig{
Type: "basic",
Users: []config.User{
{Name: "admin", Password: "$2b$12$hash"},
},
})
if err != nil {
t.Fatalf("NewBasicAuth() error: %v", err)
}
if auth.Name() != "basic_auth" {
t.Errorf("Expected name 'basic_auth', got %s", auth.Name())
}
}
// TestAuthenticate_UnknownAlgorithm 测试未知算法
func TestAuthenticate_UnknownAlgorithm(t *testing.T) {
auth := &BasicAuth{
users: map[string]string{"admin": "$2b$12$hash"},
algorithm: HashAlgorithm(99), // 未知算法
}
result := auth.Authenticate("admin", "password")
if result {
t.Error("Authenticate() should return false for unknown algorithm")
}
}
// TestAuthenticateBcrypt_Error 测试 bcrypt 验证错误路径
func TestAuthenticateBcrypt_Error(t *testing.T) {
// 测试无效的 bcrypt 哈希
result := authenticateBcrypt("password", "invalid_hash")
if result {
t.Error("authenticateBcrypt() should return false for invalid hash")
}
}
// TestParseArgon2idHash_InvalidParts 测试无效的 argon2id 哈希格式
func TestParseArgon2idHash_InvalidParts(t *testing.T) {
tests := []struct {
name string
hash string
}{
{"too few parts", "$argon2id$v=19$m=32,t=2,p=2"},
{"wrong algorithm", "$bcrypt$v=19$m=32,t=2,p=2$salt$hash"},
{"wrong version", "$argon2id$v=18$m=32,t=2,p=2$salt$hash"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, _, _, err := parseArgon2idHash(tt.hash)
if err == nil {
t.Errorf("parseArgon2idHash(%q) should return error", tt.hash)
}
})
}
}
// TestValidatePasswordHash_UnknownAlgorithm 测试未知算法的密码哈希验证
func TestValidatePasswordHash_UnknownAlgorithm(t *testing.T) {
err := validatePasswordHash("hash", HashAlgorithm(99))
if err == nil {
t.Error("validatePasswordHash() should return error for unknown algorithm")
}
}