Skip to content

Instantly share code, notes, and snippets.

@Hailong
Last active November 25, 2020 01:23
Show Gist options
  • Save Hailong/a0d13065529e03b3493e9cb46a5e115d to your computer and use it in GitHub Desktop.
Save Hailong/a0d13065529e03b3493e9cb46a5e115d to your computer and use it in GitHub Desktop.
Fix ShipStation plugin for Magento 2.3
From 15db78fd75a7fc473d72157cb952f36739b125c7 Mon Sep 17 00:00:00 2001
From: Hailong Zhao <[email protected]>
Date: Sat, 13 Apr 2019 23:13:58 -0400
Subject: [PATCH] Fix ShipStation plugin.
---
.../Auctane/Api/Controller/Auctane/Index.php | 22 ++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/app/code/Auctane/Api/Controller/Auctane/Index.php b/app/code/Auctane/Api/Controller/Auctane/Index.php
index 53f9664f..a3319f17 100644
--- a/app/code/Auctane/Api/Controller/Auctane/Index.php
+++ b/app/code/Auctane/Api/Controller/Auctane/Index.php
@@ -2,9 +2,29 @@
namespace Auctane\Api\Controller\Auctane;
use Exception;
+use Magento\Framework\App\CsrfAwareActionInterface;
+use Magento\Framework\App\Request\InvalidRequestException;
+use Magento\Framework\App\RequestInterface;
-class Index extends \Magento\Framework\App\Action\Action
+class Index extends \Magento\Framework\App\Action\Action implements CsrfAwareActionInterface
{
+ /**
+ * @inheritDoc
+ */
+ public function createCsrfValidationException(
+ RequestInterface $request
+ ): ?InvalidRequestException {
+ return null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validateForCsrf(RequestInterface $request): ?bool
+ {
+ return true;
+ }
+
/**
* Default function
*
--
2.20.1 (Apple Git-117)
<?php
namespace Auctane\Api\Controller\Auctane;
use Exception;
use Magento\Framework\App\CsrfAwareActionInterface;
use Magento\Framework\App\Request\InvalidRequestException;
use Magento\Framework\App\RequestInterface;
class Index extends \Magento\Framework\App\Action\Action implements CsrfAwareActionInterface
{
/**
* @inheritDoc
*/
public function createCsrfValidationException(
RequestInterface $request
): ?InvalidRequestException {
return null;
}
/**
* @inheritDoc
*/
public function validateForCsrf(RequestInterface $request): ?bool
{
return true;
}
/**
* Default function
*
* @return void
*/
public function execute()
{
$authUser = $this->getRequest()->getParam('SS-UserName');
$authPassword = $this->getRequest()->getParam('SS-Password');
// \Magento\Store\Model\StoreManagerInterface $storeManager
$storeManager = $this->_objectManager->get(
'Magento\Store\Model\StoreManagerInterface'
);
$storeId = $storeManager->getStore()->getId();
$storageInterface = $this->_objectManager->get(
'\Magento\Backend\Model\Auth\Credential\StorageInterface'
);
$userAuthentication = $storageInterface->authenticate(
$authUser,
$authPassword
);
$dataHelper = $this->_objectManager->get('Auctane\Api\Helper\Data');
if (!$userAuthentication) {
header(sprintf('WWW-Authenticate: Basic realm=ShipStation'));
$result = $dataHelper->fault(401, 'Authentication failed');
header('Content-Type: text/xml; charset=UTF-8');
$this->getResponse()->setBody($result);
return false;
}
//Get the requested action
$action = $this->getRequest()->getParam('action');
try {
switch ($action) {
case 'export':
$export = $this->_objectManager->get(
'Auctane\Api\Model\Action\Export'
);
$result = $export->process($this->getRequest(), $storeId);
break;
case 'shipnotify':
$shipNotify = $this->_objectManager->get(
'Auctane\Api\Model\Action\ShipNotify'
);
$result = $shipNotify->process($this->getRequest());
// if there hasn't been an error then "200 OK" is given
break;
}
} catch (Exception $fault) {
$result = $dataHelper->fault($fault->getCode(), $fault->getMessage());
}
$this->getResponse()->setBody($result);
}
}
From d922c449c13246f370c66865ad2714f7f2fe4a8e Mon Sep 17 00:00:00 2001
From: Hailong Zhao <[email protected]>
Date: Wed, 8 Apr 2020 22:21:41 -0400
Subject: [PATCH] Patch for ShipStation 2.1.24
---
Index.php | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/Index.php b/Index.php
index da9748a..b64be58 100644
--- a/Index.php
+++ b/Index.php
@@ -10,10 +10,13 @@ use Magento\Backend\Model\Auth\Credential\StorageInterface;
use Magento\Backend\Model\View\Result\RedirectFactory;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
+use Magento\Framework\App\CsrfAwareActionInterface;
+use Magento\Framework\App\Request\InvalidRequestException;
+use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\StoreManagerInterface;
-class Index extends Action
+class Index extends Action implements CsrfAwareActionInterface
{
/**
* @var StoreManagerInterface
@@ -65,6 +68,23 @@ class Index extends Action
$this->redirectFactory = $redirectFactory;
}
+ /**
+ * @inheritDoc
+ */
+ public function createCsrfValidationException(
+ RequestInterface $request
+ ): ?InvalidRequestException {
+ return null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validateForCsrf(RequestInterface $request): ?bool
+ {
+ return true;
+ }
+
/**
* Default function
*
--
2.24.1 (Apple Git-126)
<?php
namespace Auctane\Api\Controller\Auctane;
use Auctane\Api\Helper\Data;
use Auctane\Api\Model\Action\Export;
use Auctane\Api\Model\Action\ShipNotify;
use Exception;
use Magento\Backend\Model\Auth\Credential\StorageInterface;
use Magento\Backend\Model\View\Result\RedirectFactory;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\CsrfAwareActionInterface;
use Magento\Framework\App\Request\InvalidRequestException;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Model\StoreManagerInterface;
class Index extends Action implements CsrfAwareActionInterface
{
/**
* @var StoreManagerInterface
*/
private $storeManager;
/**
* @var StorageInterface
*/
private $storage;
/**
* @var ScopeConfigInterface
*/
private $scopeConfig;
/**
* @var Data
*/
private $dataHelper;
/**
* @var Export
*/
private $export;
/**
* @var ShipNotify
*/
private $shipNotify;
/**
* @var RedirectFactory
*/
private $redirectFactory;
public function __construct(
Context $context,
StoreManagerInterface $storeManager,
StorageInterface $storage,
ScopeConfigInterface $scopeConfig,
Data $dataHelper,
Export $export,
ShipNotify $shipNotify,
RedirectFactory $redirectFactory
) {
parent::__construct($context);
$this->storeManager = $storeManager;
$this->storage = $storage;
$this->scopeConfig = $scopeConfig;
$this->dataHelper = $dataHelper;
$this->export = $export;
$this->shipNotify = $shipNotify;
$this->redirectFactory = $redirectFactory;
}
/**
* @inheritDoc
*/
public function createCsrfValidationException(
RequestInterface $request
): ?InvalidRequestException {
return null;
}
/**
* @inheritDoc
*/
public function validateForCsrf(RequestInterface $request): ?bool
{
return true;
}
/**
* Default function
*
* @return bool
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function execute()
{
/** @var $request \Magento\Framework\App\Request\Http */
$request = $this->getRequest();
$authUser = $request->getParam('SS-UserName');
$authPassword = $request->getParam('SS-Password');
$apiKey = $this->scopeConfig->getValue(
'shipstation_general/shipstation/ship_api_key'
);
$apiKeyFromShipStation = $request->getHeader('ShipStation-Access-Token');
$apiKeyHasBeenGenerated = !empty($apiKey);
$apiKeyHasBeenProvided = !empty($apiKeyFromShipStation);
if ($apiKeyHasBeenGenerated
&& $apiKeyHasBeenProvided
&& ($apiKeyFromShipStation === $apiKey)) {
$userAuthentication = true;
} else {
$userAuthentication = $this->storage->authenticate(
$authUser,
$authPassword
);
}
if (!$userAuthentication) {
$this->getResponse()->setHeader('WWW-Authenticate: ', 'Basic realm=ShipStation', true);
$this->getResponse()->setHeader('Content-Type', 'text/xml; charset=UTF-8', true);
$result = $this->dataHelper->fault(401, 'Authentication failed');
$this->getResponse()->setBody($result);
return false;
}
//Get the requested action
$action = $request->getParam('action');
try {
switch ($action) {
case 'export':
$storeId = $this->storeManager->getStore()->getId();
$result = $this->export->process($request, $this->getResponse(), $storeId);
break;
case 'shipnotify':
$result = $this->shipNotify->process();
// if there hasn't been an error then "200 OK" is given
break;
}
} catch (Exception $fault) {
$result = $this->dataHelper->fault($fault->getCode(), $fault->getMessage());
}
$this->getResponse()->setBody($result);
}
}
@danmentzer
Copy link

Just tested the patch on Magento 2.3.2 using Shipstation 2.0.21 worked perfectly.
Thanks so much for creating this patch since Shipstation support does not seem to express any concern about this issue.

@elfeffe
Copy link

elfeffe commented Dec 17, 2019

It works in M2.3.3 and ShipStation 2.1.24
I have reported to ShipStation, but I don't know if they will update their extension

@jackrevate
Copy link

Does anyone have a fix for Magento 2.3.4 - as I can't get it to work (2.1.24).

Unless the patch is different completely as it seems about 1000kb less than the original file which I find odd.

Appreciate any help as this is a nightmare and could really do without it during this corona pandemic.

@Hailong
Copy link
Author

Hailong commented Apr 8, 2020

@jackrevate, what does your ShipStation plugin file looks like, can you show me the following file in your installation?
app/code/Auctane/Api/Controller/Auctane/Index.php

@btekbtek
Copy link

Hi All

For me, that fix worked, however, I have to click manually Notify Marketplace in ship station, so it's not notifying automatically.

When auto:
Post HTTP/1.1" 500 - "-" "-"
When Manually:
Post HTTP/1.1" 200 162 "-" "-"

@j-lloyd-slc
Copy link

Like @jackrevate, I am having no luck getting ship notification to work with Auctane v2.1.24 and Magento v2.3.4.

  • I have tried versions of the above CsrfAwareActionInterface, which I have used successfully with other 2.3.4 implementations.
  • I have tried changing the router id/frontName to use something other than the overly generic "api" in case that was conflicting with some other component.

The bottom line seems to be that by the time the request is handled by Magento\Framework\App\Request\CsrfValidator, the action does not pass the instanceOf CsrfAwareActionInterface test.

What is odd is that I created a CLI script to test the Auctane\Api\Controller\Auctane\Index class directly, and it returns true for instanceOf CsrfAwareActionInterface.

require 'app/bootstrap.php';
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, array());
$app = $bootstrap->createApplication(\Magento\Framework\App\Http::class);
$bootstrap->run($app);
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$action = $objectManager->create('\Auctane\Api\Controller\Auctane\Index');
echo get_class($action) . PHP_EOL;
// echos 'Auctane\Api\Controller\Auctane\Index\Interceptor'
echo ($action instanceOf Magento\Framework\App\CsrfAwareActionInterface) ? 'true' : 'false';
// echos 'true'

