Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.40% covered (success)
97.40%
75 / 77
92.86% covered (success)
92.86%
13 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Application
97.40% covered (success)
97.40%
75 / 77
92.86% covered (success)
92.86%
13 / 14
49
0.00% covered (danger)
0.00%
0 / 1
 initialize
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 initializeMaintenanceMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 initializeLogger
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
11
 initializeCaptcha
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
7
 initializeCache
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 initializeMiddleware
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
12
 reinitializeMiddlewareConfig
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validateCacheDirectory
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
6.60
 setupCache
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getLoggerConfig
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 getRateLimit
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 getRequestLimits
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 getIpBlacklist
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getAccessUnpublishedOnDomain
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace BO\Zmscitizenapi;
6
7use Psr\SimpleCache\CacheInterface;
8use Symfony\Component\Cache\Adapter\FilesystemAdapter;
9use Symfony\Component\Cache\Psr16Cache;
10
11/**
12 * @SuppressWarnings(PHPMD.TooManyFields)
13 * @SuppressWarnings(PHPMD.NPathComplexity)
14 * @TODO: Refactor this class into smaller focused classes (LoggerInitializer, MiddlewareInitializer) to reduce complexity
15 */
16class Application extends \BO\Slim\Application
17{
18    public const IDENTIFIER = 'zms';
19    public const MODULE_NAME = 'zmscitizenapi';
20    public static string $source_name = "dldb,zms";
21    public static $http = null;
22    public static array $http_curl_config = [];
23    public static ?CacheInterface $cache = null;
24    // Cache config
25    public static string $CACHE_DIR;
26    public static int $SOURCE_CACHE_TTL;
27    public static bool $MAINTENANCE_MODE_ENABLED;
28    // Logger config
29
30    public static int $LOGGER_MAX_REQUESTS;
31    public static int $LOGGER_MAX_ERROR_REQUESTS;
32    public static int $LOGGER_RESPONSE_LENGTH;
33    public static int $LOGGER_STACK_LINES;
34    public static int $LOGGER_MESSAGE_SIZE;
35    public static int $LOGGER_CACHE_TTL;
36    public static int $LOGGER_MAX_RETRIES;
37    public static int $LOGGER_BACKOFF_MIN;
38    public static int $LOGGER_BACKOFF_MAX;
39    public static int $LOGGER_LOCK_TIMEOUT;
40    // Captcha config
41    public static bool $CAPTCHA_ENABLED;
42    public static string $CAPTCHA_TOKEN_SECRET;
43    public static int $CAPTCHA_TOKEN_TTL;
44    public static string $ALTCHA_CAPTCHA_SITE_KEY;
45    public static string $ALTCHA_CAPTCHA_SITE_SECRET;
46    public static string $ALTCHA_CAPTCHA_ENDPOINT_CHALLENGE;
47    public static string $ALTCHA_CAPTCHA_ENDPOINT_VERIFY;
48    // Rate limiting config
49    public static int $RATE_LIMIT_MAX_REQUESTS;
50    public static int $RATE_LIMIT_CACHE_TTL;
51    public static int $RATE_LIMIT_MAX_RETRIES;
52    public static int $RATE_LIMIT_BACKOFF_MIN;
53    public static int $RATE_LIMIT_BACKOFF_MAX;
54    public static int $RATE_LIMIT_LOCK_TIMEOUT;
55    // Request limits config
56    public static int $MAX_REQUEST_SIZE;
57    public static int $MAX_STRING_LENGTH;
58    public static int $MAX_RECURSION_DEPTH;
59    // IP Filter config
60    public static string $IP_BLACKLIST;
61
62    public static string $ACCESS_UNPUBLISHED_ON_DOMAIN;
63
64    public static function initialize(): void
65    {
66        self::initializeMaintenanceMode();
67        self::initializeLogger();
68        self::initializeCaptcha();
69        self::initializeCache();
70        self::initializeMiddleware();
71    }
72
73    private static function initializeMaintenanceMode(): void
74    {
75        self::$MAINTENANCE_MODE_ENABLED = filter_var(getenv('MAINTENANCE_ENABLED'), FILTER_VALIDATE_BOOLEAN);
76    }
77
78    /**
79     * @SuppressWarnings(PHPMD.NPathComplexity)
80     * @TODO: Extract logger initialization logic into a dedicated LoggerInitializer class
81     */
82    private static function initializeLogger(): void
83    {
84        self::$LOGGER_MAX_REQUESTS = (int) (getenv('ZMS_CITIZENAPI_LOGGER_MAX_REQUESTS') ?: 1000);
85        self::$LOGGER_MAX_ERROR_REQUESTS = (int) (getenv('ZMS_CITIZENAPI_LOGGER_MAX_ERROR_REQUESTS') ?: 0);
86        self::$LOGGER_RESPONSE_LENGTH = (int) (getenv('ZMS_CITIZENAPI_LOGGER_RESPONSE_LENGTH') ?: 1048576);
87        // 1MB
88        self::$LOGGER_STACK_LINES = (int) (getenv('ZMS_CITIZENAPI_LOGGER_STACK_LINES') ?: 20);
89        self::$LOGGER_MESSAGE_SIZE = (int) (getenv('ZMS_CITIZENAPI_LOGGER_MESSAGE_SIZE') ?: 8192);
90        // 8KB
91        self::$LOGGER_CACHE_TTL = (int) (getenv('ZMS_CITIZENAPI_LOGGER_CACHE_TTL') ?: 60);
92        self::$LOGGER_MAX_RETRIES = (int) (getenv('ZMS_CITIZENAPI_LOGGER_MAX_RETRIES') ?: 3);
93        self::$LOGGER_BACKOFF_MIN = (int) (getenv('ZMS_CITIZENAPI_LOGGER_BACKOFF_MIN') ?: 100);
94        self::$LOGGER_BACKOFF_MAX = (int) (getenv('ZMS_CITIZENAPI_LOGGER_BACKOFF_MAX') ?: 1000);
95        self::$LOGGER_LOCK_TIMEOUT = (int) (getenv('ZMS_CITIZENAPI_LOGGER_LOCK_TIMEOUT') ?: 5);
96        \BO\Slim\LoggerService::configure(self::getLoggerConfig());
97    }
98
99    private static function initializeCaptcha(): void
100    {
101        self::$CAPTCHA_ENABLED = filter_var(getenv('CAPTCHA_ENABLED'), FILTER_VALIDATE_BOOLEAN);
102        self::$CAPTCHA_TOKEN_SECRET = getenv('CAPTCHA_TOKEN_SECRET') ?: '';
103        self::$CAPTCHA_TOKEN_TTL = (int) getenv('CAPTCHA_TOKEN_TTL') ?: 300;
104        self::$ALTCHA_CAPTCHA_SITE_KEY = getenv('ALTCHA_CAPTCHA_SITE_KEY') ?: '';
105        self::$ALTCHA_CAPTCHA_SITE_SECRET = getenv('ALTCHA_CAPTCHA_SITE_SECRET') ?: '';
106        self::$ALTCHA_CAPTCHA_ENDPOINT_CHALLENGE = getenv('ALTCHA_CAPTCHA_ENDPOINT_CHALLENGE')
107            ?: 'https://captcha.muenchen.de/api/v1/captcha/challenge';
108        self::$ALTCHA_CAPTCHA_ENDPOINT_VERIFY = getenv('ALTCHA_CAPTCHA_ENDPOINT_VERIFY')
109            ?: 'https://captcha.muenchen.de/api/v1/captcha/verify';
110    }
111
112    private static function initializeCache(): void
113    {
114        self::$CACHE_DIR = getenv('CACHE_DIR') ?: __DIR__ . '/cache';
115        self::$SOURCE_CACHE_TTL = (int) (getenv('SOURCE_CACHE_TTL') ?: 3600);
116        self::validateCacheDirectory();
117        self::setupCache();
118    }
119
120    /**
121     * @SuppressWarnings(PHPMD.NPathComplexity)
122     * @TODO: Extract middleware initialization logic into a dedicated MiddlewareInitializer class
123     */
124    private static function initializeMiddleware(): void
125    {
126        // Rate limiting
127        self::$RATE_LIMIT_MAX_REQUESTS = (int) (getenv('RATE_LIMIT_MAX_REQUESTS') ?: 60);
128        self::$RATE_LIMIT_CACHE_TTL = (int) (getenv('RATE_LIMIT_CACHE_TTL') ?: 60);
129        self::$RATE_LIMIT_MAX_RETRIES = (int) (getenv('RATE_LIMIT_MAX_RETRIES') ?: 3);
130        self::$RATE_LIMIT_BACKOFF_MIN = (int) (getenv('RATE_LIMIT_BACKOFF_MIN') ?: 10);
131        self::$RATE_LIMIT_BACKOFF_MAX = (int) (getenv('RATE_LIMIT_BACKOFF_MAX') ?: 50);
132        self::$RATE_LIMIT_LOCK_TIMEOUT = (int) (getenv('RATE_LIMIT_LOCK_TIMEOUT') ?: 1);
133        // Request limits
134        self::$MAX_REQUEST_SIZE = (int) (getenv('MAX_REQUEST_SIZE') ?: 10485760);
135        // 10MB
136        self::$MAX_STRING_LENGTH = (int) (getenv('MAX_STRING_LENGTH') ?: 32768);
137        // 32KB
138        self::$MAX_RECURSION_DEPTH = (int) (getenv('MAX_RECURSION_DEPTH') ?: 10);
139        // IP Filter
140        self::$IP_BLACKLIST = getenv('IP_BLACKLIST') ?: '';
141
142        self::$ACCESS_UNPUBLISHED_ON_DOMAIN = getenv('ACCESS_UNPUBLISHED_ON_DOMAIN') ?: '';
143    }
144
145    public static function reinitializeMiddlewareConfig(): void
146    {
147        self::initializeMiddleware();
148    }
149
150    private static function validateCacheDirectory(): void
151    {
152        if (!is_dir(self::$CACHE_DIR)) {
153            if (!@mkdir(self::$CACHE_DIR, 0750, true) && !is_dir(self::$CACHE_DIR)) {
154                throw new \RuntimeException(sprintf('Cache directory "%s" could not be created', self::$CACHE_DIR));
155            }
156        }
157
158        if (!is_writable(self::$CACHE_DIR)) {
159            throw new \RuntimeException(sprintf('Cache directory "%s" is not writable', self::$CACHE_DIR));
160        }
161    }
162
163    private static function setupCache(): void
164    {
165        $psr6 = new FilesystemAdapter(namespace: '', defaultLifetime: self::$SOURCE_CACHE_TTL, directory: self::$CACHE_DIR);
166        self::$cache = new Psr16Cache($psr6);
167        \BO\Slim\LoggerService::$cache = self::$cache;
168    }
169
170    public static function getLoggerConfig(): array
171    {
172        return [
173            'maxRequests' => self::$LOGGER_MAX_REQUESTS,
174            'maxErrorRequests' => self::$LOGGER_MAX_ERROR_REQUESTS,
175            'responseLength' => self::$LOGGER_RESPONSE_LENGTH,
176            'stackLines' => self::$LOGGER_STACK_LINES,
177            'messageSize' => self::$LOGGER_MESSAGE_SIZE,
178            'cacheTtl' => self::$LOGGER_CACHE_TTL,
179            'maxRetries' => self::$LOGGER_MAX_RETRIES,
180            'backoffMin' => self::$LOGGER_BACKOFF_MIN,
181            'backoffMax' => self::$LOGGER_BACKOFF_MAX,
182            'lockTimeout' => self::$LOGGER_LOCK_TIMEOUT
183        ];
184    }
185
186    public static function getRateLimit(): array
187    {
188        return [
189            'maxRequests' => self::$RATE_LIMIT_MAX_REQUESTS,
190            'cacheExpiry' => self::$RATE_LIMIT_CACHE_TTL,
191            'maxRetries' => self::$RATE_LIMIT_MAX_RETRIES,
192            'backoffMin' => self::$RATE_LIMIT_BACKOFF_MIN,
193            'backoffMax' => self::$RATE_LIMIT_BACKOFF_MAX,
194            'lockTimeout' => self::$RATE_LIMIT_LOCK_TIMEOUT
195        ];
196    }
197
198    public static function getRequestLimits(): array
199    {
200        return [
201            'maxSize' => self::$MAX_REQUEST_SIZE,
202            'maxStringLength' => self::$MAX_STRING_LENGTH,
203            'maxRecursionDepth' => self::$MAX_RECURSION_DEPTH
204        ];
205    }
206
207    public static function getIpBlacklist(): string
208    {
209        return self::$IP_BLACKLIST ?: '';
210    }
211
212    public static function getAccessUnpublishedOnDomain(): ?string
213    {
214        return self::$ACCESS_UNPUBLISHED_ON_DOMAIN ?: null;
215    }
216}
217
218Application::initialize();