Test Failed
Pull Request — master (#152)
by
unknown
03:30 queued 01:24
created

SoapBase::__destruct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 3
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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 = true;
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
        $this->setTemporaryFolder(sys_get_temp_dir() . '/sped/');
158
159
        if (null !== $certificate) {
160
            $this->saveTemporarilyKeyFiles();
161
        }
162
    }
163
164
    /**
165
     * Check if certificate is valid to currently used date
166
     * @param Certificate $certificate
167
     * @return void
168
     * @throws Certificate\Exception\Expired
169
     */
170 5
    private function isCertificateExpired(Certificate $certificate = null)
171
    {
172 5
        if (! $this->disableCertValidation) {
173 5
            if (null !== $certificate && $certificate->isExpired()) {
174 1
                throw new Certificate\Exception\Expired($certificate);
175
            }
176
        }
177 4
    }
178
179
    /**
180
     * Destructor
181
     * Clean temporary files
182
     */
183
    public function __destruct()
184
    {
185
        $this->removeTemporarilyFiles();
186
    }
187
188
    /**
189
     * Disables the security checking of host and peer certificates
190
     * @param bool $flag
191
     * @return bool
192
     */
193
    public function disableSecurity($flag = false)
194
    {
195
        return $this->disablesec = $flag;
196
    }
197
198
    /**
199
     * ONlY for tests
200
     * @param bool $flag
201
     * @return bool
202
     */
203
    public function disableCertValidation($flag = true)
204
    {
205
        return $this->disableCertValidation = $flag;
206
    }
207
208
    /**
209
     * Load path to CA and enable to use on SOAP
210
     * @param string $capath
211
     * @return void
212
     */
213
    public function loadCA($capath)
214
    {
215
        if (is_file($capath)) {
216
            $this->casefaz = $capath;
217
        }
218
    }
219
220
    /**
221
     * Set option to encrypt private key before save in filesystem
222
     * for an additional layer of protection
223
     * @param bool $encript
224
     * @return bool
225
     */
226
    public function setEncriptPrivateKey($encript = true)
227
    {
228
        return $this->encriptPrivateKey = $encript;
229
    }
230
231
    /**
232
     * Set another temporayfolder for saving certificates for SOAP utilization
233
     * @param string $folderRealPath
234
     * @return void
235
     */
236 4
    public function setTemporaryFolder($folderRealPath)
237
    {
238 4
        if (null !== $this->filesystem) {
239
            $this->removeTemporarilyFiles();
240
        }
241
242 4
        $this->tempdir = $folderRealPath;
243 4
        $this->setLocalFolder($folderRealPath);
244
245 4
        $this->saveTemporarilyKeyFiles();
246
    }
247
248
    /**
249
     * Set Local folder for flysystem
250
     * @param string $folder
251
     */
252 4
    protected function setLocalFolder($folder = '')
253
    {
254 4
        $this->adapter = new Local($folder);
255 4
        $this->filesystem = new Filesystem($this->adapter);
256 4
    }
257
258
    /**
259
     * Set debug mode, this mode will save soap envelopes in temporary directory
260
     * @param bool $value
261
     * @return bool
262
     */
263
    public function setDebugMode($value = false)
264
    {
265
        return $this->debugmode = $value;
266
    }
267
268
    /**
269
     * Set certificate class for SSL communications
270
     * @param Certificate $certificate
271
     * @return void
272
     */
273 5
    public function loadCertificate(Certificate $certificate = null)
274
    {
275 5
        $this->isCertificateExpired($certificate);
276 4
        if (null !== $certificate) {
277
            $this->certificate = $certificate;
278
        }
279 4
    }
280
281
    /**
282
     * Set logger class
283
     * @param LoggerInterface $logger
284
     * @return LoggerInterface
285
     */
286
    public function loadLogger(LoggerInterface $logger)
287
    {
288
        return $this->logger = $logger;
289
    }
290
291
    /**
292
     * Set timeout for communication
293
     * @param int $timesecs
294
     * @return int
295
     */
296
    public function timeout($timesecs)
297
    {
298
        return $this->soaptimeout = $timesecs;
299
    }
300
301
    /**
302
     * Set security protocol
303
     * @param int $protocol
304
     * @return int
305
     */
306
    public function protocol($protocol = self::SSL_DEFAULT)
