Completed
Branch devel-3.0 (330e85)
by Rubén
03:35
created

SyspassImport   F

Complexity

Total Complexity 61

Size/Duplication

Total Lines 373
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 182
dl 0
loc 373
rs 3.52
c 0
b 0
f 0
wmc 61

11 Methods

Rating   Name   Duplication   Size   Complexity  
A getXmlVersion() 0 3 1
B doImport() 0 40 7
B processEncrypted() 0 44 8
A detectEncrypted() 0 3 1
B processCategories() 0 30 6
A processTags() 0 29 5
B processClients() 0 30 6
A processAccountTags() 0 14 4
A checkIntegrity() 0 9 3
C processAccounts() 0 52 14
B processCustomers() 0 30 6

How to fix   Complexity   

Complex Class

Complex classes like SyspassImport 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 SyspassImport, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * sysPass
4
 *
5
 * @author    nuxsmin
6
 * @link      https://syspass.org
7
 * @copyright 2012-2018, Rubén Domínguez nuxsmin@$syspass.org
8
 *
9
 * This file is part of sysPass.
10
 *
11
 * sysPass is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by
13
 * the Free Software Foundation, either version 3 of the License, or
14
 * (at your option) any later version.
15
 *
16
 * sysPass is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 *  along with sysPass.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
namespace SP\Services\Import;
26
27
use Defuse\Crypto\Exception\CryptoException;
28
use DOMXPath;
29
use SP\Core\Crypt\Crypt;
30
use SP\Core\Crypt\Hash;
31
use SP\Core\Crypt\OldCrypt;
32
use SP\Core\Events\Event;
33
use SP\Core\Events\EventMessage;
34
use SP\DataModel\CategoryData;
35
use SP\DataModel\ClientData;
36
use SP\DataModel\TagData;
37
use SP\Services\Account\AccountRequest;
38
use SP\Services\Export\XmlVerifyService;
39
40
defined('APP_ROOT') || die();
41
42
/**
43
 * Esta clase es la encargada de importar cuentas desde sysPass
44
 */
45
final class SyspassImport extends XmlImportBase implements ImportInterface
46
{
47
    /**
48
     * Iniciar la importación desde sysPass.
49
     *
50
     * @throws ImportException
51
     * @return ImportInterface
52
     */
53
    public function doImport()
54
    {
55
        try {
56
            $this->eventDispatcher->notifyEvent('run.import.syspass',
57
                new Event($this, EventMessage::factory()
58
                    ->addDescription(__u('Importación XML sysPass')))
59
            );
60
61
            if ($this->importParams->getImportMasterPwd() !== '') {
62
                $this->mPassValidHash = Hash::checkHashKey($this->importParams->getImportMasterPwd(), $this->configService->getByParam('masterPwd'));
63
            }
64
65
            $this->version = $this->getXmlVersion();
66
67
            if ($this->detectEncrypted()) {
68
                if ($this->importParams->getImportPwd() === '') {
69
                    throw new ImportException(__u('Clave de encriptación no indicada'), ImportException::INFO);
70
                }
71
72
                $this->processEncrypted();
73
            }
74
75
            $this->checkIntegrity();
76
77
            $this->processCategories();
78
79
            if ($this->version >= 300) {
80
                $this->processClients();
81
            } else {
82
                $this->processCustomers();
0 ignored issues
show
Deprecated Code introduced by
The function SP\Services\Import\Syspa...ort::processCustomers() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

82
                /** @scrutinizer ignore-deprecated */ $this->processCustomers();
Loading history...
83
            }
84
85
            $this->processTags();
86
            $this->processAccounts();
87
88
            return $this;
89
        } catch (ImportException $e) {
90
            throw $e;
91
        } catch (\Exception $e) {
92
            throw new ImportException($e->getMessage(), ImportException::CRITICAL);
93
        }
94
    }
95
96
    /**
97
     * Obtener la versión del XML
98
     */
99
    protected function getXmlVersion()
100
    {
101
        return (int)str_replace('.', '', (new DOMXPath($this->xmlDOM))->query('/Root/Meta/Version')->item(0)->nodeValue);
102
    }
103
104
    /**
105
     * Verificar si existen datos encriptados
106
     *
107
     * @return bool
108
     */
109
    protected function detectEncrypted()
110
    {
111
        return ($this->xmlDOM->getElementsByTagName('Encrypted')->length > 0);
112
    }
113
114
    /**
115
     * Procesar los datos encriptados y añadirlos al árbol DOM desencriptados
116
     *
117
     * @throws ImportException
118
     */
119
    protected function processEncrypted()
120
    {
121
        $hash = $this->xmlDOM->getElementsByTagName('Encrypted')->item(0)->getAttribute('hash');
122
123
        if (!empty($hash) && !Hash::checkHashKey($this->importParams->getImportPwd(), $hash)) {
124
            throw new ImportException(__u('Clave de encriptación incorrecta'));
125
        }
126
127
        foreach ($this->xmlDOM->getElementsByTagName('Data') as $node) {
128
            /** @var $node \DOMElement */
129
            $data = base64_decode($node->nodeValue);
130
131
            try {
132
                if ($this->version >= 210) {
133
                    $xmlDecrypted = Crypt::decrypt($data, $node->getAttribute('key'), $this->importParams->getImportPwd());
134
                } else {
135
                    $xmlDecrypted = OldCrypt::getDecrypt($data, base64_decode($node->getAttribute('iv')), $this->importParams->getImportPwd());
136
                }
137
            } catch (CryptoException $e) {
138
                processException($e);
139
140
                $this->eventDispatcher->notifyEvent('exception', new Event($e));
141
142
                continue;
143
            }
144
145
            $newXmlData = new \DOMDocument();
146
147
            if ($newXmlData->loadXML($xmlDecrypted) === false) {
148
                throw new ImportException(__u('Clave de encriptación incorrecta'));
149
            }
150
151
            $this->xmlDOM->documentElement->appendChild($this->xmlDOM->importNode($newXmlData->documentElement, TRUE));
152
        }
153
154
        // Eliminar los datos encriptados tras desencriptar los mismos
155
        if ($this->xmlDOM->getElementsByTagName('Data')->length > 0) {
156
            $nodeData = $this->xmlDOM->getElementsByTagName('Encrypted')->item(0);
157
            $nodeData->parentNode->removeChild($nodeData);
158
        }
159
160
        $this->eventDispatcher->notifyEvent('run.import.syspass.process.decryption',
161
            new Event($this, EventMessage::factory()
162
                ->addDescription(__u('Datos desencriptados')))
163
        );
164
    }
165
166
    /**
167
     * Checks XML file's data integrity using the signed hash
168
     */
169
    protected function checkIntegrity()
170
    {
171
        $key = $this->importParams->getImportPwd() ?: sha1($this->configData->getPasswordSalt());
172
173
        if (!XmlVerifyService::checkXmlHash($this->xmlDOM, $key)) {
174
            $this->eventDispatcher->notifyEvent('run.import.syspass.process.verify',
175
                new Event($this, EventMessage::factory()
176
                    ->addDescription(__u('Fallo en la verificación del hash de integridad'))
177
                    ->addDescription(__u('Si está importando un archivo exportado desde el mismo origen, los datos pueden estar comprometidos.')))
178
            );
179
        }
180
    }
181
182
    /**
183
     * Obtener las categorías y añadirlas a sysPass.
184
     *
185
     * @throws ImportException
186
     */
187
    protected function processCategories()
188
    {
189
        $this->getNodesData('Categories', 'Category',
190
            function (\DOMElement $category) {
191
                $categoryData = new CategoryData();
192
193
                foreach ($category->childNodes as $node) {
194
                    if (isset($node->tagName)) {
195
                        switch ($node->tagName) {
196
                            case 'name':
197
                                $categoryData->setName($node->nodeValue);
198
                                break;
199
                            case 'description':
200
                                $categoryData->setDescription($node->nodeValue);
201
                                break;
202
                        }
203
                    }
204
                }
205
206
                try {
207
                    $this->addWorkingItem('category', (int)$category->getAttribute('id'), $this->addCategory($categoryData));
208
209
                    $this->eventDispatcher->notifyEvent('run.import.syspass.process.category',
210
                        new Event($this, EventMessage::factory()
211
                            ->addDetail(__u('Categoría importada'), $categoryData->getName()))
212
                    );
213
                } catch (\Exception $e) {
214
                    processException($e);
215
216
                    $this->eventDispatcher->notifyEvent('exception', new Event($e));
217
                }
218
            });
219
    }
220
221
    /**
222
     * Obtener los clientes y añadirlos a sysPass.
223
     *
224
     * @throws ImportException
225
     */
226
    protected function processClients()
227
    {
228
        $this->getNodesData('Clients', 'Client',
229
            function (\DOMElement $client) {
230
                $clientData = new ClientData();
231
232
                foreach ($client->childNodes as $node) {
233
                    if (isset($node->tagName)) {
234
                        switch ($node->tagName) {
235
                            case 'name':
236
                                $clientData->setName($node->nodeValue);
237
                                break;
238
                            case 'description':
239
                                $clientData->setDescription($node->nodeValue);
240
                                break;
241
                        }
242
                    }
243
                }
244
245
                try {
246
                    $this->addWorkingItem('client', (int)$client->getAttribute('id'), $this->addClient($clientData));
247
248
                    $this->eventDispatcher->notifyEvent('run.import.syspass.process.client',
249
                        new Event($this, EventMessage::factory()
250
                            ->addDetail(__u('Cliente importado'), $clientData->getName()))
251
                    );
252
                } catch (\Exception $e) {
253
                    processException($e);
254
255
                    $this->eventDispatcher->notifyEvent('exception', new Event($e));
256
                }
257
            });
258
    }
259
260
    /**
261
     * Obtener los clientes y añadirlos a sysPass.
262
     *
263
     * @throws ImportException
264
     * @deprecated
265
     */
266
    protected function processCustomers()
267
    {
268
        $this->getNodesData('Customers', 'Customer',
269
            function (\DOMElement $client) {
270
                $clientData = new ClientData();
271
272
                foreach ($client->childNodes as $node) {
273
                    if (isset($node->tagName)) {
274
                        switch ($node->tagName) {
275
                            case 'name':
276
                                $clientData->setName($node->nodeValue);
277
                                break;
278
                            case 'description':
279
                                $clientData->setDescription($node->nodeValue);
280
                                break;
281
                        }
282
                    }
283
                }
284
285
                try {
286
                    $this->addWorkingItem('client', (int)$client->getAttribute('id'), $this->addClient($clientData));
287
288
                    $this->eventDispatcher->notifyEvent('run.import.syspass.process.customer',
289
                        new Event($this, EventMessage::factory()
290
                            ->addDetail(__u('Cliente importado'), $clientData->getName()))
291
                    );
292
                } catch (\Exception $e) {
293
                    processException($e);
294
295
                    $this->eventDispatcher->notifyEvent('exception', new Event($e));
296
                }
297
            });
298
    }
299
300
    /**
301
     * Obtener las etiquetas y añadirlas a sysPass.
302
     *
303
     * @throws ImportException
304
     */
305
    protected function processTags()
306
    {
307
        $this->getNodesData('Tags', 'Tag',
308
            function (\DOMElement $tag) {
309
                $tagData = new TagData();
310
311
                foreach ($tag->childNodes as $node) {
312
                    if (isset($node->tagName)) {
313
                        switch ($node->tagName) {
314
                            case 'name':
315
                                $tagData->setName($node->nodeValue);
316
                                break;
317
                        }
318
                    }
319
                }
320
321
                try {
322
                    $this->addWorkingItem('tag', (int)$tag->getAttribute('id'), $this->addTag($tagData));
323
324
                    $this->eventDispatcher->notifyEvent('run.import.syspass.process.tag',
325
                        new Event($this, EventMessage::factory()
326
                            ->addDetail(__u('Etiqueta importada'), $tagData->getName()))
327
                    );
328
                } catch (\Exception $e) {
329
                    processException($e);
330
331
                    $this->eventDispatcher->notifyEvent('exception', new Event($e));
332
                }
333
            }, false);
334
    }
335
336
    /**
337
     * Obtener los datos de las cuentas de sysPass y crearlas.
338
     *
339
     * @throws ImportException
340
     */
341
    protected function processAccounts()
342
    {
343
        $this->getNodesData('Accounts', 'Account',
344
            function (\DOMElement $account) {
345
                $accountRequest = new AccountRequest();
346
347
                /** @var \DOMElement $node */
348
                foreach ($account->childNodes as $node) {
349
                    if (isset($node->tagName)) {
350
                        switch ($node->tagName) {
351
                            case 'name';
352
                                $accountRequest->name = $node->nodeValue;
353
                                break;
354
                            case 'login';
355
                                $accountRequest->login = $node->nodeValue;
356
                                break;
357
                            case 'categoryId';
358
                                $accountRequest->categoryId = $this->getWorkingItem('category', (int)$node->nodeValue);
359
                                break;
360
                            case 'clientId';
361
                            case 'customerId';
362
                                $accountRequest->clientId = $this->getWorkingItem('client', (int)$node->nodeValue);
363
                                break;
364
                            case 'url';
365
                                $accountRequest->url = $node->nodeValue;
366
                                break;
367
                            case 'pass';
368
                                $accountRequest->pass = $node->nodeValue;
369
                                break;
370
                            case 'key';
371
                                $accountRequest->key = $node->nodeValue;
372
                                break;
373
                            case 'notes';
374
                                $accountRequest->notes = $node->nodeValue;
375
                                break;
376
                            case 'tags':
377
                                $accountRequest->tags = $this->processAccountTags($node->childNodes);
378
                        }
379
                    }
380
                }
381
382
                try {
383
                    $this->addAccount($accountRequest);
384
385
                    $this->eventDispatcher->notifyEvent('run.import.syspass.process.account',
386
                        new Event($this, EventMessage::factory()
387
                            ->addDetail(__u('Cuenta importada'), $accountRequest->name))
388
                    );
389
                } catch (\Exception $e) {
390
                    processException($e);
391
392
                    $this->eventDispatcher->notifyEvent('exception', new Event($e));
393
                }
394
            });
395
    }
396
397
    /**
398
     * Procesar las etiquetas de la cuenta
399
     *
400
     * @param \DOMNodeList $nodes
401
     *
402
     * @return array
403
     */
404
    protected function processAccountTags(\DOMNodeList $nodes)
405
    {
406
        $tags = [];
407
408
        if ($nodes->length > 0) {
409
            /** @var \DOMElement $node */
410
            foreach ($nodes as $node) {
411
                if (isset($node->tagName)) {
412
                    $tags[] = $this->getWorkingItem('tag', (int)$node->getAttribute('id'));
413
                }
414
            }
415
        }
416
417
        return $tags;
418
    }
419
}