From 09dfcf32efab159f84264f6a7574376c9a1ee5e9 Mon Sep 17 00:00:00 2001 From: Can Demiralp Date: Tue, 24 Dec 2024 10:48:18 +0100 Subject: [PATCH] [ECP-9240] Refactor the process to download the Apple Pay domain association file, and change the misleading UI field label (#2818) * [ECP-9240] Refactor download process * [ECP-9240] Write unit tests for the controller * [ECP-9240] Formatting * [ECP-9240] Write unit tests * [ECP-9240] Convert string label to phrase for localisation * [ECP-9240] Start using CDN to download domain association file --------- Co-authored-by: Can Demiralp --- .../DownloadApplePayCertificate.php | 158 ------------------ .../DownloadApplePayDomainAssociationFile.php | 82 +++++++++ ...> ApplePayDomainAssociationFileButton.php} | 14 +- ...nloadApplePayDomainAssociationFileTest.php | 144 ++++++++++++++++ ...pplePayDomainAssociationFileButtonTest.php | 102 +++++++++++ .../adyen_alternative_payment_methods.xml | 4 +- .../templates/config/applepay_button.phtml | 3 - ...lepay_domain_association_file_button.phtml | 4 + 8 files changed, 341 insertions(+), 170 deletions(-) delete mode 100644 Controller/Adminhtml/Configuration/DownloadApplePayCertificate.php create mode 100644 Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFile.php rename Model/Config/Adminhtml/{ApplepayCertificateButton.php => ApplePayDomainAssociationFileButton.php} (76%) create mode 100644 Test/Unit/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFileTest.php create mode 100644 Test/Unit/Model/Config/Adminhtml/ApplePayDomainAssociationFileButtonTest.php delete mode 100644 view/adminhtml/templates/config/applepay_button.phtml create mode 100644 view/adminhtml/templates/config/applepay_domain_association_file_button.phtml diff --git a/Controller/Adminhtml/Configuration/DownloadApplePayCertificate.php b/Controller/Adminhtml/Configuration/DownloadApplePayCertificate.php deleted file mode 100644 index 5af0c4dcea..0000000000 --- a/Controller/Adminhtml/Configuration/DownloadApplePayCertificate.php +++ /dev/null @@ -1,158 +0,0 @@ - - */ - -namespace Adyen\Payment\Controller\Adminhtml\Configuration; - -use Adyen\AdyenException; -use Adyen\Payment\Helper\Config; -use Adyen\Payment\Logger\AdyenLogger; -use Magento\Framework\App\ResponseInterface; -use Magento\Framework\Controller\Result\Redirect; -use Magento\Framework\Controller\ResultInterface; -use Magento\Framework\Exception\FileSystemException; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Filesystem\DirectoryList; -use Magento\Backend\App\Action\Context; -use Magento\Framework\Controller\ResultFactory; -use Magento\Backend\App\Action; -use Magento\Framework\Filesystem\Io\File; -use Exception; -use ZipArchive; - -class DownloadApplePayCertificate extends Action -{ - const READ_LENGTH = 2048; - const MAX_FILES = 10; - const MAX_SIZE = 1000000; - const MAX_RATIO = 5; - const FILE_NAME = 'apple-developer-merchantid-domain-association'; - const APPLEPAY_CERTIFICATE_URL = 'https://docs.adyen.com/payment-methods/apple-pay/web-component/apple-developer-merchantid-domain-association-2024.zip'; - private $directoryList; - private $fileIo; - private $adyenLogger; - - public function __construct( - Context $context, - DirectoryList $directoryList, - File $fileIo, - AdyenLogger $adyenLogger - ) - { - parent::__construct($context); - $this->directoryList = $directoryList; - $this->fileIo = $fileIo; - $this->adyenLogger = $adyenLogger; - } - - /** - * @return ResponseInterface|Redirect|Redirect&ResultInterface|ResultInterface - * @throws FileSystemException - * @throws LocalizedExceptionff - */ - public function execute() - { - $redirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); - $redirect->setUrl($this->_redirect->getRefererUrl()); - - $pubPath = $this->directoryList->getPath('pub'); - $directoryName = '.well-known'; - - $wellknownPath = $pubPath . '/' . $directoryName; - $applepayPath = $wellknownPath . '/' . self::FILE_NAME; - - $applepayUrl = self::APPLEPAY_CERTIFICATE_URL; - - try { - if ($this->fileIo->checkAndCreateFolder($wellknownPath, 0700)) { - $this->downloadAndUnzip($applepayUrl, $wellknownPath); - } else { - $this->fileIo->chmod($wellknownPath, 0770); - if (!$this->fileIo->fileExists($applepayPath)) { - $this->downloadAndUnzip($applepayUrl, $wellknownPath); - } - } - } catch (Exception $e) { - $errormessage = 'Failed to download the ApplePay certificate, please do so manually'; - $this->adyenLogger->addAdyenWarning($errormessage); - $this->messageManager->addErrorMessage($errormessage); - } - - return $redirect; - } - - /** - * @param string $applepayUrl - * @param string $applepayPath - * @return void - * @throws LocalizedException - */ - private function downloadAndUnzip(string $applepayUrl, string $applepayPath) - { - $tmpPath = tempnam(sys_get_temp_dir(), self::FILE_NAME); - file_put_contents($tmpPath, file_get_contents($applepayUrl)); - - $zip = new ZipArchive; - $fileCount = 0; - $totalSize = 0; - - if ($zip->open($tmpPath) === true) { - for ($i = 0; $i < $zip->numFiles; $i++) { - $filename = $zip->getNameIndex($i); - if (self::FILE_NAME !== $filename) { - continue; - } - $stats = $zip->statIndex($i); - - // Prevent ZipSlip path traversal (S6096) - if (strpos($filename, '../') !== false || - substr($filename, 0, 1) === '/') { - throw new AdyenException('The zip file is trying to ZipSlip please check the file'); - } - - if (substr($filename, -1) !== '/') { - $fileCount++; - if ($fileCount > 10) { - // Reached max. number of files - throw new AdyenException('Reached max number of files please check the zip file'); - } - - $applepayCerticateFilestream = $zip->getStream($filename); // Compliant - $currentSize = 0; - while (!feof($applepayCerticateFilestream)) { - $currentSize += self::READ_LENGTH; - $totalSize += self::READ_LENGTH; - - if ($totalSize > self::MAX_SIZE) { - // Reached max. size - throw new AdyenException('The file is larger than expected please check the zip file'); - } - - // Additional protection: check compression ratio - if ($stats['comp_size'] > 0) { - $ratio = $currentSize / $stats['comp_size']; - if ($ratio > self::MAX_RATIO) { - // Reached max. compression ratio - throw new AdyenException('The uncompressed file is larger than expected'); - } - } - file_put_contents( - $applepayPath .'/' . $filename, - fread($applepayCerticateFilestream, $totalSize), - FILE_APPEND - ); - } - fclose($applepayCerticateFilestream); - } - } - $zip->close(); - } - } -} diff --git a/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFile.php b/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFile.php new file mode 100644 index 0000000000..0726b316cc --- /dev/null +++ b/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFile.php @@ -0,0 +1,82 @@ + + */ + +namespace Adyen\Payment\Controller\Adminhtml\Configuration; + +use Adyen\Payment\Logger\AdyenLogger; +use Exception; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Filesystem\DirectoryList; +use Magento\Backend\App\Action\Context; +use Magento\Framework\Controller\ResultFactory; +use Magento\Backend\App\Action; +use Magento\Framework\Filesystem\Io\File; + +class DownloadApplePayDomainAssociationFile extends Action +{ + const FILE_NAME = 'apple-developer-merchantid-domain-association'; + const REMOTE_PATH = 'https://bae81f955b.cdn.adyen.com/checkoutshopper/.well-known'; + const PUB_PATH = 'pub'; + const WELL_KNOWN_PATH = '.well-known'; + + public function __construct( + private readonly Context $context, + private readonly DirectoryList $directoryList, + private readonly File $fileIo, + private readonly AdyenLogger $adyenLogger + ) { + parent::__construct($context); + } + + /** + * @return ResultInterface|ResponseInterface + * @throws FileSystemException + */ + public function execute(): ResultInterface|ResponseInterface + { + $redirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + $redirect->setUrl($this->_redirect->getRefererUrl()); + + try { + $pubPath = $this->directoryList->getPath(self::PUB_PATH); + + $this->fileIo->checkAndCreateFolder( + sprintf("%s/%s", $pubPath, self::WELL_KNOWN_PATH), + 0700 + ); + + $source = sprintf("%s/%s", self::REMOTE_PATH, self::FILE_NAME); + $destination = sprintf("%s/%s/%s", $pubPath, self::WELL_KNOWN_PATH, self::FILE_NAME); + + $file = $this->fileIo->read($source, $destination); + + if (!$file) { + $errorMessage = + __('Error while downloading Apple Pay domain association file from the remote source!'); + $this->adyenLogger->error(sprintf("%s %s", $errorMessage, $source)); + $this->messageManager->addErrorMessage($errorMessage); + } else { + $successMessage = __('Apple Pay domain association file has been downloaded successfully!'); + $this->adyenLogger->addAdyenDebug($successMessage); + $this->messageManager->addSuccessMessage($successMessage); + } + } catch (Exception $e) { + $errorMessage = + __('Unknown error while downloading Apple Pay domain association file!'); + $this->adyenLogger->error(sprintf("%s %s", $errorMessage, $e->getMessage())); + $this->messageManager->addErrorMessage($errorMessage); + } + + return $redirect; + } +} diff --git a/Model/Config/Adminhtml/ApplepayCertificateButton.php b/Model/Config/Adminhtml/ApplePayDomainAssociationFileButton.php similarity index 76% rename from Model/Config/Adminhtml/ApplepayCertificateButton.php rename to Model/Config/Adminhtml/ApplePayDomainAssociationFileButton.php index 4dc52c5ded..16de30dd46 100644 --- a/Model/Config/Adminhtml/ApplepayCertificateButton.php +++ b/Model/Config/Adminhtml/ApplePayDomainAssociationFileButton.php @@ -16,14 +16,14 @@ use Magento\Backend\Helper\Data; use Magento\Framework\Data\Form\Element\AbstractElement; -class ApplepayCertificateButton extends Field +class ApplePayDomainAssociationFileButton extends Field { - const APPLEPAY_BUTTON = 'Adyen_Payment::config/applepay_button.phtml'; + const APPLEPAY_BUTTON = 'Adyen_Payment::config/applepay_domain_association_file_button.phtml'; /** * @var Data */ - protected $backendHelper; + protected Data $backendHelper; /** * @param Context $context @@ -40,9 +40,9 @@ public function __construct( } /** - * @return $this|ApplepayCertificateButton + * @return $this|ApplePayDomainAssociationFileButton */ - protected function _prepareLayout() + protected function _prepareLayout(): ApplePayDomainAssociationFileButton|static { parent::_prepareLayout(); @@ -57,7 +57,7 @@ protected function _prepareLayout() * @param AbstractElement $element * @return string */ - protected function _getElementHtml(AbstractElement $element) + protected function _getElementHtml(AbstractElement $element): string { $this->addData([ 'id' => 'addbutton_button', @@ -73,6 +73,6 @@ protected function _getElementHtml(AbstractElement $element) */ public function getActionUrl(): string { - return $this->backendHelper->getUrl("adyen/configuration/DownloadApplePayCertificate"); + return $this->backendHelper->getUrl("adyen/configuration/DownloadApplePayDomainAssociationFile"); } } diff --git a/Test/Unit/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFileTest.php b/Test/Unit/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFileTest.php new file mode 100644 index 0000000000..954e61f634 --- /dev/null +++ b/Test/Unit/Controller/Adminhtml/Configuration/DownloadApplePayDomainAssociationFileTest.php @@ -0,0 +1,144 @@ +redirectMock = $this->createMock(RedirectInterface::class); + + $this->resultMock = $this->createGeneratedMock( + Redirect::class + ); + + $this->resultFactoryMock = $this->createGeneratedMock( + ResultFactory::class, + ['create'] + ); + $this->resultFactoryMock->method('create')->willReturn($this->resultMock); + + $this->managerMock = $this->createMock(ManagerInterface::class); + + $this->contextMock = $this->createMock(Context::class); + $this->contextMock->method('getResultFactory')->willReturn($this->resultFactoryMock); + $this->contextMock->method('getRedirect')->willReturn($this->redirectMock); + $this->contextMock->method('getMessageManager')->willReturn($this->managerMock); + + $this->directoryListMock = $this->createMock(DirectoryList::class); + $this->fileIoMock = $this->createMock(File::class); + $this->adyenLoggerMock = $this->createMock(AdyenLogger::class); + + + $this->downloadApplePayDomainAssociationFile = new DownloadApplePayDomainAssociationFile( + $this->contextMock, + $this->directoryListMock, + $this->fileIoMock, + $this->adyenLoggerMock + ); + } + + /** + * @return void + * @throws FileSystemException + */ + public function testDownloadFileSuccessfully(): void + { + $this->directoryListMock->method('getPath') + ->with('pub') + ->willReturn('/var/www/html/pub'); + + $this->fileIoMock->method('checkAndCreateFolder') + ->with('/var/www/html/pub/.well-known') + ->willReturn(true); + + $this->fileIoMock->method('read') + ->with( + 'https://bae81f955b.cdn.adyen.com/checkoutshopper/.well-known/apple-developer-merchantid-domain-association', + '/var/www/html/pub/.well-known/apple-developer-merchantid-domain-association' + ) + ->willReturn(true); + + $this->adyenLoggerMock->expects($this->atLeastOnce())->method('addAdyenDebug'); + $this->managerMock->expects($this->once())->method('addSuccessMessage'); + + $redirectResponse = $this->downloadApplePayDomainAssociationFile->execute(); + + $this->assertInstanceOf(Redirect::class, $redirectResponse); + } + + /** + * @return void + * @throws FileSystemException + */ + public function testHttpNotFound(): void + { + $this->directoryListMock->method('getPath') + ->with('pub') + ->willReturn('/var/www/html/pub'); + + $this->fileIoMock->method('checkAndCreateFolder') + ->with('/var/www/html/pub/.well-known') + ->willReturn(true); + + $this->fileIoMock->method('read') + ->with( + 'https://bae81f955b.cdn.adyen.com/checkoutshopper/.well-known/apple-developer-merchantid-domain-association', + '/var/www/html/pub/.well-known/apple-developer-merchantid-domain-association' + ) + ->willReturn(false); + + $this->adyenLoggerMock->expects($this->atLeastOnce())->method('error'); + $this->managerMock->expects($this->once())->method('addErrorMessage'); + + $redirectResponse = $this->downloadApplePayDomainAssociationFile->execute(); + + $this->assertInstanceOf(Redirect::class, $redirectResponse); + } + + /** + * @return void + * @throws FileSystemException + */ + public function testFileSystemIOError(): void + { + $this->directoryListMock->method('getPath') + ->with('pub') + ->willReturn('/var/www/html/pub'); + + $this->fileIoMock->method('checkAndCreateFolder') + ->willThrowException(new LocalizedException(__('mock error message'))); + + $this->adyenLoggerMock->expects($this->atLeastOnce())->method('error'); + $this->managerMock->expects($this->once())->method('addErrorMessage'); + + $redirectResponse = $this->downloadApplePayDomainAssociationFile->execute(); + + $this->assertInstanceOf(Redirect::class, $redirectResponse); + } +} diff --git a/Test/Unit/Model/Config/Adminhtml/ApplePayDomainAssociationFileButtonTest.php b/Test/Unit/Model/Config/Adminhtml/ApplePayDomainAssociationFileButtonTest.php new file mode 100644 index 0000000000..f79be89c48 --- /dev/null +++ b/Test/Unit/Model/Config/Adminhtml/ApplePayDomainAssociationFileButtonTest.php @@ -0,0 +1,102 @@ +createMock(ObjectManagerInterface::class); + ObjectManager::setInstance($objectManagerMock); + + $this->managerMock = $this->createMock(ManagerInterface::class); + $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); + + $this->contextMock = $this->createMock(Context::class); + $this->contextMock->method('getScopeConfig')->willReturn($this->scopeConfigMock); + $this->contextMock->method('getEventManager')->willReturn($this->managerMock); + + $this->backendHelperMock = $this->createMock(Data::class); + $this->layoutMock = $this->createMock(LayoutInterface::class); + + $this->applePayDomainAssociationFileButton = new ApplePayDomainAssociationFileButton( + $this->contextMock, + $this->backendHelperMock, + [] + ); + } + + /** + * @return void + */ + public function tearDown(): void + { + $this->applePayDomainAssociationFileButton = null; + } + + /** + * Asserts default HTML template value + * + * @return void + */ + public function testGetElementHtml() + { + $expected = ''; + + $result = $this->applePayDomainAssociationFileButton + ->render($this->createMock(AbstractElement::class)); + + $this->assertEquals($expected, $result); + } + + /** + * Asserts return type of the button's backend model + * + * @return void + */ + public function testPrepareLayout() + { + $result = $this->applePayDomainAssociationFileButton->setLayout($this->layoutMock); + $this->assertInstanceOf(ApplePayDomainAssociationFileButton::class, $result); + } + + /** + * Asserts file download controller URL + * + * @return void + */ + public function testGetActionUrl() + { + $expected = 'https://www.magento.demo/adyen/configuration/DownloadApplePayDomainAssociationFile'; + $this->backendHelperMock->expects($this->once()) + ->method('getUrl') + ->with('adyen/configuration/DownloadApplePayDomainAssociationFile', []) + ->willReturn($expected); + + $url = $this->applePayDomainAssociationFileButton->getActionUrl(); + + $this->assertEquals($expected, $url); + } +} diff --git a/etc/adminhtml/system/adyen_alternative_payment_methods.xml b/etc/adminhtml/system/adyen_alternative_payment_methods.xml index 21dcfb7a81..ae858674c6 100755 --- a/etc/adminhtml/system/adyen_alternative_payment_methods.xml +++ b/etc/adminhtml/system/adyen_alternative_payment_methods.xml @@ -19,8 +19,8 @@ Set up additional payment methods to accept online and in-app payments and eliminate the need for traditional card-based transactions.

]]> - - Adyen\Payment\Model\Config\Adminhtml\ApplepayCertificateButton + + Adyen\Payment\Model\Config\Adminhtml\ApplePayDomainAssociationFileButton diff --git a/view/adminhtml/templates/config/applepay_button.phtml b/view/adminhtml/templates/config/applepay_button.phtml deleted file mode 100644 index e57195c9fe..0000000000 --- a/view/adminhtml/templates/config/applepay_button.phtml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/view/adminhtml/templates/config/applepay_domain_association_file_button.phtml b/view/adminhtml/templates/config/applepay_domain_association_file_button.phtml new file mode 100644 index 0000000000..a25f333024 --- /dev/null +++ b/view/adminhtml/templates/config/applepay_domain_association_file_button.phtml @@ -0,0 +1,4 @@ + +