Skip to content

Commit

Permalink
Issue thephpleague#131 Force expected transactionId to be supplied fo…
Browse files Browse the repository at this point in the history
…r Form complete.
  • Loading branch information
judgej committed Aug 24, 2019
1 parent fe5f59a commit b7f1312
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 4 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -696,9 +696,12 @@ user's return. This will be at your `returnUrl` endpoint:

```php
// The result will be read and decrypted from the return URL (or failure URL)
// query parameters:
// query parameters.
// You MUST provide the original expected transactionId, which is validated
// against the transactionId provided in the server request.
// This prevents different payments getting mixed up.

$result = $gateway->completeAuthorize()->send();
$result = $gateway->completeAuthorize(['transactionId' => $originalTransactionId])->send();

$result->isSuccessful();
$result->getTransactionReference();
Expand Down
26 changes: 25 additions & 1 deletion src/Message/Form/CompleteAuthorizeRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Omnipay\SagePay\Message\AbstractRequest;
use Omnipay\SagePay\Message\Response as GenericResponse;
use Omnipay\Common\Exception\InvalidResponseException;
use Omnipay\Common\Exception\InvalidRequestException;

class CompleteAuthorizeRequest extends AbstractRequest
{
Expand Down Expand Up @@ -72,14 +73,37 @@ public function getData()

/**
* Nothing to send to gateway - we have the result data in the server request.
*
* @throws InvalidResponseException
* @throws InvalidResponseException
*/
public function sendData($data)
{
$this->response = new GenericResponse($this, $data);

// Issue #131: confirm the response is for the transaction ID we are
// expecting, and not replayed from another transaction.

$originalTransactionId = $this->getTransactionId();
$returnedTransactionId = $this->response->getTransactionId();

if (empty($originalTransactionId)) {
throw new InvalidRequestException('Missing expected transactionId parameter');
}

if ($originalTransactionId !== $returnedTransactionId) {
throw new InvalidResponseException(sprintf(
'Unexpected transactionId; expected "%s" received "%s"',
$originalTransactionId,
$returnedTransactionId
));
}

// The Response in the current namespace conflicts with
// the Response in the namespace one level down, but only
// for PHP 5.6. This alias works around it.

return $this->response = new GenericResponse($this, $data);
return $this->response;
}

/**
Expand Down
48 changes: 47 additions & 1 deletion tests/FormGatewayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public function setUp()

$this->completePurchaseOptions = [
'encryptionKey' => '2f52208a25a1facf',
'transactionId' => 'phpne-demo-53922585',
];
}

Expand Down Expand Up @@ -235,7 +236,12 @@ public function testCompletePurchaseSuccess()

$this->getHttpRequest()->initialize(['crypt' => '@5548276239c33e937e4d9d847d0a01f4c05f1b71dd5cd32568b6985a6d6834aca672315cf3eec01bb20d34cd1ccd7bdd69a9cd89047f7f875103b46efd8f7b97847eea6b6bab5eb8b61da9130a75fffa1c9152b7d39f77e534ea870281b8e280ea1fdbd49a8f5a7c67d1f512fe7a030e81ae6bd2beed762ad074edcd5d7eb4456a6797911ec78e4d16e7d3ac96b919052a764b7ee4940fd6976346608ad8fed1eb6b0b14d84d802c594b3fd94378a26837df66b328f01cfd144f2e7bc166370bf7a833862173412d2798e8739ee7ef9b0568afab0fc69f66af19864480bf3e74fe2fd2043ec90396e40ab62dc9c1f32dee0e309af7561d2286380ebb497105bde2860d401ccfb4cfcd7047ad32e9408d37f5d0fe9a67bd964d5b138b2546a7d54647467c59384eaa20728cf240c460e36db68afdcf0291135f9d5ff58563f14856fd28534a5478ba2579234b247d0d5862c5742495a2ae18c5ca0d6461d641c5215b07e690280fa3eaf5d392e1d6e2791b181a500964d4bc6c76310e47468ae72edddc3c04d83363514c908624747118']);

$response = $this->gateway->completePurchase($this->completePurchaseOptions)->send();
$options = $this->completePurchaseOptions;

// Switch to the transaction ID actually encrypted in the server request.
$options['transactionId'] = 'phpne-demo-56260425';

$response = $this->gateway->completePurchase($options)->send();

$this->assertTrue($response->isSuccessful());
$this->assertFalse($response->isRedirect());
Expand Down Expand Up @@ -266,4 +272,44 @@ public function testCompletePurchaseSuccess()
$response->getData()
);
}

/**
* The wrong transaction ID is supplied with the server request.
*
* @expectedException Omnipay\Common\Exception\InvalidResponseException
*/
public function testCompletePurchaseReplayAttack()
{
//$this->expectException(Complicated::class);

// Set the "crypt" query parameter.

$this->getHttpRequest()->initialize(['crypt' => '@5548276239c33e937e4d9d847d0a01f4c05f1b71dd5cd32568b6985a6d6834aca672315cf3eec01bb20d34cd1ccd7bdd69a9cd89047f7f875103b46efd8f7b97847eea6b6bab5eb8b61da9130a75fffa1c9152b7d39f77e534ea870281b8e280ea1fdbd49a8f5a7c67d1f512fe7a030e81ae6bd2beed762ad074edcd5d7eb4456a6797911ec78e4d16e7d3ac96b919052a764b7ee4940fd6976346608ad8fed1eb6b0b14d84d802c594b3fd94378a26837df66b328f01cfd144f2e7bc166370bf7a833862173412d2798e8739ee7ef9b0568afab0fc69f66af19864480bf3e74fe2fd2043ec90396e40ab62dc9c1f32dee0e309af7561d2286380ebb497105bde2860d401ccfb4cfcd7047ad32e9408d37f5d0fe9a67bd964d5b138b2546a7d54647467c59384eaa20728cf240c460e36db68afdcf0291135f9d5ff58563f14856fd28534a5478ba2579234b247d0d5862c5742495a2ae18c5ca0d6461d641c5215b07e690280fa3eaf5d392e1d6e2791b181a500964d4bc6c76310e47468ae72edddc3c04d83363514c908624747118']);

// These options contain a different transactionId from the once expected.

$options = $this->completePurchaseOptions;

$response = $this->gateway->completePurchase($options)->send();
}

/**
* The missing expected transaction ID supplied by the app.
*
* @expectedException Omnipay\Common\Exception\InvalidRequestException
*/
public function testCompletePurchaseMissingExpectedParam()
{
//$this->expectException(Complicated::class);

// Set the "crypt" query parameter.

$this->getHttpRequest()->initialize(['crypt' => '@5548276239c33e937e4d9d847d0a01f4c05f1b71dd5cd32568b6985a6d6834aca672315cf3eec01bb20d34cd1ccd7bdd69a9cd89047f7f875103b46efd8f7b97847eea6b6bab5eb8b61da9130a75fffa1c9152b7d39f77e534ea870281b8e280ea1fdbd49a8f5a7c67d1f512fe7a030e81ae6bd2beed762ad074edcd5d7eb4456a6797911ec78e4d16e7d3ac96b919052a764b7ee4940fd6976346608ad8fed1eb6b0b14d84d802c594b3fd94378a26837df66b328f01cfd144f2e7bc166370bf7a833862173412d2798e8739ee7ef9b0568afab0fc69f66af19864480bf3e74fe2fd2043ec90396e40ab62dc9c1f32dee0e309af7561d2286380ebb497105bde2860d401ccfb4cfcd7047ad32e9408d37f5d0fe9a67bd964d5b138b2546a7d54647467c59384eaa20728cf240c460e36db68afdcf0291135f9d5ff58563f14856fd28534a5478ba2579234b247d0d5862c5742495a2ae18c5ca0d6461d641c5215b07e690280fa3eaf5d392e1d6e2791b181a500964d4bc6c76310e47468ae72edddc3c04d83363514c908624747118']);

$options = $this->completePurchaseOptions;

unset($options['transactionId']);

$response = $this->gateway->completePurchase($options)->send();
}
}

0 comments on commit b7f1312

Please sign in to comment.