Skip to content

Commit

Permalink
feat: add automatic store login, fixes #16, closes #17
Browse files Browse the repository at this point in the history
  • Loading branch information
shyim committed Oct 17, 2024
1 parent 1ff5afb commit 86a8fd4
Show file tree
Hide file tree
Showing 17 changed files with 598 additions and 9 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"symfony/filesystem": "^7.0 || ^6.0",
"symfony/finder": "^7.0 || ^6.0",
"symfony/process": "^7.0 || ^6.0",
"symfony/yaml": "^7.0 || ^6.0"
"symfony/yaml": "^7.0 || ^6.0",
"symfony/http-client": "^7.0 || ^6.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "v3.64.0",
Expand Down
4 changes: 4 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ parameters:
- tests
symfony:
containerXmlPath: var/cache/container.xml
ignoreErrors:
- message: '#does not call parent constructor from#'
paths:
- tests
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.1/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory="var/cache/phpunit"
executionOrder="depends,defects"
cacheResult="false"
requireCoverageMetadata="true"
beStrictAboutOutputDuringTests="true"
failOnRisky="true"
Expand Down
20 changes: 18 additions & 2 deletions src/Config/ConfigFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Shopware\Deployment\Config;

use Shopware\Deployment\Helper\EnvironmentHelper;
use Symfony\Component\Filesystem\Path;
use Symfony\Component\Yaml\Yaml;

Expand All @@ -17,7 +18,7 @@ public static function create(string $projectDir): ProjectConfiguration
}

if (!file_exists($file)) {
return new ProjectConfiguration();
return self::fillLicenseDomain(new ProjectConfiguration());
}

$projectConfiguration = new ProjectConfiguration();
Expand All @@ -27,7 +28,7 @@ public static function create(string $projectDir): ProjectConfiguration
self::fillConfig($projectConfiguration, $config['deployment']);
}

return $projectConfiguration;
return self::fillLicenseDomain($projectConfiguration);
}

/**
Expand All @@ -49,6 +50,12 @@ private static function fillConfig(ProjectConfiguration $projectConfiguration, a
self::fillExtensionManagement($projectConfiguration->extensionManagement, $deployment['extension-management']);
}

if (isset($deployment['store']) && \is_array($deployment['store'])) {
if (isset($deployment['store']['licenseDomain']) && \is_string($deployment['store']['licenseDomain'])) {
$projectConfiguration->store->licenseDomain = $deployment['store']['licenseDomain'];
}
}

if (isset($deployment['one-time-tasks']) && \is_array($deployment['one-time-tasks'])) {
foreach ($deployment['one-time-tasks'] as $task) {
if (isset($task['id'], $task['script']) && \is_string($task['id']) && \is_string($task['script'])) {
Expand Down Expand Up @@ -101,4 +108,13 @@ public static function fillExtensionManagement(ProjectExtensionManagement $exten
$extensionManagement->excluded = $config['exclude'];
}
}

public static function fillLicenseDomain(ProjectConfiguration $projectConfiguration): ProjectConfiguration
{
if (EnvironmentHelper::hasVariable('SHOPWARE_STORE_LICENSE_DOMAIN')) {
$projectConfiguration->store->licenseDomain = EnvironmentHelper::getVariable('SHOPWARE_STORE_LICENSE_DOMAIN', '');
}

return $projectConfiguration;
}
}
3 changes: 3 additions & 0 deletions src/Config/ProjectConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class ProjectConfiguration

public ProjectMaintenance $maintenance;

public ProjectStore $store;

/**
* @var array<string, string>
*/
Expand All @@ -22,6 +24,7 @@ public function __construct()
$this->hooks = new ProjectHooks();
$this->extensionManagement = new ProjectExtensionManagement();
$this->maintenance = new ProjectMaintenance();
$this->store = new ProjectStore();
}

public function isExtensionManaged(string $name): bool
Expand Down
10 changes: 10 additions & 0 deletions src/Config/ProjectStore.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Shopware\Deployment\Config;

class ProjectStore
{
public string $licenseDomain = '';
}
114 changes: 114 additions & 0 deletions src/Services/AccountService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

declare(strict_types=1);

namespace Shopware\Deployment\Services;

use Shopware\Deployment\Helper\EnvironmentHelper;
use Shopware\Deployment\Helper\ProcessHelper;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\HttpClientInterface;