307
    {
308
        return $this->soapprotocol = $protocol;
309
    }
310
311
    /**
312
     * Set prefixes
313
     * @param array $prefixes
314
     * @return string[]
315
     */
316
    public function setSoapPrefix($prefixes = [])
317
    {
318
        return $this->prefixes = $prefixes;
319
    }
320
321
    /**
322
     * Set proxy parameters
323
     * @param string $ip
324
     * @param int    $port
325
     * @param string $user
326
     * @param string $password
327
     * @return void
328
     */
329
    public function proxy($ip, $port, $user, $password)
330
    {
331
        $this->proxyIP = $ip;
332
        $this->proxyPort = $port;
333
        $this->proxyUser = $user;
334
        $this->proxyPass = $password;
335
    }
336
337
    /**
338
     * @param string $url
339
     * @param string $operation
340
     * @param string $action
341
     * @param int $soapver
342
     * @param array $parameters
343
     * @param array $namespaces
344
     * @param string $request
345
     * @param null $soapheader
346
     * @return mixed
347
     */
348
    abstract public function send(
349
        $url,
350
        $operation = '',
351
        $action = '',
352
        $soapver = SOAP_1_2,
353
        $parameters = [],
354
        $namespaces = [],
355
        $request = '',
356
        $soapheader = null
357
    );
358
359
    /**
360
     * Mount soap envelope
361
     * @param string $request
362
     * @param array $namespaces
363
     * @param int $soapVer
364
     * @param \SoapHeader $header
365
     * @return string
366
     */
367
    protected function makeEnvelopeSoap(
368
        $request,
369
        $namespaces,
370
        $soapVer = SOAP_1_2,
371
        $header = null
372
    ) {
373
        $prefix = $this->prefixes[$soapVer];
374
375
        $envelopeAttributes = $this->getStringAttributes($namespaces);
376
377
        return $this->mountEnvelopString(
378
            $prefix,
379
            $envelopeAttributes,
380
            $this->mountSoapHeaders($prefix, $header),
381
            $request
382
        );
383
    }
384
385
    /**
386
     * Create a envelop string
387
     * @param string $envelopPrefix
388
     * @param string $envelopAttributes
389
     * @param string $header
390
     * @param string $bodyContent
391
     * @return string
392
     */
393
    private function mountEnvelopString(
394
        $envelopPrefix,
395
        $envelopAttributes = '',
396
        $header = '',
397
        $bodyContent = ''
398
    ) {
399
        return sprintf(
400
            '<%s:Envelope %s>'.$header.'<%s:Body>%s</%s:Body></%s:Envelope>',
401
            $envelopPrefix,
402
            $envelopAttributes,
403
            $envelopPrefix,
404
            $bodyContent,
405
            $envelopPrefix,
406
            $envelopPrefix
407
        );
408
    }
409
410
    /**
411
     * Create a haeader tag
412
     * @param string $envelopPrefix
413
     * @param \SoapHeader $header
414
     * @return string
415
     */
416
    private function mountSoapHeaders($envelopPrefix, $header = null)
417
    {
418
        if (null === $header) {
419
            return '';
420
        }
421
        $headerItems = '';
422
        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...
423
            $headerItems = '<'.$key.'>'.$value.'<'.$key.'>';
424
        }
425
        return sprintf(
426
            '<%s:Header><%s xmlns="%s">%s</%s></%s:Header>',
427
            $envelopPrefix,
428
            $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...
429
            $header->ns === null ? '' : $header->ns,
0 ignored issues
show
Bug introduced by
The property ns 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...
430
            $headerItems,
431
            $header->name,
432
            $envelopPrefix
433
        );
434
    }
435
436
    /**
437
     * Get attributes
438
     * @param array $namespaces
439
     * @return string
440
     */
441
    private function getStringAttributes($namespaces = [])
442
    {
443
        $envelopeAttributes = '';
444
        foreach ($namespaces as $key => $value) {
445
            $envelopeAttributes = $key.'="'.$value.'"';
446
        }
447
        return $envelopeAttributes;
448
    }
449
450
451
    /**
452
     * Temporarily saves the certificate keys for use cURL or SoapClient
453
     * @return void
454
     */
455 4
    public function saveTemporarilyKeyFiles()