In CsrfValidator, I get 'Magento\Framework\App\Action\Forward\Interceptor' and 'false', respectively.

Also, there is a plugin in this v2.1.24 that seems designed to fix the CSRF issue, but in a poor way (IMO) by testing every single request and matching it against Auctane's controller module name and controller name. It does not fix the problem.

@jackrevate
Copy link

Like @jackrevate, I am having no luck getting ship notification to work with Auctane v2.1.24 and Magento v2.3.4.

  • I have tried versions of the above CsrfAwareActionInterface, which I have used successfully with other 2.3.4 implementations.
  • I have tried changing the router id/frontName to use something other than the overly generic "api" in case that was conflicting with some other component.

The bottom line seems to be that by the time the request is handled by Magento\Framework\App\Request\CsrfValidator, the action does not pass the instanceOf CsrfAwareActionInterface test.

What is odd is that I created a CLI script to test the Auctane\Api\Controller\Auctane\Index class directly, and it returns true for instanceOf CsrfAwareActionInterface.

require 'app/bootstrap.php';
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, array());
$app = $bootstrap->createApplication(\Magento\Framework\App\Http::class);
$bootstrap->run($app);
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$action = $objectManager->create('\Auctane\Api\Controller\Auctane\Index');
echo get_class($action) . PHP_EOL;
// echos 'Auctane\Api\Controller\Auctane\Index\Interceptor'
echo ($action instanceOf Magento\Framework\App\CsrfAwareActionInterface) ? 'true' : 'false';
// echos 'true'

