@anonympins/fingerprint 0.7.5 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/README.md +1 -1
- package/package.json +3 -2
- package/public/anonympins-bot-mitigation-pow.zip +0 -0
- package/src/js/asn-lookup.js +223 -0
- package/src/js/fingerprint.client.js +1784 -1772
- package/src/js/fingerprint.client.obfuscated.js +1 -1
- package/src/js/fingerprint.js +236 -40
- package/src/js/fingerprint.utils.js +252 -2
- package/src/js/pow.worker.js +2 -2
- package/src/js/tests/asn-lookup.test.js +57 -0
- package/src/js/tests/cross_parity.test.js +3 -3
- package/src/js/tests/dns-circuit-breaker.test.js +6 -6
- package/src/js/tests/fingerprint.builder.test.js +76 -78
- package/src/js/tests/fingerprint.client.test.js +16 -17
- package/src/js/tests/fingerprint.test.js +19 -12
- package/src/js/tests/ja3AnomalyDetector.test.js +135 -136
- package/src/js/tests/library.test.js +94 -95
- package/src/js/tests/obfuscation.test.js +8 -8
- package/src/js/tests/quicFingerprint.test.js +47 -0
- package/src/js/tests/tcpFingerprint.test.js +4 -4
- package/src/js/upow-model-task.js +155 -112
- package/src/php/AsnLookupEngine.php +173 -0
- package/src/php/Challenge/ChallengeUtils.php +430 -117
- package/src/php/Config/SecurityProfiles.php +75 -62
- package/src/php/DirectFingerprint.php +80 -13
- package/src/php/FingerprintBuilder.php +13 -13
- package/src/php/FingerprintClient.php +6 -6
- package/src/php/FingerprintEngine.php +2141 -1837
- package/src/php/Ja3AnomalyDetector.php +22 -22
- package/src/php/NetworkProfile.php +68 -0
- package/src/php/Optimization/FunctionRegistry.php +4 -4
- package/src/php/Optimization/Optimization.php +6 -6
- package/src/php/ProblemManager.php +12 -2
- package/src/php/RequestContext.php +11 -11
- package/src/php/Store/InMemoryStore.php +96 -66
- package/src/php/Store/MongoDbStore.php +2 -2
- package/src/php/Store/RedisStore.php +1 -1
- package/src/php/Store/StoreManager.php +48 -35
- package/src/php/Tests/AsnLookupTest.php +61 -0
- package/src/php/Tests/ChallengeUtilsTest.php +31 -0
- package/src/php/Tests/DnsCircuitBreakerTest.php +105 -105
- package/src/php/Tests/FingerprintClientTest.php +70 -70
- package/src/php/Tests/FingerprintEngineTest.php +4 -2
- package/src/php/Tests/PowTest.php +37 -39
- package/src/php/Tests/QuicFingerprintTest.php +115 -53
- package/src/php/Tests/ThreatIntelTest.php +2 -2
- package/src/php/Utils/BigInt.php +202 -202
- package/src/php/Utils/BlockList.php +13 -13
- package/src/php/Utils/Env.php +7 -2
- package/src/php/Utils/Logger.php +19 -1
- package/src/php/Utils/MaliciousPatterns.php +6 -6
- package/src/php/Utils/MetricsManager.php +209 -209
- package/src/php/Utils/RequestUtils.php +348 -18
- package/src/php/Utils/TLSClientHelloParser.php +385 -369
- package/src/php/WordPress/WpDbStore.php +34 -7
- package/src/php/WordPress/anonympins-bot-mitigation-pow.php +1222 -0
- package/src/php/WordPress/fingerprint-anti-bot.php +0 -559
- package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/anonympins-bot-mitigation-pow-fr_FR.po +232 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-de_DE.po +297 -261
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.mo +0 -0
- package/src/php/WordPress/languages/fingerprint-wordpress-fr_FR.po +348 -267
- package/src/php/WordPress/package.php +88 -99
- package/src/php/WordPress/readme.txt +28 -6
- package/src/php/bin/auto-tune.php +1 -1
- package/public/fingerprint-anti-bot.zip +0 -0
|
@@ -1,36 +1,49 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Store;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Manages the global store singleton instance.
|
|
9
|
-
*/
|
|
10
|
-
class StoreManager
|
|
11
|
-
{
|
|
12
|
-
private static ?IStore $store = null;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Store;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Manages the global store singleton instance for the application.
|
|
9
|
+
*/
|
|
10
|
+
class StoreManager
|
|
11
|
+
{
|
|
12
|
+
private static ?IStore $store = null;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Returns the current global store instance.
|
|
16
|
+
* Lazily instantiates an InMemoryStore if no store has been configured yet.
|
|
17
|
+
*
|
|
18
|
+
* @return IStore The active store instance.
|
|
19
|
+
*/
|
|
20
|
+
public static function getStore(): IStore
|
|
21
|
+
{
|
|
22
|
+
if (self::$store === null) {
|
|
23
|
+
self::$store = new InMemoryStore();
|
|
24
|
+
}
|
|
25
|
+
return self::$store;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Replaces the active store with an externally provided IStore implementation.
|
|
30
|
+
* Typically used at bootstrap to plug in a persistent store (Redis, database, etc.).
|
|
31
|
+
*
|
|
32
|
+
* @param IStore $externalStore The external store implementation to use.
|
|
33
|
+
* @return void
|
|
34
|
+
*/
|
|
35
|
+
public static function configureStore(IStore $externalStore): void
|
|
36
|
+
{
|
|
37
|
+
self::$store = $externalStore;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Sets the active store instance. Useful for dependency injection and testing.
|
|
42
|
+
*
|
|
43
|
+
* @param mixed $store
|
|
44
|
+
*/
|
|
45
|
+
public static function setStore($store): void
|
|
46
|
+
{
|
|
47
|
+
self::$store = $store;
|
|
48
|
+
}
|
|
36
49
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\AsnLookupEngine;
|
|
8
|
+
use Anonympins\Fingerprint\NetworkProfile;
|
|
9
|
+
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
10
|
+
use PHPUnit\Framework\TestCase;
|
|
11
|
+
|
|
12
|
+
class AsnLookupTest extends TestCase
|
|
13
|
+
{
|
|
14
|
+
private AsnLookupEngine $engine;
|
|
15
|
+
|
|
16
|
+
protected function setUp(): void
|
|
17
|
+
{
|
|
18
|
+
parent::setUp();
|
|
19
|
+
$this->engine = AsnLookupEngine::getInstance();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public function testDatacenterResolutionAndPriorScore(): void
|
|
23
|
+
{
|
|
24
|
+
$hetzner = $this->engine->lookup('95.216.12.34');
|
|
25
|
+
$this->assertSame('HOSTING', $hetzner->getType());
|
|
26
|
+
$this->assertSame(55.0, $hetzner->getBaseScore());
|
|
27
|
+
$this->assertSame(0.85, $hetzner->getInflectionPoint());
|
|
28
|
+
$this->assertFalse($hetzner->isToleranceRotation());
|
|
29
|
+
|
|
30
|
+
$aws = $this->engine->lookup('54.210.1.20');
|
|
31
|
+
$this->assertSame('HOSTING', $aws->getType());
|
|
32
|
+
$this->assertSame(55.0, $aws->getBaseScore());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public function testMobileCgnatResolution(): void
|
|
36
|
+
{
|
|
37
|
+
$cgnat = $this->engine->lookup('100.70.1.25');
|
|
38
|
+
$this->assertSame('CELLULAR', $cgnat->getType());
|
|
39
|
+
$this->assertSame(10.0, $cgnat->getBaseScore());
|
|
40
|
+
$this->assertSame(0.60, $cgnat->getInflectionPoint());
|
|
41
|
+
$this->assertTrue($cgnat->isToleranceRotation());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public function testStarlinkSatelliteResolution(): void
|
|
45
|
+
{
|
|
46
|
+
$starlink = $this->engine->lookup('98.97.10.5');
|
|
47
|
+
$this->assertSame('SATELLITE', $starlink->getType());
|
|
48
|
+
$this->assertSame(15.0, $starlink->getBaseScore());
|
|
49
|
+
$this->assertSame(150.0, $starlink->getJitterTolerance());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public function testAnalogInconsistencyModulation(): void
|
|
53
|
+
{
|
|
54
|
+
$consistency = 0.80; // Incohérence légère de rendu
|
|
55
|
+
$scoreDatacenter = RequestUtils::calculateAnalogInconsistencyScore($consistency, NetworkProfile::hosting());
|
|
56
|
+
$scoreMobile = RequestUtils::calculateAnalogInconsistencyScore($consistency, NetworkProfile::cellular());
|
|
57
|
+
|
|
58
|
+
$this->assertGreaterThan(55.0, $scoreDatacenter, 'Sur Datacenter (0.80 < 0.85), la suspicion décolle au-dessus de 55.0');
|
|
59
|
+
$this->assertLessThan(10.0, $scoreMobile, 'Sur Mobile (0.80 > 0.60), la variation est entièrement absorbée (< 10.0)');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -263,6 +263,37 @@ class ChallengeUtilsTest extends TestCase
|
|
|
263
263
|
Env::clear('ED25519_PUBLIC_KEY');
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
public function testGenerateIssuerPemKeys(): void
|
|
267
|
+
{
|
|
268
|
+
if (!extension_loaded('openssl') || !defined('OPENSSL_KEYTYPE_ED25519')) {
|
|
269
|
+
$this->markTestSkipped('OpenSSL Ed25519 is not supported in this environment.');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
$pkey = @openssl_pkey_new(["private_key_type" => constant('OPENSSL_KEYTYPE_ED25519')]);
|
|
273
|
+
if (!$pkey || !@openssl_pkey_export($pkey, $testPem)) {
|
|
274
|
+
$this->markTestSkipped('Ed25519 key generation or export is not supported in this PHP/OpenSSL environment.');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
$tempDir = sys_get_temp_dir() . '/fp-issuer-keys-' . time();
|
|
278
|
+
try {
|
|
279
|
+
$result = ChallengeUtils::generateIssuerPemKeys($tempDir);
|
|
280
|
+
$this->assertFileExists($result['privateKeyPath']);
|
|
281
|
+
$this->assertFileExists($result['publicKeyPath']);
|
|
282
|
+
$this->assertStringContainsString('-----BEGIN PRIVATE KEY-----', $result['privateKey']);
|
|
283
|
+
$this->assertStringContainsString('-----BEGIN PUBLIC KEY-----', $result['publicKey']);
|
|
284
|
+
} finally {
|
|
285
|
+
if (file_exists($tempDir . '/issuer-private.pem')) {
|
|
286
|
+
unlink($tempDir . '/issuer-private.pem');
|
|
287
|
+
}
|
|
288
|
+
if (file_exists($tempDir . '/issuer-public.pem')) {
|
|
289
|
+
unlink($tempDir . '/issuer-public.pem');
|
|
290
|
+
}
|
|
291
|
+
if (is_dir($tempDir)) {
|
|
292
|
+
rmdir($tempDir);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
266
297
|
public function testCooperativePoSpaceWorkflow(): void
|
|
267
298
|
{
|
|
268
299
|
$clientIp = '127.0.0.1';
|
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\FingerprintEngine;
|
|
8
|
-
use PHPUnit\Framework\TestCase;
|
|
9
|
-
use ReflectionClass;
|
|
10
|
-
|
|
11
|
-
class DnsCircuitBreakerTest extends TestCase
|
|
12
|
-
{
|
|
13
|
-
private ReflectionClass $reflection;
|
|
14
|
-
|
|
15
|
-
protected function setUp(): void
|
|
16
|
-
{
|
|
17
|
-
parent::setUp();
|
|
18
|
-
$this->reflection = new ReflectionClass(FingerprintEngine::class);
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
$this->setCircuitBreakerState([
|
|
22
|
-
'state' => 'CLOSED',
|
|
23
|
-
'failureCount' => 0,
|
|
24
|
-
'lastStateChange' => 0,
|
|
25
|
-
'threshold' => 5,
|
|
26
|
-
'cooldown' => 1, // 1
|
|
27
|
-
]);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
private function getCircuitBreakerState(): array
|
|
31
|
-
{
|
|
32
|
-
$property = $this->reflection->getProperty('dnsCircuitBreaker');
|
|
33
|
-
if (PHP_VERSION_ID < 80100) {
|
|
34
|
-
$property->setAccessible(true);
|
|
35
|
-
}
|
|
36
|
-
return $property->getValue();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private function setCircuitBreakerState(array $state): void
|
|
40
|
-
{
|
|
41
|
-
$property = $this->reflection->getProperty('dnsCircuitBreaker');
|
|
42
|
-
if (PHP_VERSION_ID < 80100) {
|
|
43
|
-
$property->setAccessible(true);
|
|
44
|
-
}
|
|
45
|
-
$property->setValue(null, $state);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private function invokeMethod(string $methodName, array $args = [])
|
|
49
|
-
{
|
|
50
|
-
$method = $this->reflection->getMethod($methodName);
|
|
51
|
-
if (PHP_VERSION_ID < 80100) { // PHP 8.1.0
|
|
52
|
-
$method->setAccessible(true);
|
|
53
|
-
}
|
|
54
|
-
return $method->invokeArgs(null, $args);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
public function testInitialStateIsClosed(): void
|
|
58
|
-
{
|
|
59
|
-
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
60
|
-
$state = $this->getCircuitBreakerState();
|
|
61
|
-
$this->assertSame('CLOSED', $state['state']);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
public function testFailureThresholdTripsBreaker(): void
|
|
65
|
-
{
|
|
66
|
-
for ($i = 0; $i < 4; $i++) {
|
|
67
|
-
$this->invokeMethod('recordDnsFailure');
|
|
68
|
-
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
69
|
-
$state = $this->getCircuitBreakerState();
|
|
70
|
-
$this->assertSame('CLOSED', $state['state']);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
$this->invokeMethod('recordDnsFailure');
|
|
74
|
-
$this->assertFalse($this->invokeMethod('canAttemptDns'));
|
|
75
|
-
$state = $this->getCircuitBreakerState();
|
|
76
|
-
$this->assertSame('OPEN', $state['state']);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
public function testCooldownTransitionsToHalfOpen(): void
|
|
80
|
-
{
|
|
81
|
-
for ($i = 0; $i < 5; $i++) {
|
|
82
|
-
$this->invokeMethod('recordDnsFailure');
|
|
83
|
-
}
|
|
84
|
-
$this->assertFalse($this->invokeMethod('canAttemptDns'));
|
|
85
|
-
|
|
86
|
-
usleep(1100000); // 1.1s
|
|
87
|
-
|
|
88
|
-
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
89
|
-
$state = $this->getCircuitBreakerState();
|
|
90
|
-
$this->assertSame('HALF-OPEN', $state['state']);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
public function testSuccessOnHalfOpenResetsBreaker(): void
|
|
94
|
-
{
|
|
95
|
-
for ($i = 0; $i < 5; $i++) {
|
|
96
|
-
$this->invokeMethod('recordDnsFailure');
|
|
97
|
-
}
|
|
98
|
-
usleep(1100000);
|
|
99
|
-
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
100
|
-
|
|
101
|
-
$this->invokeMethod('recordDnsSuccess');
|
|
102
|
-
$state = $this->getCircuitBreakerState();
|
|
103
|
-
$this->assertSame('CLOSED', $state['state']);
|
|
104
|
-
$this->assertSame(0, $state['failureCount']);
|
|
105
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\FingerprintEngine;
|
|
8
|
+
use PHPUnit\Framework\TestCase;
|
|
9
|
+
use ReflectionClass;
|
|
10
|
+
|
|
11
|
+
class DnsCircuitBreakerTest extends TestCase
|
|
12
|
+
{
|
|
13
|
+
private ReflectionClass $reflection;
|
|
14
|
+
|
|
15
|
+
protected function setUp(): void
|
|
16
|
+
{
|
|
17
|
+
parent::setUp();
|
|
18
|
+
$this->reflection = new ReflectionClass(FingerprintEngine::class);
|
|
19
|
+
|
|
20
|
+
// Reset static circuit breaker state before each test
|
|
21
|
+
$this->setCircuitBreakerState([
|
|
22
|
+
'state' => 'CLOSED',
|
|
23
|
+
'failureCount' => 0,
|
|
24
|
+
'lastStateChange' => 0,
|
|
25
|
+
'threshold' => 5,
|
|
26
|
+
'cooldown' => 1, // 1 second cooldown for faster test execution
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private function getCircuitBreakerState(): array
|
|
31
|
+
{
|
|
32
|
+
$property = $this->reflection->getProperty('dnsCircuitBreaker');
|
|
33
|
+
if (PHP_VERSION_ID < 80100) {
|
|
34
|
+
$property->setAccessible(true);
|
|
35
|
+
}
|
|
36
|
+
return $property->getValue();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private function setCircuitBreakerState(array $state): void
|
|
40
|
+
{
|
|
41
|
+
$property = $this->reflection->getProperty('dnsCircuitBreaker');
|
|
42
|
+
if (PHP_VERSION_ID < 80100) {
|
|
43
|
+
$property->setAccessible(true);
|
|
44
|
+
}
|
|
45
|
+
$property->setValue(null, $state);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private function invokeMethod(string $methodName, array $args = [])
|
|
49
|
+
{
|
|
50
|
+
$method = $this->reflection->getMethod($methodName);
|
|
51
|
+
if (PHP_VERSION_ID < 80100) { // PHP 8.1.0
|
|
52
|
+
$method->setAccessible(true);
|
|
53
|
+
}
|
|
54
|
+
return $method->invokeArgs(null, $args);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public function testInitialStateIsClosed(): void
|
|
58
|
+
{
|
|
59
|
+
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
60
|
+
$state = $this->getCircuitBreakerState();
|
|
61
|
+
$this->assertSame('CLOSED', $state['state']);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public function testFailureThresholdTripsBreaker(): void
|
|
65
|
+
{
|
|
66
|
+
for ($i = 0; $i < 4; $i++) {
|
|
67
|
+
$this->invokeMethod('recordDnsFailure');
|
|
68
|
+
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
69
|
+
$state = $this->getCircuitBreakerState();
|
|
70
|
+
$this->assertSame('CLOSED', $state['state']);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
$this->invokeMethod('recordDnsFailure');
|
|
74
|
+
$this->assertFalse($this->invokeMethod('canAttemptDns'));
|
|
75
|
+
$state = $this->getCircuitBreakerState();
|
|
76
|
+
$this->assertSame('OPEN', $state['state']);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public function testCooldownTransitionsToHalfOpen(): void
|
|
80
|
+
{
|
|
81
|
+
for ($i = 0; $i < 5; $i++) {
|
|
82
|
+
$this->invokeMethod('recordDnsFailure');
|
|
83
|
+
}
|
|
84
|
+
$this->assertFalse($this->invokeMethod('canAttemptDns'));
|
|
85
|
+
|
|
86
|
+
usleep(1100000); // 1.1s
|
|
87
|
+
|
|
88
|
+
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
89
|
+
$state = $this->getCircuitBreakerState();
|
|
90
|
+
$this->assertSame('HALF-OPEN', $state['state']);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public function testSuccessOnHalfOpenResetsBreaker(): void
|
|
94
|
+
{
|
|
95
|
+
for ($i = 0; $i < 5; $i++) {
|
|
96
|
+
$this->invokeMethod('recordDnsFailure');
|
|
97
|
+
}
|
|
98
|
+
usleep(1100000);
|
|
99
|
+
$this->assertTrue($this->invokeMethod('canAttemptDns'));
|
|
100
|
+
|
|
101
|
+
$this->invokeMethod('recordDnsSuccess');
|
|
102
|
+
$state = $this->getCircuitBreakerState();
|
|
103
|
+
$this->assertSame('CLOSED', $state['state']);
|
|
104
|
+
$this->assertSame(0, $state['failureCount']);
|
|
105
|
+
}
|
|
106
106
|
}
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\FingerprintClient;
|
|
8
|
-
use PHPUnit\Framework\TestCase;
|
|
9
|
-
|
|
10
|
-
class FingerprintClientTest extends TestCase
|
|
11
|
-
{
|
|
12
|
-
public function testDefaultConfigurationIncludesWasmAndDefaultPath(): void
|
|
13
|
-
{
|
|
14
|
-
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
15
|
-
$scriptTag = $client->getScriptTag();
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
$this->assertStringContainsString('src="/js/fingerprint.client.js"', $scriptTag);
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
$this->assertStringContainsString('"wasm":true', $scriptTag);
|
|
22
|
-
$this->assertStringContainsString('"wasmPath":"\/fp.js"', $scriptTag);
|
|
23
|
-
$this->assertStringContainsString('wasmScript.src = config.wasmPath;', $scriptTag);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public function testWasmCanBeOverriddenOrDisabled(): void
|
|
27
|
-
{
|
|
28
|
-
$client = new FingerprintClient('/js/fingerprint.client.js', [
|
|
29
|
-
'wasm' => false,
|
|
30
|
-
'wasmPath' => '/custom/path/to/wasm.js'
|
|
31
|
-
]);
|
|
32
|
-
$scriptTag = $client->getScriptTag();
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
$this->assertStringContainsString('"wasm":false', $scriptTag);
|
|
36
|
-
$this->assertStringContainsString('"wasmPath":"\/custom\/path\/to\/wasm.js"', $scriptTag);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
public function testGenerateHoneypotFieldAddsFieldToConfig(): void
|
|
40
|
-
{
|
|
41
|
-
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
42
|
-
|
|
43
|
-
$honeypotFieldHtml = $client->generateHoneypotField('confirm_email_trap');
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
$this->assertStringContainsString('name="confirm_email_trap"', $honeypotFieldHtml);
|
|
47
|
-
$this->assertStringContainsString('tabindex="-1"', $honeypotFieldHtml);
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
$scriptTag = $client->getScriptTag();
|
|
51
|
-
$this->assertStringContainsString('"honeypots":["confirm_email_trap"]', $scriptTag);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public function testNonceIsGeneratedAndAppliedToScripts(): void
|
|
55
|
-
{
|
|
56
|
-
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
57
|
-
$nonce = $client->getNonce();
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
$this->assertNotNull($nonce);
|
|
61
|
-
$this->assertEquals(32, strlen($nonce));
|
|
62
|
-
|
|
63
|
-
$scriptTag = $client->getScriptTag();
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
$this->assertStringContainsString('nonce="' . $nonce . '"', $scriptTag);
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
$this->assertStringContainsString('wasmScript.nonce = \'' . $nonce . '\'', $scriptTag);
|
|
70
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\FingerprintClient;
|
|
8
|
+
use PHPUnit\Framework\TestCase;
|
|
9
|
+
|
|
10
|
+
class FingerprintClientTest extends TestCase
|
|
11
|
+
{
|
|
12
|
+
public function testDefaultConfigurationIncludesWasmAndDefaultPath(): void
|
|
13
|
+
{
|
|
14
|
+
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
15
|
+
$scriptTag = $client->getScriptTag();
|
|
16
|
+
|
|
17
|
+
// Verify main script tag is present
|
|
18
|
+
$this->assertStringContainsString('src="/js/fingerprint.client.js"', $scriptTag);
|
|
19
|
+
|
|
20
|
+
// Verify default WASM configuration serialized for JS runtime
|
|
21
|
+
$this->assertStringContainsString('"wasm":true', $scriptTag);
|
|
22
|
+
$this->assertStringContainsString('"wasmPath":"\/fp.js"', $scriptTag);
|
|
23
|
+
$this->assertStringContainsString('wasmScript.src = config.wasmPath;', $scriptTag);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public function testWasmCanBeOverriddenOrDisabled(): void
|
|
27
|
+
{
|
|
28
|
+
$client = new FingerprintClient('/js/fingerprint.client.js', [
|
|
29
|
+
'wasm' => false,
|
|
30
|
+
'wasmPath' => '/custom/path/to/wasm.js'
|
|
31
|
+
]);
|
|
32
|
+
$scriptTag = $client->getScriptTag();
|
|
33
|
+
|
|
34
|
+
// Verify configuration overrides are properly applied
|
|
35
|
+
$this->assertStringContainsString('"wasm":false', $scriptTag);
|
|
36
|
+
$this->assertStringContainsString('"wasmPath":"\/custom\/path\/to\/wasm.js"', $scriptTag);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public function testGenerateHoneypotFieldAddsFieldToConfig(): void
|
|
40
|
+
{
|
|
41
|
+
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
42
|
+
|
|
43
|
+
$honeypotFieldHtml = $client->generateHoneypotField('confirm_email_trap');
|
|
44
|
+
|
|
45
|
+
// Verify HTML markup of the honeypot field
|
|
46
|
+
$this->assertStringContainsString('name="confirm_email_trap"', $honeypotFieldHtml);
|
|
47
|
+
$this->assertStringContainsString('tabindex="-1"', $honeypotFieldHtml);
|
|
48
|
+
|
|
49
|
+
// Verify field dynamically registers with client-side configuration
|
|
50
|
+
$scriptTag = $client->getScriptTag();
|
|
51
|
+
$this->assertStringContainsString('"honeypots":["confirm_email_trap"]', $scriptTag);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public function testNonceIsGeneratedAndAppliedToScripts(): void
|
|
55
|
+
{
|
|
56
|
+
$client = new FingerprintClient('/js/fingerprint.client.js');
|
|
57
|
+
$nonce = $client->getNonce();
|
|
58
|
+
|
|
59
|
+
// Verify nonce generation format (32 hex characters)
|
|
60
|
+
$this->assertNotNull($nonce);
|
|
61
|
+
$this->assertEquals(32, strlen($nonce));
|
|
62
|
+
|
|
63
|
+
$scriptTag = $client->getScriptTag();
|
|
64
|
+
|
|
65
|
+
// Nonce must be attached to HTML script tag
|
|
66
|
+
$this->assertStringContainsString('nonce="' . $nonce . '"', $scriptTag);
|
|
67
|
+
|
|
68
|
+
// And dynamically set during WASM loader script injection
|
|
69
|
+
$this->assertStringContainsString('wasmScript.nonce = \'' . $nonce . '\'', $scriptTag);
|
|
70
|
+
}
|
|
71
71
|
}
|
|
@@ -303,11 +303,12 @@ class FingerprintEngineTest extends TestCase
|
|
|
303
303
|
$this->markTestSkipped('Ed25519 is not supported in this PHP/OpenSSL environment.');
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
$configDir =
|
|
306
|
+
$configDir = sys_get_temp_dir() . '/anonympins-test-keys-' . time();
|
|
307
307
|
if (!is_dir($configDir)) {
|
|
308
308
|
mkdir($configDir, 0777, true);
|
|
309
309
|
}
|
|
310
310
|
$keyPath = $configDir . '/ed25519_key.json';
|
|
311
|
+
Env::set('FINGERPRINT_KEY_DIR', $configDir);
|
|
311
312
|
|
|
312
313
|
Env::clear('ED25519_PRIVATE_KEY');
|
|
313
314
|
Env::clear('ED25519_PUBLIC_KEY');
|
|
@@ -338,11 +339,12 @@ class FingerprintEngineTest extends TestCase
|
|
|
338
339
|
|
|
339
340
|
public function testEd25519KeyLoadingFromDisk(): void
|
|
340
341
|
{
|
|
341
|
-
$configDir =
|
|
342
|
+
$configDir = sys_get_temp_dir() . '/anonympins-test-keys-' . time();
|
|
342
343
|
if (!is_dir($configDir)) {
|
|
343
344
|
mkdir($configDir, 0777, true);
|
|
344
345
|
}
|
|
345
346
|
$keyPath = $configDir . '/ed25519_key.json';
|
|
347
|
+
Env::set('FINGERPRINT_KEY_DIR', $configDir);
|
|
346
348
|
|
|
347
349
|
$dummyKeys = [
|
|
348
350
|
'privateKey' => '-----BEGIN PRIVATE KEY-----\ndummy-php-private\n-----END PRIVATE KEY-----',
|
|
@@ -1,40 +1,38 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
-
|
|
7
|
-
use Anonympins\Fingerprint\Config\SecurityProfiles;
|
|
8
|
-
use Anonympins\Fingerprint\FingerprintEngine;
|
|
9
|
-
use Anonympins\Fingerprint\ProblemManager;
|
|
10
|
-
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
11
|
-
use Anonympins\Fingerprint\Store\StoreManager;
|
|
12
|
-
use PHPUnit\Framework\TestCase;
|
|
13
|
-
|
|
14
|
-
class PowTest extends TestCase
|
|
15
|
-
{
|
|
16
|
-
private FingerprintEngine $engine;
|
|
17
|
-
|
|
18
|
-
protected function setUp(): void
|
|
19
|
-
{
|
|
20
|
-
// 1.
|
|
21
|
-
$store = new InMemoryStore();
|
|
22
|
-
StoreManager::configureStore($store);
|
|
23
|
-
|
|
24
|
-
// 2.
|
|
25
|
-
|
|
26
|
-
$configPath
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
$this->assertIsArray($problems);
|
|
39
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\Config\SecurityProfiles;
|
|
8
|
+
use Anonympins\Fingerprint\FingerprintEngine;
|
|
9
|
+
use Anonympins\Fingerprint\ProblemManager;
|
|
10
|
+
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
11
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
12
|
+
use PHPUnit\Framework\TestCase;
|
|
13
|
+
|
|
14
|
+
class PowTest extends TestCase
|
|
15
|
+
{
|
|
16
|
+
private FingerprintEngine $engine;
|
|
17
|
+
|
|
18
|
+
protected function setUp(): void
|
|
19
|
+
{
|
|
20
|
+
// 1. Configure an in-memory store for test isolation.
|
|
21
|
+
$store = new InMemoryStore();
|
|
22
|
+
StoreManager::configureStore($store);
|
|
23
|
+
|
|
24
|
+
// 2. Initialize ProblemManager with a valid configuration and store.
|
|
25
|
+
$configPath = dirname(__FILE__) . '/config/problems.config.json';
|
|
26
|
+
ProblemManager::getInstance($configPath, $store);
|
|
27
|
+
|
|
28
|
+
// 3. Create engine instance.
|
|
29
|
+
$this->engine = new FingerprintEngine(SecurityProfiles::createSecurityProfile('balanced'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public function testGetProblemsIsExposedForTesting(): void
|
|
33
|
+
{
|
|
34
|
+
// Calls ProblemManager::getInstance() internally; setUp() ensures valid initialization.
|
|
35
|
+
$problems = $this->engine->getProblems();
|
|
36
|
+
$this->assertIsArray($problems);
|
|
37
|
+
}
|
|
40
38
|
}
|