Passed
Pull Request — master (#125)
by Roberto
02:33
created

SoapBase   C

Complexity

Total Complexity 39

Size/Duplication

Total Lines 468
Duplicated Lines 0 %

Coupling/Cohesion

Components 7
Dependencies 5

Test Coverage

Coverage 31.06%

Importance

Changes 0
Metric Value
wmc 39
lcom 7
cbo 5
dl 0
loc 468
ccs 41
cts 132
cp 0.3106
rs 6.96
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A checkCertValidity() 0 14 4
A __destruct() 0 4 1
A disableSecurity() 0 5 1
A disableCertValidation() 0 5 1
A loadCA() 0 6 2
A setEncriptPrivateKey() 0 4 1
A setTemporaryFolder() 0 5 1
A setLocalFolder() 0 5 1
A setDebugMode() 0 4 1
A loadCertificate() 0 4 1
A loadLogger() 0 4 1
A timeout() 0 4 1
A protocol() 0 4 1
A setSoapPrefix() 0 4 1
A proxy() 0 7 1
send() 0 10 ?
B makeEnvelopeSoap() 0 28 5
B saveTemporarilyKeyFiles() 0 43 4
C removeTemporarilyFiles() 0 34 7
A saveDebugFiles() 0 23 3
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 string
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->checkCertValidity($certificate);
161 4
        $this->setTemporaryFolder(sys_get_temp_dir() . '/sped/');
162 4
    }
163
    
164
    /**
165
     * Check if certificate is valid
166
     * @param Certificate $certificate
167
     * @return Certificate
168
     * @throws RuntimeException
169
     */
170 5
    private function checkCertValidity(Certificate $certificate = null)
171
    {
172 5
        if ($this->disableCertValidation) {
173 1
            return $certificate;
174
        }
175 5
        if (!empty($certificate)) {
176 1
            if ($certificate->isExpired()) {
177 1
                throw new RuntimeException(
178 1
                    'The validity of the certificate has expired.'
179
                );
180
            }
181
        }
182 4
        return $certificate;
183
    }
184
    
185
    /**
186
     * Destructor
187
     * Clean temporary files
188
     */
189 4
    public function __destruct()
190
    {
191 4
        $this->removeTemporarilyFiles();
192 4
    }
193
    
194
    /**
195
     * Disables the security checking of host and peer certificates
196
     * @param bool $flag
197
     */
198 1
    public function disableSecurity($flag = false)
199
    {
200 1
        $this->disablesec = $flag;
201 1
        return $this->disablesec;
202
    }
203
    
204
    /**
205
     * ONlY for tests
206
     * @param bool $flag
207
     * @return bool
208
     */
209 1
    public function disableCertValidation($flag = true)
210
    {
211 1
        $this->disableCertValidation = $flag;
212 1
        return $this->disableCertValidation;
213
    }
214
215
    /**
216
     * Load path to CA and enable to use on SOAP
217
     * @param string $capath
218
     */
219
    public function loadCA($capath)
220
    {
221
        if (is_file($capath)) {
222
            $this->casefaz = $capath;
223
        }
224
    }
225
    
226
    /**
227
     * Set option to encript private key before save in filesystem
228
     * for an additional layer of protection
229
     * @param bool $encript
230
     * @return bool
231
     */
232
    public function setEncriptPrivateKey($encript = true)
233
    {
234
        return $this->encriptPrivateKey = $encript;
235
    }
236
    
237
    /**
238
     * Set another temporayfolder for saving certificates for SOAP utilization
239
     * @param string $folderRealPath
240
     */
241 4
    public function setTemporaryFolder($folderRealPath)
242
    {
243 4
        $this->tempdir = $folderRealPath;
244 4
        $this->setLocalFolder($folderRealPath);
245 4
    }
246
    
247
    /**
248
     * Set Local folder for flysystem
249
     * @param string $folder
250
     */
251 4
    protected function setLocalFolder($folder = '')