In CsrfValidator, I get 'Magento\Framework\App\Action\Forward\Interceptor' and 'false', respectively.

Also, there is a plugin in this v2.1.24 that seems designed to fix the CSRF issue, but in a poor way (IMO) by testing every single request and matching it against Auctane's controller module name and controller name. It does not fix the problem.

I'm a noob when it comes to this - I'm in the same boat (none of my notifications get updated to my marketplaces, I have to manually do this. I contacted Shipstation but they don't seem to care, it's been months now for myself and even longer for others.

@Hailong kindly sent me the files I needed just to get shipstation to work.

Not sure why the marketplaces don't get updated though, just done it manually for now.

Fingers crossed a solution happens soon as we can't be the only ones.

@j-lloyd-slc
Copy link

"Fingers crossed a solution happens soon as we can't be the only ones."

I can't tell you how many times I have said the same thing, working with Magento.

The more I look at this extension, the less impressed I am with how it is written with no real observance of current Magento coding best-practices.

I'm trying to create an alternative so that our personnel don't have to manually create shipments in Magento for every single order, but the combination of the way that ShipStation communicates the data and the way that Magento 2 handles it creates headaches.

@btekbtek
Copy link

btekbtek commented Jun 8, 2020

Anyone know about disturbance with notification when using Varnish cache?

Fix above works for me,
however only for manual notification. (1 auto per day works)

PS. This extension shouldn't be in Magento Marketplace.

@j-lloyd-slc
Copy link

@jackrevate, I figured out what is going on with my Magento 2.3.4 instance and Auctane 2.1.24.

Turns out an extension was creating the problem.

Executive summary:

  1. Auctane 2.1.24 should work out-of box
  2. The patch posted by @Hailong is a better approach
  3. If neither work, check extensions, especially those that have overly broad influence on requests and routing (like Auctane!).

As I mentioned, Auctane 2.1.24 includes an alternate approach to the CsrfAwareActionInterface issue. Rather than set the controller to implement this interface, it registers a Plugin for Magento\Framework\App\Request\CsrfValidator that filters every CsrfAwareActionInterface implementer and skips validation only for Auctane's controller.

After disabling Auctane's plugin and updating the controller to directly implement CsrfAwareActionInterface I still could not get my POSTs to pass validation as expected. I then checked some other POST handlers and when they didn't work I realized that there was something interfering at a broader level.

When simulated, all of these requests were being redirected (302) to the homepage with a formKey error message.

I finally tracked the problem down to an extension that was intercepting every request for its own purposes and then forwarding them in a way that broke the CsrfAwareActionInterface logic:

return $this->actionFactory->create('Magento\Framework\App\Action\Forward', ['request' => $request]);

For whatever reason this causes the test for CsrfAwareActionInterface in Magento\Framework\App\Request\CsrfValidator::createException to fail.

@jackrevate
Copy link

Still having this issue, currently using 2.3.5-p1 - so sad Ship Station don't sort a solid release.

@jackrevate
Copy link

@jackrevate, I figured out what is going on with my Magento 2.3.4 instance and Auctane 2.1.24.

Turns out an extension was creating the problem.

Executive summary:

  1. Auctane 2.1.24 should work out-of box
  2. The patch posted by @Hailong is a better approach
  3. If neither work, check extensions, especially those that have overly broad influence on requests and routing (like Auctane!).

As I mentioned, Auctane 2.1.24 includes an alternate approach to the CsrfAwareActionInterface issue. Rather than set the controller to implement this interface, it registers a Plugin for Magento\Framework\App\Request\CsrfValidator that filters every CsrfAwareActionInterface implementer and skips validation only for Auctane's controller.

After disabling Auctane's plugin and updating the controller to directly implement CsrfAwareActionInterface I still could not get my POSTs to pass validation as expected. I then checked some other POST handlers and when they didn't work I realized that there was something interfering at a broader level.

When simulated, all of these requests were being redirected (302) to the homepage with a formKey error message.

I finally tracked the problem down to an extension that was intercepting every request for its own purposes and then forwarding them in a way that broke the CsrfAwareActionInterface logic:

return $this->actionFactory->create('Magento\Framework\App\Action\Forward', ['request' => $request]);

For whatever reason this causes the test for CsrfAwareActionInterface in Magento\Framework\App\Request\CsrfValidator::createException to fail.

I think I must be using an extension that's causing an issue. Although, I've disabled all third party extensions and it still doesn't update. (I even tried to use Royal Mail and other courier extensions but it seems that no matter what I use, it just doesn't update the status).

