Skip to content

Commit

Permalink
[CVE-2022-29254] Add extra validation on payment completion
Browse files Browse the repository at this point in the history
  • Loading branch information
kinglozzer committed May 25, 2022
1 parent 270e146 commit 047daa6
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 86 deletions.
19 changes: 8 additions & 11 deletions src/Service/AuthorizeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

namespace SilverStripe\Omnipay\Service;

use SilverStripe\Omnipay\Exception\InvalidStateException;
use SilverStripe\Omnipay\Exception\InvalidConfigurationException;
use SilverStripe\Omnipay\Exception\InvalidStateException;
use SilverStripe\Omnipay\Helper\ErrorHandling;
use SilverStripe\Omnipay\Model\Message\AuthorizeRequest;
use SilverStripe\Omnipay\Model\Message\AuthorizedResponse;
use SilverStripe\Omnipay\Model\Message\CompleteAuthorizeError;
use SilverStripe\Omnipay\Model\Message\AuthorizeError;
use SilverStripe\Omnipay\Model\Message\AuthorizeRedirectResponse;
use SilverStripe\Omnipay\Model\Message\AuthorizeRequest;
use SilverStripe\Omnipay\Model\Message\AwaitingAuthorizeResponse;
use SilverStripe\Omnipay\Model\Message\CompleteAuthorizeError;
use SilverStripe\Omnipay\Model\Message\CompleteAuthorizeRequest;

class AuthorizeService extends PaymentService
Expand Down Expand Up @@ -67,7 +67,7 @@ public function initiate($data = array())
);
} elseif ($serviceResponse->isError()) {
$this->createMessage(AuthorizeError::class, $response);
} else {
} elseif ($serviceResponse->isSuccessful()) {
$this->markCompleted('Authorized', $serviceResponse, $response);
}

Expand Down Expand Up @@ -118,15 +118,12 @@ public function complete($data = array(), $isNotification = false)

$serviceResponse = $this->wrapOmnipayResponse($response, $isNotification);

