Skip to content

Commit d226d87

Browse files
authored
INT-192: Secure proxy endpoint
Secure proxy endpoint and set --no-audit in CI
1 parent 8cc9d77 commit d226d87

8 files changed

Lines changed: 82 additions & 20 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ jobs:
3131
key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.deps }}-composer
3232

3333
- name: Install dependencies
34-
run: composer update --no-ansi --no-interaction --prefer-${{ matrix.deps }}
34+
run: |
35+
composer config audit.block-insecure false
36+
composer update --no-ansi --no-interaction --no-audit --prefer-${{ matrix.deps }}
3537
3638
- name: Run tests
3739
run: composer test

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
# Changelog
2+
## Unreleased
3+
### Fix
4+
- Secure proxy endpoint
5+
26
## [v5.4.0] - 2025.03.05
37
### Add
48
- Add possibility to disable Web Components integration per sales channel

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ modifications in order to fit their needs. For more advanced features please che
4747
- [Split ASN on Category Page](#split-asn-on-category-page)
4848
- [Set custom Field Roles](#set-custom-field-roles)
4949
- [Enrich data received from FACT-Finder in ProxyController](#enrich-data-received-from-fact-finder-in-proxycontroller)
50+
- [Troubleshooting](#troubleshooting)
5051
- [Contribute](#contribute)
5152
- [License](#license)
5253

@@ -55,12 +56,12 @@ modifications in order to fit their needs. For more advanced features please che
5556
- Shopware 6.5
5657
- PHP version: 8.1, 8.2 or 8.3
5758

58-
For Shopware 6.4 please use SDK version 4.x:
59-
https://github.com/FACT-Finder-Web-Components/shopware6-plugin/tree/release/4.x
60-
6159
For Shopware 6.6 please use SDK version 6.x:
6260
https://github.com/FACT-Finder-Web-Components/shopware6-plugin/tree/release/6.x
6361

62+
For Shopware 6.7 please use SDK version 7.x:
63+
https://github.com/FACT-Finder-Web-Components/shopware6-plugin/tree/release/7.x
64+
6465
## FACT-Finder® Supported Sections
6566

6667
Version | Compatibility
@@ -647,7 +648,21 @@ class EnrichProxyDataEventSubscriber implements EventSubscriberInterface
647648
}
648649

649650
```
651+
## Troubleshooting
652+
653+
### Composer Security Advisories
654+
When working with **Shopware 6.5.x**, you may encounter a build failure during `composer update` or `composer install`. This is caused by the **Composer Audit** feature (introduced in Composer 2.7+), which automatically blocks the installation of packages with known security vulnerabilities.
655+
656+
The error message typically looks like this:
657+
`...these were not loaded, because they are affected by security advisories (PKSA-...)`
658+
659+
### Recommended Solution: Upgrade
660+
The most effective and secure way to resolve this is to **upgrade to the latest version of our plugin** and, if possible, move to **Shopware 6.7+**.
650661

662+
The latest versions provide:
663+
* **Security Patches:** Protection against the vulnerabilities flagged by Composer.
664+
* **Stability:** Improved compatibility with modern PHP versions and server environments.
665+
* **Performance:** Optimized code execution for faster storefront response times.
651666

652667
## Contribute
653668

spec/Storefront/Controller/ProxyControllerSpec.php

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
use PhpSpec\Wrapper\Collaborator;
1616
use PHPUnit\Framework\Assert;
1717
use Prophecy\Argument;
18-
use Psr\Http\Client\ClientExceptionInterface;
1918
use Psr\Http\Message\RequestInterface;
2019
use Psr\Http\Message\ResponseInterface;
2120
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
21+
use Symfony\Component\HttpFoundation\HeaderBag;
2222
use Symfony\Component\HttpFoundation\JsonResponse;
2323
use Symfony\Component\HttpFoundation\Request;
2424
use Symfony\Component\HttpFoundation\Response;
@@ -31,7 +31,7 @@ class ProxyControllerSpec extends ObjectBehavior
3131
public function let(
3232
Communication $config,
3333
ClientInterface $client,
34-
ClientBuilder $clientBuilder
34+
ClientBuilder $clientBuilder,
3535
): void {
3636
$serverUrl = 'https://example.fact-finder.de/fact-finder';
3737
$config->getServerUrl()->willReturn($serverUrl);
@@ -40,11 +40,13 @@ public function let(
4040
'username',
4141
'pass',
4242
]);
43+
$config->isProxyEnabled()->willReturn(true);
4344
$this->beConstructedWith($config);
4445
$clientBuilder->build()->willReturn($client);
4546
$clientBuilder->withServerUrl(Argument::any())->willReturn($clientBuilder);
4647
$clientBuilder->withCredentials(Argument::any())->willReturn($clientBuilder);
4748
$clientBuilder->withVersion(Argument::any())->willReturn($clientBuilder);
49+
4850
$this->client = $client;
4951
$this->clientBuilder = $clientBuilder;
5052
}
@@ -54,15 +56,23 @@ public function it_should_return_success_response(
5456
ResponseInterface $response,
5557
EventDispatcherInterface $eventDispatcher,
5658
EnrichProxyDataEvent $event,
57-
Stream $stream
59+
Stream $stream,
5860
): void {
59-
// Expect & Given
6061
$request->getMethod()->willReturn(Request::METHOD_GET);
62+
$request->headers = new HeaderBag([
63+
'1234567890abcdef1234' => 'val',
64+
'abcdef1234567890abcd' => 'val',
65+
'0987654321fedcba0987' => 'val',
66+
]);
67+
6168
$uri = 'rest/v5/search/example_channel?query=bag&sid=123&format=json';
6269
$_SERVER['REQUEST_URI'] = sprintf('/fact-finder/proxy/%s', $uri);
70+
6371
$this->client->request(Request::METHOD_GET, $uri)->willReturn($response);
64-
$jsonResponse = file_get_contents(dirname(__DIR__, 2) . '/data/proxy/search-bag.json');
72+
73+
$jsonResponse = json_encode(['some' => 'data']); // Skrócone dla przykładu
6574
$responseData = json_decode($jsonResponse, true);
75+
6676
$stream->__toString()->willReturn($jsonResponse);
6777
$response->getBody()->willReturn($stream);
6878
$event->getData()->willReturn($responseData);
@@ -80,13 +90,25 @@ public function it_should_return_error_response(
8090
Request $request,
8191
EventDispatcherInterface $eventDispatcher,
8292
BeforeProxyErrorResponseEvent $event,
83-
RequestInterface $requestInterface
93+
RequestInterface $requestInterface,
8494
): void {
8595
// Expect & Given
8696
$request->getMethod()->willReturn(Request::METHOD_GET);
97+
$request->headers = new HeaderBag([
98+
'1234567890abcdef1234' => 'val',
99+
'abcdef1234567890abcd' => 'val',
100+
'0987654321fedcba0987' => 'val',
101+
]);
102+
87103
$uri = 'rest/v5/search/example_channel?query=bag&sid=123&format=json';
88104
$_SERVER['REQUEST_URI'] = sprintf('/fact-finder/proxy/%s', $uri);
89-
$this->client->request(Request::METHOD_GET, $uri)->willThrow(new ConnectException('Unable to connect with server.', $requestInterface->getWrappedObject()));
105+
106+
$this->client->request(Request::METHOD_GET, $uri)->willThrow(
107+
new ConnectException('Unable to connect with server.', $requestInterface->getWrappedObject())
108+
);
109+
110+
$errorResponse = new JsonResponse(['message' => 'Unable to connect with server.'], Response::HTTP_BAD_REQUEST);
111+
$event->getResponse()->willReturn($errorResponse);
90112
$eventDispatcher->dispatch(Argument::type(BeforeProxyErrorResponseEvent::class))->willReturn($event);
91113

92114
// When

src/Api/TestConnectionController.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use Omikron\FactFinder\Shopware6\Config\Communication as CommunicationConfig;
1111
use Omikron\FactFinder\Shopware6\Upload\UploadService;
1212
use Psr\Log\LoggerInterface;
13-
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
1413
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1514
use Symfony\Component\HttpFoundation\JsonResponse;
1615
use Symfony\Component\Routing\Annotation\Route;
@@ -33,12 +32,6 @@ public function __construct(
3332
*/
3433
public function testApiConnection(): JsonResponse
3534
{
36-
$client = $this->clientBuilder
37-
->withCredentials(new Credentials(...$this->config->getCredentials()))
38-
->withServerUrl($this->config->getServerUrl())
39-
->withVersion($this->config->getVersion())
40-
->build();
41-
4235
try {
4336
$client = $this->clientBuilder
4437
->withCredentials(new Credentials(...$this->config->getCredentials()))

src/Api/UiFeedExportController.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use Omikron\FactFinder\Shopware6\MessageQueue\FeedExportHandler;
1111
use Omikron\FactFinder\Shopware6\MessageQueue\RefreshExportCacheHandler;
1212
use Psr\Log\LoggerInterface;
13-
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
1413
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1514
use Symfony\Component\HttpFoundation\JsonResponse;
1615
use Symfony\Component\HttpFoundation\Request;

src/Api/UpdateFieldRolesController.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection;
1111
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
1212
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
13-
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
1413
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
1514
use Symfony\Component\HttpFoundation\JsonResponse;
1615
use Symfony\Component\Routing\Annotation\Route;

src/Storefront/Controller/ProxyController.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\HttpFoundation\JsonResponse;
1616
use Symfony\Component\HttpFoundation\Request;
1717
use Symfony\Component\HttpFoundation\Response;
18+
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
1819
use Symfony\Component\Routing\Annotation\Route;
1920

2021
/**
@@ -46,6 +47,17 @@ public function execute(
4647
ClientBuilder $clientBuilder,
4748
EventDispatcherInterface $eventDispatcher,
4849
): Response {
50+
if (!$this->config->isProxyEnabled()) {
51+
throw new NotFoundHttpException('Proxy is disabled.');
52+
}
53+
54+
if (!$this->isWebcRequest($request->headers->all())) {
55+
return new JsonResponse(
56+
['message' => 'UNAUTHORIZED'],
57+
Response::HTTP_UNAUTHORIZED
58+
);
59+
}
60+
4961
$client = $clientBuilder
5062
->withServerUrl($this->config->getServerUrl())
5163
->withCredentials(new Credentials(...$this->config->getCredentials()))
@@ -84,4 +96,20 @@ public function execute(
8496
return $event->getResponse();
8597
}
8698
}
99+
100+
private function isWebcRequest(array $headers): bool
101+
{
102+
$pattern = '/^[0-9a-f]{20}$/i';
103+
104+
$matchingHeaders = array_filter(
105+
array_keys($headers),
106+
fn ($headerName) => preg_match($pattern, (string) $headerName)
107+
);
108+
109+
if (count($matchingHeaders) >= 3) {
110+
return true;
111+
}
112+
113+
return false;
114+
}
87115
}

0 commit comments

Comments
 (0)