Passed
Push — master ( 6e678e...73971d )
by Roberto
46s
created

SoapBase::setTemporaryFolder()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.1406

Importance

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