- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
245 lines
10 KiB
PHP
245 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Cryptography\SecureToken;
|
|
use App\Framework\Cryptography\SecureTokenGenerator;
|
|
use App\Framework\Random\SecureRandomGenerator;
|
|
|
|
it('generates secure token with default parameters', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$token = $generator->generate('api_key');
|
|
|
|
expect($token)->toBeInstanceOf(SecureToken::class);
|
|
expect($token->getType())->toBe('api_key');
|
|
expect($token->getFormat())->toBe(SecureTokenGenerator::FORMAT_BASE64_URL);
|
|
expect($token->getLength())->toBe(32);
|
|
});
|
|
|
|
it('generates API key with prefix', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$token = $generator->generateApiKey('myapp');
|
|
|
|
expect($token->getType())->toBe(SecureTokenGenerator::TYPE_API_KEY);
|
|
expect($token->getPrefix())->toBe('myapp');
|
|
expect($token->getValue())->toStartWith('myapp_');
|
|
});
|
|
|
|
it('generates session token', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$token = $generator->generateSessionToken();
|
|
|
|
expect($token->getType())->toBe(SecureTokenGenerator::TYPE_SESSION);
|
|
expect($token->getMetadataValue('purpose'))->toBe('session_management');
|
|
expect($token->getMetadataValue('secure'))->toBeTrue();
|
|
});
|
|
|
|
it('generates CSRF token', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$token = $generator->generateCsrfToken();
|
|
|
|
expect($token->getType())->toBe(SecureTokenGenerator::TYPE_CSRF);
|
|
expect($token->getMetadataValue('purpose'))->toBe('csrf_protection');
|
|
});
|
|
|
|
it('generates verification token', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$token = $generator->generateVerificationToken('email_verification');
|
|
|
|
expect($token->getType())->toBe(SecureTokenGenerator::TYPE_VERIFICATION);
|
|
expect($token->getMetadataValue('purpose'))->toBe('email_verification');
|
|
expect($token->getMetadataValue('single_use'))->toBeTrue();
|
|
});
|
|
|
|
it('generates OTP token with correct digits', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$token = $generator->generateOtpToken(6);
|
|
|
|
expect($token->getType())->toBe(SecureTokenGenerator::TYPE_OTP);
|
|
expect($token->getValue())->toMatch('/^\d{6}$/');
|
|
expect($token->getLength())->toBe(6);
|
|
expect($token->getMetadataValue('digits'))->toBe(6);
|
|
});
|
|
|
|
it('generates webhook token', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$token = $generator->generateWebhookToken();
|
|
|
|
expect($token->getType())->toBe(SecureTokenGenerator::TYPE_WEBHOOK);
|
|
expect($token->getPrefix())->toBe('whsec');
|
|
expect($token->getValue())->toStartWith('whsec_');
|
|
});
|
|
|
|
it('generates bearer token', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$token = $generator->generateBearerToken();
|
|
|
|
expect($token->getType())->toBe(SecureTokenGenerator::TYPE_BEARER);
|
|
expect($token->getMetadataValue('purpose'))->toBe('api_authorization');
|
|
});
|
|
|
|
it('generates refresh token with longer length', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$token = $generator->generateRefreshToken();
|
|
|
|
expect($token->getType())->toBe(SecureTokenGenerator::TYPE_REFRESH);
|
|
expect($token->getLength())->toBe(64);
|
|
expect($token->getMetadataValue('long_lived'))->toBeTrue();
|
|
});
|
|
|
|
it('generates tokens in different formats', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
|
|
$base64Token = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_BASE64);
|
|
$hexToken = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_HEX);
|
|
$base32Token = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_BASE32);
|
|
|
|
expect($base64Token->getFormat())->toBe(SecureTokenGenerator::FORMAT_BASE64);
|
|
expect($hexToken->getFormat())->toBe(SecureTokenGenerator::FORMAT_HEX);
|
|
expect($base32Token->getFormat())->toBe(SecureTokenGenerator::FORMAT_BASE32);
|
|
|
|
// Check format patterns
|
|
expect($hexToken->getValue())->toMatch('/^[0-9a-f]+$/');
|
|
expect($base32Token->getValue())->toMatch('/^[A-Z2-7]+$/');
|
|
});
|
|
|
|
it('generates custom tokens with alphabet', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$alphabet = '0123456789ABCDEF';
|
|
$token = $generator->generateCustom($alphabet, 16);
|
|
|
|
expect($token->getValue())->toMatch('/^[0-9A-F]{16}$/');
|
|
expect($token->getMetadataValue('alphabet'))->toBe($alphabet);
|
|
expect($token->getMetadataValue('alphabet_size'))->toBe(16);
|
|
});
|
|
|
|
it('generates batch of tokens', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$tokens = $generator->generateBatch('session', 5, 32);
|
|
|
|
expect($tokens)->toHaveCount(5);
|
|
expect($tokens[0])->toBeInstanceOf(SecureToken::class);
|
|
|
|
// All tokens should be unique
|
|
$values = array_map(fn ($token) => $token->getValue(), $tokens);
|
|
$uniqueValues = array_unique($values);
|
|
expect($uniqueValues)->toHaveCount(5);
|
|
});
|
|
|
|
it('validates token formats correctly', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
|
|
expect($generator->isValidFormat('dGVzdA', SecureTokenGenerator::FORMAT_BASE64))->toBeTrue();
|
|
expect($generator->isValidFormat('dGVzdA-_', SecureTokenGenerator::FORMAT_BASE64_URL))->toBeTrue();
|
|
expect($generator->isValidFormat('deadbeef', SecureTokenGenerator::FORMAT_HEX))->toBeTrue();
|
|
expect($generator->isValidFormat('MFRGG', SecureTokenGenerator::FORMAT_BASE32))->toBeTrue();
|
|
expect($generator->isValidFormat('Test123', SecureTokenGenerator::FORMAT_ALPHANUMERIC))->toBeTrue();
|
|
|
|
// Invalid formats
|
|
expect($generator->isValidFormat('invalid!@#', SecureTokenGenerator::FORMAT_BASE64_URL))->toBeFalse();
|
|
expect($generator->isValidFormat('invalidhex', SecureTokenGenerator::FORMAT_HEX))->toBeFalse();
|
|
});
|
|
|
|
it('calculates token entropy correctly', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
|
|
$base64Token = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_BASE64_URL);
|
|
$hexToken = $generator->generate(SecureTokenGenerator::TYPE_API_KEY, 32, SecureTokenGenerator::FORMAT_HEX);
|
|
|
|
$base64Entropy = $generator->getEntropy($base64Token);
|
|
$hexEntropy = $generator->getEntropy($hexToken);
|
|
|
|
expect($base64Entropy)->toBeGreaterThan($hexEntropy); // Base64 has higher entropy per character
|
|
expect($base64Entropy)->toBeGreaterThan(100); // Should have significant entropy
|
|
});
|
|
|
|
it('throws exception for too short token length', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $generator->generate('test', 8))
|
|
->toThrow(InvalidArgumentException::class, 'Token length must be at least 16 bytes');
|
|
});
|
|
|
|
it('throws exception for too long token length', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $generator->generate('test', 512))
|
|
->toThrow(InvalidArgumentException::class, 'Token length cannot exceed 256 bytes');
|
|
});
|
|
|
|
it('throws exception for unsupported token type', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $generator->generate('unsupported_type'))
|
|
->toThrow(InvalidArgumentException::class, 'Unsupported token type');
|
|
});
|
|
|
|
it('throws exception for unsupported format', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $generator->generate('api_key', 32, 'unsupported_format'))
|
|
->toThrow(InvalidArgumentException::class, 'Unsupported format');
|
|
});
|
|
|
|
it('throws exception for invalid OTP digits', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $generator->generateOtpToken(2))
|
|
->toThrow(InvalidArgumentException::class, 'OTP digits must be between 4 and 12');
|
|
|
|
expect(fn () => $generator->generateOtpToken(15))
|
|
->toThrow(InvalidArgumentException::class, 'OTP digits must be between 4 and 12');
|
|
});
|
|
|
|
it('throws exception for too small custom alphabet', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $generator->generateCustom('ABC', 16))
|
|
->toThrow(InvalidArgumentException::class, 'Alphabet must contain at least 16 characters');
|
|
});
|
|
|
|
it('throws exception for too short custom token', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $generator->generateCustom('0123456789ABCDEF', 4))
|
|
->toThrow(InvalidArgumentException::class, 'Token length must be at least 8 characters');
|
|
});
|
|
|
|
it('throws exception for invalid batch count', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
|
|
expect(fn () => $generator->generateBatch('session', 0))
|
|
->toThrow(InvalidArgumentException::class, 'Count must be positive');
|
|
|
|
expect(fn () => $generator->generateBatch('session', 2000))
|
|
->toThrow(InvalidArgumentException::class, 'Batch size cannot exceed 1000');
|
|
});
|
|
|
|
it('creates instance using factory method', function () {
|
|
$randomGen = new SecureRandomGenerator();
|
|
$generator = SecureTokenGenerator::create($randomGen);
|
|
|
|
expect($generator)->toBeInstanceOf(SecureTokenGenerator::class);
|
|
});
|
|
|
|
it('removes duplicate characters from custom alphabet', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$alphabetWithDuplicates = '0123456789ABCDEFABCDEF'; // Has duplicate ABCDEF
|
|
$token = $generator->generateCustom($alphabetWithDuplicates, 8);
|
|
|
|
$metadata = $token->getMetadata();
|
|
$actualAlphabet = $metadata['alphabet'];
|
|
expect($actualAlphabet)->toBe('0123456789ABCDEF'); // Duplicates removed
|
|
expect($metadata['alphabet_size'])->toBe(16);
|
|
});
|
|
|
|
it('generates unique tokens in batch', function () {
|
|
$generator = new SecureTokenGenerator(new SecureRandomGenerator());
|
|
$tokens = $generator->generateBatch('api_key', 10, 32);
|
|
|
|
$values = array_map(fn ($token) => $token->getValue(), $tokens);
|
|
$uniqueValues = array_unique($values);
|
|
|
|
expect($uniqueValues)->toHaveCount(10); // All should be unique
|
|
});
|