Passed
Pull Request — master (#180)
by
unknown
05:21
created

SoapBase::makeEnvelopeSoap()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 0
cts 5
cp 0
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 12
nc 1
nop 4
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
    }
158
159
    /**
160
     * Check if certificate is valid to currently used date
161
     * @param Certificate $certificate
162
     * @return void
163
     * @throws Certificate\Exception\Expired
164
     */
165 5
    private function isCertificateExpired(Certificate $certificate = null)
166
    {
167 5
        if (!$this->disableCertValidation) {
168 5
            if (null !== $certificate && $certificate->isExpired()) {
169 1
                throw new Certificate\Exception\Expired($certificate);
170
            }
171
        }
172 4
    }
173
174
    /**
175
     * Destructor
176
     * Clean temporary files
177
     */
178 4
    public function __destruct()
179
    {
180 4
        $this->removeTemporarilyFiles();
181 4
    }
182
183
    /**
184
     * Disables the security checking of host and peer certificates
185
     * @param bool $flag
186
     * @return bool
187
     */
188 1
    public function disableSecurity($flag = false)
189
    {
190 1
        return $this->disablesec = $flag;
191
    }
192
193
    /**
194
     * ONlY for tests
195
     * @param bool $flag
196
     * @return bool
197
     */
198 1
    public function disableCertValidation($flag = true)
199
    {
200 1
        return $this->disableCertValidation = $flag;
201
    }
202
203
    /**
204
     * Load path to CA and enable to use on SOAP
205
     * @param string $capath
206
     * @return void
207
     */
208
    public function loadCA($capath)
209
    {
210
        if (is_file($capath)) {
211
            $this->casefaz = $capath;
212
        }
213
    }
214
215
    /**
216
     * Set option to encrypt private key before save in filesystem
217
     * for an additional layer of protection
218
     * @param bool $encript
219
     * @return bool
220
     */
221
    public function setEncriptPrivateKey($encript = true)
222
    {
223
        $this->encriptPrivateKey = $encript;
224
        if (null !== $this->filesystem) {
225
            $this->removeTemporarilyFiles();
226
        }
227
        if (null !== $this->certificate) {
228
            $this->saveTemporarilyKeyFiles();
229
        }
230
        return $this->encriptPrivateKey;
231
    }
232
233
    /**
234
     * Set another temporayfolder for saving certificates for SOAP utilization
235
     * @param string | null $folderRealPath
236
     * @return void
237
     */
238
    public function setTemporaryFolder($folderRealPath = null)
239
    {
240
        if (empty($folderRealPath)) {
241
            $path = '/sped-'. getmyuid() .'/';
242
            if (function_exists('posix_getuid')) {
243
                $path = '/sped-'. posix_getuid() .'/';
244
            }
245
            $folderRealPath = sys_get_temp_dir() . $path;
246
        }
247
        $this->tempdir = $folderRealPath;
248
        $this->setLocalFolder($folderRealPath);
249
    }
250
251
    /**
252
     * Set Local folder for flysystem
253
     * @param string $folder
254
     */
255
    protected function setLocalFolder($folder = '')
256
    {
257
        $this->adapter = new Local($folder);
258
        $this->filesystem = new Filesystem($this->adapter);
259
    }
260
261
    /**
262
     * Set debug mode, this mode will save soap envelopes in temporary directory
263
     * @param bool $value
264
     * @return bool
265
     */
266
    public function setDebugMode($value = false)
267
    {
268
        return $this->debugmode = $value;
269
    }
270
271
    /**
272
     * Set certificate class for SSL communications
273
     * @param Certificate $certificate
274
     * @return void
275
     */
276 5
    public function loadCertificate(Certificate $certificate = null)
277
    {
278 5
        $this->isCertificateExpired($certificate);
279 4
        if (null !== $certificate) {
280 1
            $this->certificate = $certificate;
281
        }
282 4
    }
