Skip to content

Commit ec1431f

Browse files
committed
cache clean ips
1 parent e886d60 commit ec1431f

File tree

8 files changed

+152
-139
lines changed

8 files changed

+152
-139
lines changed

docs/configuration.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ Full configuration reference
2121
2222
// Optional. Cap the remediation to the selected one. Select from 'bypass' (minimum remediation), 'captcha' or 'ban' (maximum remediation). Defaults to 'ban'.
2323
'max_remediation'=> 'ban',
24+
25+
// Optional. Set the duration we keep in cache the fact that an IP is clean. In seconds. Defaults to 600 (10 minutes).
26+
'cache_expiration_for_clean_ip'=> '600',
2427
]
2528
$cacheAdapter = (...)
2629
$bouncer = new Bouncer();

src/ApiCache.php

Lines changed: 89 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ class ApiCache
2525
/** @var bool */
2626
private $ruptureMode;
2727

28+
/** @var int */
29+
private $cacheExpirationForCleanIp;
30+
2831
/** @var ApiClient */
2932
private $apiClient;
3033

@@ -39,10 +42,11 @@ public function __construct(ApiClient $apiClient = null)
3942
/**
4043
* Configure this instance.
4144
*/
42-
public function configure(AbstractAdapter $adapter, bool $ruptureMode, string $apiUrl, int $timeout, string $userAgent, string $token): void
45+
public function configure(AbstractAdapter $adapter, bool $ruptureMode, string $apiUrl, int $timeout, string $userAgent, string $token, int $cacheExpirationForCleanIp): void
4346
{
4447
$this->adapter = $adapter;
4548
$this->ruptureMode = $ruptureMode;
49+
$this->cacheExpirationForCleanIp = $cacheExpirationForCleanIp;
4650

4751
$this->apiClient->configure($apiUrl, $timeout, $userAgent, $token);
4852
}
@@ -85,22 +89,89 @@ private function saveDeferred(CacheItem $item, int $ip, string $type, int $expir
8589
}
8690
}
8791

88-
/*
92+
/**
93+
* Parse "duration" entries returned from API to a number of seconds.
94+
*
95+
* TODO P3 TEST
96+
* 9999h59m56.603445s
97+
* 10m33.3465483s
98+
* 33.3465483s
99+
* -285.876962ms
100+
* 33s'// should break!;
101+
*/
102+
private static function parseDurationToSeconds(string $duration): int
103+
{
104+
$re = '/(-?)(?:(?:(\d+)h)?(\d+)m)?(\d+).\d+(m?)s/m';
105+
preg_match($re, $duration, $matches);
106+
if (!count($matches)) {
107+
throw new BouncerException("Unable to parse the following duration: ${$duration}.");
108+
};
109+
$seconds = 0;
110+
if (null !== $matches[2]) {
111+
$seconds += ((int) $matches[1]) * 3600; // hours
112+
}
113+
if (null !== $matches[3]) {
114+
$seconds += ((int) $matches[2]) * 60; // minutes
115+
}
116+
if (null !== $matches[4]) {
117+
$seconds += ((int) $matches[1]); // seconds
118+
}
119+
if (null !== $matches[5]) { // units in milliseconds
120+
$seconds *= 0.001;
121+
}
122+
if (null !== $matches[1]) { // negative
123+
$seconds *= -1;
124+
}
125+
$seconds = round($seconds);
126+
127+
return (int)$seconds;
128+
}
129+
130+
131+
132+
/**
133+
* Format a remediation item of a cache item.
134+
* This format use a minimal amount of data allowing less cache data consumption.
135+
*
136+
* TODO P3 TESTS
137+
*/
138+
private function formatRemediationFromDecision(?array $decision): array
139+
{
140+
if (!$decision) {
141+
return ['clean', time() + $this->cacheExpirationForCleanIp, 0];
142+
}
143+
144+
return [
145+
$decision['type'], // ex: captcha
146+
time() + self::parseDurationToSeconds($decision['duration']), // expiration timestamp
147+
$decision['id'],
148+
149+
/*
150+
TODO P3 useful to keep in cache?
151+
[
152+
$decision['origin'],// ex cscli
153+
$decision['scenario'],//ex: "manual 'captcha' from '25b9f1216f9344b780963bd281ae5573UIxCiwc74i2mFqK4'"
154+
$decision['scope'],// ex: IP
155+
]
156+
*/
157+
];
158+
}
89159

