How to append XML string to DOMDocument without namespaces - php

I'm using PHP and have to build a SOAP (1.1, Document/Literal) request that contains a XML message inside soap:Body tag.
My first issue is that I'd never used this "protocol" before.
My XML message is quite complex so I'm using SimpleXMLElement class to build it separately. To compose the SOAP message, I have two XML strings:
1- The SOAP structure.
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header></soap:Header>
<soap:Body>...</soap:Body>
</soap:Envelope>
2- My Custom XML String
<eSocial xmlns="http://www.esocial.gov.br/schema/lote/eventos/envio/v2_2_02" grupo="1">
<envioLoteEventos>
<ideEmpregador tpInsc="1" nrInsc="0000000012"/>
<ideTransmissor tpInsc="1" nrInsc="0000000012"/>
<eventos>
<eSocial xmlns="http://www.esocial.gov.br/schema/evt/evtInfoEmpregador/v2_2_02">
<evtInfoEmpregador Id="8515">...</evtInfoEmpregador>
</eSocial>
</eventos>
</envioLoteEventos>
</eSocial>
What I need: The second string inside soap:Body tag exactly as it is.
What I get: The second string inside soap:Body with a bunch of namespaces automatically added by DOMDocument.
The algorithm (not full code) that I'm using:
$soapBodyElement = new SimpleXMLElement($soapBodyString);
$customMessageElement = new SimpleXMLElement($customMessageString);
// Some operations...
$domParent = dom_import_simplexml($soapBodyElement);
$domChild = dom_import_simplexml($customMessageElement);
$domDocument = $domParent->ownerDocument->importNode($domChild, true);
$domParent->appendChild($domDocument);
echo $domParent->ownerDocument->saveXML();
Output:
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header/>
<soap:Body>
<eSocial xmlns="http://www.esocial.gov.br/schema/lote/eventos/envio/v2_2_02" xmlns:default="http://www.esocial.gov.br/schema/evt/evtInfoEmpregador/v2_2_02" xmlns:default1="http://www.w3.org/2000/09/xmldsig#" grupo="1">
<envioLoteEventos>
<ideEmpregador tpInsc="1" nrInsc="0000000012"/>
<ideTransmissor tpInsc="1" nrInsc="0000000012"/>
<eventos>
<default:eSocial xmlns="http://www.esocial.gov.br/schema/evt/evtInfoEmpregador/v2_2_02">
<default:evtInfoEmpregador Id="2550">...</default:evtInfoEmpregador>
</default:eSocial>
</eventos>
</envioLoteEventos>
</eSocial>
</soap:Body>
</soap:Envelope>
Honestly, I don't know why this default prefixes (namespaces?) were added.
How can I append two XML strings without this automatic behavior?

You could use the Marshal XML Serializer which is much easier to use then DOMDocument and SimpleXML.
Then you could do the following:
SoapEnvelopeMapper.php
use KingsonDe\Marshal\AbstractXmlMapper;
class SoapEnvelopeMapper extends AbstractXmlMapper {
/**
* #var AbstractXmlMapper
*/
private $messageMapper;
public function __construct(AbstractXmlMapper $messageMapper) {
$this->messageMapper = $messageMapper;
}
public function map($data) {
return [
'soap:Envelope' => [
$this->attributes() => [
'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
'xmlns:soap' => 'http://www.w3.org/2003/05/soap-envelope',
],
'soap:Header' => null,
'soap:Body' => $this->item($this->messageMapper, $data),
]
];
}
}
SoapMessageMapper.php
use KingsonDe\Marshal\AbstractXmlMapper;
class SoapMessageMapper extends AbstractXmlMapper {
public function map($data) {
return [
'eSocial' => [
$this->attributes() => [
'xmlns' => 'http://www.esocial.gov.br/schema/lote/eventos/envio/v2_2_02',
'xmlns:default' => 'http://www.esocial.gov.br/schema/evt/evtInfoEmpregador/v2_2_02',
'xmlns:default1' => 'http://www.w3.org/2000/09/xmldsig#',
'grupo' => 1,
],
'envioLoteEventos' => [
'ideEmpregador' => [
$this->attributes() => [
'tpInsc' => 1,
'nrInsc' => '0000000012',
],
],
'ideTransmissor' => [
$this->attributes() => [
'tpInsc' => 1,
'nrInsc' => '0000000012',
],
],
],
'eventos' => [
'eSocial' => [
$this->attributes() => [
'xmlns' => 'http://www.esocial.gov.br/schema/evt/evtInfoEmpregador/v2_2_02',
],
'evtInfoEmpregador' => [
$this->attributes() => [
'Id' => 2550,
],
$this->data() => '...',
],
],
],
]
];
}
}
SoapResponse.php
$data = new \stdClass();
$messageMapper = new SoapMessageMapper();
$envelopeMapper = new SoapEnvelopeMapper($messageMapper);
$xml = MarshalXml::serializeItem($envelopeMapper, $data);