May I ask what extension was conflicting? Also, any particular extensions I should look into that could cause a conflict?

Thanks,

@j-lloyd-slc
Copy link

j-lloyd-slc commented Jul 13, 2020

In the instance I was investigating, Lof_Formbuilder v1.0.9 was causing the issue. It was installed long ago and not used or maintained, so it could be that current versions are fine.

The root of the problem is that this extension was intercepting all requests, filtering them for its own purporses, and then forwarding everything. The forwarded requests were altered in a way that was not compatible with Magento's CsrfAwareActionInterface processing.

I would look for anything that might be intercepting requests, e.g. Observers and Plugins, and try to determine if they are casting a wider net than necessary. With 20/20 hindsight, searching all 3rd party extension for "Magento\Framework\App\Action\Forward" would have flagged the potentially problematic extensions. There are multiple others in my case, but the one causing the issue was the only one that didn't test the request to make sure it was relevant to the extension's specific goals.

@VDuda
Copy link

VDuda commented Nov 24, 2020

Can we just make this a PR into the original ShipStation Plugin?

https://github.com/shipstation/plugin-magento

Just installed the latest plugin (2.1.24) with Magento 2.3.3 - and broken out of the box - no shipment object created. Verified with a ShipStation Support Agent that indeed there are no errors being sent back to them during shipnotify. Even when there was indeed an issue on the Magento side for creating a shipment object. I doubt that the current iteration of the plugin works with Magento 2.3.3 out of the box without this patch. But will see and edit this post if indeed this patch with CSRF fixes creating a shipment object.

@Hailong
Copy link
Author

Hailong commented Nov 24, 2020

@VDuda, I have just created a PR with this patch. https://github.com/shipstation/plugin-magento/pull/6/files

@VDuda
Copy link

VDuda commented Nov 25, 2020

Nice! I for some reason still can't seem to get the shipstation plugin to work on our end even with the CSRF patch. No errors and the customer agent reaffirms that no error message was sent back to them. But I never got a shipment object created! I think I'm just gonna dump them until this plugin actually works :/

I'm going to test for a few more days - it seems to be working with the patch. I had forgotten to turn the cronjobs on after applying this patch. I tend to turn them off during upgrading.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment