Completed
Push — master ( be8c82...d7ae84 )
by Roberto
04:38 queued 02:37
created

SoapBase::checkCertValidity()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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