Related

PHP / Docusign - Verify HMAC signature on completed event

I'm trying to secure my callback url when completed event is triggered.
My Controller:
public function callbackSubscriptionCompleted(
int $subscriptionId,
DocusignService $docusignService,
Request $request
) {
$signature = $request->headers->get("X-DocuSign-Signature-1");
$payload = file_get_contents('php://input');
$isValid = $docusignService->isValidHash($signature, $payload);
if (!$isValid) {
throw new ApiException(
Response::HTTP_BAD_REQUEST,
'invalid_subscription',
'Signature not OK'
);
}
return new Response("Signature OK", Response::HTTP_OK);
}
My DocusignService functions:
private function createEnvelope(Company $company, Subscription $subscription, LegalRepresentative $legalRepresentative, Correspondent $correspondent, $correspondents) : array
{
// ...
$data = [
'disableResponsiveDocument' => 'false',
'emailSubject' => 'Your Subscription',
'emailBlurb' => 'Subscription pending',
'status' => 'sent',
'notification' => [
'useAccountDefaults' => 'false',
'reminders' => [
'reminderEnabled' => 'true',
'reminderDelay' => '1',
'reminderFrequency' => '1'
],
'expirations' => [
'expireEnabled' => 'True',
'expireAfter' => '250',
'expireWarn' => '2'
]
],
'compositeTemplates' => [
[
'serverTemplates' => [
[
'sequence' => '1',
'templateId' => $this->templateId
]
],
'inlineTemplates' => [
[
'sequence' => '2',
'recipients' => [
'signers' => [
[
'email' => $legalRepresentative->getEmail(),
'name' => $legalRepresentative->getLastname(),
'recipientId' => '1',
'recipientSignatureProviders' => [
[
'signatureProviderName' => 'universalsignaturepen_opentrust_hash_tsp',
'signatureProviderOptions' => [
'sms' => substr($legalRepresentative->getCellphone(), 0, 3) == '+33' ? $legalRepresentative->getCellphone() : '+33' . substr($legalRepresentative->getCellphone(), 1),
]
]
],
'roleName' => 'Client',
'clientUserId' => $legalRepresentative->getId(),
'tabs' => [
'textTabs' => $textTabs,
'radioGroupTabs' => $radioTabs,
'checkboxTabs' => $checkboxTabs
]
]
]
]
]
]
]
],
'eventNotification' => [
"url" => $this->router->generate("api_post_subscription_completed_callback", [
"subscriptionId" => $subscription->getId()
], UrlGeneratorInterface::ABSOLUTE_URL),
"includeCertificateOfCompletion" => "false",
"includeDocuments" => "true",
"includeDocumentFields" => "true",
"includeHMAC" => "true",
"requireAcknowledgment" => "true",
"envelopeEvents" => [
[
"envelopeEventStatusCode" => "completed"
]
]
]
];
$response = $this->sendRequest(
'POST',
$this->getBaseUri() . '/envelopes',
[
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $this->getCacheToken()
],
json_encode($data)
);
}
public function isValidHash(string $signature, string $payload): bool
{
$hexHash = hash_hmac('sha256',utf8_encode($payload),utf8_encode($this->hmacKey));
$base64Hash = base64_encode(hex2bin($hexHash));
return $signature === $base64Hash;
}
I've created my hmac key in my Docusign Connect and i'm receiving the signature in the header and the payload but the verification always failed.
I've followed the Docusign documentation here
What's wrong ?
PS: Sorry for my bad english
Your code looks good to me. Make sure that you are only sending one HMAC signature. That way your hmacKey is the correct one.
As a check, I'd print out the utf8_encode($payload) and check that it looks right (it should be the incoming XML, no headers). Also, I don't think it should have a CR/NL in it at the beginning. That's the separator between the HTTP header and body.
Update
I have verified that the PHP code from the DocuSign web site works correctly.
The payload value must not contain either a leading or trailing newline. It should start with <?xml and end with >
I suspect that your software is adding a leading or trailing newline.
The secret (from DocuSign) ends with an =. It is a Base64 encoded value. Do not decode it. Just use it as a string.
Another update
The payload (the body of the request) contains zero new lines.
If you're printing the payload, you'll need to wrap it in <pre> since it includes < characters. Or look at the page source.
It contains UTF-8 XML such as
<?xml version="1.0" encoding="utf-8"?><DocuSignEnvelopeInformation xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.docusign.net/API/3.0"><EnvelopeStatus><RecipientStatuses><RecipientStatus><Type>Signer</Type><Email>larry#worldwidecorp.us</Email><UserName>Larry Kluger</UserName><RoutingOrder>1</RoutingOrder><Sent>2020-08-05T03:11:13.057</Sent><Delivered>2020-08-05T03:11:27.657</Delivered><DeclineReason xsi:nil="true" /><Status>Delivered</Status><RecipientIPAddress>5.102.239.40</RecipientIPAddress><CustomFields /><TabStatuses><TabStatus><TabType>Custom</TabType><Status>Active</Status><XPosition>223</XPosition><YPosition>744....
We've done some more testing, and the line
$payload = file_get_contents('php://input');
should be very early in your script. The problem is that a framework can munge the php://input stream so it won't work properly thereafter.
Note this page from the Symfony site -- it indicates that the right way to get the request body is:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\ParameterBag;
$app->before(function (Request $request) {
$payload = $request->getContent();
hmac_verify($payload, $secret);
});
I would try to use the Symfony code instead of file_get_contents('php://input');