readonly class AccountService
{
public const CORE_STORE_LICENSE_HOST = 'core.store.licenseHost';
public const CORE_STORE_SHOP_SECRET = 'core.store.shopSecret';

private HttpClientInterface $client;

public function __construct(private SystemConfigHelper $systemConfigHelper, private ProcessHelper $processHelper, ?HttpClientInterface $client = null)
{
$this->client = $client ?? HttpClient::createForBaseUri('https://api.shopware.com');
}

public function refresh(SymfonyStyle $output, string $shopwareVersion, string $licenseDomain): void
{
if (str_contains($shopwareVersion, 'dev')) {
$shopwareVersion = '___VERSION___';
}

$changed = $this->setLicenseDomain($licenseDomain);

if ($changed) {
$output->info(\sprintf("Updated license domain to %s\n", $licenseDomain));
}

$email = EnvironmentHelper::getVariable('SHOPWARE_STORE_ACCOUNT_EMAIL', '');
$password = EnvironmentHelper::getVariable('SHOPWARE_STORE_ACCOUNT_PASSWORD', '');

if ($email === '' || $password === '') {
$output->warning('No store account credentials found, skipping store account login verification and login if needed. Set SHOPWARE_STORE_ACCOUNT_EMAIL and SHOPWARE_STORE_ACCOUNT_PASSWORD to refresh the store account on deployment');
} elseif ($this->refreshShopToken($shopwareVersion, $licenseDomain, $email, $password)) {
$output->info('Refreshed global shop token to communicate to store.shopware.com');
$changed = true;
}

if ($changed) {
$this->processHelper->console(['cache:pool:invalidate-tags', '-p', 'cache.object', 'system-config']);
}
}

private function setLicenseDomain(string $licenseDomain): bool
{
$existingRecord = $this->systemConfigHelper->get(self::CORE_STORE_LICENSE_HOST);

if ($existingRecord === $licenseDomain) {
return false;
}

$this->systemConfigHelper->set(self::CORE_STORE_LICENSE_HOST, $licenseDomain);

return true;
}

private function refreshShopToken(string $shopwareVersion, string $licenseDomain, string $email, string $password): bool
{
$secret = $this->systemConfigHelper->get(self::CORE_STORE_SHOP_SECRET);
if ($secret !== null && $this->isShopSecretStillValid($secret, $shopwareVersion, $licenseDomain)) {
return false;
}

$response = $this->client->request('POST', '/swplatform/login', [
'query' => [
'shopwareVersion' => $shopwareVersion,
'domain' => $licenseDomain,
'language' => 'en-GB',
],
'json' => [
'shopwareId' => $email,
'password' => $password,
'shopwareUserId' => bin2hex(random_bytes(16)),
],
]);

$data = $response->toArray();

if (!isset($data['shopSecret']) || !\is_string($data['shopSecret'])) {
throw new \RuntimeException('Got invalid response from Shopware API: ' . json_encode($data, \JSON_THROW_ON_ERROR));
}

$this->systemConfigHelper->set(self::CORE_STORE_SHOP_SECRET, $data['shopSecret']);

return true;
}

private function isShopSecretStillValid(string $secret, string $shopwareVersion, string $licenseDomain): bool
{
$response = $this->client->request('POST', '/swplatform/pluginupdates', [
'query' => [
'shopwareVersion' => $shopwareVersion,
'domain' => $licenseDomain,
'language' => 'en-GB',
],
'json' => [
'plugins' => [],
],
'headers' => [
'X-Shopware-Shop-Secret' => $secret,
],
]);

return $response->getStatusCode() === 200;
}
}
10 changes: 10 additions & 0 deletions src/Services/InstallationManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
namespace Shopware\Deployment\Services;