252
    {
253 4
        $this->adapter = new Local($folder);
254 4
        $this->filesystem = new Filesystem($this->adapter);
255 4
    }
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 comunications
269
     * @param Certificate $certificate
270
     */
271 1
    public function loadCertificate(Certificate $certificate)
272
    {
273 1
        $this->certificate = $this->checkCertValidity($certificate);
274 1
    }
275
    
276
    /**
277
     * Set logger class
278
     * @param LoggerInterface $logger
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
     */
289
    public function timeout($timesecs)
290
    {
291
        return $this->soaptimeout = $timesecs;
292
    }
293
    
294
    /**
295
     * Set security protocol
296
     * @param int $protocol
297
     * @return type Description
298
     */
299
    public function protocol($protocol = self::SSL_DEFAULT)
300
    {
301
        return $this->soapprotocol = $protocol;
302
    }
303
    
304
    /**
305
     * Set prefixes
306
     * @param string $prefixes
307
     * @return string
308
     */
309
    public function setSoapPrefix($prefixes)
310
    {
311
        return $this->prefixes = $prefixes;
0 ignored issues
show
Documentation Bug introduced by
It seems like $prefixes of type string is incompatible with the declared type array of property $prefixes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
312
    }
313
    
314
    /**
315
     * Set proxy parameters
316
     * @param string $ip
317
     * @param int $port
318
     * @param string $user
319
     * @param string $password
320
     */
321
    public function proxy($ip, $port, $user, $password)
322
    {
323
        $this->proxyIP = $ip;
324
        $this->proxyPort = $port;
0 ignored issues
show
Documentation Bug introduced by
The property $proxyPort was declared of type string, but $port is of type integer. 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...
325
        $this->proxyUser = $user;
326
        $this->proxyPass = $password;
327
    }
328
    
329
    /**
330
     * Send message to webservice
331
     */
332
    abstract public function send(
333
        $url,
334
        $operation = '',
335
        $action = '',
336
        $soapver = SOAP_1_2,
337
        $parameters = [],
338
        $namespaces = [],
339
        $request = '',
340
        $soapheader = null
341
    );
342
    
343
    /**
344
     * Mount soap envelope
345
     * @param string $request
346
     * @param array $namespaces
347
     * @param \SOAPHeader $header
348
     * @return string
349
     */
350
    protected function makeEnvelopeSoap(
351
        $request,
352
        $namespaces,
353
        $soapver = SOAP_1_2,
354
        $header = null
355
    ) {
356
        $prefix = $this->prefixes[$soapver];
357
        $envelope = "<$prefix:Envelope";
358
        foreach ($namespaces as $key => $value) {
359
            $envelope .= " $key=\"$value\"";
360
        }
361
        $envelope .= ">";
362
        $soapheader = "<$prefix:Header/>";
363
        if (!empty($header)) {
364
            $ns = !empty($header->namespace) ? $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...
365
            $name = $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...
366
            $soapheader = "<$prefix:Header>";
367
            $soapheader .= "<$name xmlns=\"$ns\">";
368
            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...
369
                $soapheader .= "<$key>$value</$key>";
370
            }
371
            $soapheader .= "</$name></$prefix:Header>";
372
        }
373
        $envelope .= $soapheader;
374
        $envelope .= "<$prefix:Body>$request</$prefix:Body>"
375
            . "</$prefix:Envelope>";
376
        return $envelope;
377
    }
378
    
379
    /**
380
     * Temporarily saves the certificate keys for use cURL or SoapClient
381
     */
382
    public function saveTemporarilyKeyFiles()
