Initial release: Go Jdenticon library v0.1.0
- Core library with SVG and PNG generation - CLI tool with generate and batch commands - Cross-platform path handling for Windows compatibility - Comprehensive test suite with integration tests
This commit is contained in:
468
jdenticon/security_test.go
Normal file
468
jdenticon/security_test.go
Normal file
@@ -0,0 +1,468 @@
|
||||
package jdenticon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ungluedlabs/go-jdenticon/internal/constants"
|
||||
)
|
||||
|
||||
// TestDoSProtection_InputLength tests protection against large input strings.
|
||||
func TestDoSProtection_InputLength(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputLength int
|
||||
config Config
|
||||
expectError bool
|
||||
errorType string
|
||||
}{
|
||||
{
|
||||
name: "normal input with default config",
|
||||
inputLength: 100,
|
||||
config: DefaultConfig(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "maximum allowed input with default config",
|
||||
inputLength: constants.DefaultMaxInputLength,
|
||||
config: DefaultConfig(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "oversized input with default config",
|
||||
inputLength: constants.DefaultMaxInputLength + 1,
|
||||
config: DefaultConfig(),
|
||||
expectError: true,
|
||||
errorType: "*jdenticon.ErrValueTooLarge",
|
||||
},
|
||||
{
|
||||
name: "oversized input with custom smaller limit",
|
||||
inputLength: 1000,
|
||||
config: func() Config {
|
||||
c := DefaultConfig()
|
||||
c.MaxInputLength = 500
|
||||
return c
|
||||
}(),
|
||||
expectError: true,
|
||||
errorType: "*jdenticon.ErrValueTooLarge",
|
||||
},
|
||||
{
|
||||
name: "oversized input with disabled limit",
|
||||
inputLength: constants.DefaultMaxInputLength + 1000,
|
||||
config: func() Config {
|
||||
c := DefaultConfig()
|
||||
c.MaxInputLength = -1 // Disabled
|
||||
return c
|
||||
}(),
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create input string of specified length
|
||||
input := strings.Repeat("a", tt.inputLength)
|
||||
|
||||
// Test ToSVGWithConfig
|
||||
_, err := ToSVGWithConfig(context.Background(), input, 100, tt.config)
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("ToSVGWithConfig: expected error but got none")
|
||||
return
|
||||
}
|
||||
|
||||
// Check error type if specified
|
||||
if tt.errorType != "" {
|
||||
var valueErr *ErrValueTooLarge
|
||||
if !errors.As(err, &valueErr) {
|
||||
t.Errorf("ToSVGWithConfig: expected error type %s, got %T", tt.errorType, err)
|
||||
} else if valueErr.ParameterName != "InputLength" {
|
||||
t.Errorf("ToSVGWithConfig: expected InputLength error, got %s", valueErr.ParameterName)
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("ToSVGWithConfig: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Test ToPNGWithConfig
|
||||
_, err = ToPNGWithConfig(context.Background(), input, 100, tt.config)
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("ToPNGWithConfig: expected error but got none")
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("ToPNGWithConfig: unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDoSProtection_IconSize tests protection against large icon sizes.
|
||||
func TestDoSProtection_IconSize(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
config Config
|
||||
expectError bool
|
||||
errorType string
|
||||
}{
|
||||
{
|
||||
name: "normal size with default config",
|
||||
size: 256,
|
||||
config: DefaultConfig(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "maximum allowed size with default config",
|
||||
size: constants.DefaultMaxIconSize,
|
||||
config: DefaultConfig(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "oversized icon with default config",
|
||||
size: constants.DefaultMaxIconSize + 1,
|
||||
config: DefaultConfig(),
|
||||
expectError: true,
|
||||
errorType: "*jdenticon.ErrValueTooLarge",
|
||||
},
|
||||
{
|
||||
name: "oversized icon with custom smaller limit",
|
||||
size: 2000,
|
||||
config: func() Config {
|
||||
c := DefaultConfig()
|
||||
c.MaxIconSize = 1000
|
||||
return c
|
||||
}(),
|
||||
expectError: true,
|
||||
errorType: "*jdenticon.ErrValueTooLarge",
|
||||
},
|
||||
{
|
||||
name: "oversized icon with disabled limit",
|
||||
size: constants.DefaultMaxIconSize + 1000,
|
||||
config: func() Config {
|
||||
c := DefaultConfig()
|
||||
c.MaxIconSize = -1 // Disabled
|
||||
return c
|
||||
}(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "zero size",
|
||||
size: 0,
|
||||
config: DefaultConfig(),
|
||||
expectError: true,
|
||||
errorType: "jdenticon.ErrInvalidSize",
|
||||
},
|
||||
{
|
||||
name: "negative size",
|
||||
size: -100,
|
||||
config: DefaultConfig(),
|
||||
expectError: true,
|
||||
errorType: "jdenticon.ErrInvalidSize",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
input := "test"
|
||||
|
||||
// Test ToSVGWithConfig
|
||||
_, err := ToSVGWithConfig(context.Background(), input, tt.size, tt.config)
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("ToSVGWithConfig: expected error but got none")
|
||||
return
|
||||
}
|
||||
|
||||
// Check error type if specified
|
||||
if tt.errorType != "" {
|
||||
switch tt.errorType {
|
||||
case "*jdenticon.ErrValueTooLarge":
|
||||
var valueErr *ErrValueTooLarge
|
||||
if !errors.As(err, &valueErr) {
|
||||
t.Errorf("ToSVGWithConfig: expected error type %s, got %T", tt.errorType, err)
|
||||
} else if valueErr.ParameterName != "IconSize" {
|
||||
t.Errorf("ToSVGWithConfig: expected IconSize error, got %s", valueErr.ParameterName)
|
||||
}
|
||||
case "jdenticon.ErrInvalidSize":
|
||||
var sizeErr ErrInvalidSize
|
||||
if !errors.As(err, &sizeErr) {
|
||||
t.Errorf("ToSVGWithConfig: expected error type %s, got %T", tt.errorType, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("ToSVGWithConfig: unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDoSProtection_PNGEffectiveSize tests protection against PNG supersampling creating oversized effective images.
|
||||
func TestDoSProtection_PNGEffectiveSize(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
supersampling int
|
||||
config Config
|
||||
expectError bool
|
||||
errorType string
|
||||
}{
|
||||
{
|
||||
name: "normal PNG with default supersampling",
|
||||
size: 512,
|
||||
supersampling: 8, // 512 * 8 = 4096 (exactly at limit)
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "oversized effective PNG size",
|
||||
size: 1024,
|
||||
supersampling: 8, // 1024 * 8 = 8192 (exceeds 4096 limit)
|
||||
expectError: true,
|
||||
errorType: "*jdenticon.ErrEffectiveSizeTooLarge",
|
||||
},
|
||||
{
|
||||
name: "large PNG with low supersampling (within limit)",
|
||||
size: 2048,
|
||||
supersampling: 2, // 2048 * 2 = 4096 (exactly at limit)
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "maximum PNG with 1x supersampling",
|
||||
size: 4096,
|
||||
supersampling: 1, // 4096 * 1 = 4096 (exactly at limit)
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "PNG with disabled size limit",
|
||||
size: 2000,
|
||||
supersampling: 10, // Would exceed default limit but should be allowed
|
||||
config: func() Config {
|
||||
c := DefaultConfig()
|
||||
c.MaxIconSize = -1 // Disabled
|
||||
return c
|
||||
}(),
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
config := tt.config
|
||||
if config.MaxIconSize == 0 && config.MaxInputLength == 0 {
|
||||
// Use default config if not specified
|
||||
config = DefaultConfig()
|
||||
}
|
||||
config.PNGSupersampling = tt.supersampling
|
||||
|
||||
input := "test"
|
||||
|
||||
_, err := ToPNGWithConfig(context.Background(), input, tt.size, config)
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("ToPNGWithConfig: expected error but got none")
|
||||
return
|
||||
}
|
||||
|
||||
// Check error type if specified
|
||||
if tt.errorType == "*jdenticon.ErrEffectiveSizeTooLarge" {
|
||||
var effectiveErr *ErrEffectiveSizeTooLarge
|
||||
if !errors.As(err, &effectiveErr) {
|
||||
t.Errorf("ToPNGWithConfig: expected error type %s, got %T", tt.errorType, err)
|
||||
} else {
|
||||
expectedEffective := tt.size * tt.supersampling
|
||||
if effectiveErr.Actual != expectedEffective {
|
||||
t.Errorf("ToPNGWithConfig: expected effective size %d, got %d",
|
||||
expectedEffective, effectiveErr.Actual)
|
||||
}
|
||||
if effectiveErr.Size != tt.size {
|
||||
t.Errorf("ToPNGWithConfig: expected size %d, got %d",
|
||||
tt.size, effectiveErr.Size)
|
||||
}
|
||||
if effectiveErr.Supersampling != tt.supersampling {
|
||||
t.Errorf("ToPNGWithConfig: expected supersampling %d, got %d",
|
||||
tt.supersampling, effectiveErr.Supersampling)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("ToPNGWithConfig: unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDoSProtection_DynamicSupersampling tests the dynamic supersampling feature in ToPNG.
|
||||
func TestDoSProtection_DynamicSupersampling(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
expectError bool
|
||||
expectedMaxSS int // Expected maximum supersampling that should be used
|
||||
}{
|
||||
{
|
||||
name: "small size uses full supersampling",
|
||||
size: 256,
|
||||
expectError: false,
|
||||
expectedMaxSS: 8, // 256 * 8 = 2048 < 4096, so full supersampling
|
||||
},
|
||||
{
|
||||
name: "medium size uses reduced supersampling",
|
||||
size: 1024,
|
||||
expectError: false,
|
||||
expectedMaxSS: 4, // 1024 * 4 = 4096, reduced from default 8
|
||||
},
|
||||
{
|
||||
name: "large size uses minimal supersampling",
|
||||
size: 4096,
|
||||
expectError: false,
|
||||
expectedMaxSS: 1, // 4096 * 1 = 4096, minimal supersampling
|
||||
},
|
||||
{
|
||||
name: "oversized even with minimal supersampling",
|
||||
size: 4097,
|
||||
expectError: true, // Even 4097 * 1 = 4097 > 4096
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
input := "test"
|
||||
|
||||
data, err := ToPNG(context.Background(), input, tt.size)
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("ToPNG: expected error but got none")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("ToPNG: unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
t.Errorf("ToPNG: expected PNG data but got empty result")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDoSProtection_EmptyInput tests protection against empty input strings.
|
||||
func TestDoSProtection_EmptyInput(t *testing.T) {
|
||||
config := DefaultConfig()
|
||||
|
||||
_, err := ToSVGWithConfig(context.Background(), "", 100, config)
|
||||
if err == nil {
|
||||
t.Errorf("ToSVGWithConfig: expected error for empty input but got none")
|
||||
return
|
||||
}
|
||||
|
||||
var inputErr *ErrInvalidInput
|
||||
if !errors.As(err, &inputErr) {
|
||||
t.Errorf("ToSVGWithConfig: expected ErrInvalidInput, got %T", err)
|
||||
} else if inputErr.Field != "input" {
|
||||
t.Errorf("ToSVGWithConfig: expected input field error, got %s", inputErr.Field)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDoSProtection_ConfigurableLimits tests that the configurable limits work as expected.
|
||||
func TestDoSProtection_ConfigurableLimits(t *testing.T) {
|
||||
// Test custom limits
|
||||
customConfig := DefaultConfig()
|
||||
customConfig.MaxIconSize = 1000 // Much smaller than default
|
||||
customConfig.MaxInputLength = 10000 // Much smaller than default
|
||||
customConfig.PNGSupersampling = 4
|
||||
|
||||
// This should work with custom config
|
||||
_, err := ToSVGWithConfig(context.Background(), "test", 500, customConfig)
|
||||
if err != nil {
|
||||
t.Errorf("ToSVGWithConfig with custom config: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// This should fail with custom config (size too large)
|
||||
_, err = ToSVGWithConfig(context.Background(), "test", 1001, customConfig)
|
||||
if err == nil {
|
||||
t.Errorf("ToSVGWithConfig with custom config: expected error for oversized icon but got none")
|
||||
}
|
||||
|
||||
// Test disabled limits
|
||||
noLimitsConfig := DefaultConfig()
|
||||
noLimitsConfig.MaxIconSize = -1 // Disabled
|
||||
noLimitsConfig.MaxInputLength = -1 // Disabled
|
||||
noLimitsConfig.PNGSupersampling = 1
|
||||
|
||||
// This should work even with very large size (disabled limits)
|
||||
largeInput := strings.Repeat("x", constants.DefaultMaxInputLength+1000)
|
||||
_, err = ToSVGWithConfig(context.Background(), largeInput, constants.DefaultMaxIconSize+1000, noLimitsConfig)
|
||||
if err != nil {
|
||||
t.Errorf("ToSVGWithConfig with disabled limits: unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDoSProtection_GeneratorConsistency tests that Generator API respects configured limits.
|
||||
func TestDoSProtection_GeneratorConsistency(t *testing.T) {
|
||||
// Test generator with custom limits
|
||||
customConfig := DefaultConfig()
|
||||
customConfig.MaxIconSize = 1000
|
||||
customConfig.MaxInputLength = 100
|
||||
|
||||
generator, err := NewGeneratorWithConfig(customConfig, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("NewGeneratorWithConfig: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// This should work within limits
|
||||
_, err = generator.Generate(context.Background(), "test", 500)
|
||||
if err != nil {
|
||||
t.Errorf("Generator.Generate within limits: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// This should fail due to size limit
|
||||
_, err = generator.Generate(context.Background(), "test", 1001)
|
||||
if err == nil {
|
||||
t.Errorf("Generator.Generate with oversized icon: expected error but got none")
|
||||
} else {
|
||||
var valueErr *ErrValueTooLarge
|
||||
if !errors.As(err, &valueErr) {
|
||||
t.Errorf("Generator.Generate: expected ErrValueTooLarge, got %T", err)
|
||||
} else if valueErr.ParameterName != "IconSize" {
|
||||
t.Errorf("Generator.Generate: expected IconSize error, got %s", valueErr.ParameterName)
|
||||
}
|
||||
}
|
||||
|
||||
// This should fail due to input length limit
|
||||
longInput := strings.Repeat("a", 101)
|
||||
_, err = generator.Generate(context.Background(), longInput, 100)
|
||||
if err == nil {
|
||||
t.Errorf("Generator.Generate with long input: expected error but got none")
|
||||
} else {
|
||||
var valueErr *ErrValueTooLarge
|
||||
if !errors.As(err, &valueErr) {
|
||||
t.Errorf("Generator.Generate: expected ErrValueTooLarge, got %T", err)
|
||||
} else if valueErr.ParameterName != "InputLength" {
|
||||
t.Errorf("Generator.Generate: expected InputLength error, got %s", valueErr.ParameterName)
|
||||
}
|
||||
}
|
||||
|
||||
// Test generator with default limits
|
||||
defaultGenerator, err := NewGenerator()
|
||||
if err != nil {
|
||||
t.Fatalf("NewGenerator: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Should work with normal inputs
|
||||
_, err = defaultGenerator.Generate(context.Background(), "test", 256)
|
||||
if err != nil {
|
||||
t.Errorf("Default generator: unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Should fail with oversized input
|
||||
_, err = defaultGenerator.Generate(context.Background(), "test", constants.DefaultMaxIconSize+1)
|
||||
if err == nil {
|
||||
t.Errorf("Default generator with oversized icon: expected error but got none")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user