90-
Update the cached remediations from these new decisions.
160+
/**
161+
* Update the cached remediations from these new decisions.
91162
92-
TODO P2 WRITE TESTS
93-
0 decisions
94-
3 known remediation type
95-
3 decisions but 1 unknown remediation type
96-
3 unknown remediation type
163+
* TODO P2 WRITE TESTS
164+
* 0 decisions
165+
* 3 known remediation type
166+
* 3 decisions but 1 unknown remediation type
167+
* 3 unknown remediation type
97168
*/
98169
private function saveRemediations(array $decisions): bool
99170
{
100171
foreach ($decisions as $decision) {
101172
$ipRange = range($decision['start_ip'], $decision['end_ip']);
102173
foreach ($ipRange as $ip) {
103-
$remediation = Remediation::formatFromDecision($decision);
174+
$remediation = $this->formatRemediationFromDecision($decision);
104175
$item = $this->buildRemediationCacheItem($ip, $remediation[0], $remediation[1], $remediation[2]);
105176
$this->saveDeferred($item, $ip, $remediation[0], $remediation[1], $remediation[2]);
106177
}
@@ -114,8 +185,14 @@ private function saveRemediations(array $decisions): bool
114185
*/
115186
private function saveRemediationsForIp(array $decisions, int $ip): void
116187
{
117-
foreach ($decisions as $decision) {
118-
$remediation = Remediation::formatFromDecision($decision);
188+
if (\count($decisions)) {
189+
foreach ($decisions as $decision) {
190+
$remediation = $this->formatRemediationFromDecision($decision);
191+
$item = $this->buildRemediationCacheItem($ip, $remediation[0], $remediation[1], $remediation[2]);
192+
$this->saveDeferred($item, $ip, $remediation[0], $remediation[1], $remediation[2]);
193+
}
194+
} else {
195+
$remediation = $this->formatRemediationFromDecision(null);
119196
$item = $this->buildRemediationCacheItem($ip, $remediation[0], $remediation[1], $remediation[2]);
120197
$this->saveDeferred($item, $ip, $remediation[0], $remediation[1], $remediation[2]);
121198
}
@@ -164,17 +241,7 @@ public function pullUpdates(): void
164241
private function miss(int $ip): string
165242
{
166243
$decisions = $this->apiClient->getFilteredDecisions(['ip' => long2ip($ip)]);
167-
168-
if (!\count($decisions)) {
169-
// TODO P1 cache also the clean IP.
170-
//$item = $this->buildRemediationCacheItem($ip, $remediation[0], $remediation[1], $remediation[2]);
171-
//$this->saveDeferred($item, $ip, $remediation[0], $remediation[1], $remediation[2]);
172-
173-
return Remediation::formatFromDecision(null)[0];
174-
}
175-
176244
$this->saveRemediationsForIp($decisions, $ip);
177-
178245
return $this->hit($ip);
179246
}
180247

@@ -213,6 +280,6 @@ public function get(int $ip): ?string
213280
return $this->miss($ip);
214281
}
215282

216-
return Remediation::formatFromDecision(null)[0];
283+
return $this->formatRemediationFromDecision(null)[0];
217284
}
218285
}

src/Bouncer.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ public function configure(array $config, AbstractAdapter $cacheAdapter): void
4545
$this->config['api_url'],
4646
$this->config['api_timeout'],
4747
$this->config['api_user_agent'],
48-
$this->config['api_token']
48+
$this->config['api_token'],
49+
$this->config['cache_expiration_for_clean_ip']
4950
);
5051
}
5152

