Test Setup Failed
Pull Request — master (#144)
by Sebastiao
02:09
created

SoapBase::makeEnvelopeSoap()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 0
cts 12
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 12
nc 4
nop 4
crap 12
1
<?php
2
3
namespace NFePHP\Common\Soap;
4
5
/**
6
 * Soap base class
7
 *
8
 * @category  NFePHP
9
 * @package   NFePHP\Common\Soap\SoapBase
10
 * @copyright NFePHP Copyright (c) 2017
11
 * @license   http://www.gnu.org/licenses/lgpl.txt LGPLv3+
12
 * @license   https://opensource.org/licenses/MIT MIT
13
 * @license   http://www.gnu.org/licenses/gpl.txt GPLv3+
14
 * @author    Roberto L. Machado <linux.rlm at gmail dot com>
15
 * @link      http://github.com/nfephp-org/sped-nfse for the canonical source repository
16
 */
17
18
use NFePHP\Common\Certificate;
19
use NFePHP\Common\Soap\SoapInterface;
20
use NFePHP\Common\Exception\SoapException;
21
use NFePHP\Common\Exception\RuntimeException;
22
use NFePHP\Common\Strings;
23
use League\Flysystem\Filesystem;
24
use League\Flysystem\Adapter\Local;
25
use Psr\Log\LoggerInterface;
26
27
abstract class SoapBase implements SoapInterface
28
{
29
    /**
30
     * @var int
31
     */
32
    protected $soapprotocol = self::SSL_DEFAULT;
33
    /**
34
     * @var int
35
     */
36
    protected $soaptimeout = 20;
37
    /**
38
     * @var string
39
     */
40
    protected $proxyIP;
41
    /**
42
     * @var int
43
     */
44
    protected $proxyPort;
45
    /**
46
     * @var string
47
     */
48
    protected $proxyUser;
49
    /**
50
     * @var string
51
     */
52
    protected $proxyPass;
53
    /**
54
     * @var array
55
     */
56
    protected $prefixes = [1 => 'soapenv', 2 => 'soap'];
57
    /**
58
     * @var Certificate
59
     */
60
    protected $certificate;
61
    /**
62
     * @var LoggerInterface
63
     */
64
    protected $logger;
65
    /**
66
     * @var string
67
     */
68
    protected $tempdir;
69
    /**
70
     * @var string
71
     */
72
    protected $certsdir;
73
    /**
74
     * @var string
75
     */
76
    protected $debugdir;
77
    /**
78
     * @var string
79
     */
80
    protected $prifile;
81
    /**
82
     * @var string
83
     */
84
    protected $pubfile;
85
    /**
86
     * @var string
87
     */
88
    protected $certfile;
89
    /**
90
     * @var string
91
     */
92
    protected $casefaz;
93
    /**
94
     * @var bool
95
     */
96
    protected $disablesec = false;
97
    /**
98
     * @var bool
99
     */
100
    protected $disableCertValidation = false;
101
    /**
102
     * @var \League\Flysystem\Adapter\Local
103
     */
104
    protected $adapter;
105
    /**
106
     * @var \League\Flysystem\Filesystem
107
     */
108
    protected $filesystem;
109
    /**
110
     * @var string
111
     */
112
    protected $temppass = '';
113
    /**
114
     * @var bool
115
     */
116
    protected $encriptPrivateKey = true;
117
    /**
118
     * @var bool
119
     */
120
    protected $debugmode = false;
121
    /**
122
     * @var string
123
     */
124
    public $responseHead;
125
    /**
126
     * @var string
127
     */
128
    public $responseBody;
129
    /**
130
     * @var string
131
     */
132
    public $requestHead;
133
    /**
134
     * @var string
135
     */
136
    public $requestBody;
137
    /**
138
     * @var string
139
     */
140
    public $soaperror;
141
    /**
142
     * @var array
143
     */
144
    public $soapinfo = [];
145
    /**
146
     * @var int
147
     */
148
    public $waitingTime = 45;
149
150
    /**
151
     * Constructor
152
     * @param Certificate $certificate
153
     * @param LoggerInterface $logger
154
     */
155 5
    public function __construct(
156
        Certificate $certificate = null,
157
        LoggerInterface $logger = null
158
    ) {
159 5
        $this->logger = $logger;
160 5
        $this->certificate = $this->certIsExpired($certificate);
161 4
        $this->setTemporaryFolder(sys_get_temp_dir() . '/sped/');
162 4
163
        if (null !== $certificate) {
164
            $this->saveTemporarilyKeyFiles();
165
        }
166
    }
167
    
168
    /**
169
     * Check if certificate is valid to currently used date
170 5
     * @param Certificate $certificate
171
     * @return Certificate
172 5
     * @throws RuntimeException
173 1
     */
174
    private function certIsExpired(Certificate $certificate = null)
175 5
    {
176 1
        if (
177 1
            $this->disableCertValidation &&
178 1
            null !== $certificate &&
179
            $certificate->isExpired()
180
        ) {
181
            throw new RuntimeException(
182 4
                'The validity of the certificate has expired.'
183
            );
184
        }
185
186
        return $certificate;
187
    }
188
    
189 4
    /**
190
     * Destructor
191 4
     * Clean temporary files
192 4
     */
193
    public function __destruct()
194
    {
195
        $this->removeTemporarilyFiles();
196
    }
197
198 1
    /**
199
     * Disables the security checking of host and peer certificates
200 1
     * @param bool $flag
201 1
     * @return bool
202
     */
203
    public function disableSecurity(bool $flag = false)
204
    {
205
        return $this->disablesec = $flag;
206
    }
207
    
208
    /**
209 1
     * ONlY for tests
210
     * @param bool $flag
211 1
     * @return bool
212 1
     */
213
    public function disableCertValidation(bool $flag = true)
214
    {
215
        return $this->disableCertValidation = $flag;
216
    }
217
218
    /**
219
     * Load path to CA and enable to use on SOAP
220
     * @param string $capath
221
     */
222
    public function loadCA($capath)
223
    {
224
        if (is_file($capath)) {
225
            $this->casefaz = $capath;
226
        }
227
    }
228
    
229
    /**
230
     * Set option to encript private key before save in filesystem
231
     * for an additional layer of protection
232
     * @param bool $encript
233
     * @return bool
234
     */
235
    public function setEncriptPrivateKey($encript = true)
236
    {
237
        return $this->encriptPrivateKey = $encript;
238
    }
239
    
240
    /**
241 4
     * Set another temporayfolder for saving certificates for SOAP utilization
242
     * @param string $folderRealPath
243 4
     */
244 4
    public function setTemporaryFolder($folderRealPath)
245 4
    {
246
        $this->tempdir = $folderRealPath;
247
        $this->setLocalFolder($folderRealPath);
248
    }
249
    
250
    /**
251 4
     * Set Local folder for flysystem
252
     * @param string $folder
253 4
     */
254 4
    protected function setLocalFolder($folder = '')
255 4
    {
256
        $this->adapter = new Local($folder);
257
        $this->filesystem = new Filesystem($this->adapter);
258
    }
259
260
    /**
261
     * Set debug mode, this mode will save soap envelopes in temporary directory
262
     * @param bool $value
263
     * @return bool
264
     */
265
    public function setDebugMode($value = false)
266
    {
267
        return $this->debugmode = $value;
268
    }
269
    
270
    /**
271 1
     * Set certificate class for SSL communications
272
     * @param Certificate $certificate
273 1
     */
274 1
    public function loadCertificate(Certificate $certificate)
275
    {
276
        $this->certificate = $this->certIsExpired($certificate);
277
    }
278
    
279
    /**
280
     * Set logger class
281
     * @param LoggerInterface $logger
282
     * @return LoggerInterface
283
     */
284
    public function loadLogger(LoggerInterface $logger)
285
    {
286
        return $this->logger = $logger;
287
    }
288
    
289
    /**
290
     * Set timeout for communication
291
     * @param int $timesecs
292
     * @return int
293
     */
294
    public function timeout(int $timesecs)
295
    {
296
        return $this->soaptimeout = $timesecs;
297
    }
298
    
299
    /**
300
     * Set security protocol
301
     * @param int $protocol
302
     * @return int
303
     */
304
    public function protocol($protocol = self::SSL_DEFAULT)
305
    {
306
        return $this->soapprotocol = $protocol;
307
    }
308
    
309
    /**
310
     * Set prefixes
311
     * @param array $prefixes
312
     * @return string[]
313
     */
314
    public function setSoapPrefix($prefixes = [])
315
    {
316
        return $this->prefixes = $prefixes;
317
    }
318
    
319
    /**
320
     * Set proxy parameters
321
     * @param string $ip
322
     * @param int $port
323
     * @param string $user
324
     * @param string $password
325
     */
326
    public function proxy($ip, $port, $user, $password)
327
    {
328
        $this->proxyIP = $ip;
329
        $this->proxyPort = $port;
330
        $this->proxyUser = $user;
331
        $this->proxyPass = $password;
332
    }
333
334
    /**
335
     * Send message to webservice
336
     * @param string $url
337
     * @param string $operation
338
     * @param string $action
339
     * @param int $soapver
340
     * @param array $parameters
341
     * @param array $namespaces
342
     * @param string $request
343
     * @param null $soapheader
344
     * @return mixed
345
     */
346
    abstract public function send(
347
        $url,
348
        $operation = '',
349
        $action = '',
350
        $soapver = SOAP_1_2,
351
        $parameters = [],
352
        $namespaces = [],
353
        $request = '',
354
        $soapheader = null
355
    );
356
    
357
    /**
358
     * Mount soap envelope
359
     * @param string $request
360
     * @param array $namespaces
361
     * @param $soapVer int
362
     * @param \SOAPHeader $header
363
     * @return string
364
     */
365
    protected function makeEnvelopeSoap(
366
        $request,
367
        $namespaces,
368
        $soapVer = SOAP_1_2,
369
        \SoapHeader $header = null
370
    ) {
371
        $prefix = $this->prefixes[$soapVer];
372
        $envelopeAttributes = '';
373
374
        // Interessante desmembrar em uma função
375
        foreach ($namespaces as $key => $value) {
376
            $envelopeAttributes = $key.'="'.$value.'"';
377
        }
378
379
        if (null === $header) {
380
            return $this->mountEnvelopString($prefix, $envelopeAttributes, '', $request);
381
        }
382
383
        return $this->mountEnvelopString($prefix, $envelopeAttributes, $this->mountSoapHeaders($prefix, $header), $request);
384
    }
385
386
    /**
387
     * @param string $envelopPrefix
388
     * @param string $envelopAttributes
389
     * @param string $header
390
     * @param string $bodyContent
391
     * @return string
392
     */
393
    private function mountEnvelopString(string $envelopPrefix, string $envelopAttributes = '', string $header = '', string $bodyContent = '')
394
    {
395
        return sprintf(
396
            '<%s:Envelope %s >'.$header.'<%s:Body>%s</%s:Body></%s:Envelop>',
397
            $envelopPrefix,
398
            $envelopAttributes,
399
            $envelopPrefix,
400
            $bodyContent,
401
            $envelopPrefix,
402
            $envelopPrefix
403
        );
404
    }
405
406
    /**
407
     * @param string $envelopPrefix
408
     * @param \SoapHeader $header
409
     * @return string
410
     */
411
    private function mountSoapHeaders(string $envelopPrefix, \SoapHeader $header)
412
    {
413
        $headerItems = '';
414
        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...
415
            $headerItems = '<'.$key.'>'.$value.'<'.$key.'>';
416
        }
417
418
        return sprintf(
419
            '<%s:Header><%s xmlns="%s">%s</%s></%s:Header>',
420
            $envelopPrefix,
421
            $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...
422
            $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...
423
            $headerItems
424
        );
425
    }
426
427
    
428
    /**
429 4
     * Temporarily saves the certificate keys for use cURL or SoapClient
430
     */
431 4
    public function saveTemporarilyKeyFiles()
432
    {
433
        if (!is_object($this->certificate)) {
434
            throw new RuntimeException(
435
                'Certificate not found.'
436
            );
437
        }
438
        $this->certsdir = $this->certificate->getCnpj() . '/certs/';
439
        $this->prifile = $this->certsdir. Strings::randomString(10).'.pem';
440
        $this->pubfile = $this->certsdir . Strings::randomString(10).'.pem';
441
        $this->certfile = $this->certsdir . Strings::randomString(10).'.pem';
442 4
        $ret = true;
443 4
        $private = $this->certificate->privateKey;
444 4
        if ($this->encriptPrivateKey) {
445 4
            //cria uma senha temporária ALEATÓRIA para salvar a chave primaria
446 4
            //portanto mesmo que localizada e identificada não estará acessível
447
            //pois sua senha não existe além do tempo de execução desta classe
448
            $this->temppass = Strings::randomString(16);
449
            //encripta a chave privada entes da gravação do filesystem
450
            openssl_pkey_export(
451
                $this->certificate->privateKey,
452
                $private,
453
                $this->temppass
454
            );
455
        }
456
        $ret &= $this->filesystem->put(
457
            $this->prifile,
458
            $private
459
        );
460
        $ret &= $this->filesystem->put(
461
            $this->pubfile,
462 4
            $this->certificate->publicKey
463
        );
464
        $ret &= $this->filesystem->put(
465
            $this->certfile,
466
            $private."{$this->certificate}"
467
        );
468
        if (!$ret) {
469
            throw new RuntimeException(
470
                'Unable to save temporary key files in folder.'
471
            );
472
        }
473
    }
474
    
475
    /**
476
     * Delete all files in folder
477
     */
478
    public function removeTemporarilyFiles()
479
    {
480
        $contents = $this->filesystem->listContents($this->certsdir, true);
481
        //define um limite de $waitingTime min, ou seja qualquer arquivo criado a mais
482
        //de $waitingTime min será removido
483
        //NOTA: quando ocorre algum erro interno na execução do script, alguns
484
        //arquivos temporários podem permanecer
485
        //NOTA: O tempo default é de 45 minutos e pode ser alterado diretamente nas
486
        //propriedades da classe, esse tempo entre 5 a 45 min é recomendável pois
487
        //podem haver processos concorrentes para um mesmo usuário. Esses processos
488
        //como o DFe podem ser mais longos, dependendo a forma que o aplicativo
489
        //utilize a API. Outra solução para remover arquivos "perdidos" pode ser
490
        //encontrada oportunamente.
491
        $dt = new \DateTime();
492
        $tint = new \DateInterval("PT".$this->waitingTime."M");
493
        $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...
494
        $tsLimit = $dt->add($tint)->getTimestamp();
495
        foreach ($contents as $item) {
496
            if ($item['type'] == 'file') {
497
                if ($item['path'] == $this->prifile
498
                    || $item['path'] == $this->pubfile
499
                    || $item['path'] == $this->certfile
500
                ) {
501
                    $this->filesystem->delete($item['path']);
502
                    continue;
503
                }
504
                $timestamp = $this->filesystem->getTimestamp($item['path']);
505
                if ($timestamp < $tsLimit) {
506
                    //remove arquivos criados a mais de 45 min
507
                    $this->filesystem->delete($item['path']);
508
                }
509
            }
510
        }
511
    }
512
    
513
    /**
514
     * Save request envelope and response for debug reasons
515
     * @param string $operation
516
     * @param string $request
517
     * @param string $response
518
     * @return void
519
     */
520
    public function saveDebugFiles($operation, $request, $response)
521
    {
522
        if (!$this->debugmode) {
523
            return;
524
        }
525
        $this->debugdir = $this->certificate->getCnpj() . '/debug/';
526
        $now = \DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
527
        $time = substr($now->format("ymdHisu"), 0, 16);
528
        try {
529
            $this->filesystem->put(
530
                $this->debugdir . $time . "_" . $operation . "_sol.txt",
531
                $request
532
            );
533
            $this->filesystem->put(
534
                $this->debugdir . $time . "_" . $operation . "_res.txt",
535
                $response
536
            );
537
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class NFePHP\Common\Soap\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
538
            throw new RuntimeException(
539
                'Unable to create debug files.'
540
            );
541
        }
542
    }
543
}
544