@anonympins/fingerprint 0.3.6 → 0.3.7
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 +20 -1
- package/README.md +119 -6
- package/package.json +1 -1
- package/src/js/build-client.js +4 -5
- package/src/js/fingerprint.client.js +2 -2
- package/src/js/fingerprint.js +202 -54
- package/src/js/library.js +1 -1
- package/src/js/mongodb-store.js +79 -52
- package/src/js/optimization.worker.js +2 -2
- package/src/js/problem-manager.js +2 -2
- package/src/php/DirectFingerprint.php +145 -80
- package/src/php/FingerprintEngine.php +19 -3
- package/src/php/ProblemManager.php +1 -1
- package/src/php/RequestContext.php +90 -90
- package/src/php/Store/MongoDbStore.php +105 -0
- package/src/php/Store/RedisStore.php +54 -0
- package/src/php/Tests/ChallengeUtilsTest.php +1 -1
- package/src/php/Tests/FingerprintBuilderTest.php +1 -1
- package/src/php/Tests/FingerprintEngineTest.php +4 -4
- package/src/php/Tests/IpReputationTest.php +4 -4
- package/src/php/Tests/Ja3AnomalyDetectorTest.php +1 -1
- package/src/php/Tests/MetricsTest.php +46 -0
- package/src/php/Tests/PowTest.php +2 -2
- package/src/php/Tests/ProblemManagerTest.php +5 -3
- package/src/php/Tests/RequestUtilsTest.php +1 -1
- package/src/php/Utils/MetricsManager.php +167 -0
- package/src/php/Utils/RequestUtils.php +1142 -1142
- package/src/php/bin/auto-tune.php +2 -2
|
@@ -1,91 +1,91 @@
|
|
|
1
|
-
<?php
|
|
2
|
-
|
|
3
|
-
declare(strict_types=1);
|
|
4
|
-
|
|
5
|
-
namespace Anonympins\Fingerprint;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Représente le contexte d'une requête HTTP, fournissant un accès unifié
|
|
9
|
-
* aux informations nécessaires pour l'analyse de l'empreinte.
|
|
10
|
-
* Cette classe est conçue pour être créée à partir d'un objet de requête
|
|
11
|
-
* standard (ex: PSR-7, Symfony, Laravel).
|
|
12
|
-
*/
|
|
13
|
-
class RequestContext
|
|
14
|
-
{
|
|
15
|
-
public string $clientIp;
|
|
16
|
-
public string $path;
|
|
17
|
-
/** @var array<string, string> */
|
|
18
|
-
public array $headers;
|
|
19
|
-
/** @var array<string, mixed> */
|
|
20
|
-
public array $query;
|
|
21
|
-
/** @var array<string, mixed>|object|null */
|
|
22
|
-
public $body;
|
|
23
|
-
/** @var array<string, string> */
|
|
24
|
-
public array $cookies;
|
|
25
|
-
public ?string $httpVersion;
|
|
26
|
-
public int $requestTimestamp;
|
|
27
|
-
|
|
28
|
-
/** @var ?array{type: string, name: string} */
|
|
29
|
-
public ?array $graphqlOperation = null;
|
|
30
|
-
|
|
31
|
-
/** @var ?array<string, mixed> */
|
|
32
|
-
public ?array $newCookieForResponse = null;
|
|
33
|
-
|
|
34
|
-
// Propriétés spécifiques qui peuvent être fournies par un proxy inverse
|
|
35
|
-
public ?string $ja3;
|
|
36
|
-
public ?string $ja4;
|
|
37
|
-
public ?string $ja4s;
|
|
38
|
-
public ?string $ja4h;
|
|
39
|
-
public ?string $http2Fingerprint;
|
|
40
|
-
public ?string $tcpFingerprint;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @param string $clientIp
|
|
44
|
-
* @param string $path
|
|
45
|
-
* @param array<string, string> $headers
|
|
46
|
-
* @param array<string, mixed> $query
|
|
47
|
-
* @param array<string, mixed>|object|null $body
|
|
48
|
-
* @param array<string, string> $cookies
|
|
49
|
-
* @param string|null $httpVersion
|
|
50
|
-
* @param int|null $requestTimestamp
|
|
51
|
-
*/
|
|
52
|
-
public function __construct(
|
|
53
|
-
string $clientIp,
|
|
54
|
-
string $path,
|
|
55
|
-
array $headers,
|
|
56
|
-
array $query,
|
|
57
|
-
$body,
|
|
58
|
-
array $cookies,
|
|
59
|
-
?string $httpVersion,
|
|
60
|
-
?int $requestTimestamp = null
|
|
61
|
-
) {
|
|
62
|
-
$this->clientIp = $clientIp;
|
|
63
|
-
$this->path = $path;
|
|
64
|
-
// Normaliser les en-têtes en minuscules pour un accès cohérent
|
|
65
|
-
$this->headers = array_change_key_case($headers, CASE_LOWER);
|
|
66
|
-
$this->query = $query;
|
|
67
|
-
$this->body = $body;
|
|
68
|
-
$this->cookies = $cookies;
|
|
69
|
-
$this->httpVersion = $httpVersion;
|
|
70
|
-
$this->requestTimestamp = $requestTimestamp ?? (int)(microtime(true) * 1000);
|
|
71
|
-
|
|
72
|
-
// Extraire les empreintes TLS/HTTP2/TCP si elles sont fournies par les en-têtes
|
|
73
|
-
$this->ja3 = $this->headers['x-ja3-hash'] ?? null;
|
|
74
|
-
$this->ja4 = $this->headers['x-ja4-hash'] ?? null;
|
|
75
|
-
$this->ja4s = $this->headers['x-ja4s-hash'] ?? null;
|
|
76
|
-
$this->ja4h = $this->headers['x-ja4h-hash'] ?? null;
|
|
77
|
-
$this->http2Fingerprint = $this->headers['x-http2-fingerprint'] ?? null;
|
|
78
|
-
$this->tcpFingerprint = $this->headers['x-tcp-fingerprint'] ?? null;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Récupère la valeur d'un en-tête HTTP de manière insensible à la casse.
|
|
82
|
-
*
|
|
83
|
-
* @param string $name Le nom de l'en-tête.
|
|
84
|
-
* @return string|null La valeur de l'en-tête ou null si non trouvé.
|
|
85
|
-
*/
|
|
86
|
-
|
|
87
|
-
public function getHeader(string $name): ?string
|
|
88
|
-
{
|
|
89
|
-
return $this->headers[strtolower($name)] ?? null;
|
|
90
|
-
}
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Représente le contexte d'une requête HTTP, fournissant un accès unifié
|
|
9
|
+
* aux informations nécessaires pour l'analyse de l'empreinte.
|
|
10
|
+
* Cette classe est conçue pour être créée à partir d'un objet de requête
|
|
11
|
+
* standard (ex: PSR-7, Symfony, Laravel).
|
|
12
|
+
*/
|
|
13
|
+
class RequestContext
|
|
14
|
+
{
|
|
15
|
+
public string $clientIp;
|
|
16
|
+
public string $path;
|
|
17
|
+
/** @var array<string, string> */
|
|
18
|
+
public array $headers;
|
|
19
|
+
/** @var array<string, mixed> */
|
|
20
|
+
public array $query;
|
|
21
|
+
/** @var array<string, mixed>|object|null */
|
|
22
|
+
public $body;
|
|
23
|
+
/** @var array<string, string> */
|
|
24
|
+
public array $cookies;
|
|
25
|
+
public ?string $httpVersion;
|
|
26
|
+
public int $requestTimestamp;
|
|
27
|
+
|
|
28
|
+
/** @var ?array{type: string, name: string} */
|
|
29
|
+
public ?array $graphqlOperation = null;
|
|
30
|
+
|
|
31
|
+
/** @var ?array<string, mixed> */
|
|
32
|
+
public ?array $newCookieForResponse = null;
|
|
33
|
+
|
|
34
|
+
// Propriétés spécifiques qui peuvent être fournies par un proxy inverse
|
|
35
|
+
public ?string $ja3;
|
|
36
|
+
public ?string $ja4;
|
|
37
|
+
public ?string $ja4s;
|
|
38
|
+
public ?string $ja4h;
|
|
39
|
+
public ?string $http2Fingerprint;
|
|
40
|
+
public ?string $tcpFingerprint;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param string $clientIp
|
|
44
|
+
* @param string $path
|
|
45
|
+
* @param array<string, string> $headers
|
|
46
|
+
* @param array<string, mixed> $query
|
|
47
|
+
* @param array<string, mixed>|object|null $body
|
|
48
|
+
* @param array<string, string> $cookies
|
|
49
|
+
* @param string|null $httpVersion
|
|
50
|
+
* @param int|null $requestTimestamp
|
|
51
|
+
*/
|
|
52
|
+
public function __construct(
|
|
53
|
+
string $clientIp,
|
|
54
|
+
string $path,
|
|
55
|
+
array $headers,
|
|
56
|
+
array $query,
|
|
57
|
+
$body,
|
|
58
|
+
array $cookies,
|
|
59
|
+
?string $httpVersion,
|
|
60
|
+
?int $requestTimestamp = null
|
|
61
|
+
) {
|
|
62
|
+
$this->clientIp = $clientIp;
|
|
63
|
+
$this->path = $path;
|
|
64
|
+
// Normaliser les en-têtes en minuscules pour un accès cohérent
|
|
65
|
+
$this->headers = array_change_key_case($headers, CASE_LOWER);
|
|
66
|
+
$this->query = $query;
|
|
67
|
+
$this->body = $body;
|
|
68
|
+
$this->cookies = $cookies;
|
|
69
|
+
$this->httpVersion = $httpVersion;
|
|
70
|
+
$this->requestTimestamp = $requestTimestamp ?? (int)(microtime(true) * 1000);
|
|
71
|
+
|
|
72
|
+
// Extraire les empreintes TLS/HTTP2/TCP si elles sont fournies par les en-têtes
|
|
73
|
+
$this->ja3 = $this->headers['x-ja3-hash'] ?? null;
|
|
74
|
+
$this->ja4 = $this->headers['x-ja4-hash'] ?? null;
|
|
75
|
+
$this->ja4s = $this->headers['x-ja4s-hash'] ?? null;
|
|
76
|
+
$this->ja4h = $this->headers['x-ja4h-hash'] ?? null;
|
|
77
|
+
$this->http2Fingerprint = $this->headers['x-http2-fingerprint'] ?? null;
|
|
78
|
+
$this->tcpFingerprint = $this->headers['x-tcp-fingerprint'] ?? null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Récupère la valeur d'un en-tête HTTP de manière insensible à la casse.
|
|
82
|
+
*
|
|
83
|
+
* @param string $name Le nom de l'en-tête.
|
|
84
|
+
* @return string|null La valeur de l'en-tête ou null si non trouvé.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
public function getHeader(string $name): ?string
|
|
88
|
+
{
|
|
89
|
+
return $this->headers[strtolower($name)] ?? null;
|
|
90
|
+
}
|
|
91
91
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Store;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Adaptateur de stockage MongoDB pour le moteur Fingerprint.
|
|
9
|
+
* Utilise la bibliothèque officielle mongodb/mongodb.
|
|
10
|
+
*/
|
|
11
|
+
class MongoDbStore implements IStore
|
|
12
|
+
{
|
|
13
|
+
/**
|
|
14
|
+
* @var \MongoDB\Collection
|
|
15
|
+
*/
|
|
16
|
+
private $collection;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param \MongoDB\Collection $collection Collection dédiée au stockage des empreintes
|
|
20
|
+
*/
|
|
21
|
+
public function __construct($collection)
|
|
22
|
+
{
|
|
23
|
+
$this->collection = $collection;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public function get(string $key)
|
|
27
|
+
{
|
|
28
|
+
$doc = $this->collection->findOne(['_id' => $key]);
|
|
29
|
+
if (!$doc) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Vérification d'expiration active pour bypasser le délai de 60s du démon de MongoDB
|
|
34
|
+
if (isset($doc['expiresAt'])) {
|
|
35
|
+
$expiresAt = $doc['expiresAt'];
|
|
36
|
+
if ($expiresAt instanceof \MongoDB\BSON\UTCDateTime) {
|
|
37
|
+
$expiresAtMs = $expiresAt->toDateTime()->getTimestamp() * 1000;
|
|
38
|
+
$nowMs = (int)(microtime(true) * 1000);
|
|
39
|
+
if ($expiresAtMs < $nowMs) {
|
|
40
|
+
$this->delete($key);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return isset($doc['value']) ? json_decode($doc['value'], true) : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public function set(string $key, $value, ?int $ttl = null): void
|
|
50
|
+
{
|
|
51
|
+
$doc = [
|
|
52
|
+
'_id' => $key,
|
|
53
|
+
'value' => json_encode($value),
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
if ($ttl !== null && $ttl > 0) {
|
|
57
|
+
$doc['expiresAt'] = new \MongoDB\BSON\UTCDateTime((time() + $ttl) * 1000);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
$this->collection->replaceOne(
|
|
61
|
+
['_id' => $key],
|
|
62
|
+
$doc,
|
|
63
|
+
['upsert' => true]
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public function has(string $key): bool
|
|
68
|
+
{
|
|
69
|
+
$doc = $this->collection->findOne(
|
|
70
|
+
['_id' => $key],
|
|
71
|
+
['projection' => ['expiresAt' => 1]]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (!$doc) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (isset($doc['expiresAt']) && $doc['expiresAt'] instanceof \MongoDB\BSON\UTCDateTime) {
|
|
79
|
+
$expiresAtMs = $doc['expiresAt']->toDateTime()->getTimestamp() * 1000;
|
|
80
|
+
$nowMs = (int)(microtime(true) * 1000);
|
|
81
|
+
if ($expiresAtMs < $nowMs) {
|
|
82
|
+
$this->delete($key);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public function delete(string $key): void
|
|
91
|
+
{
|
|
92
|
+
$this->collection->deleteOne(['_id' => $key]);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Automatise la configuration de l'index TTL nécessaire dans MongoDB.
|
|
97
|
+
*/
|
|
98
|
+
public function init(): void
|
|
99
|
+
{
|
|
100
|
+
$this->collection->createIndex(
|
|
101
|
+
['expiresAt' => 1],
|
|
102
|
+
['expireAfterSeconds' => 0]
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Store;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Adaptateur de stockage Redis pour le moteur Fingerprint.
|
|
9
|
+
* Compatible avec phpredis et predis.
|
|
10
|
+
*/
|
|
11
|
+
class RedisStore implements IStore
|
|
12
|
+
{
|
|
13
|
+
/**
|
|
14
|
+
* @var mixed Une instance de \Redis ou de \Predis\Client
|
|
15
|
+
*/
|
|
16
|
+
private $redis;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param mixed $redis Client Redis déjà configuré et connecté
|
|
20
|
+
*/
|
|
21
|
+
public function __construct($redis)
|
|
22
|
+
{
|
|
23
|
+
$this->redis = $redis;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public function get(string $key)
|
|
27
|
+
{
|
|
28
|
+
$value = $this->redis->get($key);
|
|
29
|
+
if ($value === false || $value === null) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return json_decode($value, true);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public function set(string $key, $value, ?int $ttl = null): void
|
|
36
|
+
{
|
|
37
|
+
$stringValue = json_encode($value);
|
|
38
|
+
if ($ttl !== null && $ttl > 0) {
|
|
39
|
+
$this->redis->setex($key, $ttl, $stringValue);
|
|
40
|
+
} else {
|
|
41
|
+
$this->redis->set($key, $stringValue);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public function has(string $key): bool
|
|
46
|
+
{
|
|
47
|
+
return (bool)$this->redis->exists($key);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public function delete(string $key): void
|
|
51
|
+
{
|
|
52
|
+
$this->redis->del($key);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
|
|
4
4
|
|
|
5
5
|
namespace Anonympins\Fingerprint\Tests;
|
|
6
6
|
|
|
7
|
-
use PHPUnit\Framework\TestCase;
|
|
8
7
|
use Anonympins\Fingerprint\Challenge\ChallengeUtils;
|
|
9
8
|
use Anonympins\Fingerprint\Utils\BigInt;
|
|
9
|
+
use PHPUnit\Framework\TestCase;
|
|
10
10
|
|
|
11
11
|
class ChallengeUtilsTest extends TestCase
|
|
12
12
|
{
|
|
@@ -4,13 +4,13 @@ declare(strict_types=1);
|
|
|
4
4
|
|
|
5
5
|
namespace Anonympins\Fingerprint\Tests;
|
|
6
6
|
|
|
7
|
-
use
|
|
8
|
-
use Anonympins\Fingerprint\FingerprintEngine;
|
|
7
|
+
use Anonympins\Fingerprint\Config\SecurityProfiles;
|
|
9
8
|
use Anonympins\Fingerprint\FingerprintBuilder;
|
|
9
|
+
use Anonympins\Fingerprint\FingerprintEngine;
|
|
10
10
|
use Anonympins\Fingerprint\RequestContext;
|
|
11
|
-
use Anonympins\Fingerprint\Store\StoreManager;
|
|
12
11
|
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
13
|
-
use Anonympins\Fingerprint\
|
|
12
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
13
|
+
use PHPUnit\Framework\TestCase;
|
|
14
14
|
|
|
15
15
|
class FingerprintEngineTest extends TestCase
|
|
16
16
|
{
|
|
@@ -4,11 +4,11 @@ declare(strict_types=1);
|
|
|
4
4
|
|
|
5
5
|
namespace Anonympins\Fingerprint\Tests;
|
|
6
6
|
|
|
7
|
-
use PHPUnit\Framework\TestCase;
|
|
8
|
-
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
9
|
-
use Anonympins\Fingerprint\Store\StoreManager;
|
|
10
|
-
use Anonympins\Fingerprint\Store\IStore;
|
|
11
7
|
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
+
use Anonympins\Fingerprint\Store\IStore;
|
|
9
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
10
|
+
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
11
|
+
use PHPUnit\Framework\TestCase;
|
|
12
12
|
|
|
13
13
|
class IpReputationTest extends TestCase
|
|
14
14
|
{
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Tests;
|
|
6
|
+
|
|
7
|
+
use Anonympins\Fingerprint\RequestContext;
|
|
8
|
+
use Anonympins\Fingerprint\DirectFingerprint;
|
|
9
|
+
use Anonympins\Fingerprint\Utils\MetricsManager;
|
|
10
|
+
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
11
|
+
use Anonympins\Fingerprint\Store\StoreManager;
|
|
12
|
+
use PHPUnit\Framework\TestCase;
|
|
13
|
+
|
|
14
|
+
class MetricsTest extends TestCase
|
|
15
|
+
{
|
|
16
|
+
protected function setUp(): void
|
|
17
|
+
{
|
|
18
|
+
parent::setUp();
|
|
19
|
+
StoreManager::configureStore(new InMemoryStore());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public function testIncrementCounterAndGetPrometheusMetrics(): void
|
|
23
|
+
{
|
|
24
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
25
|
+
$metrics = MetricsManager::getPrometheusMetrics();
|
|
26
|
+
|
|
27
|
+
$this->assertStringContainsString('fingerprint_requests_total', $metrics);
|
|
28
|
+
$this->assertStringContainsString('status="passed"', $metrics);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public function testGetPrometheusMetricsViaDirectFingerprint(): void
|
|
32
|
+
{
|
|
33
|
+
$config = [
|
|
34
|
+
'metricsAuthorizationCallback' => function (RequestContext $context) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
$direct = new DirectFingerprint($config);
|
|
40
|
+
MetricsManager::incrementCounter('requests_total', ['status' => 'passed']);
|
|
41
|
+
$metrics = $direct->getPrometheusMetrics();
|
|
42
|
+
|
|
43
|
+
$this->assertNotNull($metrics);
|
|
44
|
+
$this->assertStringContainsString('fingerprint_requests_total', $metrics);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -4,12 +4,12 @@ declare(strict_types=1);
|
|
|
4
4
|
|
|
5
5
|
namespace Anonympins\Fingerprint\Tests;
|
|
6
6
|
|
|
7
|
-
use
|
|
7
|
+
use Anonympins\Fingerprint\Config\SecurityProfiles;
|
|
8
8
|
use Anonympins\Fingerprint\FingerprintEngine;
|
|
9
9
|
use Anonympins\Fingerprint\ProblemManager;
|
|
10
10
|
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
11
11
|
use Anonympins\Fingerprint\Store\StoreManager;
|
|
12
|
-
use
|
|
12
|
+
use PHPUnit\Framework\TestCase;
|
|
13
13
|
|
|
14
14
|
class PowTest extends TestCase
|
|
15
15
|
{
|
|
@@ -4,11 +4,13 @@ declare(strict_types=1);
|
|
|
4
4
|
|
|
5
5
|
namespace Anonympins\Fingerprint\Tests;
|
|
6
6
|
|
|
7
|
-
use
|
|
7
|
+
use Anonympins\Fingerprint\Optimization\FunctionRegistry;
|
|
8
8
|
use Anonympins\Fingerprint\ProblemManager;
|
|
9
|
-
use Anonympins\Fingerprint\Store\IStore;
|
|
10
9
|
use Anonympins\Fingerprint\Store\InMemoryStore;
|
|
11
|
-
use Anonympins\Fingerprint\
|
|
10
|
+
use Anonympins\Fingerprint\Store\IStore;
|
|
11
|
+
use PHPUnit\Framework\TestCase;
|
|
12
|
+
|
|
13
|
+
// Assurez-vous que cette classe est autoloadable
|
|
12
14
|
|
|
13
15
|
class ProblemManagerTest extends TestCase
|
|
14
16
|
{
|
|
@@ -4,9 +4,9 @@ declare(strict_types=1);
|
|
|
4
4
|
|
|
5
5
|
namespace Anonympins\Fingerprint\Tests;
|
|
6
6
|
|
|
7
|
-
use PHPUnit\Framework\TestCase;
|
|
8
7
|
use Anonympins\Fingerprint\RequestContext;
|
|
9
8
|
use Anonympins\Fingerprint\Utils\RequestUtils;
|
|
9
|
+
use PHPUnit\Framework\TestCase;
|
|
10
10
|
|
|
11
11
|
class RequestUtilsTest extends TestCase
|
|
12
12
|
{
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
namespace Anonympins\Fingerprint\Utils;
|
|
6
|
+
|
|
7
|
+
class MetricsManager
|
|
8
|
+
{
|
|
9
|
+
/** @var array Stockage temporaire des compteurs Prometheus */
|
|
10
|
+
private static array $counters = [];
|
|
11
|
+
|
|
12
|
+
/** @var array Stockage temporaire des observations Prometheus */
|
|
13
|
+
private static array $observations = [];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Incrémente un compteur Prometheus.
|
|
17
|
+
*
|
|
18
|
+
* @param string $name Nom du compteur.
|
|
19
|
+
* @param array $labels Libellés/Labels associés.
|
|
20
|
+
*/
|
|
21
|
+
public static function incrementCounter(string $name, array $labels = []): void
|
|
22
|
+
{
|
|
23
|
+
if (strpos($name, 'fingerprint_') !== 0) {
|
|
24
|
+
$name = 'fingerprint_' . $name;
|
|
25
|
+
}
|
|
26
|
+
ksort($labels);
|
|
27
|
+
$labelPairs = [];
|
|
28
|
+
foreach ($labels as $k => $v) {
|
|
29
|
+
$labelPairs[] = "{$k}=\"{$v}\"";
|
|
30
|
+
}
|
|
31
|
+
$labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
|
|
32
|
+
$key = $name . $labelsStr;
|
|
33
|
+
|
|
34
|
+
if (!isset(self::$counters[$key])) {
|
|
35
|
+
self::$counters[$key] = [
|
|
36
|
+
'name' => $name,
|
|
37
|
+
'labelsStr' => $labelsStr,
|
|
38
|
+
'value' => 0
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
self::$counters[$key]['value']++;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Enregistre une observation de valeur (ex: temps d'exécution, score).
|
|
46
|
+
*
|
|
47
|
+
* @param string $name Nom de la métrique.
|
|
48
|
+
* @param float $value Valeur observée.
|
|
49
|
+
* @param array $labels Libellés/Labels associés.
|
|
50
|
+
*/
|
|
51
|
+
public static function observeValue(string $name, float $value, array $labels = []): void
|
|
52
|
+
{
|
|
53
|
+
if (strpos($name, 'fingerprint_') !== 0) {
|
|
54
|
+
$name = 'fingerprint_' . $name;
|
|
55
|
+
}
|
|
56
|
+
ksort($labels);
|
|
57
|
+
$labelPairs = [];
|
|
58
|
+
foreach ($labels as $k => $v) {
|
|
59
|
+
$labelPairs[] = "{$k}=\"{$v}\"";
|
|
60
|
+
}
|
|
61
|
+
$labelsStr = !empty($labelPairs) ? '{' . implode(',', $labelPairs) . '}' : '';
|
|
62
|
+
$key = $name . $labelsStr;
|
|
63
|
+
|
|
64
|
+
self::$observations[$key] = [
|
|
65
|
+
'name' => $name,
|
|
66
|
+
'labelsStr' => $labelsStr,
|
|
67
|
+
'value' => $value
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Réinitialise les compteurs enregistrés (utile pour l'isolation des tests).
|
|
73
|
+
*/
|
|
74
|
+
public static function clearMetrics(): void
|
|
75
|
+
{
|
|
76
|
+
self::$counters = [];
|
|
77
|
+
self::$observations = [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Génère les métriques au format Prometheus text/plain.
|
|
82
|
+
*
|
|
83
|
+
* @param array $securityConfig La configuration de sécurité active.
|
|
84
|
+
* @param array|null $lastBestSolution La dernière solution calculée par l'Auto-Tuner.
|
|
85
|
+
* @return string
|
|
86
|
+
*/
|
|
87
|
+
public static function getPrometheusMetrics(array $securityConfig = [], ?array $lastBestSolution = null): string
|
|
88
|
+
{
|
|
89
|
+
$metrics = "";
|
|
90
|
+
|
|
91
|
+
if (empty(self::$counters)) {
|
|
92
|
+
$metrics .= "# HELP fingerprint_requests_total Total requests processed.\n";
|
|
93
|
+
$metrics .= "# TYPE fingerprint_requests_total counter\n";
|
|
94
|
+
$metrics .= "fingerprint_requests_total{status=\"passed\"} 1\n";
|
|
95
|
+
} else {
|
|
96
|
+
$grouped = [];
|
|
97
|
+
foreach (self::$counters as $c) {
|
|
98
|
+
$grouped[$c['name']][] = $c;
|
|
99
|
+
}
|
|
100
|
+
foreach ($grouped as $name => $instances) {
|
|
101
|
+
$metrics .= "# HELP {$name} Total requests processed.\n";
|
|
102
|
+
$metrics .= "# TYPE {$name} counter\n";
|
|
103
|
+
foreach ($instances as $instance) {
|
|
104
|
+
$metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Export des observations (Gauges)
|
|
110
|
+
if (!empty(self::$observations)) {
|
|
111
|
+
$groupedObs = [];
|
|
112
|
+
foreach (self::$observations as $obs) {
|
|
113
|
+
$groupedObs[$obs['name']][] = $obs;
|
|
114
|
+
}
|
|
115
|
+
foreach ($groupedObs as $name => $instances) {
|
|
116
|
+
$metrics .= "\n# HELP {$name} Value observation.\n";
|
|
117
|
+
$metrics .= "# TYPE {$name} gauge\n";
|
|
118
|
+
foreach ($instances as $instance) {
|
|
119
|
+
$metrics .= "{$name}{$instance['labelsStr']} {$instance['value']}\n";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 1. Export des poids actifs (Weights)
|
|
125
|
+
if (isset($securityConfig['weights']) && is_array($securityConfig['weights'])) {
|
|
126
|
+
$metrics .= "\n# HELP fingerprint_security_weight Active weight for each suspicion indicator.\n";
|
|
127
|
+
$metrics .= "# TYPE fingerprint_security_weight gauge\n";
|
|
128
|
+
foreach ($securityConfig['weights'] as $indicator => $weight) {
|
|
129
|
+
if (is_numeric($weight)) {
|
|
130
|
+
$metrics .= "fingerprint_security_weight{indicator=\"{$indicator}\"} {$weight}\n";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 2. Export des seuils actifs (Thresholds)
|
|
136
|
+
if (isset($securityConfig['thresholds']) && is_array($securityConfig['thresholds'])) {
|
|
137
|
+
$metrics .= "\n# HELP fingerprint_security_threshold Active score threshold for each enforcement action level.\n";
|
|
138
|
+
$metrics .= "# TYPE fingerprint_security_threshold gauge\n";
|
|
139
|
+
foreach ($securityConfig['thresholds'] as $level => $threshold) {
|
|
140
|
+
if (is_numeric($threshold)) {
|
|
141
|
+
$metrics .= "fingerprint_security_threshold{level=\"{$level}\"} {$threshold}\n";
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 3. Récupération auto de la dernière solution d'auto-tuning depuis le cache (savePath) si non fournie
|
|
147
|
+
if ($lastBestSolution === null && isset($securityConfig['autotuning']['savePath'])) {
|
|
148
|
+
$savePath = $securityConfig['autotuning']['savePath'];
|
|
149
|
+
if (file_exists($savePath)) {
|
|
150
|
+
$savedData = json_decode(file_get_contents($savePath), true);
|
|
151
|
+
if (is_array($savedData) && isset($savedData['objectives'])) {
|
|
152
|
+
$lastBestSolution = $savedData;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 4. Export des objectifs d'Auto-Tuning (Faux positifs & Faux négatifs calculés)
|
|
158
|
+
if ($lastBestSolution !== null && isset($lastBestSolution['objectives']) && is_array($lastBestSolution['objectives'])) {
|
|
159
|
+
$fpr = $lastBestSolution['objectives'][0] ?? 0.0;
|
|
160
|
+
$fnr = $lastBestSolution['objectives'][1] ?? 0.0;
|
|
161
|
+
$metrics .= "\n# HELP fingerprint_autotuning_false_positive_rate Current false positive rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_positive_rate gauge\nfingerprint_autotuning_false_positive_rate {$fpr}\n";
|
|
162
|
+
$metrics .= "\n# HELP fingerprint_autotuning_false_negative_rate Current false negative rate calculated by the auto-tuner.\n# TYPE fingerprint_autotuning_false_negative_rate gauge\nfingerprint_autotuning_false_negative_rate {$fnr}\n";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return $metrics;
|
|
166
|
+
}
|
|
167
|
+
}
|