Completed
Push — master ( 121fda...0652b0 )
by Roberto
08:49
created

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