Passed
Pull Request — master (#144)
by Sebastiao
02:16
created

SoapBase::saveTemporarilyKeyFiles()   B

Complexity

Conditions 4
Paths 5

Size

Total Lines 43
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
509 4
        $tsLimit = $dt->add($tint)->getTimestamp();
510 4
        foreach ($contents as $item) {
511
            if ($item['type'] == 'file') {
512
                if ($item['path'] == $this->prifile
513
                    || $item['path'] == $this->pubfile
514
                    || $item['path'] == $this->certfile
515
                ) {
516
                    $this->filesystem->delete($item['path']);
517
                    continue;
518
                }
519
                $timestamp = $this->filesystem->getTimestamp($item['path']);
520
                if ($timestamp < $tsLimit) {
521
                    //remove arquivos criados a mais de 45 min
522
                    $this->filesystem->delete($item['path']);
523
                }
524
            }
525
        }
526 4
    }
527
    
528
    /**
529
     * Save request envelope and response for debug reasons
530
     * @param string $operation
531
     * @param string $request
532
     * @param string $response
533
     * @return void
534
     */
535
    public function saveDebugFiles($operation, $request, $response)
536
    {
537
        if (!$this->debugmode) {
538
            return;
539
        }
540
        $this->debugdir = $this->certificate->getCnpj() . '/debug/';
541
        $now = \DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
542
        $time = substr($now->format("ymdHisu"), 0, 16);
543
        try {
544
            $this->filesystem->put(
545
                $this->debugdir . $time . "_" . $operation . "_sol.txt",
546
                $request
547
            );
548
            $this->filesystem->put(
549
                $this->debugdir . $time . "_" . $operation . "_res.txt",
550
                $response
551
            );
552
        } catch (\Exception $e) {
553
            throw new RuntimeException(
554
                'Unable to create debug files.'
555
            );
556
        }
557
    }
558
}
559