Skip to content

Instantly share code, notes, and snippets.

@lpj145
Created December 4, 2017 13:37
Show Gist options
  • Save lpj145/3a4c26cd913cfe0f3798e95d0d1fff60 to your computer and use it in GitHub Desktop.
Save lpj145/3a4c26cd913cfe0f3798e95d0d1fff60 to your computer and use it in GitHub Desktop.
<?php
namespace NFePHP\Common\Soap;
use NFePHP\Common\Certificate;
use NFePHP\Common\Soap\SoapInterface;
use NFePHP\Common\Exception\SoapException;
use NFePHP\Common\Exception\RuntimeException;
use NFePHP\Common\Strings;
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local;
use Psr\Log\LoggerInterface;
/**
* Soap base class
*
* @category NFePHP
* @package NFePHP\Common\Soap\SoapBase
* @copyright NFePHP Copyright (c) 2017
* @author Roberto L. Machado <[email protected]>
* @license http://www.gnu.org/licenses/lgpl.txt LGPLv3+
* @license https://opensource.org/licenses/MIT MIT
* @license http://www.gnu.org/licenses/gpl.txt GPLv3+
* @link http://github.com/nfephp-org/sped-nfse for the canonical source repository
*/
abstract class SoapBase implements SoapInterface
{
/**
* Soap SSL protocol version
*
* @var int
*/
protected $soapprotocol = self::SSL_DEFAULT;
/**
* Soap limit timeout
*
* @var int
*/
protected $soaptimeout = 20;
/**
* Connection Ip proxy
*
* @var string
*/
protected $proxyIP;
/**
* Connection Port proxy
*
* @var int
*/
protected $proxyPort;
/**
* Username to proxy
*
* @var string
*/
protected $proxyUser;
/**
* Password to proxy
*
* @var string
*/
protected $proxyPass;
/**
* Prefixes for soap envelop
*
* @var array
*/
protected $prefixes = [1 => 'soapenv', 2 => 'soap'];
/**
* Client certificate
*
* @var Certificate
*/
protected $certificate;
/**
* Logger to enable logs
*
* @var LoggerInterface
*/
protected $logger;
/**
* Temp folder
*
* @var string
*/
protected $tempdir;
/**
* Certificate dir
*
* @var string
*/
protected $certsdir;
/**
* Where save debug logs
*
* @var string
*/
protected $debugdir;
/**
* Private file
*
* @var string
*/
protected $prifile;
/**
* Public file
*
* @var string
*/
protected $pubfile;
/**
* Certificate file
*
* @var string
*/
protected $certfile;
/**
* Certificate for sefaz
*
* @var string
*/
protected $casefaz;
/**
* Disable soap security
*
* @var bool
*/
protected $disablesec = false;
/**
* Disable certificate validation
*
* @var bool
*/
protected $disableCertValidation = false;
/**
* Adapter to files
*
* @var \League\Flysystem\Adapter\Local
*/
protected $adapter;
/**
* File system Object
*
* @var \League\Flysystem\Filesystem
*/
protected $filesystem;
/**
* Temporary password
*
* @var string
*/
protected $temppass = '';
/**
* Encrypt private key
*
* @var bool
*/
protected $encriptPrivateKey = true;
/**
* Set debug mode
*
* @var bool
*/
protected $debugmode = false;
/**
* Response head
*
* @var string
*/
public $responseHead;
/**
* Response body from sefaz
*
* @var string
*/
public $responseBody;
/**
* RequestHead from soap client
*
* @var string
*/
public $requestHead;
/**
* RequestBody from soap client
*
* @var string
*/
public $requestBody;
/**
* Soap client error
*
* @var string
*/
public $soaperror;
/**
* Soap information array
*
* @var array
*/
public $soapinfo = [];
/**
* Max time limit
*
* @var int
*/
public $waitingTime = 45;
/**
* SoapBase constructor.
*
* @param Certificate|null $certificate Certificate from client
* @param LoggerInterface|null $logger Logger object for logger
*/
public function __construct(
Certificate $certificate = null,
LoggerInterface $logger = null
) {
$this->logger = $logger;
$this->certificate = $this->_certIsExpired($certificate);
$this->setTemporaryFolder(sys_get_temp_dir() . '/sped/');
if (null !== $certificate) {
$this->saveTemporarilyKeyFiles();
}
}
/**
* Check if certificate is valid to currently used date
*
* @param Certificate $certificate
*
* @return Certificate
* @throws RuntimeException
*/
private function _certIsExpired(Certificate $certificate = null)
{
if ($this->disableCertValidation
&& null !== $certificate
&& $certificate->isExpired()
) {
throw new RuntimeException(
'The validity of the certificate has expired.'
);
}
return $certificate;
}
/**
* Destructor
* Clean temporary files
*/
public function __destruct()
{
$this->removeTemporarilyFiles();
}
/**
* Disables the security checking of host and peer certificates
*
* @param bool $flag
*
* @return bool
*/
public function disableSecurity(bool $flag = false)
{
return $this->disablesec = $flag;
}
/**
* ONlY for tests
*
* @param bool $flag value for validation
*
* @return bool
*/
public function disableCertValidation(bool $flag = true)
{
return $this->disableCertValidation = $flag;
}
/**
* Load path to CA and enable to use on SOAP
*
* @param string $capath Certificate Path
*
* @return void
*/
public function loadCA($capath)
{
if (is_file($capath)) {
$this->casefaz = $capath;
}
}
/**
* Set option to encrypt private key before save in filesystem
* for an additional layer of protection
*
* @param bool $encript encrypt set?
*
* @return bool
*/
public function setEncriptPrivateKey($encript = true)
{
return $this->encriptPrivateKey = $encript;
}
/**
* Set another temporayfolder for saving certificates for SOAP utilization
*
* @param string $folderRealPath
*
* @return void
*/
public function setTemporaryFolder($folderRealPath)
{
$this->tempdir = $folderRealPath;
$this->setLocalFolder($folderRealPath);
}
/**
* Set Local folder for flysystem
* @param string $folder
*/
protected function setLocalFolder($folder = '')
{
$this->adapter = new Local($folder);
$this->filesystem = new Filesystem($this->adapter);
}
/**
* Set debug mode, this mode will save soap envelopes in temporary directory
*
* @param bool $value
*
* @return bool
*/
public function setDebugMode($value = false)
{
return $this->debugmode = $value;
}
/**
* Set certificate class for SSL communications
*
* @param Certificate $certificate
*
* @return void
*/
public function loadCertificate(Certificate $certificate)
{
$this->certificate = $this->_certIsExpired($certificate);
}
/**
* Set logger class
*
* @param LoggerInterface $logger
*
* @return LoggerInterface
*/
public function loadLogger(LoggerInterface $logger)
{
return $this->logger = $logger;
}
/**
* Set timeout for communication
*
* @param int $timesecs
*
* @return int
*/
public function timeout(int $timesecs)
{
return $this->soaptimeout = $timesecs;
}
/**
* Set security protocol
*
* @param int $protocol
*
* @return int
*/
public function protocol($protocol = self::SSL_DEFAULT)
{
return $this->soapprotocol = $protocol;
}
/**
* Set prefixes
*
* @param array $prefixes
*
* @return string[]
*/
public function setSoapPrefix($prefixes = [])
{
return $this->prefixes = $prefixes;
}
/**
* Set proxy parameters
*
* @param string $ip
* @param int $port
* @param string $user
* @param string $password
*
* @return void
*/
public function proxy($ip, $port, $user, $password)
{
$this->proxyIP = $ip;
$this->proxyPort = $port;
$this->proxyUser = $user;
$this->proxyPass = $password;
}
/**
* @param string $url
* @param string $operation
* @param string $action
* @param int $soapver
* @param array $parameters
* @param array $namespaces
* @param string $request
* @param null $soapheader
*
* @return mixed
*/
abstract public function send(
$url,
$operation = '',
$action = '',
$soapver = SOAP_1_2,
$parameters = [],
$namespaces = [],
$request = '',
$soapheader = null
);
/**
* Mount soap envelope
* @param string $request
* @param array $namespaces
* @param $soapVer int
* @param \SOAPHeader $header
* @return string
*/
protected function makeEnvelopeSoap(
$request,
$namespaces,
$soapVer = SOAP_1_2,
\SoapHeader $header = null
) {
$prefix = $this->prefixes[$soapVer];
$envelopeAttributes = '';
// Interessante desmembrar em uma função
foreach ($namespaces as $key => $value) {
$envelopeAttributes = $key.'="'.$value.'"';
}
if (null === $header) {
return $this->mountEnvelopString(
$prefix,
$envelopeAttributes,
'',
$request
);
}
return $this->mountEnvelopString(
$prefix,
$envelopeAttributes,
$this->mountSoapHeaders($prefix, $header),
$request
);
}
/**
* @param string $envelopPrefix
* @param string $envelopAttributes
* @param string $header
* @param string $bodyContent
* @return string
*/
private function mountEnvelopString(string $envelopPrefix, string $envelopAttributes = '', string $header = '', string $bodyContent = '')
{
return sprintf(
'<%s:Envelope %s >'.$header.'<%s:Body>%s</%s:Body></%s:Envelop>',
$envelopPrefix,
$envelopAttributes,
$envelopPrefix,
$bodyContent,
$envelopPrefix,
$envelopPrefix
);
}
/**
* @param string $envelopPrefix
* @param \SoapHeader $header
* @return string
*/
private function mountSoapHeaders(string $envelopPrefix, \SoapHeader $header)
{
$headerItems = '';
foreach ($header->data as $key => $value) {
$headerItems = '<'.$key.'>'.$value.'<'.$key.'>';
}
return sprintf(
'<%s:Header><%s xmlns="%s">%s</%s></%s:Header>',
$envelopPrefix,
$header->name,
$header->ns ?? '',
$headerItems
);
}
/**
* Temporarily saves the certificate keys for use cURL or SoapClient
*/
public function saveTemporarilyKeyFiles()
{
if (!is_object($this->certificate)) {
throw new RuntimeException(
'Certificate not found.'
);
}
$this->certsdir = $this->certificate->getCnpj() . '/certs/';
$this->prifile = $this->certsdir. Strings::randomString(10).'.pem';
$this->pubfile = $this->certsdir . Strings::randomString(10).'.pem';
$this->certfile = $this->certsdir . Strings::randomString(10).'.pem';
$ret = true;
$private = $this->certificate->privateKey;
if ($this->encriptPrivateKey) {
//cria uma senha temporária ALEATÓRIA para salvar a chave primaria
//portanto mesmo que localizada e identificada não estará acessível
//pois sua senha não existe além do tempo de execução desta classe
$this->temppass = Strings::randomString(16);
//encripta a chave privada entes da gravação do filesystem
openssl_pkey_export(
$this->certificate->privateKey,
$private,
$this->temppass
);
}
$ret &= $this->filesystem->put(
$this->prifile,
$private
);
$ret &= $this->filesystem->put(
$this->pubfile,
$this->certificate->publicKey
);
$ret &= $this->filesystem->put(
$this->certfile,
$private."{$this->certificate}"
);
if (!$ret) {
throw new RuntimeException(
'Unable to save temporary key files in folder.'
);
}
}
/**
* Delete all files in folder
*/
public function removeTemporarilyFiles()
{
$contents = $this->filesystem->listContents($this->certsdir, true);
//define um limite de $waitingTime min, ou seja qualquer arquivo criado a mais
//de $waitingTime min será removido
//NOTA: quando ocorre algum erro interno na execução do script, alguns
//arquivos temporários podem permanecer
//NOTA: O tempo default é de 45 minutos e pode ser alterado diretamente nas
//propriedades da classe, esse tempo entre 5 a 45 min é recomendável pois
//podem haver processos concorrentes para um mesmo usuário. Esses processos
//como o DFe podem ser mais longos, dependendo a forma que o aplicativo
//utilize a API. Outra solução para remover arquivos "perdidos" pode ser
//encontrada oportunamente.
$dt = new \DateTime();
$tint = new \DateInterval("PT".$this->waitingTime."M");
$tint->invert = true;
$tsLimit = $dt->add($tint)->getTimestamp();
foreach ($contents as $item) {
if ($item['type'] == 'file') {
if ($item['path'] == $this->prifile
|| $item['path'] == $this->pubfile
|| $item['path'] == $this->certfile
) {
$this->filesystem->delete($item['path']);
continue;
}
$timestamp = $this->filesystem->getTimestamp($item['path']);
if ($timestamp < $tsLimit) {
//remove arquivos criados a mais de 45 min
$this->filesystem->delete($item['path']);
}
}
}
}
/**
* Save request envelope and response for debug reasons
* @param string $operation
* @param string $request
* @param string $response
* @return void
*/
public function saveDebugFiles($operation, $request, $response)
{
if (!$this->debugmode) {
return;
}
$this->debugdir = $this->certificate->getCnpj() . '/debug/';
$now = \DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
$time = substr($now->format("ymdHisu"), 0, 16);
try {
$this->filesystem->put(
$this->debugdir . $time . "_" . $operation . "_sol.txt",
$request
);
$this->filesystem->put(
$this->debugdir . $time . "_" . $operation . "_res.txt",
$response
);
} catch (\Exception $e) {
throw new RuntimeException(
'Unable to create debug files.'
);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment