44
55namespace CrowdSecBouncer ;
66
7- use Symfony \Component \Cache \Adapter \NullAdapter ;
87use Symfony \Component \Cache \Adapter \AbstractAdapter ;
98use Symfony \Component \Cache \CacheItem ;
109
1110/**
1211 * The cache mecanism to store every decisions from LAPI/CAPI. Symfony Cache component powered.
13- *
12+ *
1413 * @author CrowdSec team
15- * @link https://crowdsec.net CrowdSec Official Website
14+ *
15+ * @see https://crowdsec.net CrowdSec Official Website
16+ *
1617 * @copyright Copyright (c) 2020+ CrowdSec
1718 * @license MIT License
1819 */
@@ -30,57 +31,57 @@ class ApiCache
3031 /** @var bool */
3132 private $ warmedUp = false ;
3233
33- public function __construct (ApiClient $ apiClient )
34+ public function __construct (ApiClient $ apiClient = null )
3435 {
3536 $ this ->apiClient = $ apiClient ?: new ApiClient ();
3637 }
3738
3839 /**
3940 * Configure this instance.
4041 */
41- public function configure (AbstractAdapter $ adapter , bool $ ruptureMode , array $ apiClientConfiguration )
42+ public function configure (AbstractAdapter $ adapter , bool $ ruptureMode , string $ apiUrl , int $ timeout , string $ userAgent , string $ token ): void
4243 {
43- $ this ->adapter = $ adapter ?: new NullAdapter () ;
44+ $ this ->adapter = $ adapter ;
4445 $ this ->ruptureMode = $ ruptureMode ;
4546
46- $ this ->apiClient ->configure (
47- $ apiClientConfiguration ['api_url ' ],
48- $ apiClientConfiguration ['api_timeout ' ],
49- $ apiClientConfiguration ['api_user_agent ' ],
50- $ apiClientConfiguration ['api_token ' ]
51- );
47+ $ this ->apiClient ->configure ($ apiUrl , $ timeout , $ userAgent , $ token );
5248 }
5349
5450 /**
55- * Build a Symfony Cache Item from a couple of IP and its computed remediation
51+ * Build a Symfony Cache Item from a couple of IP and its computed remediation.
5652 */
57- private function buildRemediationCacheItem (int $ ip , array $ remediation ): CacheItem
53+ private function buildRemediationCacheItem (int $ ip , string $ type , int $ expiration , int $ decisionId ): CacheItem
5854 {
59- $ item = $ this ->adapter ->getItem ((string )$ ip );
60-
55+ $ item = $ this ->adapter ->getItem ((string ) $ ip );
56+
6157 // Merge with existing remediations (if any).
6258 $ remediations = $ item ->get ();
6359 $ remediations = $ remediations ?: [];
64- $ remediations [$ remediation [2 ]] = $ remediation ;// erase previous decision with the same id
65-
60+ $ remediations [$ decisionId ] = [
61+ $ type ,
62+ $ expiration ,
63+ $ decisionId ,
64+ ]; // erase previous decision with the same id
65+
6666 // Build the item lifetime in cache and sort remediations by priority
6767 $ maxLifetime = max (array_column ($ remediations , 1 ));
6868 $ prioritizedRemediations = Remediation::sortRemediationByPriority ($ remediations );
6969
7070 $ item ->set ($ prioritizedRemediations );
7171 $ item ->expiresAfter ($ maxLifetime );
72+
7273 return $ item ;
7374 }
7475
7576 /**
7677 * Save the cache without committing it to the cache system. Useful to improve performance when updating the cache.
7778 */
78- private function saveDeferred (CacheItem $ item , int $ ip , array $ remediation ): void
79+ private function saveDeferred (CacheItem $ item , int $ ip , string $ type , int $ expiration , int $ decisionId ): void
7980 {
8081 $ isQueued = $ this ->adapter ->saveDeferred ($ item );
8182 if (!$ isQueued ) {
82- $ ipStr = long2Ip ($ ip );
83- throw new BouncerException (` Unable to save this deferred item in cache: $ {$ ipStr } => $ remediation [ 0 ] (for $ remediation [ 1 ] sec) ` );
83+ $ ipStr = long2ip ($ ip );
84+ throw new BouncerException (" Unable to save this deferred item in cache: $ {$ ipStr } => $ type (for $ expiration sec, # $ decisionId ) " );
8485 }
8586 }
8687
@@ -100,10 +101,11 @@ private function saveRemediations(array $decisions): bool
100101 $ ipRange = range ($ decision ['start_ip ' ], $ decision ['end_ip ' ]);
101102 foreach ($ ipRange as $ ip ) {
102103 $ remediation = Remediation::formatFromDecision ($ decision );
103- $ item = $ this ->buildRemediationCacheItem ($ ip , $ remediation );
104- $ this ->saveDeferred ($ item , $ ip , $ remediation );
104+ $ item = $ this ->buildRemediationCacheItem ($ ip , $ remediation[ 0 ], $ remediation [ 1 ], $ remediation [ 2 ] );
105+ $ this ->saveDeferred ($ item , $ ip , $ remediation[ 0 ], $ remediation [ 1 ], $ remediation [ 2 ] );
105106 }
106107 }
108+
107109 return $ this ->adapter ->commit ();
108110 }
109111
@@ -113,9 +115,9 @@ private function saveRemediations(array $decisions): bool
113115 private function saveRemediationsForIp (array $ decisions , int $ ip ): void
114116 {
115117 foreach ($ decisions as $ decision ) {
116- $ remediation = Remediation::formatFromDecision ($ decision );
117- $ item = $ this ->buildRemediationCacheItem ($ ip , $ remediation );
118- $ this ->saveDeferred ($ item , $ ip , $ remediation );
118+ $ remediation = Remediation::formatFromDecision ($ decision );
119+ $ item = $ this ->buildRemediationCacheItem ($ ip , $ remediation[ 0 ], $ remediation [ 1 ], $ remediation [ 2 ] );
120+ $ this ->saveDeferred ($ item , $ ip , $ remediation[ 0 ], $ remediation [ 1 ], $ remediation [ 2 ] );
119121 }
120122 $ this ->adapter ->commit ();
121123 }
@@ -124,7 +126,7 @@ private function saveRemediationsForIp(array $decisions, int $ip): void
124126 * Used in stream mode only.
125127 * Warm the cache up.
126128 * Used when the stream mode has just been activated.
127- *
129+ *
128130 * TODO P2 test for overlapping decisions strategy (max expires, remediation ordered by priorities)
129131 */
130132 public function warmUp (): void
@@ -138,7 +140,7 @@ public function warmUp(): void
138140 if ($ newDecisions ) {
139141 $ this ->warmedUp = $ this ->saveRemediations ($ newDecisions );
140142 if (!$ this ->warmedUp ) {
141- throw new BouncerException (` Unable to warm the cache up ` );
143+ throw new BouncerException (" Unable to warm the cache up " );
142144 }
143145 }
144146 }
@@ -153,7 +155,7 @@ public function pullUpdates(): void
153155 // TODO P1 Finish stream mode with pull update + dont forget to delete old decisions!
154156 }
155157
156- /**
158+ /**
157159 * Used in rupture mode only.
158160 * This method is called when nothing has been found in cache for the requested IP.
159161 * This call the API for decisions concerning the specified IP. Finally the result is stored.
@@ -163,8 +165,11 @@ private function miss(int $ip): string
163165 {
164166 $ decisions = $ this ->apiClient ->getFilteredDecisions (['ip ' => long2ip ($ ip )]);
165167
166- if (!count ($ decisions )) {
168+ if (!\ count ($ decisions )) {
167169 // 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+
168173 return Remediation::formatFromDecision (null )[0 ];
169174 }
170175
@@ -173,35 +178,41 @@ private function miss(int $ip): string
173178 return $ this ->hit ($ ip );
174179 }
175180
176-
177181 /**
178182 * Used in both mode (stream and ruptue).
179183 * This method formats the cached item as a remediation.
180184 * It returns the highest remediation level found.
181185 */
182- private function hit (int $ ip ): ? string
186+ private function hit (int $ ip ): string
183187 {
184- $ remediations = $ this ->adapter ->getItem ((string )$ ip )->get ();
188+ $ remediations = $ this ->adapter ->getItem ((string ) $ ip )->get ();
185189 // P2 TODO control before if date is not expired and if true, update cache item.
186- return $ remediations [0 ][0 ]; // 0: first remediation level, 0: the "type" string
190+
191+ // We apply array values first because keys are ids.
192+ $ firstRemediation = array_values ($ remediations )[0 ];
193+ /** @var string */
194+ $ firstRemediationString = $ firstRemediation [0 ];
195+
196+ return $ firstRemediationString ;
187197 }
188198
189199 /**
190200 * Request the cache for the specified IP.
191- *
192- * @return string The computed remediation string, or null if no decision was found.
201+ *
202+ * @return string the computed remediation string, or null if no decision was found
193203 */
194204 public function get (int $ ip ): ?string
195205 {
196206 if (!$ this ->ruptureMode && !$ this ->warmedUp ) {
197207 throw new BouncerException ('CrowdSec Bouncer configured in "stream" mode. Please warm the cache up before trying to access it. ' );
198208 }
199209
200- if ($ this ->adapter ->hasItem ((string )$ ip )) {
210+ if ($ this ->adapter ->hasItem ((string ) $ ip )) {
201211 return $ this ->hit ($ ip );
202- } else if ($ this ->ruptureMode ) {
212+ } elseif ($ this ->ruptureMode ) {
203213 return $ this ->miss ($ ip );
204214 }
215+
205216 return Remediation::formatFromDecision (null )[0 ];
206217 }
207218}
0 commit comments