456
    {
457 4
        if (!is_object($this->certificate)) {
458 4
            throw new RuntimeException(
459 4
                'Certificate not found.'
460
            );
461
        }
462
        $this->certsdir = $this->certificate->getCnpj() . '/certs/';
463
        $this->prifile = $this->certsdir. Strings::randomString(10).'.pem';
464
        $this->pubfile = $this->certsdir . Strings::randomString(10).'.pem';
465
        $this->certfile = $this->certsdir . Strings::randomString(10).'.pem';
466
        $ret = true;
467
        $private = $this->certificate->privateKey;
468
        if ($this->encriptPrivateKey) {
469
            //cria uma senha temporária ALEATÓRIA para salvar a chave primaria
470
            //portanto mesmo que localizada e identificada não estará acessível
471
            //pois sua senha não existe além do tempo de execução desta classe
472
            $this->temppass = Strings::randomString(16);
473
            //encripta a chave privada entes da gravação do filesystem
474
            openssl_pkey_export(
475
                $this->certificate->privateKey,
476
                $private,
477
                $this->temppass
478
            );
479
        }
480
        $ret &= $this->filesystem->put(
481
            $this->prifile,
482
            $private
483
        );
484
        $ret &= $this->filesystem->put(
485
            $this->pubfile,
486
            $this->certificate->publicKey
487
        );
488
        $ret &= $this->filesystem->put(
489
            $this->certfile,
490
            $private."{$this->certificate}"
491
        );
492
        if (!$ret) {
493
            throw new RuntimeException(
494
                'Unable to save temporary key files in folder.'
495
            );
496
        }
497
    }
498
499
    /**
500
     * Delete all files in folder
501
     * @return void
502
     */
503
    public function removeTemporarilyFiles()
504
    {
505
        $contents = $this->filesystem->listContents($this->certsdir, true);
506
        //define um limite de $waitingTime min, ou seja qualquer arquivo criado a mais
507
        //de $waitingTime min será removido
508
        //NOTA: quando ocorre algum erro interno na execução do script, alguns
509
        //arquivos temporários podem permanecer
510
        //NOTA: O tempo default é de 45 minutos e pode ser alterado diretamente nas
511
        //propriedades da classe, esse tempo entre 5 a 45 min é recomendável pois
512
        //podem haver processos concorrentes para um mesmo usuário. Esses processos
513
        //como o DFe podem ser mais longos, dependendo a forma que o aplicativo
514
        //utilize a API. Outra solução para remover arquivos "perdidos" pode ser
515
        //encontrada oportunamente.
516
        $dt = new \DateTime();
517
        $tint = new \DateInterval("PT".$this->waitingTime."M");
518
        $tint->invert = 1;
519
        $tsLimit = $dt->add($tint)->getTimestamp();
520
        foreach ($contents as $item) {
521
            if ($item['type'] == 'file') {
522
                if ($item['path'] == $this->prifile
523
                    || $item['path'] == $this->pubfile
524
                    || $item['path'] == $this->certfile
525
                ) {
526
                    $this->filesystem->delete($item['path']);
527
                    continue;
528
                }
529
                $timestamp = $this->filesystem->getTimestamp($item['path']);
530
                if ($timestamp < $tsLimit) {
531
                    //remove arquivos criados a mais de 45 min
532
                    $this->filesystem->delete($item['path']);
533
                }
534
            }
535
        }
536
    }
537
538
    /**
539
     * Save request envelope and response for debug reasons
540
     * @param string $operation
541
     * @param string $request
542
     * @param string $response
543
     * @return void
544
     */
545
    public function saveDebugFiles($operation, $request, $response)
546
    {
547
        if (!$this->debugmode) {
548
            return;
549
        }
550
        $this->debugdir = $this->certificate->getCnpj() . '/debug/';
551
        $now = \DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
552
        $time = substr($now->format("ymdHisu"), 0, 16);
553
        try {
554
            $this->filesystem->put(
555
                $this->debugdir . $time . "_" . $operation . "_sol.txt",
556
                $request
557
            );
558
            $this->filesystem->put(
559
                $this->debugdir . $time . "_" . $operation . "_res.txt",
560
                $response
561
            );
562
        } catch (\Exception $e) {
563
            throw new RuntimeException(
564
                'Unable to create debug files.'
565
            );
566
        }
567
    }
568
}
569