283
284
    /**
285
     * Set logger class
286
     * @param LoggerInterface $logger
287
     * @return LoggerInterface
288
     */
289
    public function loadLogger(LoggerInterface $logger)
290
    {
291
        return $this->logger = $logger;
292
    }
293
294
    /**
295
     * Set timeout for communication
296
     * @param int $timesecs
297
     * @return int
298
     */
299
    public function timeout($timesecs)
300
    {
301
        return $this->soaptimeout = $timesecs;
302
    }
303
304
    /**
305
     * Set security protocol
306
     * @param int $protocol
307
     * @return int
308
     */
309
    public function protocol($protocol = self::SSL_DEFAULT)
310
    {
311
        return $this->soapprotocol = $protocol;
312
    }
313
314
    /**
315
     * Set prefixes
316
     * @param array $prefixes
317
     * @return string[]
318
     */
319
    public function setSoapPrefix($prefixes = [])
320
    {
321
        return $this->prefixes = $prefixes;
322
    }
323
324
    /**
325
     * Set proxy parameters
326
     * @param string $ip
327
     * @param int    $port
328
     * @param string $user
329
     * @param string $password
330
     * @return void
331
     */
332
    public function proxy($ip, $port, $user, $password)
333
    {
334
        $this->proxyIP = $ip;
335
        $this->proxyPort = $port;
336
        $this->proxyUser = $user;
337
        $this->proxyPass = $password;
338
    }
339
340
    /**
341
     * @param string $url
342
     * @param string $operation
343
     * @param string $action
344
     * @param int $soapver
345
     * @param array $parameters
346
     * @param array $namespaces
347
     * @param string $request
348
     * @param null $soapheader
349
     * @return mixed
350
     */
351
    abstract public function send(
352
        $url,
353
        $operation = '',
354
        $action = '',
355
        $soapver = SOAP_1_2,
356
        $parameters = [],
357
        $namespaces = [],
358
        $request = '',
359
        $soapheader = null
360
    );
361
362
    /**
363
     * Mount soap envelope
364
     * @param string $request
365
     * @param array $namespaces
366
     * @param int $soapVer
367
     * @param \SoapHeader $header
368
     * @return string
369
     */
370
    protected function makeEnvelopeSoap(
371
        $request,
372
        $namespaces,
373
        $soapVer = SOAP_1_2,
374
        $header = null
375
    ) {
376
        $prefix = $this->prefixes[$soapVer];
377
        $envelopeAttributes = $this->getStringAttributes($namespaces);
378
        return $this->mountEnvelopString(
379
            $prefix,
380
            $envelopeAttributes,
381
            $this->mountSoapHeaders($prefix, $header),
382
            $request
383
        );
384
    }
385
386
    /**
387
     * Create a envelop string
388
     * @param string $envelopPrefix
389
     * @param string $envelopAttributes
390
     * @param string $header
391
     * @param string $bodyContent
392
     * @return string
393
     */
394
    private function mountEnvelopString(
395
        $envelopPrefix,
396
        $envelopAttributes = '',
397
        $header = '',
398
        $bodyContent = ''
399
    ) {
400
        return sprintf(
401
            '<%s:Envelope %s>' . $header . '<%s:Body>%s</%s:Body></%s:Envelope>',
402
            $envelopPrefix,
403
            $envelopAttributes,
404
            $envelopPrefix,
405
            $bodyContent,
406
            $envelopPrefix,
407
            $envelopPrefix
408
        );
409
    }
410
411
    /**
412
     * Create a haeader tag
413
     * @param string $envelopPrefix
414
     * @param \SoapHeader $header
415
     * @return string
416
     */
417
    private function mountSoapHeaders($envelopPrefix, $header = null)
418
    {
419
        if (null === $header) {
420
            return '';
421
        }
422
        $headerItems = '';
423
        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...
424
            $headerItems .= '<' . $key . '>' . $value . '</' . $key . '>';
425
        }