Delete hosted zone resource record set with PHP on amazon

I can't figure out how to delete hosted zone resource record set with Amazon PHP sdk.
So my code is following
public function __construct(\ConsoleOutput $stdout = null, \ConsoleOutput $stderr = null, \ConsoleInput $stdin = null) {
parent::__construct($stdout, $stderr, $stdin);
/** #var \Aws\Route53\Route53Client route53Client */
$this->route53Client = Route53Client::factory([
'version' => '2013-04-01',
'region' => 'eu-west-1',
'credentials' => [
'key' => <my-key>,
'secret' => <my-secret-key>
]
]);
}
And this is my function for deleting resource record set
private function deleteResourceRecordSet() {
$response = $this->route53Client->changeResourceRecordSets([
'ChangeBatch' => [
'Changes' => [
[
'Action' => 'DELETE',
'ResourceRecordSet' => [
'Name' => 'pm-bounces.subdomain.myDomain.com.',
'Region' => 'eu-west-1',
'Type' => 'CNAME',
],
]
]
],
'HostedZoneId' => '/hostedzone/<myHostedZoneId>'
]);
var_dump($response);
die();
}
And the error I'm keep getting is
Error executing "ChangeResourceRecordSets" on "https://route53.amazonaws.com/2013-04-01/hostedzone/<myHostedZoneId>/rrset/"; AWS HTTP error: Client error: `POST https://route53.amazonaws.com/2013-04-01/hostedzone/<myHostedZoneId>/rrset/` resulted in a `400 Bad Request` response:
<?xml version="1.0"?>
<ErrorResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/"><Error><Type>Sender</Type><Co (truncated...)
InvalidInput (client): Invalid request: Expected exactly one of [AliasTarget, all of [TTL, and ResourceRecords], or TrafficPolicyInstanceId], but found none in Change with [Action=DELETE, Name=pm-bounces.subdomain.myDomain.com., Type=CNAME, SetIdentifier=null] - <?xml version="1.0"?>
<ErrorResponse xmlns="https://route53.amazonaws.com/doc/2013-04-01/"><Error><Type>Sender</Type><Code>InvalidInput</Code><Message>Invalid request: Expected exactly one of [AliasTarget, all of [TTL, and ResourceRecords], or TrafficPolicyInstanceId], but found none in Change with [Action=DELETE, Name=pm-bounces.subdomain.myDomain.com., Type=CNAME, SetIdentifier=null]</Message>
So what exactly is minimum required set of params so I will be available to delete resource record from hosted zone? If you need any additional informations, please let me know and I will provide. Thank you
Ok I have figure it out. If you wan't to delete resource record set from hosted zones, then the code/function for deleting record set should look like following
private function deleteResourceRecordSet($zoneId, $name, $ResourceRecordsValue, $recordType, $ttl) {
$response = $this->route53Client->changeResourceRecordSets([
'ChangeBatch' => [
'Changes' => [
[
'Action' => 'DELETE',
"ResourceRecordSet" => [
'Name' => $name,
'Type' => $recordType,
'TTL' => $ttl,
'ResourceRecords' => [
$ResourceRecordsValue // should be reference array of all resource records set
]
]
]
]
],
'HostedZoneId' => $zoneId
]);
}

I want to send a soap request using php with multiple items

i want to send a soap request using php but i found a problem here is what i have to send as XML format.
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:tem="http://tempuri.org/">
<soap:Header/>
<soap:Body>
<tem:getImpayee>
<tem:tocken>reererer</tem:tocken>
<tem:CodeCreancier>1013</tem:CodeCreancier>
<tem:CodeCreance>01</tem:CodeCreance>
<tem:crVals>
<tem:CreancierVals>
<tem:nomChamp>montant</tem:nomChamp>
<tem:ValeurChamp>45</tem:ValeurChamp>
</tem:CreancierVals>
<tem:CreancierVals>
<tem:nomChamp>ND</tem:nomChamp>
<tem:ValeurChamp>0663143327</tem:ValeurChamp>
</tem:CreancierVals>
</tem:crVals>
</tem:getImpayee>
</soap:Body>
</soap:Envelope>
but the problem is that there is multiple "tem:CreancierVals" when i use this
$client->__setLocation(url_PaiementWS);
$result = $client->getImpayee(
array(
'tocken' => $_SESSION['tocken'],
'CodeCreancier' => $CodeCreancier,
'CodeCreance' => $CodeCreance,
'crVals' => array (
'CreancierVals' => array(
'nomChamp' => 'montant',
'ValeurChamp' => $montant
),
'CreancierVals' => array(
'nomChamp' => 'ND',
'ValeurChamp' => $ND
)
)
)
);
it doesnt work
its like reseting the "CreancierVals" val
is there a solution in which i can send pure xml using php ? or just a dynamic method that doesn't reseting the "CreancierVals" val but add one to the other as an array for example. thanks
Try this:
$result = $client->getImpayee(
[
'tocken' => $_SESSION['tocken'],
'CodeCreancier' => $CodeCreancier,
'CodeCreance' => $CodeCreance,
'crVals' => [
'CreancierVals' => [
[
'nomChamp' => 'montant',
'ValeurChamp' => $montant,
],
[
'nomChamp' => 'ND',
'ValeurChamp' => $ND,
],
],
],
]
);

How to deal with abstract types in WSDL-enabled SoapClient?

I need to handle external SOAP service with WSDL. Example of valid request (simplified) is:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<RunCommand xmlns="https://me.com/MyService">
<request>
<Credentials>
<User>jack</User>
<Password>abc123</Password>
</Credentials>
<Command xsi:type="SomeCommand">
<Foo>bar</Foo>
</Command>
</request>
</RunCommand>
</soap:Body>
</soap:Envelope>
The problem is that according to WSDL, Command element is of abstract CommandType type, so I need to specify xsi:type. I know how to do it with SoapVar, but it seems like if I use SoapVar, all WSDL-mode automation is gone.
For simple WSDLs, that would be enough:
$soapClient->RunCommand([
'Request' => [
'Credentials' => [
'user' => 'jack',
'password' => 'abc123',
],
'Command' => [
'Foo' => 'bar'
]
]
]);
But in this case, I'm getting an exception:
The specified type is abstract: name='CommandType'
I know how to make Command element for SoapClient, but I'm not sure how to mix it all together. I tried this:
$soapClient->RunCommand([
'Request' => [
'Credentials' => [
'user' => 'jack',
'password' => 'abc123',
],
new SoapVar(
['Foo' => 'bar'],
SOAP_ENC_OBJECT,
'CommandType',
null,
'Command'
)
]
]);
But it doesn't create a request I need. How can I form the request I need with SoapClient to keep as much WSDL goodies as possible?