src/Configuration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public function getConfigTreeBuilder()
3333
->integerNode('api_timeout')->defaultValue(Constants::API_TIMEOUT)->end()
3434
->booleanNode('rupture_mode')->defaultValue(true)->end()
3535
->enumNode('max_remediation')->values(['bypass', 'captcha', 'ban'])->defaultValue('ban')->end()
36+
->integerNode('cache_expiration_for_clean_ip')->defaultValue(Constants::CACHE_EXPIRATION_FOR_CLEAN_IP)->end()
3637
->end();
3738

3839
return $treeBuilder;

src/Constants.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class Constants
2323
/** @var int The timeout when calling LAPI or CAPI */
2424
const API_TIMEOUT = 1; // TODO P2 get the correct one
2525

26+
/** @var int The duration we keep a clean IP in cache 600s = 10m */
27+
const CACHE_EXPIRATION_FOR_CLEAN_IP = 600; // TODO P2 get the correct one
28+
2629
/** @var array The list of each known remediation, sorted by priority */
2730
const ORDERED_REMEDIATIONS = ['ban', 'captcha']; // TODO P2 get the correct one
2831
}

src/Remediation.php

Lines changed: 0 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -59,71 +59,4 @@ public static function sortRemediationByPriority(array $remediations): array
5959

6060
return $remediationsWithPriorities;
6161
}
62-
63-
/**
64-
* Parse "duration" entries returned from API to a number of seconds.
65-
*
66-
* TODO P3 TEST
67-
* 9999h59m56.603445s
68-
* 10m33.3465483s
69-
* 33.3465483s
70-
* -285.876962ms
71-
* 33s'// should break!;
72-
*/
73-
private static function parseDurationToSeconds(string $duration): int
74-
{
75-
$re = '/(-?)(?:(?:(\d+)h)?(\d+)m)?(\d+).\d+(m?)s/m';
76-
preg_match($re, $duration, $matches);
77-
if (!count($matches)) {
78-
throw new BouncerException("Unable to parse the following duration: ${$duration}.");
79-
};
80-
$seconds = 0;
81-
if (null !== $matches[2]) {
82-
$seconds += ((int) $matches[1]) * 3600;// hours
83-
}
84-
if (null !== $matches[3]) {
85-
$seconds += ((int) $matches[2]) * 60;// minutes
86-
}
87-
if (null !== $matches[4]) {
88-
$seconds += ((int) $matches[1]);// seconds
89-
}
90-
if (null !== $matches[5]) {// units in milliseconds
91-
$seconds *= 0.001;
92-
}
93-
if (null !== $matches[1]) {// negative
94-
$seconds *= -1;
95-
}
96-
$seconds = round($seconds);
97-
98-
return $seconds;
99-
}
100-
101-
/**
102-
* Format a remediation item of a cache item.
103-
* This format use a minimal amount of data allowing less cache data consumption.
104-
*
105-
* TODO P3 TESTS
106-
*/
107-
public static function formatFromDecision(?array $decision): array
108-
{
109-
if (!$decision) {
110-
return ['clear', 0, null];
111-
}
112-
113-
return [
114-
$decision['type'], // ex: captcha
115-
time() + self::parseDurationToSeconds($decision['duration']), // expiration
116-
$decision['id'],
117-
118-
/*
119-
TODO P3 useful to keep in cache?
120-
[
121-
$decision['id'],// id from API
122-
$decision['origin'],// ex cscli
123-
$decision['scenario'],//ex: "manual 'captcha' from '25b9f1216f9344b780963bd281ae5573UIxCiwc74i2mFqK4'"
124-
$decision['scope'],// ex: IP
125-
]
126-
*/
127-
];
128-
}
12962
}

0 commit comments

Comments
 (0)