Total Complexity | 54 |
Total Lines | 390 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like AccountStore often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use AccountStore, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
20 | class AccountStore { |
||
21 | public const LOG_CONTEXT = "AccountStore"; // Context for the Logger |
||
22 | public const ACCOUNT_STORAGE_PATH = "zarafa/v1/plugins/files/accounts"; |
||
23 | public const ACCOUNT_VERSION = 1; |
||
24 | |||
25 | /** |
||
26 | * @var Account[] Account array |
||
27 | */ |
||
28 | private $accounts = []; |
||
29 | |||
30 | public function __construct() { |
||
32 | } |
||
33 | |||
34 | /** |
||
35 | * @param array $backendConfig Backend specific account settings |
||
36 | * like username, password, serveraddress, ... |
||
37 | * @param mixed $name |
||
38 | * @param mixed $backend |
||
39 | * |
||
40 | * @return Account |
||
41 | */ |
||
42 | public function createAccount($name, $backend, $backendConfig) { |
||
43 | $newID = $this->createNewId($backendConfig); // create id out of the configuration |
||
44 | |||
45 | // create instance of backend to get features |
||
46 | $backendStore = BackendStore::getInstance(); |
||
47 | $backend = $backendStore->normalizeBackendName($backend); |
||
48 | $backendInstance = $backendStore->getInstanceOfBackend($backend); |
||
49 | $features = $backendInstance->getAvailableFeatures(); |
||
50 | |||
51 | // check backend_config for validity |
||
52 | $status = $this->checkBackendConfig($backendInstance, $backendConfig); |
||
53 | |||
54 | // get sequence number |
||
55 | $sequence = $this->getNewSequenceNumber(); |
||
56 | |||
57 | $newAccount = new Account($newID, strip_tags((string) $name), $status[0], $status[1], strip_tags((string) $backend), $backendConfig, $features, $sequence, false); |
||
58 | |||
59 | // now store all the values to the user settings |
||
60 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $newID . "/id", $newAccount->getId()); |
||
61 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $newID . "/name", $newAccount->getName()); |
||
62 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $newID . "/status", $newAccount->getStatus()); |
||
63 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $newID . "/status_description", $newAccount->getStatusDescription()); |
||
64 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $newID . "/backend", $newAccount->getBackend()); |
||
65 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $newID . "/account_sequence", $newAccount->getSequence()); |
||
66 | // User defined accounts are never administrative. So set cannot_change to false. |
||
67 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $newID . "/cannot_change", false); |
||
68 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $newID . "/backend_config/version", self::ACCOUNT_VERSION); |
||
69 | // store all backend configurations |
||
70 | foreach ($newAccount->getBackendConfig() as $key => $value) { |
||
71 | if ($key !== "version") { |
||
72 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $newID . "/backend_config/" . $key, $this->encryptBackendConfigProperty($value, self::ACCOUNT_VERSION)); |
||
73 | } |
||
74 | } |
||
75 | |||
76 | // store all features |
||
77 | foreach ($newAccount->getFeatures() as $feature) { |
||
78 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $newID . "/backend_features/" . $feature, true); |
||
79 | } |
||
80 | |||
81 | $GLOBALS["settings"]->saveSettings(); // save to MAPI storage |
||
82 | |||
83 | // add account to our local store after it was saved to the zarafa-settings |
||
84 | $this->accounts[$newID] = $newAccount; |
||
85 | |||
86 | return $newAccount; |
||
87 | } |
||
88 | |||
89 | /** |
||
90 | * @param Account $account |
||
91 | * |
||
92 | * @return Account |
||
93 | */ |
||
94 | public function updateAccount($account) { |
||
95 | $accId = $account->getId(); |
||
96 | $isAdministrativeAccount = $account->getCannotChangeFlag(); |
||
97 | |||
98 | // create instance of backend to get features |
||
99 | $backendStore = BackendStore::getInstance(); |
||
100 | $normalizedBackend = $backendStore->normalizeBackendName($account->getBackend()); |
||
101 | $account->setBackend($normalizedBackend); |
||
102 | $backendInstance = $backendStore->getInstanceOfBackend($normalizedBackend); |
||
103 | $features = $backendInstance->getAvailableFeatures(); |
||
104 | $account->setFeatures($features); |
||
105 | |||
106 | // check backend_config for validity |
||
107 | $status = $this->checkBackendConfig($backendInstance, $account->getBackendConfig()); |
||
108 | $account->setStatus($status[0]); // update status |
||
109 | $account->setStatusDescription($status[1]); // update status description |
||
110 | |||
111 | // add account to local store |
||
112 | $this->accounts[$accId] = $account; |
||
113 | |||
114 | // save values to MAPI settings |
||
115 | // now store all the values to the user settings |
||
116 | // but if we have an administrative account only save the account sequence |
||
117 | if (!$isAdministrativeAccount) { |
||
118 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $accId . "/name", $account->getName()); |
||
119 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $accId . "/status", $account->getStatus()); |
||
120 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $accId . "/status_description", $account->getStatusDescription()); |
||
121 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $accId . "/backend", $account->getBackend()); |
||
122 | |||
123 | $acc = $account->getBackendConfig(); |
||
124 | $version = 0; |
||
125 | if (isset($acc["version"])) { |
||
126 | $version = $acc["version"]; |
||
127 | } |
||
128 | |||
129 | // Unable to decrypt, don't update |
||
130 | if ($version == 0 && !defined('FILES_PASSWORD_IV') && !defined('FILES_PASSWORD_KEY')) { |
||
131 | Logger::error(self::LOG_CONTEXT, "Unable to update the account to as FILES_PASSWORD_IV/FILES_PASSWORD_KEY is not set"); |
||
132 | } |
||
133 | else { |
||
134 | // store all backend configurations |
||
135 | foreach ($account->getBackendConfig() as $key => $value) { |
||
136 | if ($key !== "version") { |
||
137 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $accId . "/backend_config/" . $key, $this->encryptBackendConfigProperty($value, self::ACCOUNT_VERSION)); |
||
138 | } |
||
139 | } |
||
140 | |||
141 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $accId . "/backend_config/version", self::ACCOUNT_VERSION); |
||
142 | } |
||
143 | |||
144 | // store all features |
||
145 | foreach ($account->getFeatures() as $feature) { |
||
146 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $accId . "/backend_features/" . $feature, true); |
||
147 | } |
||
148 | } |
||
149 | // when getSequence returns 0, there is no account_sequence setting yet. So create one. |
||
150 | $account_sequence = ($account->getSequence() === 0 ? $this->getNewSequenceNumber() : $account->getSequence()); |
||
151 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $accId . "/account_sequence", $account_sequence); |
||
152 | |||
153 | $GLOBALS["settings"]->saveSettings(); // save to MAPI storage |
||
154 | |||
155 | return $account; |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * Delete account from local store and from the MAPI settings. |
||
160 | * |
||
161 | * @param mixed $accountId |
||
162 | * |
||
163 | * @return bool |
||
164 | */ |
||
165 | public function deleteAccount($accountId) { |
||
166 | $account = $this->getAccount($accountId); |
||
167 | // Do not allow deleting administrative accounts, but fail silently. |
||
168 | if (!$account->getCannotChangeFlag()) { |
||
169 | $GLOBALS["settings"]->delete(self::ACCOUNT_STORAGE_PATH . "/" . $accountId); |
||
170 | $GLOBALS["settings"]->saveSettings(); // save to MAPI storage |
||
171 | } |
||
172 | |||
173 | return true; |
||
174 | } |
||
175 | |||
176 | /** |
||
177 | * Return the instance of the local account. |
||
178 | * |
||
179 | * @param mixed $accountId |
||
180 | * |
||
181 | * @return Account |
||
182 | */ |
||
183 | public function getAccount($accountId) { |
||
184 | return $this->accounts[$accountId]; |
||
185 | } |
||
186 | |||
187 | /** |
||
188 | * @return Account[] all Accounts |
||
189 | */ |
||
190 | public function getAllAccounts() { |
||
191 | return $this->accounts; |
||
192 | } |
||
193 | |||
194 | /** |
||
195 | * Initialize the accountstore. Reads all accountinformation from the MAPI settings. |
||
196 | */ |
||
197 | private function initialiseAccounts() { |
||
198 | // Parse accounts from the Settings |
||
199 | $tmpAccs = $GLOBALS["settings"]->get(self::ACCOUNT_STORAGE_PATH); |
||
200 | $backendStore = BackendStore::getInstance(); |
||
201 | |||
202 | if (is_array($tmpAccs)) { |
||
203 | $this->accounts = []; |
||
204 | |||
205 | foreach ($tmpAccs as $acc) { |
||
206 | // set backend_features if it is not set to prevent warning |
||
207 | if (!isset($acc["backend_features"])) { |
||
208 | $acc["backend_features"] = []; |
||
209 | } |
||
210 | // account_sequence was introduced later. So set and save it if missing. |
||
211 | if (!isset($acc["account_sequence"])) { |
||
212 | $acc["account_sequence"] = $this->getNewSequenceNumber(); |
||
213 | Logger::debug(self::LOG_CONTEXT, "Account sequence missing. New seq: " . $acc["account_sequence"]); |
||
214 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $acc["id"] . "/account_sequence", $acc["account_sequence"]); |
||
215 | $GLOBALS["settings"]->saveSettings(); |
||
216 | } |
||
217 | // cannot_change flag was introduced later. So set it to false and save it if missing. |
||
218 | if (!isset($acc["cannot_change"])) { |
||
219 | $acc["cannot_change"] = false; |
||
220 | Logger::debug(self::LOG_CONTEXT, "Cannot change flag missing. Setting to false."); |
||
221 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $acc["id"] . "/cannot_change", false); |
||
222 | $GLOBALS["settings"]->saveSettings(); |
||
223 | } |
||
224 | |||
225 | $backend_config = $acc["backend_config"]; |
||
226 | $version = 0; |
||
227 | |||
228 | if (isset($acc["backend_config"], $acc["backend_config"]["version"])) { |
||
229 | $version = $acc["backend_config"]["version"]; |
||
230 | } |
||
231 | |||
232 | if (($version === 0 && defined('FILES_PASSWORD_IV') && defined('FILES_PASSWORD_KEY')) || $version === self::ACCOUNT_VERSION) { |
||
233 | $backend_config = $this->decryptBackendConfig($acc["backend_config"], $version); |
||
234 | // version is lost after decryption, add it again |
||
235 | $backend_config["version"] = $version; |
||
236 | } |
||
237 | elseif ($version === 0) { |
||
238 | Logger::error(self::LOG_CONTEXT, "FILES_PASSWORD_IV or FILES_PASSWORD_KEY not set, unable to decrypt backend configuration"); |
||
239 | } |
||
240 | else { |
||
241 | Logger::error(self::LOG_CONTEXT, "Unsupported account version {$version}, unable to decrypt backend configuration"); |
||
242 | } |
||
243 | |||
244 | $normalizedBackend = $backendStore->normalizeBackendName($acc["backend"]); |
||
245 | if ($normalizedBackend !== $acc["backend"]) { |
||
246 | $GLOBALS["settings"]->set(self::ACCOUNT_STORAGE_PATH . "/" . $acc["id"] . "/backend", $normalizedBackend); |
||
247 | $GLOBALS["settings"]->saveSettings(); |
||
248 | } |
||
249 | $this->accounts[$acc["id"]] = new Account( |
||
250 | $acc["id"], |
||
251 | $acc["name"], |
||
252 | $acc["status"], |
||
253 | $acc["status_description"], |
||
254 | $normalizedBackend, |
||
255 | $backend_config, |
||
256 | array_keys($acc["backend_features"]), |
||
257 | $acc["account_sequence"], |
||
258 | $acc["cannot_change"] |
||
259 | ); |
||
260 | } |
||
261 | } |
||
262 | Logger::debug(self::LOG_CONTEXT, "Found " . count($this->accounts) . " accounts."); |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * @param AbstractBackend $backendInstance |
||
|
|||
267 | * @param array $backendConfig Backend specific account settings |
||
268 | * like username, password, serveraddress, ... |
||
269 | * |
||
270 | * @return array |
||
271 | */ |
||
272 | private function checkBackendConfig($backendInstance, $backendConfig) { |
||
273 | $status = Account::STATUS_NEW; |
||
274 | $description = _('Account is ready to use.'); |
||
275 | |||
276 | try { |
||
277 | $backendInstance->init_backend($backendConfig); |
||
278 | $backendInstance->open(); |
||
279 | $backendInstance->ls("/"); |
||
280 | $status = Account::STATUS_OK; |
||
281 | } |
||
282 | catch (BackendException $e) { |
||
283 | $status = Account::STATUS_ERROR; |
||
284 | $description = $e->getMessage(); |
||
285 | |||
286 | Logger::error(self::LOG_CONTEXT, "Account check failed: " . $description); |
||
287 | } |
||
288 | |||
289 | return [$status, $description]; |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * @param array $backendConfig Backend specific account settings |
||
294 | * like username, password, serveraddress, ... |
||
295 | * |
||
296 | * @return an unique id |
||
297 | */ |
||
298 | private function createNewId($backendConfig) { |
||
299 | // lets create a hash |
||
300 | return md5(json_encode($backendConfig) . time()); // json_encode is faster than serialize |
||
301 | } |
||
302 | |||
303 | /** |
||
304 | * Generate a new sequence number. It will always be the highest used sequence number +1. |
||
305 | * |
||
306 | * @return int |
||
307 | */ |
||
308 | private function getNewSequenceNumber() { |
||
309 | $seq = 0; |
||
310 | foreach ($this->accounts as $acc) { |
||
311 | if ($acc->getSequence() > $seq) { |
||
312 | $seq = $acc->getSequence(); |
||
313 | } |
||
314 | } |
||
315 | |||
316 | return $seq + 1; |
||
317 | } |
||
318 | |||
319 | /** |
||
320 | * Decrypt the backend configuration using the standard grommunio Web key. |
||
321 | * |
||
322 | * @param array $backendConfig Backend specific account settings |
||
323 | * like username, password, serveraddress, ... |
||
324 | * @param mixed $version |
||
325 | * |
||
326 | * @return array |
||
327 | */ |
||
328 | private function decryptBackendConfig($backendConfig, $version = 0) { |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * Encrypt the given string. |
||
347 | * |
||
348 | * @param $version the storage version used to identify what encryption to use |
||
349 | * @param mixed $value |
||
350 | * |
||
351 | * @return string |
||
352 | */ |
||
353 | private function encryptBackendConfigProperty($value, $version = 0) { |
||
354 | if ($version == self::ACCOUNT_VERSION && !is_bool($value)) { |
||
355 | // Guard against missing libsodium extension on PHP 8.1/8.2 |
||
356 | if (!defined('SODIUM_CRYPTO_SECRETBOX_NONCEBYTES') || !function_exists('sodium_crypto_secretbox')) { |
||
357 | // Keep original value to avoid fatals; features may be limited |
||
358 | error_log('[Files][AccountStore] Libsodium not available for encryption; storing value as-is.'); |
||
359 | |||
360 | return $value; |
||
361 | } |
||
362 | |||
363 | $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); |
||
364 | $key = $GLOBALS["operations"]->getFilesEncryptionKey(); |
||
365 | $encrypted = sodium_crypto_secretbox((string) $value, $nonce, (string) $key); |
||
366 | $value = bin2hex($nonce) . bin2hex($encrypted); |
||
367 | } |
||
368 | elseif ($version !== self::ACCOUNT_VERSION) { |
||
369 | throw new Exception("Unable to encrypt backend configuration unsupported version {$version}"); |
||
370 | } |
||
371 | |||
372 | return $value; |
||
373 | } |
||
374 | |||
375 | /** |
||
376 | * Decrypt the given string. |
||
377 | * |
||
378 | * @param $version the storage version used to identify what encryption to use |
||
379 | * @param mixed $value |
||
380 | * |
||
381 | * @return string |
||
382 | */ |
||
383 | private function decryptBackendConfigProperty($value, $version = 0) { |
||
410 | } |
||
411 | } |
||
412 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths