Passed
Pull Request — master (#177)
by Roberto
03:03
created

SoapBase::removeTemporarilyFiles()   D

Complexity

Conditions 9
Paths 6

Size

Total Lines 37
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 57.3699

Importance

Changes 0
Metric Value
dl 0
loc 37
ccs 3
cts 19
cp 0.1579
rs 4.909
c 0
b 0
f 0
cc 9
eloc 18
nc 6
nop 0
crap 57.3699
1
<?php
2
namespace NFePHP\Common\Soap;
3
4
use NFePHP\Common\Certificate;
5
use NFePHP\Common\Exception\RuntimeException;
6
use NFePHP\Common\Strings;
7
use League\Flysystem\Filesystem;
8
use League\Flysystem\Adapter\Local;
9
use Psr\Log\LoggerInterface;
10
11
/**
12
 * Soap base class
13
 *
14
 * @category  NFePHP
15
 * @package   NFePHP\Common\Soap\SoapBase
16
 * @copyright NFePHP Copyright (c) 2017
17
 * @author    Roberto L. Machado <linux.rlm at gmail dot com>
18
 * @license   http://www.gnu.org/licenses/lgpl.txt LGPLv3+
19
 * @license   https://opensource.org/licenses/MIT MIT
20
 * @license   http://www.gnu.org/licenses/gpl.txt GPLv3+
21
 * @link      http://github.com/nfephp-org/sped-nfse for the canonical source repository
22
 */
23
abstract class SoapBase implements SoapInterface
24
{
25
    /**
26
     * @var int
27
     */
28
    protected $soapprotocol = self::SSL_DEFAULT;
29
    /**
30
     * @var int
31
     */
32
    protected $soaptimeout = 20;
33
    /**
34
     * @var string
35
     */
36
    protected $proxyIP;
37
    /**
38
     * @var int
39
     */
40
    protected $proxyPort;
41
    /**
42
     * @var string
43
     */
44
    protected $proxyUser;
45
    /**
46
     * @var string
47
     */
48
    protected $proxyPass;
49
    /**
50
     * @var array
51
     */
52
    protected $prefixes = [1 => 'soapenv', 2 => 'soap'];
53
    /**
54
     * @var Certificate
55
     */
56
    protected $certificate;
57
    /**
58
     * @var LoggerInterface|null
59
     */
60
    protected $logger;
61
    /**
62
     * @var string
63
     */
64
    protected $tempdir;
65
    /**
66
     * @var string
67
     */
68
    protected $certsdir;
69
    /**
70
     * @var string
71
     */
72
    protected $debugdir;
73
    /**
74
     * @var string
75
     */
76
    protected $prifile;
77
    /**
78
     * @var string
79
     */
80
    protected $pubfile;
81
    /**
82
     * @var string
83
     */
84
    protected $certfile;
85
    /**
86
     * @var string
87
     */
88
    protected $casefaz;
89
    /**
90
     * @var bool
91
     */
92
    protected $disablesec = false;
93
    /**
94
     * @var bool
95
     */
96
    protected $disableCertValidation = false;
97
    /**
98
     * @var \League\Flysystem\Adapter\Local
99
     */
100
    protected $adapter;
101
    /**
102
     * @var \League\Flysystem\Filesystem
103
     */
104
    protected $filesystem;
105
    /**
106
     * @var string
107
     */
108
    protected $temppass = '';
109
    /**
110
     * @var bool
111
     */
112
    protected $encriptPrivateKey = false;
113
    /**
114
     * @var bool
115
     */
116
    protected $debugmode = false;
117
    /**
118
     * @var string
119
     */
120
    public $responseHead;
121
    /**
122
     * @var string
123
     */
124
    public $responseBody;
125
    /**
126
     * @var string
127
     */
128
    public $requestHead;
129
    /**
130
     * @var string
131
     */
132
    public $requestBody;
133
    /**
134
     * @var string
135
     */
136
    public $soaperror;
137
    /**
138
     * @var array
139
     */
140
    public $soapinfo = [];
141
    /**
142
     * @var int
143
     */
144
    public $waitingTime = 45;
145
146
    /**
147
     * SoapBase constructor.
148
     * @param Certificate|null $certificate
149
     * @param LoggerInterface|null $logger
150
     */
151 5
    public function __construct(
152
        Certificate $certificate = null,
153
        LoggerInterface $logger = null
154
    ) {
155 5
        $this->logger = $logger;
156 5
        $this->loadCertificate($certificate);
157 4
    }
158
159
    /**
160
     * Check if certificate is valid to currently used date
161
     * @param Certificate $certificate
162
     * @return void
163
     * @throws Certificate\Exception\Expired
164
     */
165 5
    private function isCertificateExpired(Certificate $certificate = null)
166
    {
167 5
        if (!$this->disableCertValidation) {
168 5
            if (null !== $certificate && $certificate->isExpired()) {
169 1
                throw new Certificate\Exception\Expired($certificate);
170
            }
171
        }
172 4
    }
173
174
    /**
175
     * Destructor
176
     * Clean temporary files
177
     */
178 4
    public function __destruct()
179
    {
180 4
        $this->removeTemporarilyFiles();
181 4
    }
182
183
    /**
184
     * Disables the security checking of host and peer certificates
185
     * @param bool $flag
186
     * @return bool
187
     */
188 1
    public function disableSecurity($flag = false)
189
    {
190 1
        return $this->disablesec = $flag;
191
    }
192
193
    /**
194
     * ONlY for tests
195
     * @param bool $flag
196
     * @return bool
197
     */
198 1
    public function disableCertValidation($flag = true)
199
    {
200 1
        return $this->disableCertValidation = $flag;
201
    }
202
203
    /**
204
     * Load path to CA and enable to use on SOAP
205
     * @param string $capath
206
     * @return void
207
     */
208
    public function loadCA($capath)
209
    {
210
        if (is_file($capath)) {
211
            $this->casefaz = $capath;
212
        }
213
    }
214
215
    /**
216
     * Set option to encrypt private key before save in filesystem
217
     * for an additional layer of protection
218
     * @param bool $encript
219
     * @return bool
220
     */
221
    public function setEncriptPrivateKey($encript = true)
222
    {
223
        $this->encriptPrivateKey = $encript;
224
        if (null !== $this->filesystem) {
225
            $this->removeTemporarilyFiles();
226
        }
227
        if (null !== $this->certificate) {
228
            $this->saveTemporarilyKeyFiles();
229
        }
230
        return $this->encriptPrivateKey;
231
    }
232
233
    /**
234
     * Set another temporayfolder for saving certificates for SOAP utilization
235
     * @param string | null $folderRealPath
236
     * @return void
237
     */
238
    public function setTemporaryFolder($folderRealPath = null)
239
    {
240
        if (empty($folderRealPath)) {
241
            $folderRealPath = sys_get_temp_dir() . '/sped/';
242
        }
243
        $this->tempdir = $folderRealPath;
244
        $this->setLocalFolder($folderRealPath);
245
    }
246
247
    /**
248
     * Set Local folder for flysystem
249
     * @param string $folder
250
     */
251
    protected function setLocalFolder($folder = '')
252
    {
253
        $this->adapter = new Local($folder);
254
        $this->filesystem = new Filesystem($this->adapter);
255
    }
256
257
    /**
258
     * Set debug mode, this mode will save soap envelopes in temporary directory
259
     * @param bool $value
260
     * @return bool
261
     */
262
    public function setDebugMode($value = false)
263
    {
264
        return $this->debugmode = $value;
265
    }
266
267
    /**
268
     * Set certificate class for SSL communications
269
     * @param Certificate $certificate
270
     * @return void
271
     */
272 5
    public function loadCertificate(Certificate $certificate = null)
273
    {
274 5
        $this->isCertificateExpired($certificate);
275 4
        if (null !== $certificate) {
276 1
            $this->certificate = $certificate;
277
        }
278 4
    }
279
280
    /**
281
     * Set logger class
282
     * @param LoggerInterface $logger
283
     * @return LoggerInterface
284
     */
285
    public function loadLogger(LoggerInterface $logger)
286
    {
287
        return $this->logger = $logger;
288
    }
289
290
    /**
291
     * Set timeout for communication
292
     * @param int $timesecs
293
     * @return int
294
     */
295
    public function timeout($timesecs)
296
    {
297
        return $this->soaptimeout = $timesecs;
298
    }
299
300
    /**
301
     * Set security protocol
302
     * @param int $protocol
303
     * @return int
304
     */
305
    public function protocol($protocol = self::SSL_DEFAULT)
306
    {
307
        return $this->soapprotocol = $protocol;
308
    }
309
310
    /**
311
     * Set prefixes
312
     * @param array $prefixes
313
     * @return string[]
314
     */
315
    public function setSoapPrefix($prefixes = [])
316
    {
317
        return $this->prefixes = $prefixes;
318
    }
319
320
    /**
321
     * Set proxy parameters
322
     * @param string $ip
323
     * @param int    $port
324
     * @param string $user
325
     * @param string $password
326
     * @return void
327
     */
328
    public function proxy($ip, $port, $user, $password)
329
    {
330
        $this->proxyIP = $ip;
331
        $this->proxyPort = $port;
332
        $this->proxyUser = $user;
333
        $this->proxyPass = $password;
334
    }
335
336
    /**
337
     * @param string $url
338
     * @param string $operation
339
     * @param string $action
340
     * @param int $soapver
341
     * @param array $parameters
342
     * @param array $namespaces
343
     * @param string $request
344
     * @param null $soapheader
345
     * @return mixed
346
     */
347
    abstract public function send(
348
        $url,
349
        $operation = '',
350
        $action = '',
351
        $soapver = SOAP_1_2,
352
        $parameters = [],
353
        $namespaces = [],
354
        $request = '',
355
        $soapheader = null
356
    );
357
358
    /**
359
     * Mount soap envelope
360
     * @param string $request
361
     * @param array $namespaces
362
     * @param int $soapVer
363
     * @param \SoapHeader $header
364
     * @return string
365
     */
366
    protected function makeEnvelopeSoap(
367
        $request,
368
        $namespaces,
369
        $soapVer = SOAP_1_2,
370
        $header = null
371
    ) {
372
        $prefix = $this->prefixes[$soapVer];
373
        $envelopeAttributes = $this->getStringAttributes($namespaces);
374
        return $this->mountEnvelopString(
375
            $prefix,
376
            $envelopeAttributes,
377
            $this->mountSoapHeaders($prefix, $header),
378
            $request
379
        );
380
    }
381
382
    /**
383
     * Create a envelop string
384
     * @param string $envelopPrefix
385
     * @param string $envelopAttributes
386
     * @param string $header
387
     * @param string $bodyContent
388
     * @return string
389
     */
390
    private function mountEnvelopString(
391
        $envelopPrefix,
392
        $envelopAttributes = '',
393
        $header = '',
394
        $bodyContent = ''
395
    ) {
396
        return sprintf(
397
            '<%s:Envelope %s>' . $header . '<%s:Body>%s</%s:Body></%s:Envelope>',
398
            $envelopPrefix,
399
            $envelopAttributes,
400
            $envelopPrefix,
401
            $bodyContent,
402
            $envelopPrefix,
403
            $envelopPrefix
404
        );
405
    }
406
407
    /**
408
     * Create a haeader tag
409
     * @param string $envelopPrefix
410
     * @param \SoapHeader $header
411
     * @return string
412
     */
413
    private function mountSoapHeaders($envelopPrefix, $header = null)
414
    {
415
        if (null === $header) {
416
            return '';
417
        }
418
        $headerItems = '';
419
        foreach ($header->data as $key => $value) {
0 ignored issues
show
Bug introduced by
The property data does not seem to exist in SoapHeader.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
420
            $headerItems .= '<' . $key . '>' . $value . '</' . $key . '>';
421
        }
422
        return sprintf(
423
            '<%s:Header><%s xmlns="%s">%s</%s></%s:Header>',
424
            $envelopPrefix,
425
            $header->name,
0 ignored issues
show
Bug introduced by
The property name does not seem to exist in SoapHeader.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
426
            $header->namespace === null ? '' : $header->namespace,
0 ignored issues
show
Bug introduced by
The property namespace does not seem to exist in SoapHeader.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
427
            $headerItems,
428
            $header->name,
429
            $envelopPrefix
430
        );
431
    }
432
433
    /**
434
     * Get attributes
435
     * @param array $namespaces
436
     * @return string
437
     */
438
    private function getStringAttributes($namespaces = [])
439
    {
440
        $envelopeAttributes = '';
441
        foreach ($namespaces as $key => $value) {
442
            $envelopeAttributes .= $key . '="' . $value . '" ';
443
        }
444
        return $envelopeAttributes;
445
    }
446
447
448
    /**
449
     * Temporarily saves the certificate keys for use cURL or SoapClient
450
     * @return void
451
     */
452
    public function saveTemporarilyKeyFiles()
453
    {
454
        if (!empty($this->certsdir)) {
455
            return;
456
        }
457
        if (!is_object($this->certificate)) {
458
            throw new RuntimeException(
459
                'Certificate not found.'
460
            );
461
        }
462
        if (empty($this->filesystem)) {
463
            $this->setTemporaryFolder();
464
        }
465
        $this->certsdir = $this->certificate->getCnpj() . '/certs/';
466
        $this->prifile = $this->certsdir . Strings::randomString(10) . '.pem';
467
        $this->pubfile = $this->certsdir . Strings::randomString(10) . '.pem';
468
        $this->certfile = $this->certsdir . Strings::randomString(10) . '.pem';
469
        $ret = true;
470
        $private = $this->certificate->privateKey;
471
        if ($this->encriptPrivateKey) {
472
            //cria uma senha temporária ALEATÓRIA para salvar a chave primaria
473
            //portanto mesmo que localizada e identificada não estará acessível
474
            //pois sua senha não existe além do tempo de execução desta classe
475
            $this->temppass = Strings::randomString(16);
476
            //encripta a chave privada entes da gravação do filesystem
477
            openssl_pkey_export(
478
                $this->certificate->privateKey,
479
                $private,
480
                $this->temppass
481
            );
482
        }
483
        $ret &= $this->filesystem->put(
484
            $this->prifile,
485
            $private
486
        );
487
        $ret &= $this->filesystem->put(
488
            $this->pubfile,
489
            $this->certificate->publicKey
490
        );
491
        $ret &= $this->filesystem->put(
492
            $this->certfile,
493
            $private . "{$this->certificate}"
494
        );
495
        if (!$ret) {
496
            throw new RuntimeException(
497
                'Unable to save temporary key files in folder.'
498
            );
499
        }
500
    }
501
502
    /**
503
     * Delete all files in folder
504
     * @return void
505
     */
506 4
    public function removeTemporarilyFiles()
507
    {
508 4
        if (empty($this->filesystem) || empty($this->certsdir)) {
509 4
            return;
510
        }
511
        $contents = $this->filesystem->listContents($this->certsdir, true);
512
        //define um limite de $waitingTime min, ou seja qualquer arquivo criado a mais
513
        //de $waitingTime min será removido
514
        //NOTA: quando ocorre algum erro interno na execução do script, alguns
515
        //arquivos temporários podem permanecer
516
        //NOTA: O tempo default é de 45 minutos e pode ser alterado diretamente nas
517
        //propriedades da classe, esse tempo entre 5 a 45 min é recomendável pois
518
        //podem haver processos concorrentes para um mesmo usuário. Esses processos
519
        //como o DFe podem ser mais longos, dependendo a forma que o aplicativo
520
        //utilize a API. Outra solução para remover arquivos "perdidos" pode ser
521
        //encontrada oportunamente.
522
        $dt = new \DateTime();
523
        $tint = new \DateInterval("PT" . $this->waitingTime . "M");
524
        $tint->invert = 1;
525
        $tsLimit = $dt->add($tint)->getTimestamp();
526
        foreach ($contents as $item) {
527
            if ($item['type'] == 'file') {
528
                if ($item['path'] == $this->prifile
529
                    || $item['path'] == $this->pubfile
530
                    || $item['path'] == $this->certfile
531
                ) {
532
                    $this->filesystem->delete($item['path']);
533
                    continue;
534
                }
535
                $timestamp = $this->filesystem->getTimestamp($item['path']);
536
                if ($timestamp < $tsLimit) {
537
                    //remove arquivos criados a mais de 45 min
538
                    $this->filesystem->delete($item['path']);
539
                }
540
            }
541
        }
542
    }
543
544
    /**
545
     * Save request envelope and response for debug reasons
546
     * @param string $operation
547
     * @param string $request
548
     * @param string $response
549
     * @return void
550
     */
551
    public function saveDebugFiles($operation, $request, $response)
552
    {
553
        if (!$this->debugmode) {
554
            return;
555
        }
556
        $this->debugdir = $this->certificate->getCnpj() . '/debug/';
557
        $now = \DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
558
        $time = substr($now->format("ymdHisu"), 0, 16);
559
        try {
560
            $this->filesystem->put(
561
                $this->debugdir . $time . "_" . $operation . "_sol.txt",
562
                $request
563
            );
564
            $this->filesystem->put(
565
                $this->debugdir . $time . "_" . $operation . "_res.txt",
566
                $response
567
            );
568
        } catch (\Exception $e) {
569
            throw new RuntimeException(
570
                'Unable to create debug files.'
571
            );
572
        }
573
    }
574
}
575