383
    {
384
        if (!is_object($this->certificate)) {
385
            throw new RuntimeException(
386
                'Certificate not found.'
387
            );
388
        }
389
        $this->certsdir = $this->certificate->getCnpj() . '/certs/';
390
        $this->prifile = $this->certsdir. Strings::randomString(10).'.pem';
391
        $this->pubfile = $this->certsdir . Strings::randomString(10).'.pem';
392
        $this->certfile = $this->certsdir . Strings::randomString(10).'.pem';
393
        $ret = true;
394
        $private = $this->certificate->privateKey;
395
        if ($this->encriptPrivateKey) {
396
            //cria uma senha temporária ALEATÓRIA para salvar a chave primaria
397
            //portanto mesmo que localizada e identificada não estará acessível
398
            //pois sua senha não existe além do tempo de execução desta classe
399
            $this->temppass = Strings::randomString(16);
400
            //encripta a chave privada entes da gravação do filesystem
401
            openssl_pkey_export(
402
                $this->certificate->privateKey,
403
                $private,
404
                $this->temppass
405
            );
406
        }
407
        $ret &= $this->filesystem->put(
408
            $this->prifile,
409
            $private
410
        );
411
        $ret &= $this->filesystem->put(
412
            $this->pubfile,
413
            $this->certificate->publicKey
414
        );
415
        $ret &= $this->filesystem->put(
416
            $this->certfile,
417
            $private."{$this->certificate}"
418
        );
419
        if (!$ret) {
420
            throw new RuntimeException(
421
                'Unable to save temporary key files in folder.'
422
            );
423
        }
424
    }
425
    
426
    /**
427
     * Delete all files in folder
428
     */
429 4
    public function removeTemporarilyFiles()
430
    {
431 4
        $contents = $this->filesystem->listContents($this->certsdir, true);
432
        //define um limite de $waitingTime min, ou seja qualquer arquivo criado a mais
433
        //de $waitingTime min será removido
434
        //NOTA: quando ocorre algum erro interno na execução do script, alguns
435
        //arquivos temporários podem permanecer
436
        //NOTA: O tempo default é de 45 minutos e pode ser alterado diretamente nas
437
        //propriedades da classe, esse tempo entre 5 a 45 min é recomendável pois
438
        //podem haver processos concorrentes para um mesmo usuário. Esses processos
439
        //como o DFe podem ser mais longos, dependendo a forma que o aplicativo
440
        //utilize a API. Outra solução para remover arquivos "perdidos" pode ser
441
        //encontrada oportunamente.
442 4
        $dt = new \DateTime();
443 4
        $tint = new \DateInterval("PT".$this->waitingTime."M");
444 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...
445 4
        $tsLimit = $dt->add($tint)->getTimestamp();
446 4
        foreach ($contents as $item) {
447
            if ($item['type'] == 'file') {
448
                if ($item['path'] == $this->prifile
449
                    || $item['path'] == $this->pubfile
450
                    || $item['path'] == $this->certfile
451
                ) {
452
                    $this->filesystem->delete($item['path']);
453
                    continue;
454
                }
455
                $timestamp = $this->filesystem->getTimestamp($item['path']);
456
                if ($timestamp < $tsLimit) {
457
                    //remove arquivos criados a mais de 45 min
458
                    $this->filesystem->delete($item['path']);
459
                }
460
            }
461
        }
462 4
    }
463
    
464
    /**
465
     * Save request envelope and response for debug reasons
466
     * @param string $operation
467
     * @param string $request
468
     * @param string $response
469
     * @return void
470
     */
471
    public function saveDebugFiles($operation, $request, $response)
472
    {
473
        if (!$this->debugmode) {
474
            return;
475
        }
476
        $this->debugdir = $this->certificate->getCnpj() . '/debug/';
477
        $now = \DateTime::createFromFormat('U.u', microtime(true));
478
        $time = substr($now->format("ymdHisu"), 0, 16);
479
        try {
480
            $this->filesystem->put(
481
                $this->debugdir . $time . "_" . $operation . "_sol.txt",
482
                $request
483
            );
484
            $this->filesystem->put(
485
                $this->debugdir . $time . "_" . $operation . "_res.txt",
486
                $response
487
            );
488
        } 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...
489
            throw new RuntimeException(
490
                'Unable to create debug files.'
491
            );
492
        }
493
    }
494
}
495