use Doctrine\DBAL\Connection;
use Shopware\Deployment\Config\ProjectConfiguration;
use Shopware\Deployment\Helper\EnvironmentHelper;
use Shopware\Deployment\Helper\ProcessHelper;
use Shopware\Deployment\Struct\RunConfiguration;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class InstallationManager
{
Expand All @@ -19,6 +22,8 @@ public function __construct(
private readonly PluginHelper $pluginHelper,
private readonly AppHelper $appHelper,
private readonly HookExecutor $hookExecutor,
private readonly ProjectConfiguration $configuration,
private readonly AccountService $accountService,
) {
}

Expand Down Expand Up @@ -74,6 +79,11 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v
$this->processHelper->console(['plugin:refresh']);
$this->pluginHelper->installPlugins($configuration->skipAssetsInstall);
$this->pluginHelper->updatePlugins($configuration->skipAssetsInstall);

if ($this->configuration->store->licenseDomain !== '') {
$this->accountService->refresh(new SymfonyStyle(new ArgvInput([]), $output), $this->state->getCurrentVersion(), $this->configuration->store->licenseDomain);
}

$this->appHelper->installApps();
$this->appHelper->updateApps();

Expand Down
43 changes: 43 additions & 0 deletions src/Services/SystemConfigHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace Shopware\Deployment\Services;

use Doctrine\DBAL\Connection;

class SystemConfigHelper
{
public function __construct(private readonly Connection $connection)
{
}

public function get(string $key): ?string
{
$data = $this->connection->fetchOne('SELECT configuration_value FROM system_config WHERE configuration_key = ? AND sales_channel_id IS NULL', [$key]);

if ($data === false) {
return null;
}

$value = json_decode($data, true, 512, \JSON_THROW_ON_ERROR)['_value'];

if (\is_array($value)) {
throw new \UnexpectedValueException('Expected string, got array');
}

return (string) $value;
}

public function set(string $key, string $value): void
{
$id = (string) $this->connection->fetchOne('SELECT id FROM system_config WHERE configuration_key = ? AND sales_channel_id IS NULL', [$key]);
$payload = json_encode(['_value' => $value], \JSON_THROW_ON_ERROR);

if ($id !== '') {
$this->connection->executeStatement('UPDATE system_config SET configuration_value = ? WHERE id = ?', [$payload, $id]);
} else {
$this->connection->executeStatement('INSERT INTO system_config (id, configuration_key, configuration_value, sales_channel_id, created_at) VALUES (?, ?, ?, NULL, NOW())', [random_bytes(16), $key, $payload]);
}
}
}
8 changes: 8 additions & 0 deletions src/Services/UpgradeManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
use Shopware\Deployment\Helper\EnvironmentHelper;
use Shopware\Deployment\Helper\ProcessHelper;
use Shopware\Deployment\Struct\RunConfiguration;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

class UpgradeManager
{
Expand All @@ -20,6 +22,7 @@ public function __construct(
private readonly HookExecutor $hookExecutor,
private readonly OneTimeTasks $oneTimeTasks,
private readonly ProjectConfiguration $configuration,
private readonly AccountService $accountService,
) {
}

Expand Down Expand Up @@ -63,6 +66,11 @@ public function run(RunConfiguration $configuration, OutputInterface $output): v

$this->pluginHelper->installPlugins($configuration->skipAssetsInstall);
$this->pluginHelper->updatePlugins($configuration->skipAssetsInstall);

if ($this->configuration->store->licenseDomain !== '') {
$this->accountService->refresh(new SymfonyStyle(new ArgvInput([]), $output), $this->state->getCurrentVersion(), $this->configuration->store->licenseDomain);
}

$this->appHelper->installApps();
$this->appHelper->updateApps();

Expand Down
14 changes: 14 additions & 0 deletions tests/Config/ConfigFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Shopware\Deployment\Config\ConfigFactory;
use Zalas\PHPUnit\Globals\Attribute\Env;

#[CoversClass(ConfigFactory::class)]
class ConfigFactoryTest extends TestCase
Expand Down Expand Up @@ -48,4 +49,17 @@ public function testExistingConfigWithMaintenance(): void
$config = ConfigFactory::create(__DIR__ . '/_fixtures/maintenance-mode');
static::assertTrue($config->maintenance->enabled);
}

#[Env('SHOPWARE_STORE_LICENSE_DOMAIN', 'test')]
public function testLicenseDomainPopulatedByEnv(): void
{
$config = ConfigFactory::create(__DIR__);
static::assertSame('test', $config->store->licenseDomain);
}

public function testExistingConfigWithStoreCOnfig(): void
{
$config = ConfigFactory::create(__DIR__ . '/_fixtures/license-domain');
static::assertSame('example.com', $config->store->licenseDomain);
}
}
3 changes: 3 additions & 0 deletions tests/Config/_fixtures/license-domain/.shopware-project.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
deployment:
store:
licenseDomain: "example.com"
Loading

0 comments on commit 86a8fd4

Please sign in to comment.