SoapBase::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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