@@ -66,6 +66,42 @@ public function getIpRemediation(string $ip): array
6666 return $ this ->processCachedDecisions ($ cachedDecisions );
6767 }
6868
69+ /**
70+ * @throws CacheStorageException
71+ * @throws InvalidArgumentException
72+ * @throws CacheException|ClientException
73+ */
74+ public function refreshDecisions (): array
75+ {
76+ $ rawDecisions = $ this ->client ->getStreamDecisions ();
77+ $ newDecisions = $ this ->convertRawCapiDecisionsToDecisions ($ rawDecisions [self ::CS_NEW ] ?? []);
78+ $ deletedDecisions = $ this ->convertRawCapiDecisionsToDecisions ($ rawDecisions [self ::CS_DEL ] ?? []);
79+ $ listDecisions = $ this ->handleListDecisions ($ rawDecisions [self ::CS_LINK ][self ::CS_BLOCK ] ?? []);
80+ $ allowListDecisions = $ this ->handleAllowListDecisions ($ rawDecisions [self ::CS_LINK ][self ::CS_ALLOW ] ?? []);
81+
82+ $ stored = $ this ->storeDecisions (array_merge (
83+ $ newDecisions ,
84+ $ listDecisions ,
85+ $ allowListDecisions
86+ ));
87+ $ removed = $ this ->removeDecisions ($ deletedDecisions );
88+
89+ return [
90+ self ::CS_NEW => $ stored [AbstractCache::DONE ] ?? 0 ,
91+ self ::CS_DEL => $ removed [AbstractCache::DONE ] ?? 0 ,
92+ ];
93+ }
94+
95+ /**
96+ * Process and validate input configurations.
97+ */
98+ private function configure (array $ configs ): void
99+ {
100+ $ configuration = new CapiRemediationConfig ();
101+ $ processor = new Processor ();
102+ $ this ->configs = $ processor ->processConfiguration ($ configuration , [$ configuration ->cleanConfigs ($ configs )]);
103+ }
104+
69105 private function convertRawCapiDecisionsToDecisions (array $ rawDecisions ): array
70106 {
71107 $ decisions = [];
@@ -100,31 +136,90 @@ private function formatIfModifiedSinceHeader(int $timestamp): string
100136 return gmdate ('D, d M Y H:i:s \G\M\T ' , $ timestamp );
101137 }
102138
103- /**
104- * This method allows to know if the "If-Modified-Since" should be added when pulling list decisions.
105- *
106- * @param int $pullTime // Moment when the list is pulled
107- * @param int $listExpirationTime // Expiration of the cached list decisions
108- * @param int $frequency // Amount of time in seconds that represents the average decision pull frequency
109- */
110- private function shouldAddModifiedSince (int $ pullTime , int $ listExpirationTime , int $ frequency ): bool
139+ private function getDurationInSeconds (string $ futureDate ): int
111140 {
112- return ($ listExpirationTime - $ frequency ) > $ pullTime ;
141+ try {
142+ // Remove microseconds for better compatibility with older PHP versions
143+ $ cleanDate = preg_replace ('/\.\d{3,6}/ ' , '' , $ futureDate );
144+
145+ $ expiration = new \DateTime ($ cleanDate , new \DateTimeZone ('UTC ' ));
146+ $ now = new \DateTime ('now ' , new \DateTimeZone ('UTC ' ));
147+
148+ $ duration = $ expiration ->getTimestamp () - $ now ->getTimestamp ();
149+
150+ return max (0 , $ duration );
151+ } catch (\Throwable $ e ) {
152+ // If parsing fails, return 0 as a fallback
153+ return 0 ;
154+ }
113155 }
114156
115- private function handleListPullHeaders (array $ headers , array $ lastPullContent , int $ pullTime ): array
157+ /**
158+ * @throws InvalidArgumentException|CacheException
159+ */
160+ private function handleAllowListDecisions (array $ allowlists ): array
116161 {
117- $ shouldAddModifiedSince = false ;
118- if (isset ($ lastPullContent [AbstractCache::INDEX_EXP ])) {
119- $ frequency = $ this ->getConfig ('refresh_frequency_indicator ' ) ?? Constants::REFRESH_FREQUENCY ;
120- $ shouldAddModifiedSince = $ this ->shouldAddModifiedSince (
121- $ pullTime ,
122- (int ) $ lastPullContent [AbstractCache::INDEX_EXP ],
123- (int ) $ frequency
124- );
162+ $ decisions = [];
163+ try {
164+ foreach ($ allowlists as $ allowlist ) {
165+ $ headers = [];
166+ if ($ this ->validateAllowlist ($ allowlist )) {
167+ // The existence of the following indexes must be guaranteed by the validateAllowlist method
168+ $ listName = strtolower ((string ) $ allowlist ['name ' ]);
169+ $ listId = (string ) $ allowlist ['id ' ];
170+ $ url = (string ) $ allowlist ['url ' ];
171+ $ origin = Constants::ORIGIN_LISTS ;
172+ $ allowDecision = [
173+ 'type ' => Constants::ALLOW_LIST_REMEDIATION ,
174+ 'origin ' => $ origin ,
175+ 'scenario ' => $ listName ,
176+ ];
177+
178+ $ lastPullCacheKey = $ this ->getCacheStorage ()->getCacheKey (
179+ AbstractCache::ALLOW_LIST ,
180+ $ listId
181+ );
182+
183+ $ lastPullItem = $ this ->getCacheStorage ()->getItem ($ lastPullCacheKey );
184+
185+ $ pullTime = time ();
186+ if ($ lastPullItem ->isHit ()) {
187+ $ lastPullContent = $ lastPullItem ->get ();
188+ $ headers = $ this ->handleAllowListPullHeaders ($ headers , $ lastPullContent );
189+ }
190+
191+ $ listResponse = rtrim (
192+ $ this ->client ->getCapiHandler ()->getListDecisions ($ url , $ headers ),
193+ \PHP_EOL
194+ );
195+
196+ if ($ listResponse ) {
197+ $ this ->cacheStorage ->upsertItem (
198+ $ lastPullCacheKey ,
199+ [
200+ AbstractCache::LAST_PULL => $ pullTime ,
201+ ],
202+ 0 ,
203+ [AbstractCache::ALLOW_LIST , $ listName ]
204+ );
205+ $ decisions = $ this ->handleAllowListResponse ($ listResponse , $ allowDecision );
206+ }
207+ }
208+ }
209+ } catch (\Exception $ e ) {
210+ $ this ->logger ->info ('Something went wrong during list decisions process ' , [
211+ 'type ' => 'CAPI_REM_HANDLE_ALLOW_LIST_DECISIONS ' ,
212+ 'message ' => $ e ->getMessage (),
213+ 'code ' => $ e ->getCode (),
214+ ]);
125215 }
126216
127- if ($ shouldAddModifiedSince && isset ($ lastPullContent [AbstractCache::LAST_PULL ])) {
217+ return $ decisions ;
218+ }
219+
220+ private function handleAllowListPullHeaders (array $ headers , array $ lastPullContent ): array
221+ {
222+ if (isset ($ lastPullContent [AbstractCache::LAST_PULL ])) {
128223 $ headers ['If-Modified-Since ' ] = $ this ->formatIfModifiedSinceHeader (
129224 (int ) $ lastPullContent [AbstractCache::LAST_PULL ]
130225 );
@@ -133,6 +228,34 @@ private function handleListPullHeaders(array $headers, array $lastPullContent, i
133228 return $ headers ;
134229 }
135230
231+ private function handleAllowListResponse (string $ listResponse , array $ allowDecision ): array
232+ {
233+ $ decisions = [];
234+ $ listedAllows = explode (\PHP_EOL , $ listResponse );
235+ $ this ->logger ->debug ('Handle allow list decisions ' , [
236+ 'type ' => 'CAPI_REM_HANDLE_ALLOW_LIST_DECISIONS ' ,
237+ 'list_count ' => count ($ listedAllows ),
238+ ]);
239+ foreach ($ listedAllows as $ listedAllow ) {
240+ $ decoded = json_decode ($ listedAllow , true );
241+ $ allowDecision ['value ' ] = $ decoded ['value ' ];
242+ $ allowDecision ['scope ' ] = $ decoded ['scope ' ];
243+ $ allowDecision ['duration ' ] = '1s ' ; // Will be overwritten by the duration in the allowlist
244+ $ decision = $ this ->convertRawDecision ($ allowDecision );
245+
246+ if ($ decision ) {
247+ $ durationInSeconds = isset ($ decoded ['expiration ' ]) ?
248+ $ this ->getDurationInSeconds ($ decoded ['expiration ' ]) :
249+ Constants::MAX_DURATION ;
250+
251+ $ decision ->setExpiresAt (time () + $ durationInSeconds );
252+ $ decisions [] = $ decision ;
253+ }
254+ }
255+
256+ return $ decisions ;
257+ }
258+
136259 /**
137260 * @throws InvalidArgumentException|CacheException
138261 */
@@ -168,7 +291,9 @@ private function handleListDecisions(array $blocklists): array
168291 $ pullTime = time ();
169292 if ($ lastPullItem ->isHit ()) {
170293 $ lastPullContent = $ lastPullItem ->get ();
171- $ headers = $ this ->handleListPullHeaders ($ headers , $ lastPullContent , $ pullTime );
294+ $ frequency = $ this ->getConfig ('refresh_frequency_indicator ' ) ?? Constants::REFRESH_FREQUENCY ;
295+ $ headers = $ this ->handleListPullHeaders ($ headers , $ lastPullContent , $ pullTime , (int )
296+ $ frequency );
172297 }
173298
174299 $ listResponse = rtrim (
@@ -202,6 +327,26 @@ private function handleListDecisions(array $blocklists): array
202327 return $ decisions ;
203328 }
204329
330+ private function handleListPullHeaders (array $ headers , array $ lastPullContent , int $ pullTime , int $ frequency ): array
331+ {
332+ $ shouldAddModifiedSince = false ;
333+ if (isset ($ lastPullContent [AbstractCache::INDEX_EXP ])) {
334+ $ shouldAddModifiedSince = $ this ->shouldAddModifiedSince (
335+ $ pullTime ,
336+ (int ) $ lastPullContent [AbstractCache::INDEX_EXP ],
337+ $ frequency
338+ );
339+ }
340+
341+ if ($ shouldAddModifiedSince && isset ($ lastPullContent [AbstractCache::LAST_PULL ])) {
342+ $ headers ['If-Modified-Since ' ] = $ this ->formatIfModifiedSinceHeader (
343+ (int ) $ lastPullContent [AbstractCache::LAST_PULL ]
344+ );
345+ }
346+
347+ return $ headers ;
348+ }
349+
205350 private function handleListResponse (string $ listResponse , array $ blockDecision ): array
206351 {
207352 $ decisions = [];
@@ -221,6 +366,37 @@ private function handleListResponse(string $listResponse, array $blockDecision):
221366 return $ decisions ;
222367 }
223368
369+ /**
370+ * This method allows to know if the "If-Modified-Since" should be added when pulling list decisions.
371+ *
372+ * @param int $pullTime // Moment when the list is pulled
373+ * @param int $listExpirationTime // Expiration of the cached list decisions
374+ * @param int $frequency // Amount of time in seconds that represents the average decision pull frequency
375+ */
376+ private function shouldAddModifiedSince (int $ pullTime , int $ listExpirationTime , int $ frequency ): bool
377+ {
378+ return ($ listExpirationTime - $ frequency ) > $ pullTime ;
379+ }
380+
381+ private function validateAllowlist (array $ allowlist ): bool
382+ {
383+ if (
384+ !empty ($ allowlist ['id ' ])
385+ && !empty ($ allowlist ['name ' ])
386+ && !empty ($ allowlist ['description ' ])
387+ && !empty ($ allowlist ['url ' ])
388+ ) {
389+ return true ;
390+ }
391+
392+ $ this ->logger ->error ('Retrieved allowlist is not as expected ' , [
393+ 'type ' => 'REM_RAW_DECISION_NOT_AS_EXPECTED ' ,
394+ 'raw_decision ' => json_encode ($ allowlist ),
395+ ]);
396+
397+ return false ;
398+ }
399+
224400 private function validateBlocklist (array $ blocklist ): bool
225401 {
226402 if (
@@ -240,35 +416,4 @@ private function validateBlocklist(array $blocklist): bool
240416
241417 return false ;
242418 }
243-
244- /**
245- * @throws CacheStorageException
246- * @throws InvalidArgumentException
247- * @throws CacheException|ClientException
248- */
249- public function refreshDecisions (): array
250- {
251- $ rawDecisions = $ this ->client ->getStreamDecisions ();
252- $ newDecisions = $ this ->convertRawCapiDecisionsToDecisions ($ rawDecisions [self ::CS_NEW ] ?? []);
253- $ deletedDecisions = $ this ->convertRawCapiDecisionsToDecisions ($ rawDecisions [self ::CS_DEL ] ?? []);
254- $ listDecisions = $ this ->handleListDecisions ($ rawDecisions [self ::CS_LINK ][self ::CS_BLOCK ] ?? []);
255-
256- $ stored = $ this ->storeDecisions (array_merge ($ newDecisions , $ listDecisions ));
257- $ removed = $ this ->removeDecisions ($ deletedDecisions );
258-
259- return [
260- self ::CS_NEW => $ stored [AbstractCache::DONE ] ?? 0 ,
261- self ::CS_DEL => $ removed [AbstractCache::DONE ] ?? 0 ,
262- ];
263- }
264-
265- /**
266- * Process and validate input configurations.
267- */
268- private function configure (array $ configs ): void
269- {
270- $ configuration = new CapiRemediationConfig ();
271- $ processor = new Processor ();
272- $ this ->configs = $ processor ->processConfiguration ($ configuration , [$ configuration ->cleanConfigs ($ configs )]);
273- }
274419}
0 commit comments