Lines 98.83% 85 / 86
Methods 93.75% 15 / 16
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 initialize 100.00% 7 / 7 100.00% 1 / 1 1
 initializeMaintenanceMode 100.00% 1 / 1 100.00% 1 / 1 1
 initializeSource 66.66% 2 / 3 0.00% 0 / 1 3.33
 initializePlaceholderEmail 100.00% 4 / 4 100.00% 1 / 1 1
 getPlaceholderEmail 100.00% 3 / 3 100.00% 1 / 1 2
 initializeLogger 100.00% 11 / 11 100.00% 1 / 1 1
 initializeCaptcha 100.00% 14 / 14 100.00% 1 / 1 2
 initializeMiddleware 100.00% 11 / 11 100.00% 1 / 1 1
 getenvInt 100.00% 2 / 2 100.00% 1 / 1 3
 getenvString 100.00% 2 / 2 100.00% 1 / 1 3
 reinitializeMiddlewareConfig 100.00% 1 / 1 100.00% 1 / 1 1
 getLoggerConfig 100.00% 12 / 12 100.00% 1 / 1 1
 getRateLimit 100.00% 8 / 8 100.00% 1 / 1 1
 getRequestLimits 100.00% 5 / 5 100.00% 1 / 1 1
 getIpBlacklist 100.00% 1 / 1 100.00% 1 / 1 2
 getAccessUnpublishedOnDomain 100.00% 1 / 1 100.00% 1 / 1 2