if ($serviceResponse->isError()) {
if ($serviceResponse->isAwaitingNotification()) {
ErrorHandling::safeExtend($this->payment, 'onAwaitingAuthorized', $serviceResponse);
} elseif ($serviceResponse->isError()) {
$this->createMessage(CompleteAuthorizeError::class, $response);
return $serviceResponse;
}

if (!$serviceResponse->isAwaitingNotification()) {
} elseif ($serviceResponse->isSuccessful()) {
$this->markCompleted('Authorized', $serviceResponse, $response);
} else {
ErrorHandling::safeExtend($this->payment, 'onAwaitingAuthorized', $serviceResponse);
}

return $serviceResponse;
Expand Down
20 changes: 9 additions & 11 deletions src/Service/CaptureService.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,25 +138,23 @@ public function initiate($data = array())

$serviceResponse = $this->wrapOmnipayResponse($response);

if ($serviceResponse->isAwaitingNotification()) {
if ($serviceResponse->isError()) {
$this->createMessage($this->errorMessageType, $response);
} elseif ($serviceResponse->isRedirect() || $serviceResponse->isAwaitingNotification()) {
if ($diff < 0) {
$this->createPartialPayment(PaymentMath::multiply($amount, '-1'), $this->pendingState);
} elseif ($diff > 0) {
$this->createPartialPayment($diff, $this->pendingState);
}
$this->payment->Status = $this->pendingState;
$this->payment->write();
} else {
if ($serviceResponse->isError()) {
$this->createMessage($this->errorMessageType, $response);
} else {
if ($diff < 0) {
$this->createPartialPayment(PaymentMath::multiply($amount, '-1'), $this->pendingState);
} elseif ($diff > 0) {
$this->createPartialPayment($diff, $this->pendingState);
}
$this->markCompleted($this->endState, $serviceResponse, $response);
} elseif ($serviceResponse->isSuccessful()) {
if ($diff < 0) {
$this->createPartialPayment(PaymentMath::multiply($amount, '-1'), $this->pendingState);
} elseif ($diff > 0) {
$this->createPartialPayment($diff, $this->pendingState);
}
$this->markCompleted($this->endState, $serviceResponse, $response);
}

return $serviceResponse;
Expand Down
40 changes: 21 additions & 19 deletions src/Service/CreateCardService.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@
namespace SilverStripe\Omnipay\Service;

use Omnipay\Common\Message\RequestInterface;
use SilverStripe\Omnipay\Exception\InvalidStateException;
use SilverStripe\Omnipay\Exception\InvalidConfigurationException;
use SilverStripe\Omnipay\Exception\InvalidStateException;
use SilverStripe\Omnipay\Helper\ErrorHandling;
use SilverStripe\Omnipay\Model\Message;
use SilverStripe\Omnipay\Model\Message\AwaitingCreateCardResponse;
use SilverStripe\Omnipay\Model\Message\CompleteCreateCardError;
use SilverStripe\Omnipay\Model\Message\CompleteCreateCardRequest;
use SilverStripe\Omnipay\Model\Message\CreateCardError;
use SilverStripe\Omnipay\Model\Message\CreateCardRedirectResponse;
use SilverStripe\Omnipay\Model\Message\CreateCardRequest;
use SilverStripe\Omnipay\Model\Message\CreateCardResponse;

class CreateCardService extends PaymentService
{

/**
* Start a createcard request
*
Expand Down Expand Up @@ -39,12 +44,12 @@ public function initiate($data = array())
$request = $this->oGateway()->createCard($gatewayData);
$this->extend('onAfterCreateCard', $request);

$this->createMessage(Message\CreateCardRequest::class, $request);
$this->createMessage(CreateCardRequest::class, $request);

try {
$response = $this->response = $request->send();
} catch (\Omnipay\Common\Exception\OmnipayException $e) {
$this->createMessage(Message\CreateCardError::class, $e);
$this->createMessage(CreateCardError::class, $e);
// create an error response
return $this->generateServiceResponse(ServiceResponse::SERVICE_ERROR);
}
Expand All @@ -58,12 +63,12 @@ public function initiate($data = array())
$this->payment->write();

$this->createMessage(
$serviceResponse->isRedirect() ? Message\CreateCardRedirectResponse::class : Message\AwaitingCreateCardResponse::class,
$serviceResponse->isRedirect() ? CreateCardRedirectResponse::class : AwaitingCreateCardResponse::class,
$response
);
} elseif ($serviceResponse->isError()) {
$this->createMessage(Message\CreateCardError::class, $response);
} else {
$this->createMessage(CreateCardError::class, $response);
} elseif ($serviceResponse->isSuccessful()) {
$this->markCompleted('CardCreated', $serviceResponse, $response);
}

Expand Down Expand Up @@ -103,26 +108,23 @@ public function complete($data = array(), $isNotification = false)
$request = $gateway->completeCreateCard($gatewayData);
$this->extend('onAfterCompleteCreateCard', $request);

$this->createMessage(Message\CompleteCreateCardRequest::class, $request);
$this->createMessage(CompleteCreateCardRequest::class, $request);
$response = null;
try {
$response = $this->response = $request->send();
} catch (\Omnipay\Common\Exception\OmnipayException $e) {
$this->createMessage(Message\CompleteCreateCardError::class, $e);
$this->createMessage(CompleteCreateCardError::class, $e);
return $this->generateServiceResponse($flags | ServiceResponse::SERVICE_ERROR);
}

$serviceResponse = $this->wrapOmnipayResponse($response, $isNotification);

if ($serviceResponse->isError()) {
$this->createMessage(Message\CompleteCreateCardError::class, $response);
return $serviceResponse;
}

if (!$serviceResponse->isAwaitingNotification()) {
$this->markCompleted('CardCreated', $serviceResponse, $response);
} else {
if ($serviceResponse->isAwaitingNotification()) {
ErrorHandling::safeExtend($this->payment, 'onAwaitingCreateCard', $serviceResponse);
} elseif ($serviceResponse->isError()) {
$this->createMessage(CompleteCreateCardError::class, $response);
} elseif ($serviceResponse->isSuccessful()) {
$this->markCompleted('CardCreated', $serviceResponse, $response);
}

return $serviceResponse;
Expand All @@ -131,7 +133,7 @@ public function complete($data = array(), $isNotification = false)
protected function markCompleted($endStatus, ServiceResponse $serviceResponse, $gatewayMessage)
{
parent::markCompleted($endStatus, $serviceResponse, $gatewayMessage);
$this->createMessage(Message\CreateCardResponse::class, $gatewayMessage);
$this->createMessage(CreateCardResponse::class, $gatewayMessage);
ErrorHandling::safeExtend($this->payment, 'onCardCreated', $serviceResponse);
}
}
5 changes: 4 additions & 1 deletion src/Service/NotificationCompleteService.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ public function complete($data = array(), $isNotification = true)
}

// check if we're done
if (!$serviceResponse->isError() && !$serviceResponse->isAwaitingNotification()) {
if (!$serviceResponse->isError()
&& !$serviceResponse->isAwaitingNotification()
&& $serviceResponse->isSuccessful()
) {
$this->markCompleted($this->endState, $serviceResponse, $serviceResponse->getOmnipayResponse());
}

Expand Down
38 changes: 20 additions & 18 deletions src/Service/PurchaseService.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@
use SilverStripe\Omnipay\Exception\InvalidStateException;
use SilverStripe\Omnipay\Exception\InvalidConfigurationException;
use SilverStripe\Omnipay\Helper\ErrorHandling;
use SilverStripe\Omnipay\Model\Message;
use SilverStripe\Omnipay\Model\Message\AwaitingPurchaseResponse;
use SilverStripe\Omnipay\Model\Message\CompletePurchaseError;
use SilverStripe\Omnipay\Model\Message\CompletePurchaseRequest;
use SilverStripe\Omnipay\Model\Message\PurchasedResponse;
use SilverStripe\Omnipay\Model\Message\PurchaseError;
use SilverStripe\Omnipay\Model\Message\PurchaseRedirectResponse;
use SilverStripe\Omnipay\Model\Message\PurchaseRequest;

class PurchaseService extends PaymentService
{
Expand Down Expand Up @@ -43,12 +49,12 @@ public function initiate($data = array())
$request = $this->oGateway()->purchase($gatewayData);
$this->extend('onAfterPurchase', $request);

$this->createMessage(Message\PurchaseRequest::class, $request);
$this->createMessage(PurchaseRequest::class, $request);

try {
$response = $this->response = $request->send();
} catch (\Omnipay\Common\Exception\OmnipayException $e) {
$this->createMessage(Message\PurchaseError::class, $e);
$this->createMessage(PurchaseError::class, $e);
// create an error response
return $this->generateServiceResponse(ServiceResponse::SERVICE_ERROR);
}
Expand All @@ -62,12 +68,12 @@ public function initiate($data = array())
$this->payment->write();

$this->createMessage(
$serviceResponse->isRedirect() ? Message\PurchaseRedirectResponse::class : Message\AwaitingPurchaseResponse::class,
$serviceResponse->isRedirect() ? PurchaseRedirectResponse::class : AwaitingPurchaseResponse::class,
$response
);
} elseif ($serviceResponse->isError()) {
$this->createMessage(Message\PurchaseError::class, $response);
} else {
$this->createMessage(PurchaseError::class, $response);
} elseif ($serviceResponse->isSuccessful()) {
$this->markCompleted('Captured', $serviceResponse, $response);
}

Expand Down Expand Up @@ -105,36 +111,32 @@ public function complete($data = array(), $isNotification = false)
$request = $gateway->completePurchase($gatewayData);
$this->extend('onAfterCompletePurchase', $request);

$this->createMessage(Message\CompletePurchaseRequest::class, $request);
$this->createMessage(CompletePurchaseRequest::class, $request);
$response = null;
try {
$response = $this->response = $request->send();
} catch (\Omnipay\Common\Exception\OmnipayException $e) {
$this->createMessage(Message\CompletePurchaseError::class, $e);
$this->createMessage(CompletePurchaseError::class, $e);
return $this->generateServiceResponse($flags | ServiceResponse::SERVICE_ERROR);
}

$serviceResponse = $this->wrapOmnipayResponse($response, $isNotification);
if ($serviceResponse->isError()) {
$this->createMessage(Message\CompletePurchaseError::class, $response);
return $serviceResponse;
}

// only update payment status if we're not waiting for a notification
if (!$serviceResponse->isAwaitingNotification()) {
$this->markCompleted('Captured', $serviceResponse, $response);
} else {
if ($serviceResponse->isAwaitingNotification()) {
ErrorHandling::safeExtend($this->payment, 'onAwaitingCaptured', $serviceResponse);
} elseif ($serviceResponse->isError()) {
$this->createMessage(CompletePurchaseError::class, $response);
} elseif ($serviceResponse->isSuccessful()) {
$this->markCompleted('Captured', $serviceResponse, $response);
}


return $serviceResponse;
}

protected function markCompleted($endStatus, ServiceResponse $serviceResponse, $gatewayMessage)
{
parent::markCompleted($endStatus, $serviceResponse, $gatewayMessage);
$this->createMessage(Message\PurchasedResponse::class, $gatewayMessage);
$this->createMessage(PurchasedResponse::class, $gatewayMessage);
ErrorHandling::safeExtend($this->payment, 'onCaptured', $serviceResponse);
}
}
30 changes: 16 additions & 14 deletions src/Service/RefundService.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
use SilverStripe\Omnipay\GatewayInfo;
use SilverStripe\Omnipay\Helper\ErrorHandling;
use SilverStripe\Omnipay\Helper\PaymentMath;
use SilverStripe\Omnipay\Model\Message;
use SilverStripe\Omnipay\Model\Message\PartiallyRefundedResponse;
use SilverStripe\Omnipay\Model\Message\RefundedResponse;
use SilverStripe\Omnipay\Model\Message\RefundError;
use SilverStripe\Omnipay\Model\Message\RefundRequest;
use SilverStripe\Omnipay\Model\Payment;

class RefundService extends NotificationCompleteService
Expand All @@ -20,9 +23,9 @@ class RefundService extends NotificationCompleteService

protected $pendingState = 'PendingRefund';

protected $requestMessageType = Message\RefundRequest::class;
protected $requestMessageType = RefundRequest::class;

protected $errorMessageType = Message\RefundError::class;
protected $errorMessageType = RefundError::class;

/**
* Return money to the previously charged credit card.
Expand Down Expand Up @@ -127,21 +130,20 @@ public function initiate($data = array())

$serviceResponse = $this->wrapOmnipayResponse($response);

if ($serviceResponse->isAwaitingNotification()) {
if ($serviceResponse->isError()) {
$this->createMessage($this->errorMessageType, $response);
} elseif ($serviceResponse->isRedirect() || $serviceResponse->isAwaitingNotification()) {
if ($isPartial) {
$this->createPartialPayment(PaymentMath::multiply($amount, '-1'), $this->pendingState);
}
$this->payment->Status = $this->pendingState;
$this->payment->write();
} else {
if ($serviceResponse->isError()) {
$this->createMessage($this->errorMessageType, $response);
} else {
if ($isPartial) {
$this->createPartialPayment(PaymentMath::multiply($amount, '-1'), $this->pendingState);
}
$this->markCompleted($this->endState, $serviceResponse, $response);
} elseif ($serviceResponse->isSuccessful()) {
if ($isPartial) {
$this->createPartialPayment(PaymentMath::multiply($amount, '-1'), $this->pendingState);
}

$this->markCompleted($this->endState, $serviceResponse, $response);
}

return $serviceResponse;
Expand Down Expand Up @@ -182,9 +184,9 @@ protected function markCompleted($endStatus, ServiceResponse $serviceResponse, $

parent::markCompleted($endStatus, $serviceResponse, $gatewayMessage);
if ($endStatus === 'Captured') {
$this->createMessage(Message\PartiallyRefundedResponse::class, $gatewayMessage);
$this->createMessage(PartiallyRefundedResponse::class, $gatewayMessage);
} else {
$this->createMessage(Message\RefundedResponse::class, $gatewayMessage);
$this->createMessage(RefundedResponse::class, $gatewayMessage);
}

ErrorHandling::safeExtend($this->payment, 'onRefunded', $serviceResponse);
Expand Down
16 changes: 16 additions & 0 deletions src/Service/ServiceResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,22 @@ public function getPayment()
return $this->payment;
}

/**
* Whether the response is marked as successful by Omnipay.
*
* @return bool
*/
public function isSuccessful()
{
if ($this->omnipayResponse instanceof NotificationInterface) {
return $this->omnipayResponse->getTransactionStatus() === NotificationInterface::STATUS_COMPLETED;
} elseif ($this->omnipayResponse instanceof AbstractResponse) {
return $this->omnipayResponse->isSuccessful();
}

return false;
}

/**
* Whether or not this is an *offsite* redirect.
* This is only the case when there's an Omnipay response present that *is* a redirect.
Expand Down

0 comments on commit 047daa6

Please sign in to comment.