Passed
Pull Request — master (#219)
by
unknown
01:52
created

SoapBase::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
1
<?php
2
3
namespace NFePHP\Common\Soap;
4
5
use NFePHP\Common\Certificate;
6
use NFePHP\Common\Exception\RuntimeException;
7
use NFePHP\Common\Strings;
8
use League\Flysystem\Filesystem;
9
use League\Flysystem\Adapter\Local;
10
use Psr\Log\LoggerInterface;
11
12
/**
13
 * Soap base class
14
 *
15
 * @category  NFePHP
16
 * @package   NFePHP\Common\Soap\SoapBase
17
 * @copyright NFePHP Copyright (c) 2017-2019
18
 * @author    Roberto L. Machado <linux.rlm at gmail dot com>
19
 * @license   http://www.gnu.org/licenses/lgpl.txt LGPLv3+
20
 * @license   https://opensource.org/licenses/MIT MIT
21
 * @license   http://www.gnu.org/licenses/gpl.txt GPLv3+
22
 * @link      http://github.com/nfephp-org/sped-nfse for the canonical source repository
23
 */
24
abstract class SoapBase implements SoapInterface
25
{
26
    /**
27
     * @var int
28
     */
29
    protected $soapprotocol = self::SSL_DEFAULT;
30
    /**
31
     * @var int
32
     */
33
    protected $soaptimeout = 20;
34
    /**
35
     * @var string
36
     */
37
    protected $proxyIP;
38
    /**
39
     * @var int
40
     */
41
    protected $proxyPort;
42
    /**
43
     * @var string
44
     */
45
    protected $proxyUser;
46
    /**
47
     * @var string
48
     */
49
    protected $proxyPass;
50
    /**
51
     * @var array
52
     */
53
    protected $prefixes = [1 => 'soapenv', 2 => 'soap'];
54
    /**
55
     * @var Certificate
56
     */
57
    protected $certificate;
58
    /**
59
     * @var LoggerInterface|null
60
     */
61
    protected $logger;
62
    /**
63
     * @var string
64
     */
65
    protected $tempdir;
66
    /**
67
     * @var string
68
     */
69
    protected $certsdir;
70
    /**
71
     * @var string
72
     */
73
    protected $debugdir;
74
    /**
75
     * @var string
76
     */
77
    protected $prifile;
78
    /**
79
     * @var string
80
     */
81
    protected $pubfile;
82
    /**
83
     * @var string
84
     */
85
    protected $certfile;
86
    /**
87
     * @var string
88
     */
89
    protected $casefaz;
90
    /**
91
     * @var bool
92
     */
93
    protected $disablesec = false;
94
    /**
95
     * @var bool
96
     */
97
    protected $disableCertValidation = false;
98
    /**
99
     * @var \League\Flysystem\Adapter\Local
100
     */
101
    protected $adapter;
102
    /**
103
     * @var \League\Flysystem\Filesystem
104
     */
105
    protected $filesystem;
106
    /**
107
     * @var string
108
     */
109
    protected $temppass = '';
110
    /**
111
     * @var bool
112
     */
113
    protected $encriptPrivateKey = false;
114
    /**
115
     * @var integer
116
     */
117
    protected $httpver;
118
    /**
119
     * @var bool
120
     */
121
    protected $debugmode = false;
122
    /**
123
     * @var string
124
     */
125
    public $responseHead;
126
    /**
127
     * @var string
128
     */
129
    public $responseBody;
130
    /**
131
     * @var string
132
     */
133
    public $requestHead;
134
    /**
135
     * @var string
136
     */
137
    public $requestBody;
138
    /**
139
     * @var string
140
     */
141
    public $soaperror;
142
    /**
143
     * @var array
144
     */
145
    public $soapinfo = [];
146
    /**
147
     * @var int
148
     */
149
    public $waitingTime = 45;
150
151
    /**
152
     * SoapBase constructor.
153
     * @param Certificate|null $certificate
154
     * @param LoggerInterface|null $logger
155
     */
156 5
    public function __construct(
157
        Certificate $certificate = null,
158
        LoggerInterface $logger = null
159
    ) {
160 5
        $this->logger = $logger;
161 5
        $this->loadCertificate($certificate);
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
     * Force http protocol version
210
     *
211
     * @param null|string $version
212
     */
213
    public function httpVersion($version = null)
214
    {
215
        switch ($version) {
216
            case '1.0':
217
                $this->httpver = CURL_HTTP_VERSION_1_0;
218
                break;
219
            case '1.1':
220
                $this->httpver = CURL_HTTP_VERSION_1_1;
221
                break;
222
            case '2.0':
223
                $this->httpver = CURL_HTTP_VERSION_2_0;
224
                break;
225
            default:
226
                $this->httpver = CURL_HTTP_VERSION_NONE;
227
        }
228
    }
229
230
    /**
231
     * Load path to CA and enable to use on SOAP
232
     * @param string $capath
233
     * @return void
234
     */
235
    public function loadCA($capath)
236
    {
237
        if (is_file($capath)) {
238
            $this->casefaz = $capath;
239
        }
240
    }
241
242
    /**
243
     * Set option to encrypt private key before save in filesystem
244
     * for an additional layer of protection
245
     * @param bool $encript
246
     * @return bool
247
     */
248
    public function setEncriptPrivateKey($encript = true)
249
    {
250
        $this->encriptPrivateKey = $encript;
251
        return $this->encriptPrivateKey;
252
    }
253
254
    /**
255
     * Set another temporayfolder for saving certificates for SOAP utilization
256
     * @param string | null $folderRealPath
257
     * @return void
258
     */
259
    public function setTemporaryFolder($folderRealPath = null)
260
    {
261
        $mapto = $this->certificate->getCnpj() ?? $this->certificate->getCpf();
262
        if (empty($mapto)) {
263
            throw new RuntimeException(
264
                'Foi impossivel identificar o OID do CNPJ ou do CPF.'
265
            );
266
        }
267
        if (empty($folderRealPath)) {
268
            $path = '/sped-'
269
                . $this->uid()
270
                . '/'
271
                . $mapto
272
                . '/';
273
            $folderRealPath = sys_get_temp_dir() . $path;
274
        }
275
        if (substr($folderRealPath, -1) !== '/') {
276
            $folderRealPath .= '/';
277
        }
278
        $this->tempdir = $folderRealPath;
279
        $this->setLocalFolder($folderRealPath);
280
    }
281
282
    /**
283
     * Return uid from user
284
     * @return string
285
     */
286
    protected function uid()
287
    {
288
        if (function_exists('posix_getuid')) {
289
            return posix_getuid();
290
        } else {
291
            return getmyuid();
292
        }
293
    }
294
295
    /**
296
     * Set Local folder for flysystem
297
     * @param string $folder
298
     */
299
    protected function setLocalFolder($folder = '')
300
    {
301
        $this->adapter = new Local($folder);
302
        $this->filesystem = new Filesystem($this->adapter);
303
    }
304
305
    /**
306
     * Set debug mode, this mode will save soap envelopes in temporary directory
307
     * @param bool $value
308
     * @return bool
309
     */
310
    public function setDebugMode($value = false)
311
    {
312
        return $this->debugmode = $value;
313
    }
314
315
    /**
316
     * Set certificate class for SSL communications
317
     * @param Certificate $certificate
318
     * @return void
319
     */
320 5
    public function loadCertificate(Certificate $certificate = null)
321
    {
322 5
        $this->isCertificateExpired($certificate);
323 4
        if (null !== $certificate) {
324 1
            $this->certificate = $certificate;
325
        }
326 4
    }
327
328
    /**
329
     * Set logger class
330
     * @param LoggerInterface $logger
331
     * @return LoggerInterface
332
     */
333
    public function loadLogger(LoggerInterface $logger)
334
    {
335
        return $this->logger = $logger;
336
    }
337
338
    /**
339
     * Set timeout for communication
340
     * @param int $timesecs
341
     * @return int
342
     */
343
    public function timeout($timesecs)
344
    {
345
        return $this->soaptimeout = $timesecs;
346
    }
347
348
    /**
349
     * Set security protocol
350
     * @param int $protocol
351
     * @return int
352
     */
353
    public function protocol($protocol = self::SSL_DEFAULT)
354
    {
355
        return $this->soapprotocol = $protocol;
356
    }
357
358
    /**
359
     * Set prefixes
360
     * @param array $prefixes
361
     * @return string[]
362
     */
363
    public function setSoapPrefix($prefixes = [])
364
    {
365
        return $this->prefixes = $prefixes;
366
    }
367
368
    /**
369
     * Set proxy parameters
370
     * @param string $ip
371
     * @param int    $port
372
     * @param string $user
373
     * @param string $password
374
     * @return void
375
     */
376
    public function proxy($ip, $port, $user, $password)
377
    {
378
        $this->proxyIP = $ip;
379
        $this->proxyPort = $port;
380
        $this->proxyUser = $user;
381
        $this->proxyPass = $password;
382
    }
383
384
    /**
385
     * @param string $url
386
     * @param string $operation
387
     * @param string $action
388
     * @param int $soapver
389
     * @param array $parameters
390
     * @param array $namespaces
391
     * @param string $request
392
     * @param null $soapheader
393
     * @return mixed
394
     */
395
    abstract public function send(
396
        $url,
397
        $operation = '',
398
        $action = '',
399
        $soapver = SOAP_1_2,
400
        $parameters = [],
401
        $namespaces = [],
402
        $request = '',
403
        $soapheader = null
404
    );
405
406
    /**
407
     * Mount soap envelope
408
     * @param string $request
409
     * @param array $namespaces
410
     * @param int $soapVer
411
     * @param \SoapHeader $header
412
     * @return string
413
     */
414
    protected function makeEnvelopeSoap(
415
        $request,
416
        $namespaces,
417
        $soapVer = SOAP_1_2,
418
        $header = null
419
    ) {
420
        $prefix = $this->prefixes[$soapVer];
421
        $envelopeAttributes = $this->getStringAttributes($namespaces);
422
        return $this->mountEnvelopString(
423
            $prefix,
424
            $envelopeAttributes,
425
            $this->mountSoapHeaders($prefix, $header),
426
            $request
427
        );
428
    }
429
430
    /**
431
     * Create a envelop string
432
     * @param string $envelopPrefix
433
     * @param string $envelopAttributes
434
     * @param string $header
435
     * @param string $bodyContent
436
     * @return string
437
     */
438
    private function mountEnvelopString(
439
        $envelopPrefix,
440
        $envelopAttributes = '',
441
        $header = '',
442
        $bodyContent = ''
443
    ) {
444
        return sprintf(
445
            '<%s:Envelope %s>' . $header . '<%s:Body>%s</%s:Body></%s:Envelope>',
446
            $envelopPrefix,
447
            $envelopAttributes,
448
            $envelopPrefix,
449
            $bodyContent,
450
            $envelopPrefix,
451
            $envelopPrefix
452
        );
453
    }
454
455
    /**
456
     * Create a haeader tag
457
     * @param string $envelopPrefix
458
     * @param \SoapHeader $header
459
     * @return string
460
     */
461
    private function mountSoapHeaders($envelopPrefix, $header = null)
462
    {
463
        if (null === $header) {
464
            //return '';
465
            return sprintf(
466
                '<%s:Header/>',
467
                $envelopPrefix
468
            );
469
        }
470
        $headerItems = '';
471
        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...
472
            $headerItems .= '<' . $key . '>' . $value . '</' . $key . '>';
473
        }
474
        return sprintf(
475
            '<%s:Header><%s xmlns="%s">%s</%s></%s:Header>',
476
            $envelopPrefix,
477
            $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...
478
            $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...
479
            $headerItems,
480
            $header->name,
481
            $envelopPrefix
482
        );
483
    }
484
485
    /**
486
     * Get attributes
487
     * @param array $namespaces
488
     * @return string
489
     */
490
    private function getStringAttributes($namespaces = [])
491
    {
492
        $envelopeAttributes = '';
493
        foreach ($namespaces as $key => $value) {
494
            $envelopeAttributes .= $key . '="' . $value . '" ';
495
        }
496
        return $envelopeAttributes;
497
    }
498
499
    /**
500
     * Temporarily saves the certificate keys for use cURL or SoapClient
501
     * @return void
502
     */
503
    public function saveTemporarilyKeyFiles()
504
    {
505
        //certs already exists
506
        if (!empty($this->certsdir)) {
507
            return;
508
        }
509
        if (!is_object($this->certificate)) {
510
            throw new RuntimeException(
511
                'Certificate not found.'
512
            );
513
        }
514
        if (empty($this->filesystem)) {
515
            $this->setTemporaryFolder();
516
        }
517
        //clear dir cert
518
        $this->removeTemporarilyFiles();
519
        $this->certsdir = 'certs/';
520
        $this->prifile = $this->randomName();
521
        $this->pubfile = $this->randomName();
522
        $this->certfile = $this->randomName();
523
        $ret = true;
524
        //load private key pem
525
        $private = $this->certificate->privateKey;
526
        if ($this->encriptPrivateKey) {
527
            //replace private key pem with password
528
            $this->temppass = Strings::randomString(16);
529
            //encripta a chave privada entes da gravação do filesystem
530
            openssl_pkey_export(
531
                $this->certificate->privateKey,
532
                $private,
533
                $this->temppass
534
            );
535
        }
536
        $ret &= $this->filesystem->put(
537
            $this->prifile,
538
            $private
539
        );
540
        $ret &= $this->filesystem->put(
541
            $this->pubfile,
542
            $this->certificate->publicKey
543
        );
544
        $ret &= $this->filesystem->put(
545
            $this->certfile,
546
            $private . "{$this->certificate}"
547
        );
548
        if (!$ret) {
549
            throw new RuntimeException(
550
                'Unable to save temporary key files in folder.'
551
            );
552
        }
553
    }
554
555
    /**
556
     * Create a unique random file name
557
     * @param integer $n
558
     * @return string
559
     */
560
    protected function randomName($n = 10)
561
    {
562
        $name = $this->certsdir . Strings::randomString($n) . '.pem';
563
        if (!$this->filesystem->has($name)) {
564
            return $name;
565
        }
566
        $this->randomName($n + 5);
567
    }
568
569
    /**
570
     * Delete all files in folder
571
     * @return void
572
     */
573 4
    public function removeTemporarilyFiles()
574
    {
575 4
        if (empty($this->filesystem) || empty($this->certsdir)) {
576 4
            return;
577
        }
578
        //remove os certificados
579
        $this->filesystem->delete($this->certfile);
580
        $this->filesystem->delete($this->prifile);
581
        $this->filesystem->delete($this->pubfile);
582
        //remove todos os arquivos antigos
583
        $contents = $this->filesystem->listContents($this->certsdir, true);
584
        $dt = new \DateTime();
585
        $tint = new \DateInterval("PT" . $this->waitingTime . "M");
586
        $tint->invert = 1;
587
        $tsLimit = $dt->add($tint)->getTimestamp();
588
        foreach ($contents as $item) {
589
            if ($item['type'] == 'file') {
590
                $timestamp = $this->filesystem->getTimestamp($item['path']);
591
                if ($timestamp < $tsLimit) {
592
                    $this->filesystem->delete($item['path']);
593
                }
594
            }
595
        }
596
    }
597
598
    /**
599
     * Save request envelope and response for debug reasons
600
     * @param string $operation
601
     * @param string $request
602
     * @param string $response
603
     * @return void
604
     */
605
    public function saveDebugFiles($operation, $request, $response)
606
    {
607
        if (!$this->debugmode) {
608
            return;
609
        }
610
        $this->debugdir = $this->certificate->getCnpj() . '/debug/';
611
        $now = \DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
612
        $time = substr($now->format("ymdHisu"), 0, 16);
613
        try {
614
            $this->filesystem->put(
615
                $this->debugdir . $time . "_" . $operation . "_sol.txt",
616
                $request
617
            );
618
            $this->filesystem->put(
619
                $this->debugdir . $time . "_" . $operation . "_res.txt",
620
                $response
621
            );
622
        } catch (\Exception $e) {
623
            throw new RuntimeException(
624
                'Unable to create debug files.'
625
            );
626
        }
627
    }
628
}
629