- config: 为 Config 和所有子配置结构添加完整文档,包含使用示例和注意事项 - stream: 为负载均衡器和服务器添加详细的参数、返回值和功能说明 - logging: 为日志格式化和输出函数添加文档,说明支持的变量替换 - handler: 为路由器、静态文件和 sendfile 处理器添加文档 - proxy: 为健康检查器和代理功能添加完整文档 - cache/server/ssl/middleware: 补充相关模块的文档注释 - config.example.yaml: 添加可信代理配置、加密套件示例,更新压缩级别说明 Co-Authored-By: Claude <noreply@anthropic.com>
410 lines
8.8 KiB
Go
410 lines
8.8 KiB
Go
// 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 {
|
||
name string
|
||
cfg *config.AuthConfig
|
||
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 TestBasicAuthProcess(t *testing.T) {
|
||
password := "testpassword"
|
||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||
|
||
auth, err := NewBasicAuth(&config.AuthConfig{
|
||
Type: "basic",
|
||
RequireTLS: false, // Disable TLS for testing
|
||
Users: []config.User{
|
||
{Name: "admin", Password: string(hashedPassword)},
|
||
},
|
||
Realm: "Test Realm",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("NewBasicAuth() error: %v", err)
|
||
}
|
||
|
||
nextHandler := func(ctx *fasthttp.RequestCtx) {
|
||
_, _ = ctx.WriteString("OK")
|
||
}
|
||
|
||
handler := auth.Process(nextHandler)
|
||
if handler == nil {
|
||
t.Error("Process() returned nil handler")
|
||
}
|
||
}
|
||
|
||
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 TestHashPasswordBcrypt(t *testing.T) {
|
||
password := "testpassword"
|
||
|
||
hash, err := HashPasswordBcrypt(password, bcrypt.DefaultCost)
|
||
if err != nil {
|
||
t.Fatalf("HashPasswordBcrypt() error: %v", err)
|
||
}
|
||
|
||
if hash == "" {
|
||
t.Error("Expected non-empty hash")
|
||
}
|
||
|
||
// Verify the hash works
|
||
err = bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||
if err != nil {
|
||
t.Errorf("Hash verification failed: %v", err)
|
||
}
|
||
}
|
||
|
||
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 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)
|
||
}
|
||
|
||
// Create a mock request context
|
||
ctx := &fasthttp.RequestCtx{}
|
||
|
||
// Test without Authorization header
|
||
_, _, ok := auth.extractCredentials(ctx)
|
||
if ok {
|
||
t.Error("Expected no credentials without header")
|
||
}
|
||
|
||
// Test with valid Basic auth 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)
|
||
}
|
||
}
|
||
|
||
func TestName(t *testing.T) {
|
||
password := "test"
|
||
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)
|
||
}
|
||
|
||
if auth.Name() != "basic_auth" {
|
||
t.Errorf("Expected name 'basic_auth', got %s", auth.Name())
|
||
}
|
||
}
|