Making the SoapHeader for DHL

I'm trying to make a soapHeader with nested parameters as it shown in an example from dhl developer portal:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:cis="http://dhl.de/webservice/cisbase"
xmlns:bcs="http://dhl.de/webservices/businesscustomershipping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soap:Header>
<cis:Authentification>
<cis:user>2222222222_01</cis:user>
<cis:signature>pass</cis:signature>
</cis:Authentification>
</soap:Header>
The way I do the header:
class DhlSoapClient {
public $apiUrl = 'https://cig.dhl.de/cig-wsdls/com/dpdhl/wsdl/geschaeftskundenversand-api/1.0/geschaeftskundenversand-api-1.0.wsdl';
public $dhlSandboxUrl = 'https://cig.dhl.de/services/sandbox/soap';
public $soapClient;
public $credentials;
public function buildClient($credentials, $sandbox = false)
{
$this->soapClient = new \SoapClient($this->apiUrl, ['trace' => 1]);
$this->credentials = $credentials;
$this->buildAuthHeader();
return $this->soapClient;
}
public function buildAuthHeader()
{
$authParams = [
'user' => $this->credentials['user'],
'signature' => $this->credentials['signature'],
'type' => 0
];
$authvalues = new \SoapVar(new \SoapVar($authParams, SOAP_ENC_OBJECT), SOAP_ENC_OBJECT);
$soapHeader = new \SoapHeader($this->dhlSandboxUrl, 'Authentification', $authvalues);
$this->soapClient->__setSoapHeaders($soapHeader);
}}
So I get this client:
object(SoapClient) {
trace => (int) 1
_soap_version => (int) 1
sdl => resource
__default_headers => [
(int) 0 => object(SoapHeader) {
namespace => 'https://cig.dhl.de/services/sandbox/soap'
name => 'Authentification'
data => object(SoapVar) {
enc_type => (int) 301
enc_value => object(SoapVar) {
enc_type => (int) 301
enc_value => [
'user' => '2222222222_01',
'signature' => 'pass'
]}}mustUnderstand => false}]}
And as a result I get this header:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="http://dhl.de/webservice/cisbase"
xmlns:ns2="http://de.ws.intraship"
xmlns:ns3="https://cig.dhl.de/services/sandbox/soap">
<SOAP-ENV:Header>
<ns3:Authentification>
<user>2222222222_01</user>
<signature>pass</signature>
</ns3:Authentification>
</SOAP-ENV:Header>
As I try to get $response = $this->client->CreateShipmentDD($shipmentInfo);
I get this:
object(SoapFault) {
faultstring => 'Authorization Required'
faultcode => 'HTTP'
[protected] message => 'Authorization Required'
Maybe the problem is that parameters user and signature inside Authentification have no prefix as it has to be and it cause the SoapFault exception, but they are adding automatically so I have to make kind of nested SoapHeader.
I had the same problem. The Authorization Required is for the HTTP Auth. You need to add the authorization values to the $options array in SoapClient::__construct($wsdlUri, $options).
$this->soapClient = new \SoapClient($this->apiUrl,
['trace' => 1,
'login' => <DHL_DEVELOPER_ID>,
'password' => <DHL_DEVELOPER_PASSWD>,
]
);
You can add the location to select the sandbox:
$this->soapClient = new \SoapClient($this->apiUrl,
['trace' => 1,
'login' => <DHL_DEVELOPER_ID>,
'password' => <DHL_DEVELOPER_PASSWD>,
'location' => <DHL_SANDBOX_URL>|<DHL_PRODUCTION_URL>,
'soap_version' => SOAP_1_1,
'encoding' => 'utf-8',
]
);
The DHL_DEVELOPER_ID is not a mail address. You find it in "Mein Konto" (My Account).

Categories