You've already forked wc-licensed-product-client
Add security layer with response signature verification
Security classes: - ResponseSignature: HMAC-SHA256 signing and verification - StringEncoder: XOR-based string obfuscation for source code - IntegrityChecker: Source file hash verification - SignatureException, IntegrityException for error handling SecureLicenseClient: - Verifies server response signatures - Prevents response tampering and replay attacks - Per-license derived signing keys - Optional code integrity checking Documentation: - docs/server-implementation.md with complete WordPress/WooCommerce integration guide for signing responses Tests: - 34 new security tests (66 total, all passing) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
148
tests/Security/StringEncoderTest.php
Normal file
148
tests/Security/StringEncoderTest.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Magdev\WcLicensedProductClient\Tests\Security;
|
||||
|
||||
use Magdev\WcLicensedProductClient\Security\StringEncoder;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
#[CoversClass(StringEncoder::class)]
|
||||
final class StringEncoderTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function itEncodesAndDecodesStrings(): void
|
||||
{
|
||||
$encoder = new StringEncoder();
|
||||
$original = 'Hello, World!';
|
||||
|
||||
$encoded = $encoder->encode($original);
|
||||
$decoded = $encoder->decode($encoded);
|
||||
|
||||
self::assertSame($original, $decoded);
|
||||
self::assertNotSame($original, $encoded);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itEncodesApiPaths(): void
|
||||
{
|
||||
$encoder = new StringEncoder();
|
||||
$path = '/wp-json/wc-licensed-product/v1';
|
||||
|
||||
$encoded = $encoder->encode($path);
|
||||
$decoded = $encoder->decode($encoded);
|
||||
|
||||
self::assertSame($path, $decoded);
|
||||
self::assertStringNotContainsString('wp-json', $encoded);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itEncodesEndpointNames(): void
|
||||
{
|
||||
$encoder = new StringEncoder();
|
||||
$endpoints = ['validate', 'status', 'activate'];
|
||||
|
||||
foreach ($endpoints as $endpoint) {
|
||||
$encoded = $encoder->encode($endpoint);
|
||||
$decoded = $encoder->decode($encoded);
|
||||
|
||||
self::assertSame($endpoint, $decoded);
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itHandlesEmptyString(): void
|
||||
{
|
||||
$encoder = new StringEncoder();
|
||||
|
||||
$encoded = $encoder->encode('');
|
||||
$decoded = $encoder->decode($encoded);
|
||||
|
||||
self::assertSame('', $decoded);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itHandlesUnicodeStrings(): void
|
||||
{
|
||||
$encoder = new StringEncoder();
|
||||
$original = 'Ümläut and émojis 🔐';
|
||||
|
||||
$encoded = $encoder->encode($original);
|
||||
$decoded = $encoder->decode($encoded);
|
||||
|
||||
self::assertSame($original, $decoded);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itProducesDifferentOutputWithDifferentKeys(): void
|
||||
{
|
||||
$encoder1 = new StringEncoder('key1');
|
||||
$encoder2 = new StringEncoder('key2');
|
||||
$original = 'test string';
|
||||
|
||||
$encoded1 = $encoder1->encode($original);
|
||||
$encoded2 = $encoder2->encode($original);
|
||||
|
||||
self::assertNotSame($encoded1, $encoded2);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itRequiresSameKeyForDecoding(): void
|
||||
{
|
||||
$encoder1 = new StringEncoder('key1');
|
||||
$encoder2 = new StringEncoder('key2');
|
||||
$original = 'test string';
|
||||
|
||||
$encoded = $encoder1->encode($original);
|
||||
$decodedWithWrongKey = $encoder2->decode($encoded);
|
||||
|
||||
self::assertNotSame($original, $decodedWithWrongKey);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itGeneratesEncodedConstants(): void
|
||||
{
|
||||
$encoder = new StringEncoder();
|
||||
$constants = [
|
||||
'API_PATH' => '/wp-json/wc-licensed-product/v1',
|
||||
'VALIDATE' => 'validate',
|
||||
'STATUS' => 'status',
|
||||
];
|
||||
|
||||
$encoded = $encoder->generateEncodedConstants($constants);
|
||||
|
||||
self::assertCount(3, $encoded);
|
||||
self::assertArrayHasKey('API_PATH', $encoded);
|
||||
self::assertArrayHasKey('VALIDATE', $encoded);
|
||||
self::assertArrayHasKey('STATUS', $encoded);
|
||||
|
||||
// Verify all can be decoded back
|
||||
foreach ($constants as $name => $value) {
|
||||
self::assertSame($value, $encoder->decode($encoded[$name]));
|
||||
}
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itThrowsOnInvalidEncodedString(): void
|
||||
{
|
||||
$encoder = new StringEncoder();
|
||||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$encoder->decode('not-valid-base64!!!');
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function itHandlesLongStrings(): void
|
||||
{
|
||||
$encoder = new StringEncoder();
|
||||
$original = str_repeat('A', 10000);
|
||||
|
||||
$encoded = $encoder->encode($original);
|
||||
$decoded = $encoder->decode($encoded);
|
||||
|
||||
self::assertSame($original, $decoded);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user