15class Application extends \BO\Slim\Application
16{
17    use CacheInitializationTrait;
18
19    public const string IDENTIFIER = 'zms';
20    public const string MODULE_NAME = 'zmscitizenapi';
21    public static string $source_name = "dldb,zms";
22    public static $http = null;
23    public static array $http_curl_config = [];
24    public static ?CacheInterface $cache = null;
25    // Cache config
26    public static string $CACHE_DIR;
27    public static int $SOURCE_CACHE_TTL;
28    public static bool $MAINTENANCE_MODE_ENABLED;
29    // Logger config
30
31    public static int $LOGGER_MAX_REQUESTS;
32    public static int $LOGGER_MAX_ERROR_REQUESTS;
33    public static int $LOGGER_RESPONSE_LENGTH;
34    public static int $LOGGER_STACK_LINES;
35    public static int $LOGGER_MESSAGE_SIZE;
36    public static int $LOGGER_CACHE_TTL;
37    public static int $LOGGER_MAX_RETRIES;
38    public static int $LOGGER_BACKOFF_MIN;
39    public static int $LOGGER_BACKOFF_MAX;
40    public static int $LOGGER_LOCK_TIMEOUT;
41    // Captcha config
42    public static bool $CAPTCHA_ENABLED;
43    public static string $CAPTCHA_TOKEN_SECRET;
44    public static int $CAPTCHA_TOKEN_TTL;
45    public static string $ALTCHA_CAPTCHA_SITE_KEY;
46    public static string $ALTCHA_CAPTCHA_SITE_SECRET;
47    public static string $ALTCHA_CAPTCHA_ENDPOINT_CHALLENGE;
48    public static string $ALTCHA_CAPTCHA_ENDPOINT_VERIFY;
49    // Rate limiting config
50    public static int $RATE_LIMIT_MAX_REQUESTS;
51    public static int $RATE_LIMIT_CACHE_TTL;
52    public static int $RATE_LIMIT_MAX_RETRIES;
53    public static int $RATE_LIMIT_BACKOFF_MIN;
54    public static int $RATE_LIMIT_BACKOFF_MAX;
55    public static int $RATE_LIMIT_LOCK_TIMEOUT;
56    // Request limits config
57    public static int $MAX_REQUEST_SIZE;
58    public static int $MAX_STRING_LENGTH;
59    public static int $MAX_RECURSION_DEPTH;
60    // IP Filter config
61    public static string $IP_BLACKLIST;
62
63    public static string $ACCESS_UNPUBLISHED_ON_DOMAIN;
64
65    public const string DEFAULT_PLACEHOLDER_EMAIL = 'noreply-terminvereinbarung@muenchen.de';
66    public static string $PLACEHOLDER_EMAIL = self::DEFAULT_PLACEHOLDER_EMAIL;
67
68    public static function initialize(): void
69    {
70        self::initializeMaintenanceMode();
71        self::initializeSource();
72        self::initializePlaceholderEmail();
73        self::initializeLogger();
74        self::initializeCaptcha();
75        self::initializeCache(__DIR__ . '/cache');
76        self::initializeMiddleware();
77    }
78
79    private static function initializeMaintenanceMode(): void
80    {
81        self::$MAINTENANCE_MODE_ENABLED = filter_var(getenv('MAINTENANCE_ENABLED'), FILTER_VALIDATE_BOOLEAN);
82    }
83
84    private static function initializeSource(): void
85    {
86        $fromEnv = getenv('SOURCE_NAME');
87        if (is_string($fromEnv) && trim($fromEnv) !== '') {
88            self::$source_name = trim($fromEnv);
89        }
90    }
91
92    private static function initializePlaceholderEmail(): void
93    {
94        self::$PLACEHOLDER_EMAIL = self::getenvString(
95            'ZMS_CITIZENAPI_PLACEHOLDER_EMAIL',
96            self::DEFAULT_PLACEHOLDER_EMAIL
97        );
98    }
99
100    public static function getPlaceholderEmail(): string
101    {
102        return self::$PLACEHOLDER_EMAIL !== ''
103            ? self::$PLACEHOLDER_EMAIL
104            : self::DEFAULT_PLACEHOLDER_EMAIL;
105    }
106
107    /**
108     * @SuppressWarnings(PHPMD.NPathComplexity)
109     * @TODO: Extract logger initialization logic into a dedicated LoggerInitializer class
110     */
111    private static function initializeLogger(): void
112    {
113        self::$LOGGER_MAX_REQUESTS = self::getenvInt('ZMS_CITIZENAPI_LOGGER_MAX_REQUESTS', 1000);
114        self::$LOGGER_MAX_ERROR_REQUESTS = self::getenvInt('ZMS_CITIZENAPI_LOGGER_MAX_ERROR_REQUESTS', 0);
115        self::$LOGGER_RESPONSE_LENGTH = self::getenvInt('ZMS_CITIZENAPI_LOGGER_RESPONSE_LENGTH', 1048576);
116        // 1MB
117        self::$LOGGER_STACK_LINES = self::getenvInt('ZMS_CITIZENAPI_LOGGER_STACK_LINES', 20);
118        self::$LOGGER_MESSAGE_SIZE = self::getenvInt('ZMS_CITIZENAPI_LOGGER_MESSAGE_SIZE', 8192);
119        // 8KB
120        self::$LOGGER_CACHE_TTL = self::getenvInt('ZMS_CITIZENAPI_LOGGER_CACHE_TTL', 60);
121        self::$LOGGER_MAX_RETRIES = self::getenvInt('ZMS_CITIZENAPI_LOGGER_MAX_RETRIES', 3);
122        self::$LOGGER_BACKOFF_MIN = self::getenvInt('ZMS_CITIZENAPI_LOGGER_BACKOFF_MIN', 100);
123        self::$LOGGER_BACKOFF_MAX = self::getenvInt('ZMS_CITIZENAPI_LOGGER_BACKOFF_MAX', 1000);
124        self::$LOGGER_LOCK_TIMEOUT = self::getenvInt('ZMS_CITIZENAPI_LOGGER_LOCK_TIMEOUT', 5);
125        \BO\Slim\LoggerService::configure(self::getLoggerConfig());
126    }
127
128    private static function initializeCaptcha(): void
129    {
130        self::$CAPTCHA_ENABLED = filter_var(getenv('CAPTCHA_ENABLED'), FILTER_VALIDATE_BOOLEAN);
131        self::$CAPTCHA_TOKEN_SECRET = self::getenvString('CAPTCHA_TOKEN_SECRET', '');
132        $captchaTtl = self::getenvInt('CAPTCHA_TOKEN_TTL', 0);
133        self::$CAPTCHA_TOKEN_TTL = $captchaTtl === 0 ? 300 : $captchaTtl;
134        self::$ALTCHA_CAPTCHA_SITE_KEY = self::getenvString('ALTCHA_CAPTCHA_SITE_KEY', '');
135        self::$ALTCHA_CAPTCHA_SITE_SECRET = self::getenvString('ALTCHA_CAPTCHA_SITE_SECRET', '');
136        self::$ALTCHA_CAPTCHA_ENDPOINT_CHALLENGE = self::getenvString(
137            'ALTCHA_CAPTCHA_ENDPOINT_CHALLENGE',
138            'https://captcha.muenchen.de/api/v1/captcha/challenge'
139        );
140        self::$ALTCHA_CAPTCHA_ENDPOINT_VERIFY = self::getenvString(
141            'ALTCHA_CAPTCHA_ENDPOINT_VERIFY',
142            'https://captcha.muenchen.de/api/v1/captcha/verify'
143        );
144    }
145
146    /**
147     * @TODO: Extract middleware initialization logic into a dedicated MiddlewareInitializer class
148     */
149    private static function initializeMiddleware(): void
150    {
151        // Rate limiting
152        self::$RATE_LIMIT_MAX_REQUESTS = self::getenvInt('RATE_LIMIT_MAX_REQUESTS', 600);
153        self::$RATE_LIMIT_CACHE_TTL = self::getenvInt('RATE_LIMIT_CACHE_TTL', 60);
154        self::$RATE_LIMIT_MAX_RETRIES = self::getenvInt('RATE_LIMIT_MAX_RETRIES', 3);
155        self::$RATE_LIMIT_BACKOFF_MIN = self::getenvInt('RATE_LIMIT_BACKOFF_MIN', 10);
156        self::$RATE_LIMIT_BACKOFF_MAX = self::getenvInt('RATE_LIMIT_BACKOFF_MAX', 50);
157        self::$RATE_LIMIT_LOCK_TIMEOUT = self::getenvInt('RATE_LIMIT_LOCK_TIMEOUT', 1);
158        // Request limits
159        self::$MAX_REQUEST_SIZE = self::getenvInt('MAX_REQUEST_SIZE', 10485760);
160        // 10MB
161        self::$MAX_STRING_LENGTH = self::getenvInt('MAX_STRING_LENGTH', 32768);
162        // 32KB
163        self::$MAX_RECURSION_DEPTH = self::getenvInt('MAX_RECURSION_DEPTH', 10);
164        // IP Filter
165        self::$IP_BLACKLIST = self::getenvString('IP_BLACKLIST', '');
166
167        self::$ACCESS_UNPUBLISHED_ON_DOMAIN = self::getenvString('ACCESS_UNPUBLISHED_ON_DOMAIN', '');
168    }
169
170    private static function getenvInt(string $name, int $default): int
171    {
172        $value = getenv($name);
173        return ($value === false || $value === '') ? $default : (int) $value;
174    }
175
176    private static function getenvString(string $name, string $default): string
177    {
178        $value = getenv($name);
179        return ($value === false || $value === '') ? $default : $value;
180    }
181
182    /** @psalm-api */
183    public static function reinitializeMiddlewareConfig(): void
184    {
185        self::initializeMiddleware();
186    }
187
188    public static function getLoggerConfig(): array
189    {
190        return [
191            'maxRequests' => self::$LOGGER_MAX_REQUESTS,
192            'maxErrorRequests' => self::$LOGGER_MAX_ERROR_REQUESTS,
193            'responseLength' => self::$LOGGER_RESPONSE_LENGTH,
194            'stackLines' => self::$LOGGER_STACK_LINES,
195            'messageSize' => self::$LOGGER_MESSAGE_SIZE,
196            'cacheTtl' => self::$LOGGER_CACHE_TTL,
197            'maxRetries' => self::$LOGGER_MAX_RETRIES,
198            'backoffMin' => self::$LOGGER_BACKOFF_MIN,
199            'backoffMax' => self::$LOGGER_BACKOFF_MAX,
200            'lockTimeout' => self::$LOGGER_LOCK_TIMEOUT
201        ];
202    }
203
204    public static function getRateLimit(): array
205    {
206        return [
207            'maxRequests' => self::$RATE_LIMIT_MAX_REQUESTS,
208            'cacheExpiry' => self::$RATE_LIMIT_CACHE_TTL,
209            'maxRetries' => self::$RATE_LIMIT_MAX_RETRIES,
210            'backoffMin' => self::$RATE_LIMIT_BACKOFF_MIN,
211            'backoffMax' => self::$RATE_LIMIT_BACKOFF_MAX,
212            'lockTimeout' => self::$RATE_LIMIT_LOCK_TIMEOUT
213        ];
214    }
215
216    public static function getRequestLimits(): array
217    {
218        return [
219            'maxSize' => self::$MAX_REQUEST_SIZE,
220            'maxStringLength' => self::$MAX_STRING_LENGTH,
221            'maxRecursionDepth' => self::$MAX_RECURSION_DEPTH
222        ];
223    }
224
225    public static function getIpBlacklist(): string
226    {
227        return self::$IP_BLACKLIST ?: '';
228    }
229
230    /** @psalm-api */
231    public static function getAccessUnpublishedOnDomain(): ?string
232    {
233        return self::$ACCESS_UNPUBLISHED_ON_DOMAIN ?: null;
234    }
235}