426
        return sprintf(
427
            '<%s:Header><%s xmlns="%s">%s</%s></%s:Header>',
428
            $envelopPrefix,
429
            $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...
430
            $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...
431
            $headerItems,
432
            $header->name,
433
            $envelopPrefix
434
        );
435
    }
436
437
    /**
438
     * Get attributes
439
     * @param array $namespaces
440
     * @return string
441
     */
442
    private function getStringAttributes($namespaces = [])
443
    {
444
        $envelopeAttributes = '';
445
        foreach ($namespaces as $key => $value) {
446
            $envelopeAttributes .= $key . '="' . $value . '" ';
447
        }
448
        return $envelopeAttributes;
449
    }
450
451
452
    /**
453
     * Temporarily saves the certificate keys for use cURL or SoapClient
454
     * @return void
455
     */
456
    public function saveTemporarilyKeyFiles()
457
    {
458
        if (!empty($this->certsdir)) {
459
            return;
460
        }
461
        if (!is_object($this->certificate)) {
462
            throw new RuntimeException(
463
                'Certificate not found.'
464
            );
465
        }
466
        if (empty($this->filesystem)) {
467
            $this->setTemporaryFolder();
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
        if (empty($this->filesystem) || empty($this->certsdir)) {
513 4
            return;
514
        }
515
        $contents = $this->filesystem->listContents($this->certsdir, true);
516
        //define um limite de $waitingTime min, ou seja qualquer arquivo criado a mais
517
        //de $waitingTime min será removido
518
        //NOTA: quando ocorre algum erro interno na execução do script, alguns
519
        //arquivos temporários podem permanecer
520
        //NOTA: O tempo default é de 45 minutos e pode ser alterado diretamente nas
521
        //propriedades da classe, esse tempo entre 5 a 45 min é recomendável pois
522
        //podem haver processos concorrentes para um mesmo usuário. Esses processos
523
        //como o DFe podem ser mais longos, dependendo a forma que o aplicativo
524
        //utilize a API. Outra solução para remover arquivos "perdidos" pode ser
525
        //encontrada oportunamente.
526
        $dt = new \DateTime();
527
        $tint = new \DateInterval("PT" . $this->waitingTime . "M");
528
        $tint->invert = 1;
529
        $tsLimit = $dt->add($tint)->getTimestamp();
530
        foreach ($contents as $item) {
531
            if ($item['type'] == 'file') {
532
                if ($item['path'] == $this->prifile
533
                    || $item['path'] == $this->pubfile
534
                    || $item['path'] == $this->certfile
535
                ) {
536
                    $this->filesystem->delete($item['path']);
537
                    continue;
538
                }
539
                $timestamp = $this->filesystem->getTimestamp($item['path']);
540
                if ($timestamp < $tsLimit) {
541
                    //remove arquivos criados a mais de 45 min
542
                    $this->filesystem->delete($item['path']);
543
                }
544
            }
545
        }
546
    }
547
548
    /**
549
     * Save request envelope and response for debug reasons
550
     * @param string $operation
551
     * @param string $request
552
     * @param string $response
553
     * @return void
554
     */
555
    public function saveDebugFiles($operation, $request, $response)
556
    {
557
        if (!$this->debugmode) {
558
            return;
559
        }
560
        $this->debugdir = $this->certificate->getCnpj() . '/debug/';
561
        $now = \DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
562
        $time = substr($now->format("ymdHisu"), 0, 16);
563
        try {
564
            $this->filesystem->put(
565
                $this->debugdir . $time . "_" . $operation . "_sol.txt",
566
                $request
567
            );
568
            $this->filesystem->put(
569
                $this->debugdir . $time . "_" . $operation . "_res.txt",
570
                $response
571
            );
572
        } catch (\Exception $e) {
573
            throw new RuntimeException(
574
                'Unable to create debug files.'
575
            );
576
        }
577
    }
578
}
579