From 5feb6b177ed8ada832500ffcc05c248f18234f2c Mon Sep 17 00:00:00 2001 From: hschoenenberger Date: Tue, 6 May 2025 15:29:03 +0200 Subject: [PATCH 01/46] chore: init new major v8 --- _dev/package.json | 2 +- config.xml | 2 +- ps_accounts.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/_dev/package.json b/_dev/package.json index 57e0e94dc..d17ae8ded 100644 --- a/_dev/package.json +++ b/_dev/package.json @@ -1,6 +1,6 @@ { "name": "ps_accounts", - "version": "7.2.0", + "version": "8.0.0", "private": true, "scripts": { "dev": "vite", diff --git a/config.xml b/config.xml index cb421f943..f40abe5a7 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_accounts - + diff --git a/ps_accounts.php b/ps_accounts.php index 94d4fbf09..9b305d66e 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -32,7 +32,7 @@ class Ps_accounts extends Module // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '7.2.0'; + const VERSION = '8.0.0'; /** * Admin tabs @@ -129,7 +129,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '7.2.0'; + $this->version = '8.0.0'; $this->module_key = 'abf2cd758b4d629b2944d3922ef9db73'; From 7027a096b56fd99385aa615c2e54c4ac4163a5e2 Mon Sep 17 00:00:00 2001 From: atourneriePresta <76098514+atourneriePresta@users.noreply.github.com> Date: Thu, 5 Jun 2025 16:09:15 +0200 Subject: [PATCH 02/46] [ACCOUNT-2595/2604] feat: create & verify shop identity (#433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: working identity creation --------- Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> Co-authored-by: Antoine Metifeu Co-authored-by: hschoenenberger --- .dir-scoped | 1 - .ver-scoped | 1 + .zip-contents | 1 + Makefile | 12 + composer.json | 4 +- composer.lock | 112 +----- config.bulle.php | 9 +- config.dist.php | 1 + config.local.php | 7 +- config.preprod.php | 1 + config.prod.php | 1 + config.xml | 2 +- .../admin/AdminAjaxPsAccountsController.php | 18 +- .../admin/AdminDebugPsAccountsController.php | 8 +- .../admin/AdminLoginPsAccountsController.php | 2 +- controllers/front/apiV1ShopLinkAccount.php | 92 ----- controllers/front/apiV1ShopOauth2Client.php | 95 ----- controllers/front/apiV2ShopHealthCheck.php | 33 +- controllers/front/apiV2ShopProof.php | 82 ++++ fix-autoload.php | 6 +- ps_accounts.php | 79 ++-- scoper.inc.php | 6 +- src/Account/CachedShopStatus.php | 81 ++++ ...ommand.php => CreateIdentitiesCommand.php} | 3 +- ...pCommand.php => CreateIdentityCommand.php} | 15 +- ...ommand.php => VerifyIdentitiesCommand.php} | 16 +- ...pCommand.php => VerifyIdentityCommand.php} | 16 +- ...andler.php => CreateIdentitiesHandler.php} | 43 +-- .../CommandHandler/CreateIdentityHandler.php | 105 ++++++ .../CommandHandler/DeleteUserShopHandler.php | 17 +- .../CommandHandler/MultiShopHandler.php | 71 ++++ .../CommandHandler/UnlinkShopHandler.php | 102 ----- .../CommandHandler/UpdateUserShopHandler.php | 17 +- .../CommandHandler/UpgradeModuleHandler.php | 167 --------- .../UpgradeModuleMultiHandler.php | 96 ----- .../VerifyIdentitiesHandler.php | 51 +++ .../CommandHandler/VerifyIdentityHandler.php | 114 ++++++ src/Account/Dto/LinkShop.php | 44 --- ...ception.php => UnknownStatusException.php} | 2 +- src/Account/LinkShop.php | 200 ---------- src/Account/ProofManager.php | 71 ++++ .../Session/Firebase/FirebaseSession.php | 46 ++- src/Account/Session/Session.php | 21 +- src/Account/Session/ShopSession.php | 91 +---- src/Account/ShopUrl.php | 103 ++++++ src/Account/StatusManager.php | 255 +++++++++++++ src/Adapter/Configuration.php | 24 ++ src/Adapter/ConfigurationKeys.php | 4 + src/Api/Client/AccountsClient.php | 182 +-------- src/Api/Client/ExternalAssetsClient.php | 5 +- src/Context/ShopContext.php | 44 ++- src/Controller/Admin/OAuth2Controller.php | 2 +- src/Hook/ActionObjectEmployeeDeleteAfter.php | 2 + src/Hook/ActionObjectShopUpdateAfter.php | 16 +- src/Hook/ActionShopAccountUnlinkAfter.php | 5 - ...Header.php => DisplayAdminAfterHeader.php} | 25 +- .../Controller/AbstractRestController.php | 38 +- .../Controller/AbstractV2RestController.php | 8 +- src/Http/Resource/Resource.php | 47 +++ src/Presenter/PsAccountsPresenter.php | 38 +- src/Provider/OAuth2/ShopProvider.php | 0 src/Provider/RsaKeysProvider.php | 189 ---------- src/Provider/ShopProvider.php | 64 +++- src/Repository/ConfigurationRepository.php | 100 +++-- src/Service/Accounts/AccountsException.php | 25 ++ src/Service/Accounts/AccountsService.php | 350 ++++++++++++++++++ .../Accounts/Resource/FirebaseTokens.php | 46 +++ .../Accounts/Resource/IdentityCreated.php} | 19 +- src/Service/Accounts/Resource/ShopStatus.php | 122 ++++++ src/Service/AnalyticsService.php | 2 +- src/Service/PsAccountsService.php | 24 +- src/Service/SentryService.php | 14 +- src/ServiceProvider/ApiClientProvider.php | 10 +- src/ServiceProvider/CommandProvider.php | 76 ++-- src/ServiceProvider/DefaultProvider.php | 19 +- src/ServiceProvider/SessionProvider.php | 5 +- src/Type/Dto.php | 10 +- src/enforce_autoload.php | 21 ++ tests/src/BaseTestCase.php | 19 +- .../UpgradeModuleHandlerTest.php | 4 +- .../{v1 => V1}/ShopLinkAccount/DeleteTest.php | 24 +- .../{v1 => V1}/ShopLinkAccount/StoreTest.php | 27 +- .../ShopOauth2Client/DeleteTest.php | 11 +- .../{v1 => V1}/ShopOauth2Client/StoreTest.php | 11 +- .../Api/{v1 => V1}/ShopToken/ShowTest.php | 11 +- .../Api/{v1 => V1}/ShopUrl/ShowTest.php | 11 +- .../{v2 => V2}/ShopHealthCheck/ShowTest.php | 145 +------- .../Api/V2/ShopVerifyProof/ShowTest.php | 99 +++++ tests/src/Feature/Api/V2/TestCase.php | 134 +++++++ .../src/Feature/Api/v1/DecodePayloadTest.php | 81 ---- .../{FeatureTestCase.php => TestCase.php} | 19 +- .../CreateIdentityHandlerTest.php | 153 ++++++++ .../CommandHandler/UnlinkShopHandlerTest.php | 70 ---- .../UpgradeModuleHandlerTest.php | 253 ------------- .../VerifyIdentityHandlerTest.php | 147 ++++++++ .../OwnerSession/GetValidTokenTest.php | 6 +- .../OwnerSession/RefreshTokenTest.php | 4 +- .../ShopSession/GetValidTokenTest.php | 6 +- .../Firebase/ShopSession/RefreshTokenTest.php | 4 +- .../Unit/Account/Session/SessionHelpers.php | 36 +- .../Session/ShopSession/RefreshTokenTest.php | 81 +--- tests/src/Unit/Account/StatusManagerTest.php | 255 +++++++++++++ .../Hook/ActionObjectShopUpdateAfterTest.php | 19 +- .../RsaKeysProvider/CreatePairTest.php | 32 -- .../RsaKeysProvider/GenerateKeysTest.php | 53 --- .../RsaKeysProvider/VerifySignatureTest.php | 30 -- tests/src/Unit/Service/OAuth2/TestCase.php | 22 +- .../PsAccountsService/IsAccountLinkedTest.php | 39 +- .../IsAccountLinkedV4Test.php | 4 + upgrade/upgrade-6.0.0.php | 4 +- upgrade/upgrade-6.1.4.php | 3 +- upgrade/upgrade-6.1.6.php | 3 +- upgrade/upgrade-7.0.0.php | 2 + upgrade/upgrade-8.0.0.php | 32 ++ 114 files changed, 3108 insertions(+), 2602 deletions(-) create mode 100644 .ver-scoped delete mode 100644 controllers/front/apiV1ShopLinkAccount.php delete mode 100644 controllers/front/apiV1ShopOauth2Client.php create mode 100644 controllers/front/apiV2ShopProof.php create mode 100644 src/Account/CachedShopStatus.php rename src/Account/Command/{UpgradeModuleMultiCommand.php => CreateIdentitiesCommand.php} (96%) rename src/Account/Command/{LinkShopCommand.php => CreateIdentityCommand.php} (79%) rename src/Account/Command/{UpgradeModuleCommand.php => VerifyIdentitiesCommand.php} (75%) rename src/Account/Command/{UnlinkShopCommand.php => VerifyIdentityCommand.php} (79%) rename src/Account/CommandHandler/{LinkShopHandler.php => CreateIdentitiesHandler.php} (51%) create mode 100644 src/Account/CommandHandler/CreateIdentityHandler.php create mode 100644 src/Account/CommandHandler/MultiShopHandler.php delete mode 100644 src/Account/CommandHandler/UnlinkShopHandler.php delete mode 100644 src/Account/CommandHandler/UpgradeModuleHandler.php delete mode 100644 src/Account/CommandHandler/UpgradeModuleMultiHandler.php create mode 100644 src/Account/CommandHandler/VerifyIdentitiesHandler.php create mode 100644 src/Account/CommandHandler/VerifyIdentityHandler.php delete mode 100644 src/Account/Dto/LinkShop.php rename src/Account/Exception/{InconsistentAssociationStateException.php => UnknownStatusException.php} (93%) delete mode 100644 src/Account/LinkShop.php create mode 100644 src/Account/ProofManager.php create mode 100644 src/Account/ShopUrl.php create mode 100644 src/Account/StatusManager.php rename src/Hook/{DisplayBackOfficeHeader.php => DisplayAdminAfterHeader.php} (61%) delete mode 100644 src/Provider/OAuth2/ShopProvider.php delete mode 100644 src/Provider/RsaKeysProvider.php create mode 100644 src/Service/Accounts/AccountsException.php create mode 100644 src/Service/Accounts/AccountsService.php create mode 100644 src/Service/Accounts/Resource/FirebaseTokens.php rename src/{Account/Dto/UpgradeModule.php => Service/Accounts/Resource/IdentityCreated.php} (76%) create mode 100644 src/Service/Accounts/Resource/ShopStatus.php create mode 100644 src/enforce_autoload.php rename tests/src/Feature/Api/{v1 => V1}/ShopLinkAccount/DeleteTest.php (79%) rename tests/src/Feature/Api/{v1 => V1}/ShopLinkAccount/StoreTest.php (74%) rename tests/src/Feature/Api/{v1 => V1}/ShopOauth2Client/DeleteTest.php (87%) rename tests/src/Feature/Api/{v1 => V1}/ShopOauth2Client/StoreTest.php (93%) rename tests/src/Feature/Api/{v1 => V1}/ShopToken/ShowTest.php (92%) rename tests/src/Feature/Api/{v1 => V1}/ShopUrl/ShowTest.php (93%) rename tests/src/Feature/Api/{v2 => V2}/ShopHealthCheck/ShowTest.php (54%) create mode 100644 tests/src/Feature/Api/V2/ShopVerifyProof/ShowTest.php create mode 100644 tests/src/Feature/Api/V2/TestCase.php delete mode 100644 tests/src/Feature/Api/v1/DecodePayloadTest.php rename tests/src/Feature/{FeatureTestCase.php => TestCase.php} (89%) create mode 100644 tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php delete mode 100644 tests/src/Unit/Account/CommandHandler/UnlinkShopHandlerTest.php delete mode 100644 tests/src/Unit/Account/CommandHandler/UpgradeModuleHandlerTest.php create mode 100644 tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php create mode 100644 tests/src/Unit/Account/StatusManagerTest.php delete mode 100644 tests/src/Unit/Provider/RsaKeysProvider/CreatePairTest.php delete mode 100644 tests/src/Unit/Provider/RsaKeysProvider/GenerateKeysTest.php delete mode 100644 tests/src/Unit/Provider/RsaKeysProvider/VerifySignatureTest.php create mode 100644 upgrade/upgrade-8.0.0.php diff --git a/.dir-scoped b/.dir-scoped index 314124e1d..6a106f42c 100644 --- a/.dir-scoped +++ b/.dir-scoped @@ -4,4 +4,3 @@ prestashopcorp/lightweight-container lcobucci monolog ramsey/uuid -phpseclib/phpseclib/phpseclib diff --git a/.ver-scoped b/.ver-scoped new file mode 100644 index 000000000..d22b9edda --- /dev/null +++ b/.ver-scoped @@ -0,0 +1 @@ +PsAccounts diff --git a/.zip-contents b/.zip-contents index 62c1e5aac..78fd9151f 100644 --- a/.zip-contents +++ b/.zip-contents @@ -11,6 +11,7 @@ config.php config.local.php config.prod.php config.preprod.php +config.bulle.php config.xml LICENSE logo.png diff --git a/Makefile b/Makefile index 3c6a84a15..3ad8b0b0a 100644 --- a/Makefile +++ b/Makefile @@ -238,6 +238,10 @@ phpstan-8.1.5-7.4: platform-8.1.5-7.4 phpstan PHP_SCOPER_VENDOR_DIRS = $(shell cat .dir-scoped) PHP_SCOPER_OUTPUT_DIR := vendor-scoped PHP_SCOPER_VERSION := 0.18.11 +PHP_SCOPER_VERSION_PREFIX = $(shell cat .ver-scoped) + +php-scoper-version-prefix: + @echo "${PHP_SCOPER_VERSION_PREFIX}" ${WORKDIR}/php-scoper.phar: curl -s -f -L -O "https://github.com/humbug/php-scoper/releases/download/${PHP_SCOPER_VERSION}/php-scoper.phar" @@ -258,6 +262,13 @@ php-scoper-add-prefix: scoper.inc.php vendor-clean vendor ${WORKDIR}/php-scoper. $(foreach DIR,$(PHP_SCOPER_VENDOR_DIRS), rm -rf "./vendor/${DIR}" && mv "./${PHP_SCOPER_OUTPUT_DIR}/${DIR}" ./vendor/${DIR};) if [ ! -z ${PHP_SCOPER_OUTPUT_DIR} ]; then rm -rf "./${PHP_SCOPER_OUTPUT_DIR}"; fi +REGEX_UPDATE_PREFIX := "s/PrestaShop\\\\Module\\\\PsAccounts\([0-9]*\)\\\\Vendor/PrestaShop\\\\Module\\\\${PHP_SCOPER_VERSION_PREFIX}\\\\Vendor/" +REGEX_UPDATE_PREFIX2 := "s/PrestaShop\\\\\\\\Module\\\\\\\\PsAccounts\([0-9]*\)\\\\\\\\Vendor/PrestaShop\\\\\\\\Module\\\\\\\\${PHP_SCOPER_VERSION_PREFIX}\\\\\\\\Vendor/" +php-scoper-update-prefix: + @echo "updating prefix..." + find ./tests -type f -exec sed -i -e ${REGEX_UPDATE_PREFIX} {} \; + find ./src -type f -exec sed -i -e ${REGEX_UPDATE_PREFIX} {} \; + sed -i -e ${REGEX_UPDATE_PREFIX2} ./composer.json php-scoper-dump-autoload: ${COMPOSER} dump-autoload --classmap-authoritative @@ -265,6 +276,7 @@ php-scoper-dump-autoload: php-scoper-fix-autoload: php fix-autoload.php +#php-scoper: php-scoper-add-prefix php-scoper-update-prefix php-scoper-dump-autoload php-scoper-fix-autoload php-scoper: php-scoper-add-prefix php-scoper-dump-autoload php-scoper-fix-autoload ########## diff --git a/composer.json b/composer.json index 8b2278aab..1d7690fab 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,6 @@ "PrestaShop\\Module\\PsAccounts\\Vendor\\Firebase\\JWT\\": "vendor/firebase/php-jwt/src", "PrestaShop\\Module\\PsAccounts\\Vendor\\Lcobucci\\JWT\\": "vendor/lcobucci/jwt/src", "PrestaShop\\Module\\PsAccounts\\Vendor\\Monolog\\": "vendor/monolog/monolog/src/Monolog", - "PrestaShop\\Module\\PsAccounts\\Vendor\\phpseclib\\": "vendor/phpseclib/phpseclib/phpseclib", "PrestaShop\\Module\\PsAccounts\\Vendor\\Psr\\Log\\": "vendor/psr/log/Psr/Log", "PrestaShop\\Module\\PsAccounts\\Vendor\\Ramsey\\Uuid\\": "vendor/ramsey/uuid/src", "PrestaShop\\Module\\PsAccounts\\Vendor\\PrestaShopCorp\\LightweightContainer\\": "vendor/prestashopcorp/lightweight-container/src" @@ -37,7 +36,6 @@ }, "require": { "php": ">=5.6", - "phpseclib/phpseclib": "^2.0", "ext-json": "*", "lcobucci/jwt": "^3.3", "monolog/monolog": "^1.27.1", @@ -54,4 +52,4 @@ } ], "author": "PrestaShop" -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index 5456b294a..51e7211b3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e25422c217caea07ccfd00fcf94955de", + "content-hash": "8cf0e9751b64642799d08075e90841b5", "packages": [ { "name": "firebase/php-jwt", @@ -280,116 +280,6 @@ }, "time": "2022-02-16T17:07:03+00:00" }, - { - "name": "phpseclib/phpseclib", - "version": "2.0.48", - "source": { - "type": "git", - "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "eaa7be704b8b93a6913b69eb7f645a59d7731b61" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/eaa7be704b8b93a6913b69eb7f645a59d7731b61", - "reference": "eaa7be704b8b93a6913b69eb7f645a59d7731b61", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phing/phing": "~2.7", - "phpunit/phpunit": "^4.8.35|^5.7|^6.0|^9.4", - "squizlabs/php_codesniffer": "~2.0" - }, - "suggest": { - "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", - "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", - "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", - "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.", - "ext-xml": "Install the XML extension to load XML formatted public keys." - }, - "type": "library", - "autoload": { - "files": [ - "phpseclib/bootstrap.php" - ], - "psr-4": { - "phpseclib\\": "phpseclib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "role": "Lead Developer" - }, - { - "name": "Patrick Monnerat", - "email": "pm@datasphere.ch", - "role": "Developer" - }, - { - "name": "Andreas Fischer", - "email": "bantu@phpbb.com", - "role": "Developer" - }, - { - "name": "Hans-Jürgen Petrich", - "email": "petrich@tronic-media.com", - "role": "Developer" - }, - { - "name": "Graham Campbell", - "email": "graham@alt-three.com", - "role": "Developer" - } - ], - "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", - "homepage": "http://phpseclib.sourceforge.net", - "keywords": [ - "BigInteger", - "aes", - "asn.1", - "asn1", - "blowfish", - "crypto", - "cryptography", - "encryption", - "rsa", - "security", - "sftp", - "signature", - "signing", - "ssh", - "twofish", - "x.509", - "x509" - ], - "support": { - "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/2.0.48" - }, - "funding": [ - { - "url": "https://github.com/terrafrost", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpseclib", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", - "type": "tidelift" - } - ], - "time": "2024-12-14T21:03:54+00:00" - }, { "name": "prestashopcorp/lightweight-container", "version": "v0.1.0", diff --git a/config.bulle.php b/config.bulle.php index 11a8dc3c0..fb8e92023 100644 --- a/config.bulle.php +++ b/config.bulle.php @@ -20,8 +20,8 @@ return [ 'ps_accounts.environment' => 'integration', - 'ps_accounts.accounts_api_url' => 'https://accounts-api-prestabulle2.distribution-integration.prestashop.net/', - 'ps_accounts.accounts_ui_url' => 'https://accounts-prestabulle2.distribution-integration.prestashop.net', + 'ps_accounts.accounts_api_url' => 'https://accounts-api-prestabulle6.distribution-integration.prestashop.net/', + 'ps_accounts.accounts_ui_url' => 'https://accounts-prestabulle6.distribution-integration.prestashop.net', 'ps_accounts.sso_account_url' => 'https://prestashop-newsso-staging.appspot.com/login', 'ps_accounts.sso_resend_verification_email_url' => 'https://prestashop-newsso-staging.appspot.com/account/send-verification-email', 'ps_accounts.billing_api_url' => 'https://billing-api.distribution-preprod.prestashop.net/', @@ -32,10 +32,7 @@ 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5', 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.psessentials-integration.net', 'ps_accounts.oauth2_url' => 'https://oauth-integration.prestashop.com', - //ps_accounts.oauth2_url_authorize' => 'https://oauth-integration.prestashop.com/oauth2/auth' - //ps_accounts.oauth2_url_access_token' => 'https://oauth-integration.prestashop.com/oauth2/token' - //ps_accounts.oauth2_url_resource_owner_details' => 'https://oauth-integration.prestashop.com/userinfo' - //ps_accounts.oauth2_url_session_logout' => 'https://oauth-integration.prestashop.com/oauth2/sessions/logout' + 'ps_accounts.token_audience' => 'https://accounts-api.distribution-integration.prestashop.net', 'ps_accounts.testimonials_url' => 'https://assets.prestashop3.com/dst/accounts/assets/testimonials.json', diff --git a/config.dist.php b/config.dist.php index 5951a8980..1be28c78d 100644 --- a/config.dist.php +++ b/config.dist.php @@ -38,6 +38,7 @@ // OAuth2 configuration url 'ps_accounts.oauth2_url' => 'https://oauth.prestashop.localhost', + 'ps_accounts.token_audience' => 'https://accounts-api.prestashop.localhost', // Login page testimonials url 'ps_accounts.testimonials_url' => 'https://assets.prestashop3.com/dst/accounts/assets/testimonials.json', diff --git a/config.local.php b/config.local.php index 838d8cfa3..e64e2fb90 100644 --- a/config.local.php +++ b/config.local.php @@ -28,20 +28,21 @@ 'ps_accounts.billing_api_url' => 'https://billing-api.psessentials-integration.net', 'ps_accounts.indirect_channel_api_url' => 'https://indirect-channel-api-integration.prestashop.net', 'ps_accounts.sentry_credentials' => 'https://12e8e4574d50b54d878db8ee2c3f8380@o298402.ingest.us.sentry.io/5354585', - '#ps_accounts.segment_write_key' => 'UITzSdsFTgYsXaiJG09hsCiupUPwgJQB', - 'ps_accounts.segment_write_key' => 'eYODaH20rT1lMRTTUtAa15BKBlV1XUXQ', + 'ps_accounts.segment_write_key' => 'UITzSdsFTgYsXaiJG09hsCiupUPwgJQB', + //'ps_accounts.segment_write_key' => 'eYODaH20rT1lMRTTUtAa15BKBlV1XUXQ', 'ps_accounts.check_api_ssl_cert' => false, 'ps_accounts.verify_account_tokens' => false, 'ps_accounts.accounts_vue_cdn_url' => 'http://prestashop8.docker.localhost/upload/psaccountsVue.umd.min.js', //ps_accounts.accounts_cdn_url' => 'http://prestashop8.docker.localhost/upload/psaccountsVue.js' //ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5.1.0-test-1/dist/psaccountsVue.js' - 'ps_accounts.accounts_cdn_url' => 'http://localhost:5174/dist/psaccountsVue.js', + 'ps_accounts.accounts_cdn_url' => 'http://127.0.0.1:3010/psaccountsVue.js', // a page to display "Update Your Module" message 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.prestashop.local/', // OAuth2 setup 'ps_accounts.oauth2_url' => 'https://oauth.prestashop.local', + 'ps_accounts.token_audience' => 'https://accounts-api.prestashop.local', 'ps_accounts.testimonials_url' => 'https://assets.prestashop3.com/dst/accounts/assets/testimonials.json', 'ps_accounts.log_level' => 'INFO', diff --git a/config.preprod.php b/config.preprod.php index ee414b1e0..4ca636141 100644 --- a/config.preprod.php +++ b/config.preprod.php @@ -43,6 +43,7 @@ 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.psessentials-integration.net', 'ps_accounts.oauth2_url' => 'https://oauth-preprod.prestashop.com', + 'ps_accounts.token_audience' => 'https://accounts-api.distribution-preprod.prestashop.net', 'ps_accounts.testimonials_url' => 'https://assets.prestashop3.com/dst/accounts/assets/testimonials.json', diff --git a/config.prod.php b/config.prod.php index 3d12e757f..af38c5940 100644 --- a/config.prod.php +++ b/config.prod.php @@ -35,6 +35,7 @@ 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5', 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.psessentials.net', 'ps_accounts.oauth2_url' => 'https://oauth.prestashop.com', + 'ps_accounts.token_audience' => 'https://accounts-api.distribution.prestashop.net', 'ps_accounts.testimonials_url' => 'https://assets.prestashop3.com/dst/accounts/assets/testimonials.json', ]; diff --git a/config.xml b/config.xml index f40abe5a7..867dcef89 100644 --- a/config.xml +++ b/config.xml @@ -9,4 +9,4 @@ 1 0 - + \ No newline at end of file diff --git a/controllers/admin/AdminAjaxPsAccountsController.php b/controllers/admin/AdminAjaxPsAccountsController.php index cfd976113..bf290b9a9 100644 --- a/controllers/admin/AdminAjaxPsAccountsController.php +++ b/controllers/admin/AdminAjaxPsAccountsController.php @@ -20,10 +20,11 @@ require_once __DIR__ . '/../../src/Polyfill/Traits/Controller/AjaxRender.php'; use PrestaShop\Module\PsAccounts\Account\Command\DeleteUserShopCommand; -use PrestaShop\Module\PsAccounts\Account\Command\UnlinkShopCommand; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\AccountLogin\OAuth2Session; use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; +use PrestaShop\Module\PsAccounts\Hook\ActionShopAccountUnlinkAfter; use PrestaShop\Module\PsAccounts\Polyfill\Traits\Controller\AjaxRender; use PrestaShop\Module\PsAccounts\Presenter\PsAccountsPresenter; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; @@ -120,12 +121,17 @@ public function ajaxProcessUnlinkShop() public function ajaxProcessResetLinkAccount() { try { - /** @var ConfigurationRepository $configurationRepository */ - $configurationRepository = $this->module->getService(ConfigurationRepository::class); + /** @var StatusManager $statusManager */ + $statusManager = $this->module->getService(StatusManager::class); - $this->commandBus->handle(new UnlinkShopCommand( - $configurationRepository->getShopId() - )); + $status = $statusManager->getStatus(); + + $statusManager->invalidateCache(); + + Hook::exec(ActionShopAccountUnlinkAfter::getName(), [ + 'cloudShopId' => $status->cloudShopId, + 'shopId' => \Context::getContext()->shop->id, + ]); header('Content-Type: text/json'); diff --git a/controllers/admin/AdminDebugPsAccountsController.php b/controllers/admin/AdminDebugPsAccountsController.php index 1a1ff2900..38aafee98 100755 --- a/controllers/admin/AdminDebugPsAccountsController.php +++ b/controllers/admin/AdminDebugPsAccountsController.php @@ -18,9 +18,9 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -use PrestaShop\Module\PsAccounts\Account\LinkShop; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Service\PsAccountsService; class AdminDebugPsAccountsController extends \ModuleAdminController @@ -47,13 +47,13 @@ public function initContent() /** @var PsAccountsService $psAccountsService */ $psAccountsService = $this->module->getService(PsAccountsService::class); - /** @var LinkShop $linkShop */ - $linkShop = $this->module->getService(LinkShop::class); + /** @var StatusManager $statusManager */ + $statusManager = $this->module->getService(StatusManager::class); $this->context->smarty->assign([ 'config' => [ 'shopId' => (int) $this->context->shop->id, - 'shopUuidV4' => $linkShop->getShopUuid(), + 'shopUuidV4' => $statusManager->getStatus()->isVerified, 'moduleVersion' => \Ps_accounts::VERSION, 'psVersion' => _PS_VERSION_, 'phpVersion' => phpversion(), diff --git a/controllers/admin/AdminLoginPsAccountsController.php b/controllers/admin/AdminLoginPsAccountsController.php index 5c8276ec5..78c7643dd 100644 --- a/controllers/admin/AdminLoginPsAccountsController.php +++ b/controllers/admin/AdminLoginPsAccountsController.php @@ -172,6 +172,6 @@ private function getTestimonials() { $res = $this->externalAssetsClient->getTestimonials(); - return $res['status'] ? $res['body'] : []; + return $res->isSuccessful ? $res->body : []; } } diff --git a/controllers/front/apiV1ShopLinkAccount.php b/controllers/front/apiV1ShopLinkAccount.php deleted file mode 100644 index a9ab02d46..000000000 --- a/controllers/front/apiV1ShopLinkAccount.php +++ /dev/null @@ -1,92 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -use PrestaShop\Module\PsAccounts\Account\Command\LinkShopCommand; -use PrestaShop\Module\PsAccounts\Account\Command\UnlinkShopCommand; -use PrestaShop\Module\PsAccounts\Account\Dto\LinkShop; -use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; -use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; -use PrestaShop\Module\PsAccounts\Http\Controller\AbstractShopRestController; -use PrestaShop\Module\PsAccounts\Http\Request\UpdateShopLinkAccountRequest; - -class ps_AccountsApiV1ShopLinkAccountModuleFrontController extends AbstractShopRestController -{ - /** - * @var CommandBus - */ - private $commandBus; - - /** - * @throws Exception - */ - public function __construct() - { - parent::__construct(); - - $this->commandBus = $this->module->getService(CommandBus::class); - //$this->commandBus = $this->module->getContainer()->get('prestashop.command_bus'); - } - - /** - * @param Shop $shop - * @param UpdateShopLinkAccountRequest $request - * - * @return array - * - * @throws RefreshTokenException - * @throws Exception - */ - public function update(Shop $shop, UpdateShopLinkAccountRequest $request) - { - $this->commandBus->handle(new LinkShopCommand( - new LinkShop([ - 'shopId' => $request->shop_id, - 'uid' => $request->uid, - 'ownerUid' => $request->owner_uid, - 'ownerEmail' => $request->owner_email, - 'employeeId' => $request->employee_id, - ]) - )); - - return [ - 'success' => true, - 'message' => 'Link Account stored successfully', - ]; - } - - /** - * @param Shop $shop - * @param array $payload - * - * @return array - * - * @throws PrestaShopException - * @throws Exception - */ - public function delete(Shop $shop, array $payload) - { - $this->commandBus->handle(new UnlinkShopCommand($shop->id)); - - return [ - 'success' => true, - 'message' => 'Link Account deleted successfully', - ]; - } -} diff --git a/controllers/front/apiV1ShopOauth2Client.php b/controllers/front/apiV1ShopOauth2Client.php deleted file mode 100644 index aeb440624..000000000 --- a/controllers/front/apiV1ShopOauth2Client.php +++ /dev/null @@ -1,95 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; -use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; -use PrestaShop\Module\PsAccounts\Http\Controller\AbstractShopRestController; -use PrestaShop\Module\PsAccounts\Http\Request\UpdateShopOauth2ClientRequest; -use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; - -class ps_AccountsApiV1ShopOauth2ClientModuleFrontController extends AbstractShopRestController -{ - /** - * @var OAuth2Client - */ - private $oauth2Client; - - /** - * @var ShopSession - */ - private $session; - - /** - * ps_AccountsApiV1ShopOauth2ClientModuleFrontController constructor. - * - * @throws Exception - */ - public function __construct() - { - parent::__construct(); - - $this->oauth2Client = $this->module->getService(OAuth2Client::class); - $this->session = $this->module->getService(ShopSession::class); - } - - /** - * @param Shop $shop - * @param UpdateShopOauth2ClientRequest $request - * - * @return array - */ - public function update(Shop $shop, UpdateShopOauth2ClientRequest $request) - { - $this->oauth2Client->setClientId($request->client_id); - $this->oauth2Client->setClientSecret($request->client_secret); - - try { - $this->session->getValidToken(); - } catch (RefreshTokenException $e) { - return [ - 'error' => true, - 'message' => 'Could not retrieve a valid token', - ]; - } - - return [ - 'success' => true, - 'message' => 'Oauth client stored successfully', - ]; - } - - /** - * @param Shop $shop - * @param array $payload - * - * @return array - * - * @throws Exception - */ - public function delete(Shop $shop, array $payload) - { - $this->oauth2Client->delete(); - - return [ - 'success' => true, - 'message' => 'Oauth client deleted successfully', - ]; - } -} diff --git a/controllers/front/apiV2ShopHealthCheck.php b/controllers/front/apiV2ShopHealthCheck.php index a4101a5a6..b82f9ea43 100644 --- a/controllers/front/apiV2ShopHealthCheck.php +++ b/controllers/front/apiV2ShopHealthCheck.php @@ -19,14 +19,14 @@ */ use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; -use PrestaShop\Module\PsAccounts\Account\LinkShop; use PrestaShop\Module\PsAccounts\Account\Session\Firebase; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Account\Token\NullToken; use PrestaShop\Module\PsAccounts\Account\Token\Token; -use PrestaShop\Module\PsAccounts\Api\Client\AccountsClient; use PrestaShop\Module\PsAccounts\Http\Controller\AbstractV2ShopRestController; use PrestaShop\Module\PsAccounts\Http\Request\ShopHealthCheckRequest; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Exception; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; @@ -35,9 +35,9 @@ class ps_AccountsApiV2ShopHealthCheckModuleFrontController extends AbstractV2ShopRestController { /** - * @var LinkShop + * @var StatusManager */ - private $linkShop; + private $statusManager; /** * @var OAuth2Client @@ -65,9 +65,9 @@ class ps_AccountsApiV2ShopHealthCheckModuleFrontController extends AbstractV2Sho private $psAccountsService; /** - * @var AccountsClient + * @var AccountsService */ - private $accountsClient; + private $accountsService; /** * @var OAuth2Service @@ -90,7 +90,7 @@ protected function getScope() protected function getAudience() { return [ - 'ps_accounts/' . $this->linkShop->getShopUuid(), + 'ps_accounts/' . $this->statusManager->getCloudShopId(), ]; } @@ -100,16 +100,16 @@ public function __construct() // public healthcheck $this->authenticated = false; - if ($this->getRequestHeader('Authorization') !== null) { + if ($this->getRequestHeader(self::HEADER_AUTHORIZATION) !== null) { $this->authenticated = true; } - $this->linkShop = $this->module->getService(LinkShop::class); + $this->statusManager = $this->module->getService(StatusManager::class); $this->oauth2Client = $this->module->getService(OAuth2Client::class); $this->shopSession = $this->module->getService(ShopSession::class); $this->firebaseShopSession = $this->module->getService(Firebase\ShopSession::class); $this->firebaseOwnerSession = $this->module->getService(Firebase\OwnerSession::class); - $this->accountsClient = $this->module->getService(AccountsClient::class); + $this->accountsService = $this->module->getService(AccountsService::class); $this->psAccountsService = $this->module->getService(PsAccountsService::class); $this->oauth2Service = $this->module->getService(OAuth2Service::class); } @@ -125,7 +125,7 @@ public function __construct() public function show(Shop $shop, ShopHealthCheckRequest $request) { // $this->assertAudience([ -// 'ps_accounts/' . $this->linkShop->getShopUuid(), +// 'ps_accounts/' . $this->shopIdentity->getShopUuid(), // ]); // $this->assertScope([ // 'shop.health', @@ -145,7 +145,7 @@ public function show(Shop $shop, ShopHealthCheckRequest $request) $healthCheckMessage = [ 'oauth2Client' => $this->oauth2Client->exists(), - 'shopLinked' => (bool) $this->linkShop->getShopUuid(), + 'shopLinked' => (bool) $this->statusManager->getCloudShopId(), 'isSsoEnabled' => $this->psAccountsService->getLoginActivated(), 'oauthToken' => $this->tokenInfos($shopToken), 'firebaseOwnerToken' => $this->tokenInfos($firebaseOwnerToken), @@ -173,10 +173,9 @@ public function show(Shop $shop, ShopHealthCheckRequest $request) 'psVersion' => _PS_VERSION_, 'moduleVersion' => Ps_accounts::VERSION, 'phpVersion' => phpversion(), - 'cloudShopId' => (string) $this->linkShop->getShopUuid(), + 'cloudShopId' => (string) $this->statusManager->getCloudShopId(), 'shopName' => $shop->name, - 'ownerEmail' => (string) $this->linkShop->getOwnerEmail(), - 'publicKey' => (string) $this->linkShop->getPublicKey(), + 'ownerEmail' => (string) $this->statusManager->getPointOfContactEmail(), ]); } @@ -242,9 +241,9 @@ private function tokenInfos(Token $token) */ private function getAccountsApiStatus() { - $response = $this->accountsClient->healthCheck(); + $response = $this->accountsService->healthCheck(); - return (bool) $response['status']; + return $response->isSuccessful; } /** diff --git a/controllers/front/apiV2ShopProof.php b/controllers/front/apiV2ShopProof.php new file mode 100644 index 000000000..0a3ef4b39 --- /dev/null +++ b/controllers/front/apiV2ShopProof.php @@ -0,0 +1,82 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +use PrestaShop\Module\PsAccounts\Account\ProofManager; +use PrestaShop\Module\PsAccounts\Account\StatusManager; +use PrestaShop\Module\PsAccounts\Http\Controller\AbstractV2ShopRestController; + +class ps_AccountsApiV2ShopProofModuleFrontController extends AbstractV2ShopRestController +{ + /** + * @var ProofManager + */ + private $proofManager; + + /** + * @var StatusManager + */ + private $statusManager; + + /** + * @var bool + */ + protected $authenticated = true; + + /** + * @return array + */ + protected function getScope() + { + return [ + 'shop.proof.read', + ]; + } + + /** + * @return array + */ + protected function getAudience() + { + return [ + 'ps_accounts/' . $this->statusManager->getCloudShopId(), + ]; + } + + public function __construct() + { + parent::__construct(); + + $this->proofManager = $this->module->getService(ProofManager::class); + $this->statusManager = $this->module->getService(StatusManager::class); + } + + /** + * @param Shop $shop + * @param array $payload + * + * @return array + */ + public function show(Shop $shop, array $payload) + { + return [ + 'proof' => $this->proofManager->getProof(), + ]; + } +} diff --git a/fix-autoload.php b/fix-autoload.php index e2d0f684f..e87fea87e 100644 --- a/fix-autoload.php +++ b/fix-autoload.php @@ -6,16 +6,16 @@ * * More information also found here: https://github.com/humbug/php-scoper/issues/298 */ -// TODO: fix all scoped deps +$versionPrefix = rtrim(shell_exec('cat .ver-scoped')); $match = '(guzzlehttp|symfony|lcobucci\/jwt|ramsey\/uuid|paragonie\/random_compat|ralouphie\/getallheaders|phpseclib\/phpseclib\/phpseclib)'; $scoper_path = __DIR__ . '/vendor/composer'; $static_loader_path = $scoper_path . '/autoload_static.php'; echo "Fixing $static_loader_path \n"; $static_loader = file_get_contents($static_loader_path); -$static_loader = preg_replace('/\'([A-Za-z0-9]*?)\' => __DIR__ \. (.*?' . $match . '.*?),/', '\'PsAccounts$1\' => __DIR__ . $2,', $static_loader); +$static_loader = preg_replace('/\'([A-Za-z0-9]*?)\' => __DIR__ \. (.*?' . $match . '.*?),/', '\'' . $versionPrefix . '$1\' => __DIR__ . $2,', $static_loader); file_put_contents($static_loader_path, $static_loader); $files_loader_path = $scoper_path . '/autoload_files.php'; echo "Fixing $files_loader_path \n"; $files_loader = file_get_contents($files_loader_path); -$files_loader = preg_replace('/\'(.*?)\' => (.*?' . $match . '.*?),/', '\'PsAccounts$1\' => $2,', $files_loader); +$files_loader = preg_replace('/\'(.*?)\' => (.*?' . $match . '.*?),/', '\'' . $versionPrefix . '$1\' => $2,', $files_loader); file_put_contents($files_loader_path, $files_loader); diff --git a/ps_accounts.php b/ps_accounts.php index 9b305d66e..f16224d40 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -21,10 +21,11 @@ exit; } require_once __DIR__ . '/vendor/autoload.php'; +//require __DIR__ . '/src/autoload_module.php'; -if (!class_exists('\PrestaShop\Module\PsAccounts\Hook\HookableTrait')) { - ps_accounts_fix_upgrade(); -} +//if (!class_exists('\PrestaShop\Module\PsAccounts\Hook\HookableTrait')) { +// ps_accounts_fix_upgrade(); +//} class Ps_accounts extends Module { @@ -105,7 +106,7 @@ class Ps_accounts extends Module // Login/Logout OAuth // PS 1.6 - 1.7 - 'displayBackOfficeHeader', + 'displayAdminAfterHeader', // FIXME: for alpha version only 'actionAdminLoginControllerSetMedia', // PS >= 8 //'actionAdminControllerInitBefore', @@ -135,9 +136,7 @@ public function __construct() parent::__construct(); - $this->displayName = $this->l( - 'PrestaShop Account' - ); + $this->displayName = $this->l('PrestaShop Account'); $this->description = $this->l( 'Link your store to your PrestaShop account to activate and manage your subscriptions in your ' . 'back office. Do not uninstall this module if you have a current subscription.' @@ -454,22 +453,58 @@ public function onModuleReset() // FIXME: this wont prevent from re-implanting override on reset of module $uninstaller = new PrestaShop\Module\PsAccounts\Module\Uninstall($this, Db::getInstance()); $uninstaller->deleteAdminTab('AdminLogin'); + + /** @var \PrestaShop\Module\PsAccounts\Cqrs\CommandBus $commandBus */ + $commandBus = $this->getService(\PrestaShop\Module\PsAccounts\Cqrs\CommandBus::class); + + // Verification flow + $commandBus->handle(new \PrestaShop\Module\PsAccounts\Account\Command\CreateIdentitiesCommand()); + $commandBus->handle(new \PrestaShop\Module\PsAccounts\Account\Command\VerifyIdentitiesCommand()); } -} -/** - * @return void - */ -function ps_accounts_fix_upgrade() -{ - $root = __DIR__; - $requires = array_merge([ - $root . '/src/Module/Install.php', -// $root . '/src/Hook/Hook.php', - $root . '/src/Hook/HookableTrait.php', - ], []/*, glob($root . '/src/Hook/*.php')*/); - - foreach ($requires as $filename) { - require_once $filename; + /** + * @return string + */ + public function getCloudShopId() + { + /** @var \PrestaShop\Module\PsAccounts\Account\StatusManager $statusManager */ + $statusManager = $this->getService(\PrestaShop\Module\PsAccounts\Account\StatusManager::class); + + return $statusManager->getCloudShopId(); + } + + /** + * @return bool + */ + public function getVerifiedStatus() + { + /** @var \PrestaShop\Module\PsAccounts\Account\StatusManager $statusManager */ + $statusManager = $this->getService(\PrestaShop\Module\PsAccounts\Account\StatusManager::class); + + try { + if ($statusManager->getStatus()->isVerified) { + return true; + } + } catch (\PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException $e) { + } + + return false; } } +// +///** +// * @return void +// */ +//function ps_accounts_fix_upgrade() +//{ +// $root = __DIR__; +// $requires = array_merge([ +// $root . '/src/Module/Install.php', +//// $root . '/src/Hook/Hook.php', +// $root . '/src/Hook/HookableTrait.php', +// ], []/*, glob($root . '/src/Hook/*.php')*/); +// +// foreach ($requires as $filename) { +// require_once $filename; +// } +//} diff --git a/scoper.inc.php b/scoper.inc.php index 65988f353..415902339 100644 --- a/scoper.inc.php +++ b/scoper.inc.php @@ -34,6 +34,8 @@ // Vendor dependency dirs your want to scope // Note: you'll have to manually add namespaces in your composer.json $dirScoped = explode("\n", shell_exec('cat .dir-scoped')); +$versionPrefix = rtrim(shell_exec('cat .ver-scoped')); + /** * TODO: cannot scope psr0 libs * segmentio/analytics-php @@ -65,7 +67,7 @@ // will be generated instead. // // For more see: https://github.com/humbug/php-scoper/blob/master/docs/configuration.md#prefix - 'prefix' => 'PrestaShop\Module\PsAccounts\Vendor', + 'prefix' => 'PrestaShop\Module\\' . $versionPrefix . '\Vendor', // The base output directory for the prefixed files. // This will be overridden by the 'output-dir' command line option if present. @@ -114,7 +116,7 @@ static function ($filePath, $prefix, $contents) { } // if ($filePath === __DIR__ . '/vendor/symfony/dependency-injection/Compiler/PassConfig.php') { // return str_replace( -// "'PrestaShop\\\\Module\\\\PsAccounts\\\\Vendor\\\\array_merge'", +// "'PrestaShop\\\\Module\\\\" . $versionPrefix . "\\\\Vendor\\\\array_merge'", // "'\\array_merge'", // $contents // ); diff --git a/src/Account/CachedShopStatus.php b/src/Account/CachedShopStatus.php new file mode 100644 index 000000000..c658a042e --- /dev/null +++ b/src/Account/CachedShopStatus.php @@ -0,0 +1,81 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account; + +use DateTime; +use PrestaShop\Module\PsAccounts\Http\Resource\Resource; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; + +class CachedShopStatus extends Resource +{ + /** + * @var ShopStatus + */ + public $shopStatus; + + /** + * @var bool + */ + public $isValid = false; + + /** + * @var DateTime|null + */ + public $updatedAt; + +// /** +// * @var string[] +// */ +// protected $required = [ +// 'shopStatus', +// ]; + + public function __construct($values = []) + { + $this->castDateTime($values, [ + 'updatedAt', + ]); + $this->castBool($values, [ + 'isValid', + ]); + $this->castChildResource($values, ShopStatus::class, [ + 'shopStatus', + ]); + + parent::__construct($values); + } + + /** + * @param bool $all + * + * @return array + */ + public function toArray($all = true) + { + $values = parent::toArray($all); + + if (isset($values['shopStatus'])) { + $values['shopStatus'] = $this->shopStatus->toArray(); + } + + return $values; + } +} diff --git a/src/Account/Command/UpgradeModuleMultiCommand.php b/src/Account/Command/CreateIdentitiesCommand.php similarity index 96% rename from src/Account/Command/UpgradeModuleMultiCommand.php rename to src/Account/Command/CreateIdentitiesCommand.php index efbee5072..6b4fd1802 100644 --- a/src/Account/Command/UpgradeModuleMultiCommand.php +++ b/src/Account/Command/CreateIdentitiesCommand.php @@ -1,4 +1,5 @@ payload = $payload; + $this->shopId = $shopId; } } diff --git a/src/Account/Command/UpgradeModuleCommand.php b/src/Account/Command/VerifyIdentitiesCommand.php similarity index 75% rename from src/Account/Command/UpgradeModuleCommand.php rename to src/Account/Command/VerifyIdentitiesCommand.php index 54a8f39a8..30fbe5790 100644 --- a/src/Account/Command/UpgradeModuleCommand.php +++ b/src/Account/Command/VerifyIdentitiesCommand.php @@ -1,4 +1,5 @@ payload = $payload; } } diff --git a/src/Account/Command/UnlinkShopCommand.php b/src/Account/Command/VerifyIdentityCommand.php similarity index 79% rename from src/Account/Command/UnlinkShopCommand.php rename to src/Account/Command/VerifyIdentityCommand.php index ef6d5cfa4..53535f27a 100644 --- a/src/Account/Command/UnlinkShopCommand.php +++ b/src/Account/Command/VerifyIdentityCommand.php @@ -1,4 +1,5 @@ shopId = $shopId; - $this->errorMsg = $errorMsg; } } diff --git a/src/Account/CommandHandler/LinkShopHandler.php b/src/Account/CommandHandler/CreateIdentitiesHandler.php similarity index 51% rename from src/Account/CommandHandler/LinkShopHandler.php rename to src/Account/CommandHandler/CreateIdentitiesHandler.php index 2b8b429a9..b1d45c309 100644 --- a/src/Account/CommandHandler/LinkShopHandler.php +++ b/src/Account/CommandHandler/CreateIdentitiesHandler.php @@ -20,38 +20,29 @@ namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; -use Hook; -use PrestaShop\Module\PsAccounts\Account\Command\LinkShopCommand; -use PrestaShop\Module\PsAccounts\Account\LinkShop; -use PrestaShop\Module\PsAccounts\Hook\ActionShopAccountLinkAfter; -use PrestaShopException; +use PrestaShop\Module\PsAccounts\Account\Command\CreateIdentitiesCommand; +use PrestaShop\Module\PsAccounts\Account\Command\CreateIdentityCommand; +use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; +use PrestaShop\Module\PsAccounts\Log\Logger; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; -class LinkShopHandler +class CreateIdentitiesHandler extends MultiShopHandler { /** - * @var LinkShop - */ - private $linkShop; - - public function __construct(LinkShop $linkShop) - { - $this->linkShop = $linkShop; - } - - /** - * @param LinkShopCommand $command + * @param CreateIdentitiesCommand $command * * @return void - * - * @throws PrestaShopException */ - public function handle(LinkShopCommand $command) + public function handle(CreateIdentitiesCommand $command) { - $this->linkShop->update($command->payload); - - Hook::exec(ActionShopAccountLinkAfter::getName(), [ - 'shopUuid' => $this->linkShop->getShopUuid(), - 'shopId' => $command->payload->shopId, - ]); + $this->handleMulti(function ($multiShopId) { + try { + $this->commandBus->handle(new CreateIdentityCommand($multiShopId)); + } catch (RefreshTokenException $e) { + Logger::getInstance()->error($e->getMessage()); + } catch (AccountsException $e) { + Logger::getInstance()->error($e->getMessage()); + } + }); } } diff --git a/src/Account/CommandHandler/CreateIdentityHandler.php b/src/Account/CommandHandler/CreateIdentityHandler.php new file mode 100644 index 000000000..b219dce70 --- /dev/null +++ b/src/Account/CommandHandler/CreateIdentityHandler.php @@ -0,0 +1,105 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; + +use PrestaShop\Module\PsAccounts\Account\Command\CreateIdentityCommand; +use PrestaShop\Module\PsAccounts\Account\StatusManager; +use PrestaShop\Module\PsAccounts\Provider\ShopProvider; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; +use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; + +class CreateIdentityHandler +{ + /** + * @var AccountsService + */ + private $accountsService; + + /** + * @var OAuth2Client + */ + private $oAuth2Client; + + /** + * @var ShopProvider + */ + private $shopProvider; + + /** + * @var StatusManager + */ + private $statusManager; + + /** + * @param AccountsService $accountsService + * @param ShopProvider $shopProvider + * @param OAuth2Client $oauth2Client + * @param StatusManager $shopStatus + */ + public function __construct( + AccountsService $accountsService, + ShopProvider $shopProvider, + OAuth2Client $oauth2Client, + StatusManager $shopStatus + ) { + $this->accountsService = $accountsService; + $this->shopProvider = $shopProvider; + $this->oAuth2Client = $oauth2Client; + $this->statusManager = $shopStatus; + } + + /** + * @param CreateIdentityCommand $command + * + * @return void + * + * @throws AccountsException + */ + public function handle(CreateIdentityCommand $command) + { + if ($this->isAlreadyCreated()) { + return; + } + + $shopId = $command->shopId ?: \Shop::getContextShopID(); + + $identityCreated = $this->accountsService->createShopIdentity( + $this->shopProvider->getUrl($shopId) + ); + $this->oAuth2Client->update( + $identityCreated->clientId, + $identityCreated->clientSecret + ); + $this->statusManager->setCloudShopId($identityCreated->cloudShopId); + } + + /** + * Idempotency check + * + * @return bool + */ + private function isAlreadyCreated() + { + // FIXME: define where this code belongs + return $this->oAuth2Client->exists() && $this->statusManager->identityCreated(); + } +} diff --git a/src/Account/CommandHandler/DeleteUserShopHandler.php b/src/Account/CommandHandler/DeleteUserShopHandler.php index 329d26510..3dd4c1532 100644 --- a/src/Account/CommandHandler/DeleteUserShopHandler.php +++ b/src/Account/CommandHandler/DeleteUserShopHandler.php @@ -24,15 +24,16 @@ use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; -use PrestaShop\Module\PsAccounts\Api\Client\AccountsClient; use PrestaShop\Module\PsAccounts\Context\ShopContext; +use PrestaShop\Module\PsAccounts\Http\Client\Response; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; class DeleteUserShopHandler { /** - * @var AccountsClient + * @var AccountsService */ - private $accountClient; + private $accountsService; /** * @var ShopContext @@ -50,18 +51,18 @@ class DeleteUserShopHandler private $ownerSession; /** - * @param AccountsClient $accountClient + * @param AccountsService $accountsService * @param ShopContext $shopContext * @param ShopSession $shopSession * @param OwnerSession $ownerSession */ public function __construct( - AccountsClient $accountClient, + AccountsService $accountsService, ShopContext $shopContext, ShopSession $shopSession, OwnerSession $ownerSession ) { - $this->accountClient = $accountClient; + $this->accountsService = $accountsService; $this->shopContext = $shopContext; $this->shopSession = $shopSession; $this->ownerSession = $ownerSession; @@ -70,7 +71,7 @@ public function __construct( /** * @param DeleteUserShopCommand $command * - * @return array + * @return Response * * @throws RefreshTokenException */ @@ -80,7 +81,7 @@ public function handle(DeleteUserShopCommand $command) $ownerToken = $this->ownerSession->getValidToken(); $shopToken = $this->shopSession->getValidToken(); - return $this->accountClient->deleteUserShop( + return $this->accountsService->deleteUserShop( $ownerToken->getUuid(), $shopToken->getUuid(), $ownerToken->getJwt() diff --git a/src/Account/CommandHandler/MultiShopHandler.php b/src/Account/CommandHandler/MultiShopHandler.php new file mode 100644 index 000000000..d98e8e7c3 --- /dev/null +++ b/src/Account/CommandHandler/MultiShopHandler.php @@ -0,0 +1,71 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; + +use PrestaShop\Module\PsAccounts\Context\ShopContext; +use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; + +abstract class MultiShopHandler +{ + /** + * @var ShopContext + */ + protected $shopContext; + + /** + * @var CommandBus + */ + protected $commandBus; + + public function __construct( + ShopContext $shopContext, + CommandBus $commandBus + ) { + $this->shopContext = $shopContext; + $this->commandBus = $commandBus; + } + + /** + * @param \Closure $handler + * + * @return void + */ + protected function handleMulti($handler) + { + foreach ($this->getShopIds() as $multiShopId) { + $this->shopContext->execInShopContext($multiShopId, function () use ($handler, $multiShopId) { + $handler($multiShopId); + }); + } + } + + /** + * @return array|null[] + */ + protected function getShopIds() + { + if ($this->shopContext->isMultishopActive()) { + return $this->shopContext->getMultiShopIds(); + } + + return [null]; + } +} diff --git a/src/Account/CommandHandler/UnlinkShopHandler.php b/src/Account/CommandHandler/UnlinkShopHandler.php deleted file mode 100644 index a70a9d23e..000000000 --- a/src/Account/CommandHandler/UnlinkShopHandler.php +++ /dev/null @@ -1,102 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; - -use Hook; -use PrestaShop\Module\PsAccounts\Account\Command\UnlinkShopCommand; -use PrestaShop\Module\PsAccounts\Account\LinkShop; -use PrestaShop\Module\PsAccounts\Hook\ActionShopAccountUnlinkAfter; -use PrestaShop\Module\PsAccounts\Provider\ShopProvider; -use PrestaShop\Module\PsAccounts\Service\AnalyticsService; - -class UnlinkShopHandler -{ - /** - * @var LinkShop - */ - private $linkShop; - - /** - * @var AnalyticsService - */ - private $analytics; - - /** - * @var ShopProvider - */ - private $shopProvider; - - /** - * @param LinkShop $linkShop - * @param AnalyticsService $analytics - * @param ShopProvider $shopProvider - */ - public function __construct(LinkShop $linkShop, AnalyticsService $analytics, ShopProvider $shopProvider) - { - $this->linkShop = $linkShop; - $this->analytics = $analytics; - $this->shopProvider = $shopProvider; - } - - /** - * @param UnlinkShopCommand $command - * - * @return void - * - * @throws \PrestaShopException - * @throws \Exception - */ - public function handle(UnlinkShopCommand $command) - { - // FIXME: exec in shop context with $command->shopId - - $hookData = [ - 'shopUuid' => $this->linkShop->getShopUuid(), - 'shopId' => $command->shopId, - ]; - $analyticsData = [ - 'ownerUuid' => $this->linkShop->getOwnerUuid(), - 'ownerEmail' => $this->linkShop->getOwnerEmail(), - 'shopUuid' => $this->linkShop->getShopUuid(), - ]; - - $this->linkShop->delete(); - - if ($command->errorMsg) { - $this->linkShop->setUnlinkedOnError($command->errorMsg); - - $shop = $this->shopProvider->formatShopData( - (array) \Shop::getShop($command->shopId) - ); - $this->analytics->trackShopUnlinkedOnError( - $analyticsData['ownerUuid'], - $analyticsData['ownerEmail'], - $analyticsData['shopUuid'], - $shop->frontUrl, - $shop->url, - 'ps_accounts', - $command->errorMsg - ); - } - - Hook::exec(ActionShopAccountUnlinkAfter::getName(), $hookData); - } -} diff --git a/src/Account/CommandHandler/UpdateUserShopHandler.php b/src/Account/CommandHandler/UpdateUserShopHandler.php index 9e475c9ae..92b2d730e 100644 --- a/src/Account/CommandHandler/UpdateUserShopHandler.php +++ b/src/Account/CommandHandler/UpdateUserShopHandler.php @@ -24,15 +24,16 @@ use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; -use PrestaShop\Module\PsAccounts\Api\Client\AccountsClient; use PrestaShop\Module\PsAccounts\Context\ShopContext; +use PrestaShop\Module\PsAccounts\Http\Client\Response; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; class UpdateUserShopHandler { /** - * @var AccountsClient + * @var AccountsService */ - private $accountClient; + private $accountsService; /** * @var ShopContext @@ -50,18 +51,18 @@ class UpdateUserShopHandler private $ownerSession; /** - * @param AccountsClient $accountClient + * @param AccountsService $accountsService * @param ShopContext $shopContext * @param ShopSession $shopSession * @param OwnerSession $ownerSession */ public function __construct( - AccountsClient $accountClient, + AccountsService $accountsService, ShopContext $shopContext, ShopSession $shopSession, OwnerSession $ownerSession ) { - $this->accountClient = $accountClient; + $this->accountsService = $accountsService; $this->shopContext = $shopContext; $this->shopSession = $shopSession; $this->ownerSession = $ownerSession; @@ -70,7 +71,7 @@ public function __construct( /** * @param UpdateUserShopCommand $command * - * @return array + * @return Response * * @throws RefreshTokenException */ @@ -80,7 +81,7 @@ public function handle(UpdateUserShopCommand $command) $shopToken = $this->shopSession->getValidToken(); $ownerToken = $this->ownerSession->getValidToken(); - return $this->accountClient->updateUserShop( + return $this->accountsService->updateUserShop( $ownerToken->getUuid(), $shopToken->getUuid(), $ownerToken->getJwt(), diff --git a/src/Account/CommandHandler/UpgradeModuleHandler.php b/src/Account/CommandHandler/UpgradeModuleHandler.php deleted file mode 100644 index e7004f9d1..000000000 --- a/src/Account/CommandHandler/UpgradeModuleHandler.php +++ /dev/null @@ -1,167 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; - -use PrestaShop\Module\PsAccounts\Account\Command\UnlinkShopCommand; -use PrestaShop\Module\PsAccounts\Account\Command\UpgradeModuleCommand; -use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; -use PrestaShop\Module\PsAccounts\Account\LinkShop; -use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; -use PrestaShop\Module\PsAccounts\Account\Token\NullToken; -use PrestaShop\Module\PsAccounts\Api\Client\AccountsClient; -use PrestaShop\Module\PsAccounts\Context\ShopContext; -use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; -use PrestaShop\Module\PsAccounts\Log\Logger; -use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; - -class UpgradeModuleHandler -{ - /** - * @var LinkShop - */ - private $linkShop; - - /** - * @var ShopSession - */ - private $shopSession; - - /** - * @var AccountsClient - */ - private $accountsClient; - - /** - * @var ConfigurationRepository - */ - private $configRepo; - - /** - * @var ShopContext - */ - private $shopContext; - - /** - * @var CommandBus - */ - private $commandBus; - - public function __construct( - AccountsClient $accountsClient, - LinkShop $linkShop, - ShopSession $shopSession, - ShopContext $shopContext, - ConfigurationRepository $configurationRepository, - CommandBus $commandBus - ) { - $this->accountsClient = $accountsClient; - $this->linkShop = $linkShop; - $this->shopSession = $shopSession; - $this->shopContext = $shopContext; - $this->configRepo = $configurationRepository; - $this->commandBus = $commandBus; - } - - /** - * @param UpgradeModuleCommand $command - * - * @return void - * - * @throws RefreshTokenException - */ - public function handle(UpgradeModuleCommand $command) - { - $this->shopContext->execInShopContext($command->payload->shopId, function () use ($command) { - $lastUpgrade = $this->configRepo->getLastUpgrade(false); - - if (version_compare($lastUpgrade, $command->payload->version, '<')) { - Logger::getInstance()->info( - 'attempt upgrade [' . $lastUpgrade . ' to ' . $command->payload->version . ']' - ); - - // Set new version a soon as we can to avoid duplicate calls - $this->configRepo->updateLastUpgrade($command->payload->version); - - // FIXME: to be removed once oauth client has been updated - //if (version_compare($lastUpgrade, '7.0.0', '<')) { - $this->lastChanceToRefreshShopToken(); - //} - - $token = $this->shopSession->getValidToken(); - - if (!$token->getJwt() instanceof NullToken) { - $response = $this->accountsClient->upgradeShopModule( - $this->linkShop->getShopUuid(), - (string) $token, - $command->payload - ); - - if (!$response['status']) { - $this->commandBus->handle(new UnlinkShopCommand( - $this->configRepo->getShopId(), - $response['httpCode'] - )); - } - } - } - }); - } - - /** - * @return array - */ - private function getOrRefreshShopToken() - { - $token = $this->shopSession->getToken(); - if ($token->isExpired() && !empty($token->getRefreshToken())) { - $response = $this->accountsClient->refreshShopToken( - //$this->configRepo->getFirebaseRefreshToken(), - $token->getRefreshToken(), - //$this->configRepo->getShopUuid() - $this->linkShop->getShopUuid() - ); - - if (isset($response['body']['token'])) { - return [ - 'token' => $response['body']['token'], - 'refresh_token' => $response['body']['refresh_token'], - ]; - } - } - - return [ - 'token' => (string) $token, - 'refresh_token' => $token->getRefreshToken(), - ]; - } - - /** - * @return void - */ - private function lastChanceToRefreshShopToken() - { - $tokens = $this->getOrRefreshShopToken(); - $this->shopSession->setToken( - $tokens['token'], - $tokens['refresh_token'] - ); - } -} diff --git a/src/Account/CommandHandler/UpgradeModuleMultiHandler.php b/src/Account/CommandHandler/UpgradeModuleMultiHandler.php deleted file mode 100644 index 6151fef9a..000000000 --- a/src/Account/CommandHandler/UpgradeModuleMultiHandler.php +++ /dev/null @@ -1,96 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; - -use PrestaShop\Module\PsAccounts\Account\Command\UpgradeModuleCommand; -use PrestaShop\Module\PsAccounts\Account\Command\UpgradeModuleMultiCommand; -use PrestaShop\Module\PsAccounts\Account\Dto\UpgradeModule; -use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; -use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; -use PrestaShop\Module\PsAccounts\Exception\DtoException; -use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; -use PrestaShopDatabaseException; - -class UpgradeModuleMultiHandler -{ - /** - * @var ConfigurationRepository - */ - private $configRepo; - - /** - * @var CommandBus - */ - private $commandBus; - - public function __construct( - CommandBus $commandBus, - ConfigurationRepository $configRepo - ) { - $this->commandBus = $commandBus; - $this->configRepo = $configRepo; - } - - /** - * @param UpgradeModuleMultiCommand $command - * - * @return void - */ - public function handle(UpgradeModuleMultiCommand $command) - { - foreach ($this->getShops($this->configRepo->isMultishopActive()) as $id) { - try { - $this->commandBus->handle(new UpgradeModuleCommand(new UpgradeModule([ - 'shopId' => $id, - // FIXME: should be part of the command payload - 'version' => \Ps_accounts::VERSION, - ]))); - } catch (RefreshTokenException $e) { - } catch (DtoException $e) { - } - } - } - - /** - * @param bool $multishop - * - * @return array|null[] - */ - private function getShops($multishop) - { - $shops = [null]; - if ($multishop) { - $shops = []; - $db = \Db::getInstance(); - try { - $result = $db->query('SELECT id_shop FROM ' . _DB_PREFIX_ . 'shop'); - while ($row = $db->nextRow($result)) { - /* @phpstan-ignore-next-line */ - $shops[] = $row['id_shop']; - } - } catch (PrestaShopDatabaseException $e) { - return []; - } - } - - return $shops; - } -} diff --git a/src/Account/CommandHandler/VerifyIdentitiesHandler.php b/src/Account/CommandHandler/VerifyIdentitiesHandler.php new file mode 100644 index 000000000..43c74314d --- /dev/null +++ b/src/Account/CommandHandler/VerifyIdentitiesHandler.php @@ -0,0 +1,51 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; + +use PrestaShop\Module\PsAccounts\Account\Command\VerifyIdentitiesCommand; +use PrestaShop\Module\PsAccounts\Account\Command\VerifyIdentityCommand; +use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; +use PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException; +use PrestaShop\Module\PsAccounts\Log\Logger; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; + +class VerifyIdentitiesHandler extends MultiShopHandler +{ + /** + * @param VerifyIdentitiesCommand $command + * + * @return void + */ + public function handle(VerifyIdentitiesCommand $command) + { + $this->handleMulti(function ($multiShopId) { + try { + $this->commandBus->handle(new VerifyIdentityCommand($multiShopId)); + } catch (RefreshTokenException $e) { + Logger::getInstance()->error($e->getMessage()); + } catch (AccountsException $e) { + Logger::getInstance()->error($e->getMessage()); + } catch (UnknownStatusException $e) { + Logger::getInstance()->error($e->getMessage()); + } + }); + } +} diff --git a/src/Account/CommandHandler/VerifyIdentityHandler.php b/src/Account/CommandHandler/VerifyIdentityHandler.php new file mode 100644 index 000000000..c3b3daf58 --- /dev/null +++ b/src/Account/CommandHandler/VerifyIdentityHandler.php @@ -0,0 +1,114 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; + +use PrestaShop\Module\PsAccounts\Account\Command\VerifyIdentityCommand; +use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; +use PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException; +use PrestaShop\Module\PsAccounts\Account\ProofManager; +use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; +use PrestaShop\Module\PsAccounts\Account\StatusManager; +use PrestaShop\Module\PsAccounts\Provider\ShopProvider; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; + +class VerifyIdentityHandler +{ + /** + * @var AccountsService + */ + private $accountsService; + + /** + * @var StatusManager + */ + private $statusManager; + + /** + * @var ShopProvider + */ + private $shopProvider; + + /** + * @var ShopSession + */ + private $shopSession; + + /** + * @var ProofManager + */ + private $proofManager; + + /** + * @param AccountsService $accountsService + * @param ShopProvider $shopProvider + * @param StatusManager $statusManager + * @param ShopSession $shopSession + * @param ProofManager $proofManager + */ + public function __construct( + AccountsService $accountsService, + ShopProvider $shopProvider, + StatusManager $statusManager, + ShopSession $shopSession, + ProofManager $proofManager + ) { + $this->accountsService = $accountsService; + $this->shopProvider = $shopProvider; + $this->statusManager = $statusManager; + $this->shopSession = $shopSession; + $this->proofManager = $proofManager; + } + + /** + * @param VerifyIdentityCommand $command + * + * @return void + * + * @throws RefreshTokenException + * @throws UnknownStatusException + * @throws AccountsException + */ + public function handle(VerifyIdentityCommand $command) + { + $cachedStatus = $this->statusManager->getStatus(); + + if ($cachedStatus->isVerified) { + return; + } + $shopId = $command->shopId ?: \Shop::getContextShopID(); + + //try { + $this->accountsService->verifyShopIdentity( + $this->statusManager->getCloudShopId(), + $this->shopSession->getValidToken(), + $this->shopProvider->getUrl($shopId), + $this->proofManager->generateProof() + ); + + $this->statusManager->invalidateCache(); + + $this->proofManager->deleteProof(); + //} catch (AccountsException $e) { + // // Status not verified + //} + } +} diff --git a/src/Account/Dto/LinkShop.php b/src/Account/Dto/LinkShop.php deleted file mode 100644 index f08f6b7a3..000000000 --- a/src/Account/Dto/LinkShop.php +++ /dev/null @@ -1,44 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Account\Dto; - -use PrestaShop\Module\PsAccounts\Type\Dto; - -class LinkShop extends Dto -{ - /** @var string */ - public $shopId; - /** @var string */ - public $uid; - /** @var string */ - public $employeeId = ''; - /** @var string */ - public $ownerUid; - /** @var string */ - public $ownerEmail; - - protected $required = [ - 'shopId', - 'uid', -// 'ownerUid', -// 'ownerEmail', - ]; -} diff --git a/src/Account/Exception/InconsistentAssociationStateException.php b/src/Account/Exception/UnknownStatusException.php similarity index 93% rename from src/Account/Exception/InconsistentAssociationStateException.php rename to src/Account/Exception/UnknownStatusException.php index 0b8380fa0..1f2ef036e 100644 --- a/src/Account/Exception/InconsistentAssociationStateException.php +++ b/src/Account/Exception/UnknownStatusException.php @@ -20,6 +20,6 @@ namespace PrestaShop\Module\PsAccounts\Account\Exception; -class InconsistentAssociationStateException extends \Exception +class UnknownStatusException extends \Exception { } diff --git a/src/Account/LinkShop.php b/src/Account/LinkShop.php deleted file mode 100644 index 892b2ef15..000000000 --- a/src/Account/LinkShop.php +++ /dev/null @@ -1,200 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Account; - -use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; - -class LinkShop -{ - /** - * @var ConfigurationRepository - */ - private $configuration; - - /** - * ShopLinkAccountService constructor. - * - * @param ConfigurationRepository $configuration - */ - public function __construct( - ConfigurationRepository $configuration - ) { - $this->configuration = $configuration; - } - - /** - * @return void - * - * @throws \Exception - */ - public function delete() - { - $this->setShopUuid(''); - $this->setEmployeeId(''); - $this->setOwnerUuid(''); - $this->setOwnerEmail(''); - $this->setUnlinkedOnError(''); - } - - /** - * @param Dto\LinkShop $payload - * - * @return void - */ - public function update(Dto\LinkShop $payload) - { - $this->setShopUuid($payload->uid); - $this->setEmployeeId((int) $payload->employeeId ?: ''); - $this->setOwnerUuid($payload->ownerUid); - $this->setOwnerEmail($payload->ownerEmail); - $this->setUnlinkedOnError(''); - } - - /** - * @return bool - * - * @throws \Exception - */ - public function exists() - { - return (bool) $this->getShopUuid(); - } - - /** - * @return string|null - */ - public function linkedAt() - { - return $this->configuration->getShopUuidDateUpd(); - } - - /** - * @return bool - * - * @throws \Exception - * - * @deprecated - */ - public function existsV4() - { - return $this->configuration->getFirebaseIdToken() - && !$this->configuration->getUserFirebaseIdToken() - && $this->configuration->getFirebaseEmail(); - } - - /** - * @return string - */ - public function getShopUuid() - { - return $this->configuration->getShopUuid(); - } - - /** - * @param string $uuid - * - * @return void - */ - public function setShopUuid($uuid) - { - $this->configuration->updateShopUuid($uuid); - } - - /** - * @return int - */ - public function getEmployeeId() - { - return (int) $this->configuration->getEmployeeId(); - } - - /** - * @param int|string $employeeId - * - * @return void - */ - public function setEmployeeId($employeeId) - { - $this->configuration->updateEmployeeId((string) $employeeId); - } - - /** - * @return string - */ - public function getOwnerUuid() - { - return $this->configuration->getUserFirebaseUuid(); - } - - /** - * @param string $uuid - * - * @return void - */ - public function setOwnerUuid($uuid) - { - $this->configuration->updateUserFirebaseUuid((string) $uuid); - } - - /** - * @return string - */ - public function getOwnerEmail() - { - return $this->configuration->getFirebaseEmail(); - } - - /** - * @param string $email - * - * @return void - */ - public function setOwnerEmail($email) - { - $this->configuration->updateFirebaseEmail($email); - } - - /** - * @return string|bool - */ - public function getPublicKey() - { - return $this->configuration->getAccountsRsaPublicKey(); - } - - /** - * @return string - */ - public function getUnlinkedOnError() - { - return $this->configuration->getUnlinkedOnError(); - } - - /** - * @param string|null $errorMsg - * - * @return void - */ - public function setUnlinkedOnError($errorMsg) - { - $this->configuration->updateUnlinkedOnError($errorMsg); - } -} diff --git a/src/Account/ProofManager.php b/src/Account/ProofManager.php new file mode 100644 index 000000000..bdf1b7a60 --- /dev/null +++ b/src/Account/ProofManager.php @@ -0,0 +1,71 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account; + +use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; +use PrestaShop\Module\PsAccounts\Vendor\Ramsey\Uuid\Uuid; + +class ProofManager +{ + /** + * @var ConfigurationRepository + */ + private $configuration; + + /** + * ManageProof constructor. + * + * @param ConfigurationRepository $configuration + */ + public function __construct( + ConfigurationRepository $configuration + ) { + $this->configuration = $configuration; + } + + /** + * @return string + */ + public function generateProof() + { + $proof = base64_encode(\hash_hmac('sha512', Uuid::uuid4()->toString(), uniqid())); + + $this->configuration->updateShopProof($proof); + + return $proof; + } + + /** + * @return string|null + */ + public function getProof() + { + return $this->configuration->getShopProof(); + } + + /** + * @return void + */ + public function deleteProof() + { + $this->configuration->updateShopProof(null); + } +} diff --git a/src/Account/Session/Firebase/FirebaseSession.php b/src/Account/Session/Firebase/FirebaseSession.php index 86e525d17..ce646f7c0 100644 --- a/src/Account/Session/Firebase/FirebaseSession.php +++ b/src/Account/Session/Firebase/FirebaseSession.php @@ -26,8 +26,10 @@ use PrestaShop\Module\PsAccounts\Account\Session\SessionInterface; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; use PrestaShop\Module\PsAccounts\Account\Token\Token; -use PrestaShop\Module\PsAccounts\Api\Client\AccountsClient; use PrestaShop\Module\PsAccounts\Log\Logger; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\FirebaseTokens; abstract class FirebaseSession extends Session implements SessionInterface { @@ -50,11 +52,11 @@ public function __construct(ShopSession $shopSession) } /** - * @return AccountsClient + * @return AccountsService */ - public function getAccountsClient() + public function getAccountsService() { - return $this->module->getService(AccountsClient::class); + return $this->module->getService(AccountsService::class); } /** @@ -103,10 +105,14 @@ public function refreshToken($refreshToken = null) */ protected function refreshFirebaseTokens($token) { - $response = $this->getAccountsClient()->firebaseTokens($token); + try { + $firebaseTokens = $this->getAccountsService()->firebaseTokens($token); + } catch (AccountsException $e) { + throw new RefreshTokenException($e->getMessage()); + } - $shopToken = $this->getFirebaseTokenFromResponse($response, 'shopToken', 'shopRefreshToken'); - $ownerToken = $this->getFirebaseTokenFromResponse($response, 'userToken', 'userRefreshToken'); + $shopToken = $this->getFirebaseTokenFromResponse($firebaseTokens, 'shopToken', 'shopRefreshToken'); + $ownerToken = $this->getFirebaseTokenFromResponse($firebaseTokens, 'userToken', 'userRefreshToken'); // saving both tokens here $this->getShopSession()->setToken((string) $shopToken->getJwt(), $shopToken->getRefreshToken()); @@ -114,30 +120,20 @@ protected function refreshFirebaseTokens($token) } /** - * @param array $response + * @param FirebaseTokens $firebaseTokens * @param string $name * @param string $refreshName * * @return Token - * - * @throws RefreshTokenException */ protected function getFirebaseTokenFromResponse( - array $response, - $name, - $refreshName + FirebaseTokens $firebaseTokens, + $name, + $refreshName ) { - if ($response && true === $response['status']) { - return new Token( - $response['body'][$name], - $response['body'][$refreshName] - ); - } - - $errorMsg = isset($response['body']['message']) ? - $response['body']['message'] : - ''; - - throw new RefreshTokenException('Unable to refresh firebase ' . $name . ' token : ' . $response['httpCode'] . ' ' . print_r($errorMsg, true)); + return new Token( + $firebaseTokens->$name, + $firebaseTokens->$refreshName + ); } } diff --git a/src/Account/Session/Session.php b/src/Account/Session/Session.php index 30ff065ed..536e08811 100644 --- a/src/Account/Session/Session.php +++ b/src/Account/Session/Session.php @@ -58,9 +58,13 @@ public function getValidToken($forceRefresh = false, $throw = true) * Avoid multiple refreshToken calls in the same runtime: * if it fails once, it will subsequently fail */ - if ($this->getRefreshTokenErrors(static::class)) { + if ($message = $this->getRefreshTokenErrors(static::class)) { $this->setToken(''); + if ($throw) { + throw new RefreshTokenException('Unable to refresh shop token : ' . $message); + } + return $this->getToken(); } @@ -69,7 +73,7 @@ public function getValidToken($forceRefresh = false, $throw = true) $this->refreshToken(null); } catch (RefreshTokenException $e) { $this->setToken(''); - $this->setRefreshTokenErrors(static::class); + $this->setRefreshTokenErrors(static::class, $e->getMessage()); if ($throw) { throw $e; @@ -83,6 +87,8 @@ public function getValidToken($forceRefresh = false, $throw = true) /** * @return bool + * + * @deprecated since v8.0.0 */ public function isEmailVerified() { @@ -105,11 +111,11 @@ public function isEmailVerified() /** * @param string $refreshToken * - * @return bool + * @return string|false */ public function getRefreshTokenErrors($refreshToken) { - return isset($this->refreshTokenErrors[$refreshToken]) && $this->refreshTokenErrors[$refreshToken]; + return isset($this->refreshTokenErrors[$refreshToken]) ? $this->refreshTokenErrors[$refreshToken] : false; } /** @@ -121,12 +127,13 @@ public function resetRefreshTokenErrors() } /** - * @param string $refreshToken + * @param string $className + * @param string $message * * @return void */ - protected function setRefreshTokenErrors($refreshToken) + protected function setRefreshTokenErrors($className, $message) { - $this->refreshTokenErrors[$refreshToken] = true; + $this->refreshTokenErrors[$className] = $message; } } diff --git a/src/Account/Session/ShopSession.php b/src/Account/Session/ShopSession.php index e93f39c03..ecc9b0122 100644 --- a/src/Account/Session/ShopSession.php +++ b/src/Account/Session/ShopSession.php @@ -20,12 +20,8 @@ namespace PrestaShop\Module\PsAccounts\Account\Session; -use PrestaShop\Module\PsAccounts\Account\Command\UnlinkShopCommand; -use PrestaShop\Module\PsAccounts\Account\Exception\InconsistentAssociationStateException; use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; -use PrestaShop\Module\PsAccounts\Account\LinkShop; use PrestaShop\Module\PsAccounts\Account\Token\Token; -use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; use PrestaShop\Module\PsAccounts\Hook\ActionShopAccessTokenRefreshAfter; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Exception; @@ -34,11 +30,6 @@ class ShopSession extends Session implements SessionInterface { - /** - * @var CommandBus - */ - protected $commandBus; - /** * @var ConfigurationRepository */ @@ -50,31 +41,23 @@ class ShopSession extends Session implements SessionInterface protected $oAuth2Service; /** - * @var LinkShop - */ - protected $linkShop; - - /** - * @var int + * @var string */ - protected $oauth2ClientReceiptTimeout = 60; + protected $tokenAudience; /** * @param ConfigurationRepository $configurationRepository * @param OAuth2Service $oAuth2Service - * @param LinkShop $linkShop - * @param CommandBus $commandBus + * @param string $tokenAudience */ public function __construct( ConfigurationRepository $configurationRepository, OAuth2Service $oAuth2Service, - LinkShop $linkShop, - CommandBus $commandBus + $tokenAudience ) { $this->configurationRepository = $configurationRepository; $this->oAuth2Service = $oAuth2Service; - $this->linkShop = $linkShop; - $this->commandBus = $commandBus; + $this->tokenAudience = $tokenAudience; } /** @@ -87,9 +70,10 @@ public function __construct( public function refreshToken($refreshToken = null) { try { - $this->assertAssociationState($this->oauth2ClientReceiptTimeout); - $shopUuid = $this->getShopUuid(); - $accessToken = $this->getAccessToken($shopUuid); + $accessToken = $this->getAccessToken([], [ + //'shop_' . $shopUuid, // FIXME: remove that audience + $this->tokenAudience, + ]); $this->setToken( $accessToken->access_token, @@ -101,11 +85,6 @@ public function refreshToken($refreshToken = null) \Hook::exec(ActionShopAccessTokenRefreshAfter::getName(), ['token' => $token]); return $token; - } catch (InconsistentAssociationStateException $e) { - $this->commandBus->handle(new UnlinkShopCommand( - $this->configurationRepository->getShopId(), - $e->getMessage() - )); } catch (OAuth2Exception $e) { } catch (\Throwable $e) { /* @phpstan-ignore-next-line */ @@ -142,59 +121,15 @@ public function cleanup() } /** - * @param int $oauth2ClientReceiptTimeout - * - * @return void - */ - public function setOauth2ClientReceiptTimeout($oauth2ClientReceiptTimeout) - { - $this->oauth2ClientReceiptTimeout = $oauth2ClientReceiptTimeout; - } - - /** - * @param string $shopUid + * @param array $scope + * @param array $audience * * @return AccessToken * * @throws OAuth2Exception */ - protected function getAccessToken($shopUid) - { - $audience = [ - 'shop_' . $shopUid, - //'https://accounts-api.distribution.prestashop.net/shops/' . $shopUid, - //'another.audience' - ]; - - return $this->oAuth2Service->getAccessTokenByClientCredentials([], $audience); - } - - /** - * @param int $oauth2ClientReceiptTimeout - * - * @return void - * - * @throws InconsistentAssociationStateException - */ - protected function assertAssociationState($oauth2ClientReceiptTimeout = 60) - { - $linkedAtTs = $currentTs = time(); - if ($this->linkShop->linkedAt()) { - $linkedAtTs = (new \DateTime($this->linkShop->linkedAt()))->getTimestamp(); - } - - if ($this->linkShop->exists() && - $currentTs - $linkedAtTs > $oauth2ClientReceiptTimeout && - !$this->oAuth2Service->getOAuth2Client()->exists()) { - throw new InconsistentAssociationStateException('Invalid OAuth2 client'); - } - } - - /** - * @return string - */ - private function getShopUuid() + protected function getAccessToken(array $scope = [], array $audience = []) { - return $this->linkShop->getShopUuid(); + return $this->oAuth2Service->getAccessTokenByClientCredentials($scope, $audience); } } diff --git a/src/Account/ShopUrl.php b/src/Account/ShopUrl.php new file mode 100644 index 000000000..ef0f9d377 --- /dev/null +++ b/src/Account/ShopUrl.php @@ -0,0 +1,103 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account; + +class ShopUrl +{ + /** + * @var string + */ + private $backOfficeUrl; + + /** + * @var string + */ + private $frontendUrl; + + /** + * @var int + */ + private $multiShopId; + + /** + * ShopUrl constructor. + * + * @param string $backOfficeUrl + * @param string $frontendUrl + * @param int $multiShopId + */ + public function __construct($backOfficeUrl, $frontendUrl, $multiShopId) + { + $this->backOfficeUrl = $backOfficeUrl; + $this->frontendUrl = $frontendUrl; + $this->multiShopId = $multiShopId; + } + + /** + * @param array $shop + * + * @return ShopUrl + */ + public static function createFromShopData($shop) + { + $backOfficeUrl = explode('/index.php', $shop['url'])[0]; + $frontendUrl = rtrim($shop['frontUrl'], '/'); + $multiShopId = (int) $shop['id']; + + return new ShopUrl($backOfficeUrl, $frontendUrl, $multiShopId); + } + + /** + * @param ShopUrl $shopUrl + * + * @return bool + */ + public function equals(ShopUrl $shopUrl) + { + return $this->backOfficeUrl === $shopUrl->backOfficeUrl + && $this->frontendUrl === $shopUrl->frontendUrl + && $this->multiShopId === $shopUrl->multiShopId; + } + + /** + * @return string + */ + public function getBackOfficeUrl() + { + return $this->backOfficeUrl; + } + + /** + * @return string + */ + public function getFrontendUrl() + { + return $this->frontendUrl; + } + + /** + * @return int + */ + public function getMultiShopId() + { + return $this->multiShopId; + } +} diff --git a/src/Account/StatusManager.php b/src/Account/StatusManager.php new file mode 100644 index 000000000..4a720b7ad --- /dev/null +++ b/src/Account/StatusManager.php @@ -0,0 +1,255 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account; + +use DateTime; +use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; +use PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException; +use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; +use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; + +class StatusManager +{ + /** + * Status Cache TTL in seconds + */ + const CACHE_TTL = 10; + + /** + * Infinite Status Cache + */ + const CACHE_TTL_INFINITE = -1; + + /** + * @var ConfigurationRepository + */ + private $repository; + + /** + * @var ShopSession + */ + private $shopSession; + + /** + * @var AccountsService + */ + private $accountsService; + + /** + * @param ShopSession $shopSession + * @param AccountsService $accountsService + * @param ConfigurationRepository $repository + */ + public function __construct( + ShopSession $shopSession, + AccountsService $accountsService, + ConfigurationRepository $repository + ) { + $this->repository = $repository; + $this->shopSession = $shopSession; + $this->accountsService = $accountsService; + } + + /** + * @return bool + */ + public function identityCreated() + { + return !empty($this->getCloudShopId()); + } + + /** + * @param bool $cachedStatus + * @param int $cacheTtl + * + * @return ShopStatus + * + * @throws UnknownStatusException + */ + public function getStatus($cachedStatus = false, $cacheTtl = self::CACHE_TTL) + { + if (!$cachedStatus) { + if ($this->cacheInvalidated() || + $this->cacheExpired($cacheTtl) + ) { + try { + $this->upsetCachedStatus(new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => $this->accountsService->shopStatus( + $this->getCloudShopId(), + $this->shopSession->getValidToken() + ), + ])); + } catch (AccountsException $e) { + } catch (RefreshTokenException $e) { + } + } + } + + return $this->getCachedStatus()->shopStatus; + } + + /** + * @return void + */ + public function invalidateCache() + { + $this->upsetCachedStatus(new CachedShopStatus([ + 'isValid' => false, + ])); + } + + /** + * @return bool + */ + public function cacheInvalidated() + { + try { + $isValid = $this->getCachedStatus()->isValid; + } catch (UnknownStatusException $e) { + $isValid = false; + } + + return !$isValid; + } + + /** + * @param int $cacheTtl + * + * @return bool + */ + public function cacheExpired($cacheTtl = self::CACHE_TTL) + { + $dateUpd = $this->getCacheDateUpd(); + + return $dateUpd instanceof DateTime && + $cacheTtl != self::CACHE_TTL_INFINITE && + time() - $dateUpd->getTimestamp() >= $cacheTtl; + } + + /** + * @return \DateTime|null + */ + public function getCacheDateUpd() + { + return $this->repository->getCachedShopStatusDateUpd(); + } + + /** + * @param bool $cachedStatus + * + * @return string|null + */ + public function getCloudShopId($cachedStatus = true) + { + try { + return $this->getStatus($cachedStatus)->cloudShopId; + } catch (UnknownStatusException $e) { + return null; + } + } + + /** + * @param string $cloudShopId + * + * @return void + */ + public function setCloudShopId($cloudShopId) + { + $this->upsetCachedStatus(new CachedShopStatus([ + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $cloudShopId, + ]), + ])); + } + + /** + * @param bool $cachedStatus + * + * @return string|null + */ + public function getPointOfContactUuid($cachedStatus = true) + { + try { + return $this->getStatus($cachedStatus)->pointOfContactUuid; + } catch (UnknownStatusException $e) { + return null; + } + } + + /** + * @param bool $cachedStatus + * + * @return string|null + */ + public function getPointOfContactEmail($cachedStatus = true) + { + try { + return $this->getStatus($cachedStatus)->pointOfContactEmail; + } catch (UnknownStatusException $e) { + return null; + } + } + + /** + * @return CachedShopStatus + * + * @throws UnknownStatusException + */ + protected function getCachedStatus() + { + $status = $this->repository->getCachedShopStatus(); + + if (!$status) { + throw new UnknownStatusException('Unknown status'); + } + + return new CachedShopStatus(json_decode($status, true)); + } + + /** + * @return void + */ + protected function setCachedStatus(CachedShopStatus $cachedShopStatus) + { + $this->repository->updateCachedShopStatus(json_encode($cachedShopStatus->toArray()) ?: null); + + $this->repository->updateShopUuid($cachedShopStatus->shopStatus->cloudShopId); + } + + /** + * @return void + */ + protected function upsetCachedStatus(CachedShopStatus $cachedShopStatus) + { + try { + $this->setCachedStatus(new CachedShopStatus(array_replace_recursive( + $this->getCachedStatus()->toArray(), + $cachedShopStatus->toArray(false) + ))); + } catch (UnknownStatusException $e) { + $this->setCachedStatus($cachedShopStatus); + } + } +} diff --git a/src/Adapter/Configuration.php b/src/Adapter/Configuration.php index 6bd455add..14f4c4e2f 100644 --- a/src/Adapter/Configuration.php +++ b/src/Adapter/Configuration.php @@ -20,6 +20,8 @@ namespace PrestaShop\Module\PsAccounts\Adapter; +use PrestaShop\Module\PsAccounts\Log\Logger; + class Configuration { /** @@ -218,6 +220,28 @@ public function getUncachedConfiguration($key, $idShopGroup = null, $idShop = nu throw new \Exception('Configuration entry not found: ' . $key . '|grp:' . $idShopGroup . '|shop:' . $idShop); } + /** + * @param string $key + * + * @return \DateTime|null + */ + public function getDateUpd($key) + { + try { + $entry = $this->getUncachedConfiguration( + $key, + $this->getIdShopGroup(), + $this->getIdShop() + ); + + return new \DateTime($entry->date_upd); + } catch (\Exception $e) { + Logger::getInstance()->error(__METHOD__ . ': ' . $e->getMessage()); + + return null; + } + } + /** * is multi-shop active "right now" * diff --git a/src/Adapter/ConfigurationKeys.php b/src/Adapter/ConfigurationKeys.php index 3ff07643b..11b6c9c9f 100644 --- a/src/Adapter/ConfigurationKeys.php +++ b/src/Adapter/ConfigurationKeys.php @@ -72,4 +72,8 @@ class ConfigurationKeys extends Enum const PS_ACCOUNTS_LAST_UPGRADE = 'PS_ACCOUNTS_LAST_UPGRADE'; const PS_ACCOUNTS_UNLINKED_ON_ERROR = 'PS_ACCOUNTS_UNLINKED_ON_ERROR'; + + const PS_ACCOUNTS_SHOP_PROOF = 'PS_ACCOUNTS_SHOP_PROOF'; + + const PS_ACCOUNTS_CACHED_SHOP_STATUS = 'PS_ACCOUNTS_SHOP_STATUS'; } diff --git a/src/Api/Client/AccountsClient.php b/src/Api/Client/AccountsClient.php index 193a79820..9cedf1672 100644 --- a/src/Api/Client/AccountsClient.php +++ b/src/Api/Client/AccountsClient.php @@ -20,195 +20,33 @@ namespace PrestaShop\Module\PsAccounts\Api\Client; -use PrestaShop\Module\PsAccounts\Account\Dto\UpdateShop; -use PrestaShop\Module\PsAccounts\Account\Dto\UpgradeModule; -use PrestaShop\Module\PsAccounts\Http\Client\ClientConfig; -use PrestaShop\Module\PsAccounts\Http\Client\Curl\Client; -use PrestaShop\Module\PsAccounts\Http\Client\Factory; -use PrestaShop\Module\PsAccounts\Http\Client\Request; -use PrestaShop\Module\PsAccounts\Vendor\Ramsey\Uuid\Uuid; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; +/** + * @deprecated since v8.0.0 in favor of PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService + */ class AccountsClient { /** - * @var Client - */ - private $client; - - /** - * @var array - */ - protected $clientConfig; - - /** - * @param array $config + * @var AccountsService */ - public function __construct(array $config) - { - $this->clientConfig = array_merge([ - ClientConfig::NAME => static::class, - ClientConfig::HEADERS => $this->getHeaders(), - ], $config); - } + protected $accountsService; /** - * @return Client + * @param AccountsService $accountService */ - private function getClient() + public function __construct(AccountsService $accountService) { - if (null === $this->client) { - $this->client = (new Factory())->create($this->clientConfig); - } - - return $this->client; + $this->accountsService = $accountService; } /** - * @param array $additionalHeaders - * - * @return array - */ - private function getHeaders($additionalHeaders = []) - { - return array_merge([ - 'Accept' => 'application/json', - 'X-Module-Version' => \Ps_accounts::VERSION, - 'X-Prestashop-Version' => _PS_VERSION_, - 'X-Request-ID' => Uuid::uuid4()->toString(), - ], $additionalHeaders); - } - - /** - * @param string $accessToken - * - * @return array - * - * $response['body']['userToken'] - * $response['body']['userRefreshToken'] - * $response['body']['shopToken'] - * $response['body']['shopRefreshToken'] - */ - public function firebaseTokens($accessToken) - { - return $this->getClient()->get( - 'v2/shop/firebase/tokens', - [ - Request::HEADERS => $this->getHeaders([ - 'Authorization' => 'Bearer ' . $accessToken, - ]), - ])->toLegacy(); - } - - /** - * @param string $refreshToken - * @param string $shopUuid - * - * @return array - */ - public function refreshShopToken($refreshToken, $shopUuid) - { - return $this->getClient()->post( - 'v1/shop/token/refresh', - [ - Request::HEADERS => $this->getHeaders([ - 'X-Shop-Id' => $shopUuid, - ]), - Request::JSON => [ - 'token' => $refreshToken, - ], - ] - )->toLegacy(); - } - - /** - * @param string $ownerUid - * @param string $shopUid - * @param string $ownerToken - * - * @return array - */ - public function deleteUserShop($ownerUid, $shopUid, $ownerToken) - { - return $this->getClient()->delete( - 'v1/user/' . $ownerUid . '/shop/' . $shopUid, - [ - Request::HEADERS => $this->getHeaders([ - 'Authorization' => 'Bearer ' . $ownerToken, - 'X-Shop-Id' => $shopUid, - ]), - ] - )->toLegacy(); - } - - /** - * @param string $ownerUid - * @param string $shopUid - * @param string $ownerToken - * @param UpdateShop $shop - * - * @return array - */ - public function updateUserShop($ownerUid, $shopUid, $ownerToken, UpdateShop $shop) - { - return $this->getClient()->patch( - 'v1/user/' . $ownerUid . '/shop/' . $shopUid, - [ - Request::HEADERS => $this->getHeaders([ - // FIXME: use shop access token instead - 'Authorization' => 'Bearer ' . $ownerToken, - 'X-Shop-Id' => $shopUid, - ]), - Request::JSON => $shop->jsonSerialize(), - ] - )->toLegacy(); - } - - /** - * @param string $shopUid - * @param string $shopToken - * @param UpgradeModule $data - * - * @return array - */ - public function upgradeShopModule($shopUid, $shopToken, UpgradeModule $data) - { - return $this->getClient()->post( - '/v2/shop/module/update', - [ - Request::HEADERS => $this->getHeaders([ - 'Authorization' => 'Bearer ' . $shopToken, - 'X-Shop-Id' => $shopUid, - ]), - Request::JSON => $data->jsonSerialize(), - ] - )->toLegacy(); - } - - /** - * @deprecated - * * @param string $idToken * * @return array */ public function verifyToken($idToken) { - return $this->getClient()->post( - '/v1/shop/token/verify', - [ - Request::HEADERS => $this->getHeaders(), - Request::JSON => [ - 'token' => $idToken, - ], - ] - )->toLegacy(); - } - - /** - * @return array - */ - public function healthCheck() - { - return $this->getClient()->get('/healthcheck')->toLegacy(); + return $this->accountsService->verifyToken($idToken)->toLegacy(); } } diff --git a/src/Api/Client/ExternalAssetsClient.php b/src/Api/Client/ExternalAssetsClient.php index 521a64824..d0a0b89be 100644 --- a/src/Api/Client/ExternalAssetsClient.php +++ b/src/Api/Client/ExternalAssetsClient.php @@ -23,6 +23,7 @@ use PrestaShop\Module\PsAccounts\Http\Client\ClientConfig; use PrestaShop\Module\PsAccounts\Http\Client\Curl\Client; use PrestaShop\Module\PsAccounts\Http\Client\Factory; +use PrestaShop\Module\PsAccounts\Http\Client\Response; class ExternalAssetsClient { @@ -81,12 +82,12 @@ private function getHeaders($additionalHeaders = []) } /** - * @return array + * @return Response */ public function getTestimonials() { return $this->getClient()->get( $this->module->getParameter('ps_accounts.testimonials_url') - )->toLegacy(); + ); } } diff --git a/src/Context/ShopContext.php b/src/Context/ShopContext.php index c8576e2f4..8f4863d62 100644 --- a/src/Context/ShopContext.php +++ b/src/Context/ShopContext.php @@ -21,6 +21,7 @@ namespace PrestaShop\Module\PsAccounts\Context; use Context; +use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; /** @@ -160,7 +161,7 @@ public function getConfiguration() } /** - * @param int $shopId + * @param int|null $shopId * @param \Closure $closure * * @return mixed @@ -170,20 +171,49 @@ public function execInShopContext($shopId, $closure) $backup = $this->configuration->getShopId(); $this->configuration->setShopId($shopId); - $e = null; + $exception = null; $result = null; try { $result = $closure(); - } catch (\Throwable $e) { + } catch (\Throwable $exception) { /* @phpstan-ignore-next-line */ - } catch (\Exception $e) { + } catch (\Exception $exception) { } + $this->configuration->setShopId($backup); - if (null === $e) { - return $result; + if (null !== $exception) { + throw $exception; } - throw $e; + + return $result; + } + + /** + * @return array|null[] + */ + public function getMultiShopIds() + { + $shops = []; + $db = \Db::getInstance(); + try { + $result = $db->query('SELECT id_shop FROM ' . _DB_PREFIX_ . 'shop'); + while ($row = $db->nextRow($result)) { + /* @phpstan-ignore-next-line */ + $shops[] = $row['id_shop']; + } + } catch (\Throwable $e) { + Logger::getInstance()->error(__METHOD__ . ': ' . $e->getMessage()); + + return []; + /* @phpstan-ignore-next-line */ + } catch (\Exception $e) { + Logger::getInstance()->error(__METHOD__ . ': ' . $e->getMessage()); + + return []; + } + + return $shops; } } diff --git a/src/Controller/Admin/OAuth2Controller.php b/src/Controller/Admin/OAuth2Controller.php index 34a2fea51..dcceaca16 100644 --- a/src/Controller/Admin/OAuth2Controller.php +++ b/src/Controller/Admin/OAuth2Controller.php @@ -294,6 +294,6 @@ private function getTestimonials() { $res = $this->externalAssetsClient->getTestimonials(); - return $res['status'] ? $res['body'] : []; + return $res->isSuccessful ? $res->body : []; } } diff --git a/src/Hook/ActionObjectEmployeeDeleteAfter.php b/src/Hook/ActionObjectEmployeeDeleteAfter.php index c5b5712ff..cf5267438 100644 --- a/src/Hook/ActionObjectEmployeeDeleteAfter.php +++ b/src/Hook/ActionObjectEmployeeDeleteAfter.php @@ -42,6 +42,8 @@ public function execute(array $params = []) if ($employeeAccount) { $repository->delete($employeeAccount); } + } catch (\Throwable $e) { + /* @phpstan-ignore-next-line */ } catch (\Exception $e) { } } diff --git a/src/Hook/ActionObjectShopUpdateAfter.php b/src/Hook/ActionObjectShopUpdateAfter.php index 54e7c261e..232917274 100644 --- a/src/Hook/ActionObjectShopUpdateAfter.php +++ b/src/Hook/ActionObjectShopUpdateAfter.php @@ -20,10 +20,10 @@ namespace PrestaShop\Module\PsAccounts\Hook; -use Exception; use PrestaShop\Module\PsAccounts\Account\Command\UpdateUserShopCommand; use PrestaShop\Module\PsAccounts\Account\Dto\UpdateShop; use PrestaShop\Module\PsAccounts\Adapter\Link; +use PrestaShop\Module\PsAccounts\Http\Client\Response; use Ps_accounts; class ActionObjectShopUpdateAfter extends Hook @@ -60,6 +60,7 @@ public function execute(array $params = []) protected function updateUserShop(\Shop $shop) { try { + /** @var Response $response */ $response = $this->commandBus->handle(new UpdateUserShopCommand(new UpdateShop([ 'shopId' => (string) $shop->id, 'name' => $shop->name, @@ -70,14 +71,15 @@ protected function updateUserShop(\Shop $shop) 'boBaseUrl' => $this->link->fixAdminLink($this->link->getDashboardLink(), $shop), ]))); - if (!$response) { - $this->module->getLogger()->error('Error trying to PATCH shop : No $response object'); - } elseif (true !== $response['status']) { - $this->module->getLogger()->error('Error trying to PATCH shop : ' . $response['httpCode'] . - ' ' . print_r(isset($response['body']['message']) ? $response['body']['message'] : '', true) + if (!$response->isSuccessful) { + $this->module->getLogger()->error('Error trying to PATCH shop : ' . $response->statusCode . + ' ' . print_r(isset($response->body['message']) ? $response->body['message'] : '', true) ); } - } catch (Exception $e) { + } catch (\Throwable $e) { + $this->module->getLogger()->error('Error trying to PATCH shop: ' . $e->getMessage()); + /* @phpstan-ignore-next-line */ + } catch (\Exception $e) { $this->module->getLogger()->error('Error trying to PATCH shop: ' . $e->getMessage()); } } diff --git a/src/Hook/ActionShopAccountUnlinkAfter.php b/src/Hook/ActionShopAccountUnlinkAfter.php index 9cd0553e3..8754a8633 100644 --- a/src/Hook/ActionShopAccountUnlinkAfter.php +++ b/src/Hook/ActionShopAccountUnlinkAfter.php @@ -22,7 +22,6 @@ use PrestaShop\Module\PsAccounts\Account\Session\Firebase; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; -use PrestaShop\Module\PsAccounts\Provider\RsaKeysProvider; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; class ActionShopAccountUnlinkAfter extends Hook @@ -51,9 +50,5 @@ public function execute(array $params = []) /** @var ShopSession $session */ $session = $this->module->getService(ShopSession::class); $session->cleanup(); - - /** @var RsaKeysProvider $rsaKeysProvider */ - $rsaKeysProvider = $this->module->getService(RsaKeysProvider::class); - $rsaKeysProvider->cleanupKeys(); } } diff --git a/src/Hook/DisplayBackOfficeHeader.php b/src/Hook/DisplayAdminAfterHeader.php similarity index 61% rename from src/Hook/DisplayBackOfficeHeader.php rename to src/Hook/DisplayAdminAfterHeader.php index 1a9d6080d..cf03e43ff 100644 --- a/src/Hook/DisplayBackOfficeHeader.php +++ b/src/Hook/DisplayAdminAfterHeader.php @@ -20,20 +20,25 @@ namespace PrestaShop\Module\PsAccounts\Hook; -use PrestaShop\Module\PsAccounts\Account\Command\UpgradeModuleMultiCommand; - -class DisplayBackOfficeHeader extends Hook +class DisplayAdminAfterHeader extends Hook { /** - * @return void + * @return string */ public function execute(array $params = []) { - try { - $this->commandBus->handle(new UpgradeModuleMultiCommand()); - } catch (\Exception $e) { - /* @phpstan-ignore-next-line */ - $this->logger->error('error during upgrade : ' . $e->getMessage()); - } + $cloudShopId = $this->module->getCloudShopId(); + $verified = $this->module->getVerifiedStatus(); + $verifiedMsg = $verified ? 'verified' : 'NOT verified'; + + return << +
+ + + {$cloudShopId} ({$verifiedMsg}) +
+ +HTML; } } diff --git a/src/Http/Controller/AbstractRestController.php b/src/Http/Controller/AbstractRestController.php index c6818a0d9..21cd4c022 100644 --- a/src/Http/Controller/AbstractRestController.php +++ b/src/Http/Controller/AbstractRestController.php @@ -26,12 +26,8 @@ use PrestaShop\Module\PsAccounts\Http\Exception\MethodNotAllowedException; use PrestaShop\Module\PsAccounts\Http\Exception\UnauthorizedException; use PrestaShop\Module\PsAccounts\Polyfill\Traits\Controller\AjaxRender; -use PrestaShop\Module\PsAccounts\Provider\RsaKeysProvider; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; use PrestaShop\Module\PsAccounts\Service\SentryService; -use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Parser; -use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Signer\Hmac\Sha256; -use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Signer\Key; use ReflectionException; use ReflectionParameter; @@ -268,39 +264,13 @@ protected function decodeRawPayload($defaultShopId = null) * @param int $defaultShopId * * @return array + * + * @throws UnauthorizedException + * + * @deprecated since v8.0.0 in favor of AbstractV2RestController */ protected function decodePayload($defaultShopId = null) { - /** @var RsaKeysProvider $shopKeysService */ - $shopKeysService = $this->module->getService(RsaKeysProvider::class); - - $jwtString = $this->getRequestHeader(self::TOKEN_HEADER); - - if ($jwtString) { - $jwt = (new Parser())->parse($jwtString); - - $shop = new \Shop((int) $jwt->claims()->get('shop_id', $defaultShopId)); - - if ($shop->id) { - $this->setContextShop($shop); - $publicKey = $shopKeysService->getPublicKey(); - - $this->module->getLogger()->debug('trying to verify token with pkey: ' . $publicKey); - - if ( - null !== $publicKey && - true === $jwt->verify(new Sha256(), new Key((string) $publicKey)) - ) { - $this->module->getLogger()->debug('token verified: ' . $jwtString); - - return $jwt->claims()->all(); - } - $this->module->getLogger()->error('Failed to verify token: ' . $jwtString); - } - - $this->module->getLogger()->error('Failed to decode payload: ' . $jwtString); - } - throw new UnauthorizedException(); } diff --git a/src/Http/Controller/AbstractV2RestController.php b/src/Http/Controller/AbstractV2RestController.php index dc88cfca5..5b582de89 100644 --- a/src/Http/Controller/AbstractV2RestController.php +++ b/src/Http/Controller/AbstractV2RestController.php @@ -34,6 +34,12 @@ abstract class AbstractV2RestController extends AbstractRestController { + /** + * Header to retrieve bearer from + * FIXME: "Authorization" standard header might be filtered by server configuration + */ + const HEADER_AUTHORIZATION = 'X-Prestashop-Authorization'; + /** * @var object */ @@ -173,7 +179,7 @@ protected function extractMethod(array & $payload) */ protected function checkAuthorization() { - $authorizationHeader = $this->getRequestHeader('Authorization'); + $authorizationHeader = $this->getRequestHeader(self::HEADER_AUTHORIZATION); if (!isset($authorizationHeader)) { throw new UnauthorizedException('Authorization header is required.'); } diff --git a/src/Http/Resource/Resource.php b/src/Http/Resource/Resource.php index ee9f93148..a5c5c0962 100644 --- a/src/Http/Resource/Resource.php +++ b/src/Http/Resource/Resource.php @@ -20,6 +20,7 @@ namespace PrestaShop\Module\PsAccounts\Http\Resource; +use DateTime; use PrestaShop\Module\PsAccounts\Type\Dto; abstract class Resource extends Dto @@ -28,4 +29,50 @@ abstract class Resource extends Dto * @var bool */ protected $throwOnUnexpectedProperties = false; + + /** + * @param array $values + * @param string $className + * @param array $fields + * + * @return void + */ + protected function castChildResource(array & $values, $className, array $fields) + { + foreach ($fields as $field) { + if (isset($values[$field]) && is_array($values[$field])) { + $values[$field] = new $className($values[$field]); + } + } + } + + /** + * @param array $values + * @param array $fields + * + * @return void + */ + protected function castBool(array & $values, array $fields) + { + foreach ($fields as $field) { + if (isset($values[$field])) { + $values[$field] = (bool) $values[$field]; + } + } + } + + /** + * @param array $values + * @param array $fields + * + * @return void + */ + protected function castDateTime(array & $values, array $fields) + { + foreach ($fields as $field) { + if (!empty($values[$field])) { + $values[$field] = new Datetime($values[$field]); + } + } + } } diff --git a/src/Presenter/PsAccountsPresenter.php b/src/Presenter/PsAccountsPresenter.php index 3176cb285..201392113 100644 --- a/src/Presenter/PsAccountsPresenter.php +++ b/src/Presenter/PsAccountsPresenter.php @@ -20,10 +20,8 @@ namespace PrestaShop\Module\PsAccounts\Presenter; -use PrestaShop\Module\PsAccounts\Account\LinkShop; -use PrestaShop\Module\PsAccounts\Adapter\Link; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Installer\Installer; -use PrestaShop\Module\PsAccounts\Provider\RsaKeysProvider; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; use PrestaShop\Module\PsAccounts\Service\PsAccountsService; @@ -41,9 +39,9 @@ class PsAccountsPresenter implements PresenterInterface protected $shopProvider; /** - * @var LinkShop + * @var StatusManager */ - protected $linkShop; + protected $statusManager; /** * @var ConfigurationRepository @@ -65,11 +63,6 @@ class PsAccountsPresenter implements PresenterInterface */ private $module; - /** - * @var RsaKeysProvider - */ - private $rsaKeysProvider; - /** * @param \Ps_accounts $module * @@ -82,10 +75,9 @@ public function __construct( $this->psAccountsService = $module->getService(PsAccountsService::class); $this->shopProvider = $module->getService(ShopProvider::class); - $this->linkShop = $module->getService(LinkShop::class); + $this->statusManager = $module->getService(StatusManager::class); $this->installer = $module->getService(Installer::class); $this->configuration = $module->getService(ConfigurationRepository::class); - $this->rsaKeysProvider = $module->getService(RsaKeysProvider::class); // FIXME: find a better place for this $this->configuration->fixMultiShopConfig(); @@ -118,10 +110,6 @@ public function present($psxName = 'ps_accounts') . '?shops=' . $shopBase64; try { - $shopsTree = $this->shopProvider->getShopsTree($psxName); - - $this->initShops($shopsTree); - return array_merge( [ 'currentContext' => [ @@ -171,7 +159,7 @@ public function present($psxName = 'ps_accounts') 'isOnboardedV4' => $this->psAccountsService->isAccountLinkedV4(), - 'shops' => $shopsTree, + 'shops' => $this->shopProvider->getShopsTree($psxName), 'adminAjaxLink' => $this->psAccountsService->getAdminAjaxUrl(), 'accountsUiUrl' => $this->module->getParameter('ps_accounts.accounts_ui_url'), @@ -184,20 +172,4 @@ public function present($psxName = 'ps_accounts') return []; } - - /** - * @param array $shopTree - * - * @return void - */ - private function initShops(& $shopTree) - { - foreach ($shopTree as &$group) { - foreach ($group['shops'] as &$shop) { - $this->shopProvider->getShopContext()->execInShopContext($shop['id'], function () use (&$shop) { - $shop['publicKey'] = $this->rsaKeysProvider->getOrGenerateAccountsRsaPublicKey(); - }); - } - } - } } diff --git a/src/Provider/OAuth2/ShopProvider.php b/src/Provider/OAuth2/ShopProvider.php deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/Provider/RsaKeysProvider.php b/src/Provider/RsaKeysProvider.php deleted file mode 100644 index 19e6bd06d..000000000 --- a/src/Provider/RsaKeysProvider.php +++ /dev/null @@ -1,189 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Provider; - -use PrestaShop\Module\PsAccounts\Exception\SshKeysNotFoundException; -use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; -use PrestaShop\Module\PsAccounts\Vendor\phpseclib\Crypt\RSA; - -/** - * Manage RSA - */ -class RsaKeysProvider -{ - /** - * @var RSA - */ - private $rsa; - - /** - * @var ConfigurationRepository - */ - private $configuration; - - public function __construct(ConfigurationRepository $configuration) - { - $this->rsa = new RSA(); - $this->rsa->setHash('sha256'); - $this->rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); - - $this->configuration = $configuration; - } - - /** - * @return array - */ - public function createPair() - { - $this->rsa->setPrivateKeyFormat(RSA::PRIVATE_FORMAT_PKCS1); - $this->rsa->setPublicKeyFormat(RSA::PUBLIC_FORMAT_PKCS1); - - return $this->rsa->createKey(); - } - - /** - * @param string $privateKey - * @param string $data - * - * @return string - */ - public function signData($privateKey, $data) - { - $this->rsa->loadKey($privateKey, RSA::PRIVATE_FORMAT_PKCS1); - - return base64_encode($this->rsa->sign($data)); - } - - /** - * @param string $publicKey - * @param string $signature - * @param string $data - * - * @return bool - */ - public function verifySignature($publicKey, $signature, $data) - { - $this->rsa->loadKey($publicKey, RSA::PUBLIC_FORMAT_PKCS1); - - return $this->rsa->verify($data, base64_decode($signature)); - } - - /** - * @param string $encrypted - * - * @return false|string - */ - public function decrypt($encrypted) - { - $this->rsa->loadKey($this->getPrivateKey(), RSA::PRIVATE_FORMAT_PKCS1); - - return $this->rsa->decrypt($encrypted); - } - - /** - * @param string $string - * - * @return false|string - */ - public function encrypt($string) - { - $this->rsa->loadKey((string) $this->getPublicKey(), RSA::PUBLIC_FORMAT_PKCS1); - - return $this->rsa->encrypt($string); - } - - /** - * @param bool $refresh - * - * @return void - * - * @throws SshKeysNotFoundException - */ - public function generateKeys($refresh = false) - { - if ($refresh || false === $this->hasKeys()) { - $key = $this->createPair(); - $this->configuration->updateAccountsRsaPrivateKey($key['privatekey']); - $this->configuration->updateAccountsRsaPublicKey($key['publickey']); - - if (false === $this->hasKeys()) { - throw new SshKeysNotFoundException('No RSA keys found for the shop'); - } - } - } - - /** - * @return string|null - */ - public function getOrGenerateAccountsRsaPublicKey() - { - try { - $this->generateKeys(); - - return $this->getPublicKey(); - } catch (\Exception $e) { - return null; - } - } - - /** - * @return void - * - * @throws SshKeysNotFoundException - */ - public function regenerateKeys() - { - $this->generateKeys(true); - } - - /** - * @return bool - */ - public function hasKeys() - { - return null !== $this->getPublicKey(); - } - - /** - * @return string|null - */ - public function getPublicKey() - { - return ((string) $this->configuration->getAccountsRsaPublicKey(false, false)) ?: null; - } - - /** - * @return string - */ - public function getPrivateKey() - { - return $this->configuration->getAccountsRsaPrivateKey(); - } - - /** - * @return void - */ - public function cleanupKeys() - { - $this->configuration->updateAccountsRsaPrivateKey(''); - $this->configuration->updateAccountsRsaPublicKey(''); - } -} diff --git a/src/Provider/ShopProvider.php b/src/Provider/ShopProvider.php index 8db4fa409..e8d2369b9 100644 --- a/src/Provider/ShopProvider.php +++ b/src/Provider/ShopProvider.php @@ -21,8 +21,9 @@ namespace PrestaShop\Module\PsAccounts\Provider; use PrestaShop\Module\PsAccounts\Account\Dto\Shop; -use PrestaShop\Module\PsAccounts\Account\LinkShop; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; +use PrestaShop\Module\PsAccounts\Account\ShopUrl; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Adapter\Link; use PrestaShop\Module\PsAccounts\Context\ShopContext; @@ -67,15 +68,12 @@ public function formatShopData(array $shopData, $psxName = '', $refreshTokens = /** @var \Ps_accounts $module */ $module = \Module::getInstanceByName('ps_accounts'); - /** @var LinkShop $linkShop */ - $linkShop = $module->getService(LinkShop::class); + /** @var StatusManager $shopStatus */ + $shopStatus = $module->getService(StatusManager::class); /** @var OwnerSession $ownerSession */ $ownerSession = $module->getService(OwnerSession::class); - /** @var RsaKeysProvider $rsaKeyProvider */ - $rsaKeyProvider = $module->getService(RsaKeysProvider::class); - $shopId = $shopData['id_shop']; $shop = new Shop([ @@ -90,22 +88,22 @@ public function formatShopData(array $shopData, $psxName = '', $refreshTokens = 'frontUrl' => $this->getShopUrl($shopData), // LinkAccount - 'uuid' => $linkShop->getShopUuid() ?: null, - 'publicKey' => $rsaKeyProvider->getPublicKey() ?: null, - 'employeeId' => (int) $linkShop->getEmployeeId() ?: null, + 'uuid' => $shopStatus->getCloudShopId() ?: null, + 'publicKey' => '[deprecated]', + 'employeeId' => 0, //(int) $shopIdentity->getEmployeeId() ?: null, 'user' => [ - 'email' => $linkShop->getOwnerEmail() ?: null, - 'uuid' => $linkShop->getOwnerUuid() ?: null, + 'email' => $shopStatus->getPointOfContactEmail() ?: null, + 'uuid' => $shopStatus->getPointOfContactUuid() ?: null, 'emailIsValidated' => null, ], 'url' => $this->link->getDashboardLink(), 'isLinkedV4' => null, - 'unlinkedAuto' => !empty($linkShop->getUnlinkedOnError()), + 'unlinkedAuto' => false, ]); if ($refreshTokens) { $shop->user->emailIsValidated = $ownerSession->isEmailVerified(); - $shop->isLinkedV4 = $linkShop->existsV4(); + $shop->isLinkedV4 = false; //$shopIdentity->existsV4(); } return $shop; @@ -251,7 +249,7 @@ private function getShopVirtualUri($shopId) */ private function getShopUrl($shopData) { - if (!$shopData['domain']) { + if (!isset($shopData['domain'])) { return null; } @@ -260,4 +258,42 @@ private function getShopUrl($shopData) ($shopData['domain_ssl'] ?: $shopData['domain']) . $shopData['uri']; } + + /** + * @param int $shopId + * + * @return string|null + */ + public function getFrontendUrl($shopId) + { + return $this->getShopUrl((array) \Shop::getShop($shopId)); + } + + /** + * @param int $shopId + * + * @return string + */ + public function getBackendUrl($shopId) + { + // FIXME: throw exception in wrong context + // FIXME: unit tests + // FIXME: remove virtual uri ? + $adminPath = defined('_PS_ADMIN_DIR_') ? basename(_PS_ADMIN_DIR_) : ''; + + return rtrim($this->getFrontendUrl($shopId), '/') . '/' . $adminPath; + } + + /** + * @param int $shopId + * + * @return ShopUrl + */ + public function getUrl($shopId) + { + $backOfficeUrl = $this->getBackendUrl($shopId); + $frontendUrl = rtrim($this->getFrontendUrl($shopId), '/'); + + return new ShopUrl($backOfficeUrl, $frontendUrl, $shopId); + } } diff --git a/src/Repository/ConfigurationRepository.php b/src/Repository/ConfigurationRepository.php index cbf1ea2c3..66d1bb532 100644 --- a/src/Repository/ConfigurationRepository.php +++ b/src/Repository/ConfigurationRepository.php @@ -22,7 +22,6 @@ use PrestaShop\Module\PsAccounts\Adapter\Configuration; use PrestaShop\Module\PsAccounts\Adapter\ConfigurationKeys; -use PrestaShop\Module\PsAccounts\Log\Logger; class ConfigurationRepository { @@ -165,23 +164,11 @@ public function getShopUuid() } /** - * @return string|null + * @return \DateTime|null */ public function getShopUuidDateUpd() { - try { - $entry = $this->configuration->getUncachedConfiguration( - ConfigurationKeys::PSX_UUID_V4, - $this->configuration->getIdShopGroup(), - $this->configuration->getIdShop() - ); - - return $entry->date_upd; - } catch (\Exception $e) { - Logger::getInstance()->error(__METHOD__ . ': ' . $e->getMessage()); - - return null; - } + return $this->configuration->getDateUpd(ConfigurationKeys::PSX_UUID_V4); } /** @@ -197,45 +184,6 @@ public function updateShopUuid($uuid) $this->configuration->set(ConfigurationKeys::PSX_UUID_V4, $uuid); } - /** - * @return string - */ - public function getAccountsRsaPrivateKey() - { - return $this->configuration->get(ConfigurationKeys::PS_ACCOUNTS_RSA_PRIVATE_KEY); - } - - /** - * @param string $key - * - * @return void - */ - public function updateAccountsRsaPrivateKey($key) - { - $this->configuration->set(ConfigurationKeys::PS_ACCOUNTS_RSA_PRIVATE_KEY, $key); - } - - /** - * @param bool $cached - * @param bool|mixed $default - * - * @return string|bool - */ - public function getAccountsRsaPublicKey($default = false, $cached = true) - { - return $this->configuration->get(ConfigurationKeys::PS_ACCOUNTS_RSA_PUBLIC_KEY, $default, $cached); - } - - /** - * @param string $key - * - * @return void - */ - public function updateAccountsRsaPublicKey($key) - { - $this->configuration->set(ConfigurationKeys::PS_ACCOUNTS_RSA_PUBLIC_KEY, $key); - } - /** * @return bool */ @@ -431,6 +379,50 @@ public function updateUnlinkedOnError($error) $this->configuration->set(ConfigurationKeys::PS_ACCOUNTS_UNLINKED_ON_ERROR, $error); } + /** + * @return string|null + */ + public function getShopProof() + { + return $this->configuration->get(ConfigurationKeys::PS_ACCOUNTS_SHOP_PROOF); + } + + /** + * @param string|null $proof + * + * @return void + */ + public function updateShopProof($proof) + { + $this->configuration->set(ConfigurationKeys::PS_ACCOUNTS_SHOP_PROOF, $proof); + } + + /** + * @return string|null + */ + public function getCachedShopStatus() + { + return $this->configuration->get(ConfigurationKeys::PS_ACCOUNTS_CACHED_SHOP_STATUS); + } + + /** + * @return \DateTime|null + */ + public function getCachedShopStatusDateUpd() + { + return $this->configuration->getDateUpd(ConfigurationKeys::PS_ACCOUNTS_CACHED_SHOP_STATUS); + } + + /** + * @param string|null $status + * + * @return void + */ + public function updateCachedShopStatus($status) + { + $this->configuration->set(ConfigurationKeys::PS_ACCOUNTS_CACHED_SHOP_STATUS, $status); + } + /** * specify id_shop & id_shop_group for shop * diff --git a/src/Service/Accounts/AccountsException.php b/src/Service/Accounts/AccountsException.php new file mode 100644 index 000000000..9fb276adc --- /dev/null +++ b/src/Service/Accounts/AccountsException.php @@ -0,0 +1,25 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Service\Accounts; + +class AccountsException extends \Exception +{ +} diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php new file mode 100644 index 000000000..632b65e27 --- /dev/null +++ b/src/Service/Accounts/AccountsService.php @@ -0,0 +1,350 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Service\Accounts; + +use PrestaShop\Module\PsAccounts\Account\Dto\UpdateShop; +use PrestaShop\Module\PsAccounts\Account\ShopUrl; +use PrestaShop\Module\PsAccounts\Http\Client\ClientConfig; +use PrestaShop\Module\PsAccounts\Http\Client\Curl\Client; +use PrestaShop\Module\PsAccounts\Http\Client\Factory; +use PrestaShop\Module\PsAccounts\Http\Client\Request; +use PrestaShop\Module\PsAccounts\Http\Client\Response; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\FirebaseTokens; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\IdentityCreated; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; +use PrestaShop\Module\PsAccounts\Vendor\Ramsey\Uuid\Uuid; + +class AccountsService +{ + /** + * @var Client + */ + private $client; + + /** + * @var array + */ + protected $clientConfig; + + /** + * @param array $config + */ + public function __construct(array $config) + { + $config[ClientConfig::HEADERS] = $this->getHeaders( + isset($config[ClientConfig::HEADERS]) ? $config[ClientConfig::HEADERS] : [] + ); + + $this->clientConfig = array_merge([ + ClientConfig::NAME => static::class, + ], $config); + } + + /** + * @return Client + */ + public function getClient() + { + if (null === $this->client) { + $this->client = (new Factory())->create($this->clientConfig); + } + + return $this->client; + } + + /** + * @param Client $client + * + * @return void + */ + public function setClient(Client $client) + { + $this->client = $client; + } + + /** + * @param array $additionalHeaders + * + * @return array + */ + private function getHeaders($additionalHeaders = []) + { + return array_merge([ + 'Accept' => 'application/json', + 'X-Module-Version' => \Ps_accounts::VERSION, + 'X-Prestashop-Version' => _PS_VERSION_, + 'X-Multishop-Enabled' => \Shop::isFeatureActive() ? 'true' : 'false', + 'X-Request-ID' => Uuid::uuid4()->toString(), + ], $additionalHeaders); + } + + /** + * @param string $accessToken + * + * @return FirebaseTokens + * + * @throws AccountsException + */ + public function firebaseTokens($accessToken) + { + $response = $this->getClient()->get( + 'v2/shop/firebase/tokens', + [ + Request::HEADERS => $this->getHeaders([ + 'Authorization' => 'Bearer ' . $accessToken, + ]), + ]); + + if (!$response->isSuccessful) { + throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to refresh token.')); + } + + return new FirebaseTokens($response->body); + } + + /** + * @param string $refreshToken + * @param string $cloudShopId + * + * @return FirebaseTokens + * + * @throws AccountsException + */ + public function refreshShopToken($refreshToken, $cloudShopId) + { + $response = $this->getClient()->post( + 'v1/shop/token/refresh', + [ + Request::HEADERS => $this->getHeaders([ + 'X-Shop-Id' => $cloudShopId, + ]), + Request::JSON => [ + 'token' => $refreshToken, + ], + ] + ); + + if (!$response->isSuccessful) { + throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to refresh token.')); + } + + return new FirebaseTokens($response->body); + } + + /** + * @param string $ownerUid + * @param string $cloudShopId + * @param string $ownerToken + * + * @return Response + */ + public function deleteUserShop($ownerUid, $cloudShopId, $ownerToken) + { + return $this->getClient()->delete( + 'v1/user/' . $ownerUid . '/shop/' . $cloudShopId, + [ + Request::HEADERS => $this->getHeaders([ + 'Authorization' => 'Bearer ' . $ownerToken, + 'X-Shop-Id' => $cloudShopId, + ]), + ] + ); + } + + /** + * @param string $ownerUid + * @param string $cloudShopId + * @param string $ownerToken + * @param UpdateShop $shop + * + * @return Response + */ + public function updateUserShop($ownerUid, $cloudShopId, $ownerToken, UpdateShop $shop) + { + return $this->getClient()->patch( + 'v1/user/' . $ownerUid . '/shop/' . $cloudShopId, + [ + Request::HEADERS => $this->getHeaders([ + // FIXME: use shop access token instead + 'Authorization' => 'Bearer ' . $ownerToken, + 'X-Shop-Id' => $cloudShopId, + ]), + Request::JSON => $shop->jsonSerialize(), + ] + ); + } + +// /** +// * @param string $cloudShopId +// * @param string $shopToken +// * @param UpgradeModule $data +// * +// * @return Response +// */ +// public function upgradeShopModule($cloudShopId, $shopToken, UpgradeModule $data) +// { +// return $this->getClient()->post( +// '/v2/shop/module/update', +// [ +// Request::HEADERS => $this->getHeaders([ +// 'Authorization' => 'Bearer ' . $shopToken, +// 'X-Shop-Id' => $cloudShopId, +// ]), +// Request::JSON => $data->jsonSerialize(), +// ] +// ); +// } + + /** + * @param string $idToken + * + * @return Response + * + * @deprecated since v8.0.0 + */ + public function verifyToken($idToken) + { + return $this->getClient()->post( + '/v1/shop/token/verify', + [ +// Request::HEADERS => $this->getHeaders(), + Request::JSON => [ + 'token' => $idToken, + ], + ] + ); + } + + /** + * @return Response + */ + public function healthCheck() + { + return $this->getClient()->get('/healthcheck'); + } + + /** + * @param ShopUrl $shopUrl + * + * @return IdentityCreated + * + * @throws AccountsException + */ + public function createShopIdentity(ShopUrl $shopUrl) + { + $response = $this->getClient()->post( + '/v1/shop-identities', + [ + Request::JSON => [ + 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), + 'frontendUrl' => $shopUrl->getFrontendUrl(), + 'multiShopId' => $shopUrl->getMultiShopId(), + ], + ] + ); + + if (!$response->isSuccessful) { + throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to create shop identity.')); + } + + return new IdentityCreated($response->body); + } + + /** + * @param string $cloudShopId + * @param string $shopToken + * @param ShopUrl $shopUrl + * @param string $proof + * + * @return ShopStatus + * + * @throws AccountsException + */ + public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $proof) + { + $response = $this->getClient()->post( + '/v1/shop-identities/' . $cloudShopId . '/verify', [ + Request::HEADERS => $this->getHeaders([ + 'Authorization' => 'Bearer ' . $shopToken, + 'X-Shop-Id' => $cloudShopId, + ]), + Request::JSON => [ + 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), + 'frontendUrl' => $shopUrl->getFrontendUrl(), + 'multiShopId' => $shopUrl->getMultiShopId(), + 'proof' => $proof, + ], + ] + ); + + if (!$response->isSuccessful) { + throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to verify shop identity.')); + } + + return new ShopStatus($response->body); + } + + /** + * @param string $cloudShopId + * @param string $shopToken + * + * @return ShopStatus + * + * @throws AccountsException + */ + public function shopStatus($cloudShopId, $shopToken) + { + $response = $this->getClient()->get( + '/v1/shop-identities/' . $cloudShopId . '/status', + [ + Request::HEADERS => $this->getHeaders([ + 'Authorization' => 'Bearer ' . $shopToken, + 'X-Shop-Id' => $cloudShopId, + ]), + ] + ); + + if (!$response->isSuccessful) { + throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to retrieve shop status')); + } + + return new ShopStatus($response->body); + } + + /** + * @param Response $response + * @param string $defaultMessage + * + * @return string + */ + protected function getResponseErrorMsg(Response $response, $defaultMessage = '') + { + $msg = $defaultMessage; + $body = $response->body; + if (isset($body['error']) && + isset($body['error_description']) + ) { + $msg = $body['error'] . ': ' . $body['error_description']; + } + + return $response->statusCode . ' - ' . $msg; + } +} diff --git a/src/Service/Accounts/Resource/FirebaseTokens.php b/src/Service/Accounts/Resource/FirebaseTokens.php new file mode 100644 index 000000000..cb9b5c3a4 --- /dev/null +++ b/src/Service/Accounts/Resource/FirebaseTokens.php @@ -0,0 +1,46 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Service\Accounts\Resource; + +use PrestaShop\Module\PsAccounts\Http\Resource\Resource; + +class FirebaseTokens extends Resource +{ + /** + * @var string + */ + public $userToken; + + /** + * @var string + */ + public $userRefreshToken; + + /** + * @var string + */ + public $shopToken; + + /** + * @var string + */ + public $shopRefreshToken; +} diff --git a/src/Account/Dto/UpgradeModule.php b/src/Service/Accounts/Resource/IdentityCreated.php similarity index 76% rename from src/Account/Dto/UpgradeModule.php rename to src/Service/Accounts/Resource/IdentityCreated.php index 320d31434..e062f632f 100644 --- a/src/Account/Dto/UpgradeModule.php +++ b/src/Service/Accounts/Resource/IdentityCreated.php @@ -18,27 +18,24 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -namespace PrestaShop\Module\PsAccounts\Account\Dto; +namespace PrestaShop\Module\PsAccounts\Service\Accounts\Resource; -use PrestaShop\Module\PsAccounts\Type\Dto; +use PrestaShop\Module\PsAccounts\Http\Resource\Resource; -class UpgradeModule extends Dto +class IdentityCreated extends Resource { /** * @var string */ - public $version; + public $cloudShopId; /** - * @var int + * @var string */ - public $shopId; + public $clientId; /** - * @var string[] + * @var string */ - public $required = [ - 'version', - 'shopId', - ]; + public $clientSecret; } diff --git a/src/Service/Accounts/Resource/ShopStatus.php b/src/Service/Accounts/Resource/ShopStatus.php new file mode 100644 index 000000000..a34427212 --- /dev/null +++ b/src/Service/Accounts/Resource/ShopStatus.php @@ -0,0 +1,122 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Service\Accounts\Resource; + +use DateTime; +use PrestaShop\Module\PsAccounts\Http\Resource\Resource; + +class ShopStatus extends Resource +{ + /** + * @var string + */ + public $cloudShopId; + + /** + * @var bool + */ + public $isVerified = false; + + /** + * @var string + */ + public $frontendUrl; + + /** + * @var string + */ + public $backofficeUrl; + + /** + * @var string + */ + public $shopVerificationErrorCode; + + /** + * @var string + */ + public $pointOfContactUuid; + + /** + * @var string + */ + public $pointOfContactEmail; + + /** + * @var DateTime|null + */ + public $createdAt; + + /** + * @var DateTime|null + */ + public $updatedAt; + + /** + * @var DateTime|null + */ + public $verifiedAt; + + /** + * @var DateTime|null + */ + public $unverifiedAt; + + public function __construct($values = []) + { + $this->castDateTime($values, [ + 'createdAt', + 'updatedAt', + 'verifiedAt', + 'unverifiedAt', + ]); + $this->castBool($values, [ + 'isVerified', + ]); + + parent::__construct($values); + } + + /** + * @param bool $all + * + * @return array + */ + public function toArray($all = true) + { + $array = parent::toArray($all); + + foreach ( + [ + 'createdAt', + 'updatedAt', + 'verifiedAt', + 'unverifiedAt', + ] as $dateField + ) { + if (!empty($array[$dateField])) { + $array[$dateField] = $array[$dateField]->format(DateTime::ATOM); + } + } + + return $array; + } +} diff --git a/src/Service/AnalyticsService.php b/src/Service/AnalyticsService.php index 9006852a9..dcd9bdc71 100644 --- a/src/Service/AnalyticsService.php +++ b/src/Service/AnalyticsService.php @@ -306,7 +306,7 @@ private function initAnonymousId() if (!isset($_COOKIE[self::COOKIE_ANONYMOUS_ID])) { self::$anonymousId = Uuid::uuid4()->toString(); try { - setcookie(self::COOKIE_ANONYMOUS_ID, self::$anonymousId, time() + 3600); + setcookie(self::COOKIE_ANONYMOUS_ID, self::$anonymousId, time() + 3600, '/'); } catch (\Exception $e) { $this->logger->error($e->getMessage()); } diff --git a/src/Service/PsAccountsService.php b/src/Service/PsAccountsService.php index 0ceb8edbf..9e191af59 100644 --- a/src/Service/PsAccountsService.php +++ b/src/Service/PsAccountsService.php @@ -21,10 +21,9 @@ namespace PrestaShop\Module\PsAccounts\Service; use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; -use PrestaShop\Module\PsAccounts\Account\LinkShop; use PrestaShop\Module\PsAccounts\Account\Session\Firebase; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; -use PrestaShop\Module\PsAccounts\Account\Token\Token; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Adapter\Link; use PrestaShop\Module\PsAccounts\Entity\EmployeeAccount; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; @@ -61,9 +60,9 @@ class PsAccountsService private $ownerSession; /** - * @var LinkShop + * @var StatusManager */ - private $linkShop; + private $statusManager; /** * @param \Ps_accounts $module @@ -77,7 +76,7 @@ public function __construct(\Ps_accounts $module) $this->shopSession = $this->module->getService(Firebase\ShopSession::class); $this->ownerSession = $this->module->getService(Firebase\OwnerSession::class); $this->link = $this->module->getService(Link::class); - $this->linkShop = $module->getService(LinkShop::class); + $this->statusManager = $module->getService(StatusManager::class); } /** @@ -103,7 +102,7 @@ public function getShopUuidV4() */ public function getShopUuid() { - return $this->linkShop->getShopUuid(); + return $this->statusManager->getCloudShopId(); } /** @@ -180,7 +179,7 @@ public function getUserUuidV4() */ public function getUserUuid() { - return (string) $this->linkShop->getOwnerUuid(); + return (string) $this->statusManager->getPointOfContactUuid(); } /** @@ -198,27 +197,32 @@ public function isEmailValidated() */ public function getEmail() { - return $this->linkShop->getOwnerEmail(); + return $this->statusManager->getPointOfContactEmail(); } /** * @return bool * * @throws \Exception + * + * @deprecated since v8.0.0 */ public function isAccountLinked() { - return $this->linkShop->exists(); + return $this->statusManager->identityCreated() && + $this->statusManager->getPointOfContactUuid(); } /** * @return bool * * @throws \Exception + * + * @depercated since v8.0.0 */ public function isAccountLinkedV4() { - return $this->linkShop->existsV4(); + return false; //$this->shopIdentity->existsV4(); } /** diff --git a/src/Service/SentryService.php b/src/Service/SentryService.php index ee979d640..d79a345d9 100644 --- a/src/Service/SentryService.php +++ b/src/Service/SentryService.php @@ -22,7 +22,7 @@ use Context; use Module; -use PrestaShop\Module\PsAccounts\Account\LinkShop; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Service\Sentry\ModuleFilteredRavenClient; use Ps_accounts; use Raven_Client; @@ -67,15 +67,15 @@ class SentryService * * @param string $sentryCredentials * @param string $environment - * @param LinkShop $linkShop + * @param StatusManager $statusManager * @param Context $context * * @throws \Raven_Exception */ public function __construct( - $sentryCredentials, - $environment, - LinkShop $linkShop, + $sentryCredentials, + $environment, + StatusManager $statusManager, Context $context ) { $this->client = new ModuleFilteredRavenClient( @@ -88,8 +88,8 @@ public function __construct( 'ps_accounts_version' => \Ps_accounts::VERSION, 'prestashop_version' => _PS_VERSION_, 'ps_accounts_is_enabled' => \Module::isEnabled('ps_accounts'), - 'email' => $linkShop->getOwnerEmail(), - 'shop_uuid' => $linkShop->getShopUuid(), + 'email' => $statusManager->getPointOfContactEmail(), + 'shop_uuid' => $statusManager->getCloudShopId(), ], 'error_types' => $this->errorTypes, 'sample_rate' => $this->isContextInFrontOffice($context) ? diff --git a/src/ServiceProvider/ApiClientProvider.php b/src/ServiceProvider/ApiClientProvider.php index e7baa2f91..559d53c4b 100644 --- a/src/ServiceProvider/ApiClientProvider.php +++ b/src/ServiceProvider/ApiClientProvider.php @@ -25,6 +25,8 @@ use PrestaShop\Module\PsAccounts\Api\Client\ServicesBillingClient; use PrestaShop\Module\PsAccounts\Http\Client\ClientConfig; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; +use PrestaShop\Module\PsAccounts\Service\AnalyticsService; use PrestaShop\Module\PsAccounts\Service\PsAccountsService; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\Contract\IServiceProvider; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\ServiceContainer; @@ -39,9 +41,15 @@ class ApiClientProvider implements IServiceProvider public function provide(ServiceContainer $container) { $container->registerProvider(AccountsClient::class, static function () use ($container) { - return new AccountsClient([ + return new AccountsClient($container->get(AccountsService::class)); + }); + $container->registerProvider(AccountsService::class, static function () use ($container) { + return new AccountsService([ ClientConfig::BASE_URI => $container->getParameter('ps_accounts.accounts_api_url'), ClientConfig::SSL_CHECK => $container->getParameter('ps_accounts.check_api_ssl_cert'), + ClientConfig::HEADERS => [ + 'X-Anonymous-ID' => $container->get(AnalyticsService::class)->getAnonymousId(), + ], ]); }); $container->registerProvider(ExternalAssetsClient::class, static function () { diff --git a/src/ServiceProvider/CommandProvider.php b/src/ServiceProvider/CommandProvider.php index c96a00ce6..5008a503b 100644 --- a/src/ServiceProvider/CommandProvider.php +++ b/src/ServiceProvider/CommandProvider.php @@ -20,21 +20,20 @@ namespace PrestaShop\Module\PsAccounts\ServiceProvider; +use PrestaShop\Module\PsAccounts\Account\CommandHandler\CreateIdentitiesHandler; +use PrestaShop\Module\PsAccounts\Account\CommandHandler\CreateIdentityHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\DeleteUserShopHandler; -use PrestaShop\Module\PsAccounts\Account\CommandHandler\LinkShopHandler; -use PrestaShop\Module\PsAccounts\Account\CommandHandler\UnlinkShopHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\UpdateUserShopHandler; -use PrestaShop\Module\PsAccounts\Account\CommandHandler\UpgradeModuleHandler; -use PrestaShop\Module\PsAccounts\Account\CommandHandler\UpgradeModuleMultiHandler; -use PrestaShop\Module\PsAccounts\Account\LinkShop; -use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; -use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; -use PrestaShop\Module\PsAccounts\Api\Client\AccountsClient; +use PrestaShop\Module\PsAccounts\Account\CommandHandler\VerifyIdentitiesHandler; +use PrestaShop\Module\PsAccounts\Account\CommandHandler\VerifyIdentityHandler; +use PrestaShop\Module\PsAccounts\Account\ProofManager; +use PrestaShop\Module\PsAccounts\Account\Session; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Context\ShopContext; use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; -use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; -use PrestaShop\Module\PsAccounts\Service\AnalyticsService; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; +use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\Contract\IServiceProvider; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\ServiceContainer; @@ -44,46 +43,47 @@ public function provide(ServiceContainer $container) { $container->registerProvider(DeleteUserShopHandler::class, static function () use ($container) { return new DeleteUserShopHandler( - $container->get(AccountsClient::class), + $container->get(AccountsService::class), $container->get(ShopContext::class), - $container->get(ShopSession::class), - $container->get(OwnerSession::class) - ); - }); - $container->registerProvider(LinkShopHandler::class, static function () use ($container) { - return new LinkShopHandler( - $container->get(LinkShop::class) - ); - }); - $container->registerProvider(UnlinkShopHandler::class, static function () use ($container) { - return new UnlinkShopHandler( - $container->get(LinkShop::class), - $container->get(AnalyticsService::class), - $container->get(ShopProvider::class) + $container->get(Session\Firebase\ShopSession::class), + $container->get(Session\Firebase\OwnerSession::class) ); }); $container->registerProvider(UpdateUserShopHandler::class, static function () use ($container) { return new UpdateUserShopHandler( - $container->get(AccountsClient::class), + $container->get(AccountsService::class), $container->get(ShopContext::class), - $container->get(ShopSession::class), - $container->get(OwnerSession::class) + $container->get(Session\Firebase\ShopSession::class), + $container->get(Session\Firebase\OwnerSession::class) + ); + }); + $container->registerProvider(CreateIdentityHandler::class, static function () use ($container) { + return new CreateIdentityHandler( + $container->get(AccountsService::class), + $container->get(ShopProvider::class), + $container->get(OAuth2Client::class), + $container->get(StatusManager::class) ); }); - $container->registerProvider(UpgradeModuleHandler::class, static function () use ($container) { - return new UpgradeModuleHandler( - $container->get(AccountsClient::class), - $container->get(LinkShop::class), - $container->get(ShopSession::class), + $container->registerProvider(CreateIdentitiesHandler::class, static function () use ($container) { + return new CreateIdentitiesHandler( $container->get(ShopContext::class), - $container->get(ConfigurationRepository::class), $container->get(CommandBus::class) ); }); - $container->registerProvider(UpgradeModuleMultiHandler::class, static function () use ($container) { - return new UpgradeModuleMultiHandler( - $container->get(CommandBus::class), - $container->get(ConfigurationRepository::class) + $container->registerProvider(VerifyIdentityHandler::class, static function () use ($container) { + return new VerifyIdentityHandler( + $container->get(AccountsService::class), + $container->get(ShopProvider::class), + $container->get(StatusManager::class), + $container->get(Session\ShopSession::class), + $container->get(ProofManager::class) + ); + }); + $container->registerProvider(VerifyIdentitiesHandler::class, static function () use ($container) { + return new VerifyIdentitiesHandler( + $container->get(ShopContext::class), + $container->get(CommandBus::class) ); }); } diff --git a/src/ServiceProvider/DefaultProvider.php b/src/ServiceProvider/DefaultProvider.php index b200e3df9..52e7c19aa 100644 --- a/src/ServiceProvider/DefaultProvider.php +++ b/src/ServiceProvider/DefaultProvider.php @@ -20,7 +20,9 @@ namespace PrestaShop\Module\PsAccounts\ServiceProvider; -use PrestaShop\Module\PsAccounts\Account\LinkShop; +use PrestaShop\Module\PsAccounts\Account\ProofManager; +use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Adapter; use PrestaShop\Module\PsAccounts\Adapter\Configuration; use PrestaShop\Module\PsAccounts\Adapter\Link; @@ -33,6 +35,7 @@ use PrestaShop\Module\PsAccounts\Provider; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; use PrestaShop\Module\PsAccounts\Repository\ShopTokenRepository; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; use PrestaShop\Module\PsAccounts\Service\AnalyticsService; use PrestaShop\Module\PsAccounts\Service\PsAccountsService; use PrestaShop\Module\PsAccounts\Service\PsBillingService; @@ -58,8 +61,10 @@ public function provide(ServiceContainer $container) return \Module::getInstanceByName('ps_accounts'); }); // Entities ? - $container->registerProvider(LinkShop::class, static function () use ($container) { - return new LinkShop( + $container->registerProvider(StatusManager::class, static function () use ($container) { + return new StatusManager( + $container->get(ShopSession::class), + $container->get(AccountsService::class), $container->get(ConfigurationRepository::class) ); }); @@ -97,16 +102,16 @@ public function provide(ServiceContainer $container) return new SentryService( $container->getParameter('ps_accounts.sentry_credentials'), $container->getParameter('ps_accounts.environment'), - $container->get(LinkShop::class), + $container->get(StatusManager::class), $container->get('ps_accounts.context') ); }); - // "Providers" - $container->registerProvider(Provider\RsaKeysProvider::class, static function () use ($container) { - return new Provider\RsaKeysProvider( + $container->registerProvider(ProofManager::class, static function () use ($container) { + return new ProofManager( $container->get(ConfigurationRepository::class) ); }); + // "Providers" $container->registerProvider(Provider\ShopProvider::class, static function () use ($container) { return new Provider\ShopProvider( $container->get(ShopContext::class), diff --git a/src/ServiceProvider/SessionProvider.php b/src/ServiceProvider/SessionProvider.php index 82bde60e6..95eaab876 100644 --- a/src/ServiceProvider/SessionProvider.php +++ b/src/ServiceProvider/SessionProvider.php @@ -20,10 +20,8 @@ namespace PrestaShop\Module\PsAccounts\ServiceProvider; -use PrestaShop\Module\PsAccounts\Account\LinkShop; use PrestaShop\Module\PsAccounts\Account\Session\Firebase; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; -use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\Contract\IServiceProvider; @@ -43,8 +41,7 @@ public function provide(ServiceContainer $container) return new ShopSession( $container->get(ConfigurationRepository::class), $container->get(OAuth2Service::class), - $container->get(LinkShop::class), - $container->get(CommandBus::class) + $container->getParameter('ps_accounts.token_audience') ); }); $container->registerProvider(Firebase\OwnerSession::class, static function () use ($container) { diff --git a/src/Type/Dto.php b/src/Type/Dto.php index dea9eb751..f71beffe3 100644 --- a/src/Type/Dto.php +++ b/src/Type/Dto.php @@ -72,11 +72,17 @@ public function jsonSerialize() } /** + * @param bool $all + * * @return array */ - public function toArray() + public function toArray($all = true) { - return get_object_vars($this); + return array_filter(get_object_vars($this), function ($attrValue, $attrName) use ($all) { + return $all ? + !in_array($attrName, ['properties', 'defaults', 'required', 'throwOnUnexpectedProperties']) : + in_array($attrName, $this->properties); + }, ARRAY_FILTER_USE_BOTH); } /** diff --git a/src/enforce_autoload.php b/src/enforce_autoload.php new file mode 100644 index 000000000..9ec0087e7 --- /dev/null +++ b/src/enforce_autoload.php @@ -0,0 +1,21 @@ +clearCache(); + +$autoloadReal = __DIR__ . '/../vendor/composer/autoload_real.php'; + +if (!file_exists($autoloadReal)) { + exit(0); +} + +$contents = (string) file_get_contents($autoloadReal); +if (preg_match('/(ComposerAutoloaderInit[\w\d]*)/m', $contents, $matches)) { + $autoloaderClass = $matches[1]; + + if (!class_exists($autoloaderClass)) { + error_log('## ps_accounts autoload : [' . $autoloaderClass . ']' . PHP_EOL); + require $autoloadReal; + + return $autoloaderClass::getLoader(); + } +} diff --git a/tests/src/BaseTestCase.php b/tests/src/BaseTestCase.php index cd2efb8e6..0aedf9c14 100644 --- a/tests/src/BaseTestCase.php +++ b/tests/src/BaseTestCase.php @@ -8,6 +8,7 @@ use Module; use PrestaShop\Module\PsAccounts\Account\Session\Firebase; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; +use PrestaShop\Module\PsAccounts\Http\Client\Response; use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Builder; use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Configuration; use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Token; @@ -49,9 +50,9 @@ class BaseTestCase extends \PHPUnit\Framework\TestCase /** * @inject * - * @var \PrestaShop\Module\PsAccounts\Account\LinkShop + * @var \PrestaShop\Module\PsAccounts\Account\StatusManager */ - public $linkShop; + public $statusManager; /** * @var bool @@ -209,6 +210,20 @@ protected function createApiResponse(array $body, $httpCode, $status) ]; } + /** + * @param mixed $responseBody + * @param int $statusCode + * + * @return Response + */ + protected function createResponse($responseBody, $statusCode = 200) + { + return new Response( + $responseBody, + $statusCode + ); + } + /** * @param $class * @param $methods diff --git a/tests/src/Feature/Account/CommandHandler/UpgradeModuleHandlerTest.php b/tests/src/Feature/Account/CommandHandler/UpgradeModuleHandlerTest.php index 3a8847abb..4db26e318 100644 --- a/tests/src/Feature/Account/CommandHandler/UpgradeModuleHandlerTest.php +++ b/tests/src/Feature/Account/CommandHandler/UpgradeModuleHandlerTest.php @@ -4,10 +4,10 @@ use PrestaShop\Module\PsAccounts\Adapter\ConfigurationKeys; use PrestaShop\Module\PsAccounts\Adapter\Link; -use PrestaShop\Module\PsAccounts\Tests\Feature\FeatureTestCase; +use PrestaShop\Module\PsAccounts\Tests\Feature\TestCase; use PrestaShop\Module\PsAccounts\Vendor\GuzzleHttp\Cookie\CookieJar; -class UpgradeModuleHandlerTest extends FeatureTestCase +class UpgradeModuleHandlerTest extends TestCase { /** * @inject diff --git a/tests/src/Feature/Api/v1/ShopLinkAccount/DeleteTest.php b/tests/src/Feature/Api/V1/ShopLinkAccount/DeleteTest.php similarity index 79% rename from tests/src/Feature/Api/v1/ShopLinkAccount/DeleteTest.php rename to tests/src/Feature/Api/V1/ShopLinkAccount/DeleteTest.php index 8e1f74beb..91ce7d24c 100644 --- a/tests/src/Feature/Api/v1/ShopLinkAccount/DeleteTest.php +++ b/tests/src/Feature/Api/V1/ShopLinkAccount/DeleteTest.php @@ -2,15 +2,14 @@ namespace PrestaShop\Module\PsAccounts\Tests\Feature\Api\v1\ShopLinkAccount; -use PrestaShop\Module\PsAccounts\Account\LinkShop; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; use PrestaShop\Module\PsAccounts\Account\Token\NullToken; use PrestaShop\Module\PsAccounts\Http\Controller\AbstractRestController; use PrestaShop\Module\PsAccounts\Adapter\ConfigurationKeys; -use PrestaShop\Module\PsAccounts\Tests\Feature\FeatureTestCase; +use PrestaShop\Module\PsAccounts\Tests\Feature\TestCase; -class DeleteTest extends FeatureTestCase +class DeleteTest extends TestCase { /** * @inject @@ -33,6 +32,13 @@ class DeleteTest extends FeatureTestCase */ protected $session; + public function set_up() + { + parent::set_up(); + + $this->markTestSkipped(); + } + /** * @test * @@ -40,7 +46,7 @@ class DeleteTest extends FeatureTestCase */ public function itShouldSucceed() { - $this->linkShop->update(new \PrestaShop\Module\PsAccounts\Account\Dto\LinkShop([ + $this->statusManager->update(new \PrestaShop\Module\PsAccounts\Account\Dto\ShopIdentity([ 'shopId' => $this->faker->numberBetween(), 'uid' => $this->faker->uuid, 'employeeId' => $this->faker->numberBetween() @@ -71,12 +77,12 @@ public function itShouldSucceed() \Configuration::clearConfigurationCacheForTesting(); \Configuration::loadConfiguration(); - $this->assertFalse($this->linkShop->exists()); + $this->assertFalse($this->statusManager->identityCreated()); - $this->assertEmpty($this->linkShop->getShopUuid()); - $this->assertEmpty($this->linkShop->getEmployeeId()); - $this->assertEmpty($this->linkShop->getOwnerUuid()); - $this->assertEmpty($this->linkShop->getOwnerEmail()); + $this->assertEmpty($this->statusManager->getCloudShopId()); + $this->assertEmpty($this->statusManager->getEmployeeId()); + $this->assertEmpty($this->statusManager->getPointOfContactUuid()); + $this->assertEmpty($this->statusManager->getPointOfContactEmail()); $this->assertInstanceOf(NullToken::class, $this->shopSession->getToken()->getJwt()); $this->assertInstanceOf(NullToken::class, $this->ownerSession->getToken()->getJwt()); diff --git a/tests/src/Feature/Api/v1/ShopLinkAccount/StoreTest.php b/tests/src/Feature/Api/V1/ShopLinkAccount/StoreTest.php similarity index 74% rename from tests/src/Feature/Api/v1/ShopLinkAccount/StoreTest.php rename to tests/src/Feature/Api/V1/ShopLinkAccount/StoreTest.php index add5d8faa..97238b003 100644 --- a/tests/src/Feature/Api/v1/ShopLinkAccount/StoreTest.php +++ b/tests/src/Feature/Api/V1/ShopLinkAccount/StoreTest.php @@ -5,9 +5,9 @@ use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; use PrestaShop\Module\PsAccounts\Http\Controller\AbstractRestController; -use PrestaShop\Module\PsAccounts\Tests\Feature\FeatureTestCase; +use PrestaShop\Module\PsAccounts\Tests\Feature\TestCase; -class StoreTest extends FeatureTestCase +class StoreTest extends TestCase { /** * @inject @@ -30,6 +30,13 @@ class StoreTest extends FeatureTestCase */ protected $session; + public function set_up() + { + parent::set_up(); + + $this->markTestSkipped(); + } + /** * @test * @@ -61,11 +68,11 @@ public function itShouldSucceed() \Configuration::clearConfigurationCacheForTesting(); \Configuration::loadConfiguration(); - $this->assertTrue($this->linkShop->exists()); - $this->assertEquals($payload['uid'], $this->linkShop->getShopUuid()); - $this->assertEquals($payload['employee_id'], $this->linkShop->getEmployeeId()); - $this->assertEquals($payload['owner_uid'], $this->linkShop->getOwnerUuid()); - $this->assertEquals($payload['owner_email'], $this->linkShop->getOwnerEmail()); + $this->assertTrue($this->statusManager->identityCreated()); + $this->assertEquals($payload['uid'], $this->statusManager->getCloudShopId()); + $this->assertEquals($payload['employee_id'], $this->statusManager->getEmployeeId()); + $this->assertEquals($payload['owner_uid'], $this->statusManager->getPointOfContactUuid()); + $this->assertEquals($payload['owner_email'], $this->statusManager->getPointOfContactEmail()); } /** @@ -101,9 +108,9 @@ public function itShouldSucceedWithoutEmployeeId() \Configuration::clearConfigurationCacheForTesting(); \Configuration::loadConfiguration(); - $this->assertTrue($this->linkShop->exists()); - $this->assertEquals($shopUuid, $this->linkShop->getShopUuid()); - $this->assertEquals(null, $this->linkShop->getEmployeeId()); + $this->assertTrue($this->statusManager->identityCreated()); + $this->assertEquals($shopUuid, $this->statusManager->getCloudShopId()); + $this->assertEquals(null, $this->statusManager->getEmployeeId()); } } diff --git a/tests/src/Feature/Api/v1/ShopOauth2Client/DeleteTest.php b/tests/src/Feature/Api/V1/ShopOauth2Client/DeleteTest.php similarity index 87% rename from tests/src/Feature/Api/v1/ShopOauth2Client/DeleteTest.php rename to tests/src/Feature/Api/V1/ShopOauth2Client/DeleteTest.php index 1ad57103f..62a662fe4 100644 --- a/tests/src/Feature/Api/v1/ShopOauth2Client/DeleteTest.php +++ b/tests/src/Feature/Api/V1/ShopOauth2Client/DeleteTest.php @@ -4,9 +4,9 @@ use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; use PrestaShop\Module\PsAccounts\Http\Controller\AbstractRestController; -use PrestaShop\Module\PsAccounts\Tests\Feature\FeatureTestCase; +use PrestaShop\Module\PsAccounts\Tests\Feature\TestCase; -class DeleteTest extends FeatureTestCase +class DeleteTest extends TestCase { /** * @inject @@ -15,6 +15,13 @@ class DeleteTest extends FeatureTestCase */ protected $oauth2Client; + public function set_up() + { + parent::set_up(); + + $this->markTestSkipped(); + } + /** * @test * diff --git a/tests/src/Feature/Api/v1/ShopOauth2Client/StoreTest.php b/tests/src/Feature/Api/V1/ShopOauth2Client/StoreTest.php similarity index 93% rename from tests/src/Feature/Api/v1/ShopOauth2Client/StoreTest.php rename to tests/src/Feature/Api/V1/ShopOauth2Client/StoreTest.php index 720538b1d..6ecf06357 100644 --- a/tests/src/Feature/Api/v1/ShopOauth2Client/StoreTest.php +++ b/tests/src/Feature/Api/V1/ShopOauth2Client/StoreTest.php @@ -7,10 +7,10 @@ use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; use PrestaShop\Module\PsAccounts\Http\Controller\AbstractRestController; use PrestaShop\Module\PsAccounts\Adapter\ConfigurationKeys; -use PrestaShop\Module\PsAccounts\Tests\Feature\FeatureTestCase; +use PrestaShop\Module\PsAccounts\Tests\Feature\TestCase; use function GuzzleHttp\Psr7\str; -class StoreTest extends FeatureTestCase +class StoreTest extends TestCase { /** * @inject @@ -33,6 +33,13 @@ class StoreTest extends FeatureTestCase */ protected $ownerSession; + public function set_up() + { + parent::set_up(); + + $this->markTestSkipped(); + } + /** * @test */ diff --git a/tests/src/Feature/Api/v1/ShopToken/ShowTest.php b/tests/src/Feature/Api/V1/ShopToken/ShowTest.php similarity index 92% rename from tests/src/Feature/Api/v1/ShopToken/ShowTest.php rename to tests/src/Feature/Api/V1/ShopToken/ShowTest.php index 8ad5fcbb3..532fffb74 100644 --- a/tests/src/Feature/Api/v1/ShopToken/ShowTest.php +++ b/tests/src/Feature/Api/V1/ShopToken/ShowTest.php @@ -4,9 +4,9 @@ use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; use PrestaShop\Module\PsAccounts\Http\Controller\AbstractRestController; -use PrestaShop\Module\PsAccounts\Tests\Feature\FeatureTestCase; +use PrestaShop\Module\PsAccounts\Tests\Feature\TestCase; -class ShowTest extends FeatureTestCase +class ShowTest extends TestCase { /** * @inject @@ -15,6 +15,13 @@ class ShowTest extends FeatureTestCase */ protected $shopSession; + public function set_up() + { + parent::set_up(); + + $this->markTestSkipped(); + } + /** * @test * diff --git a/tests/src/Feature/Api/v1/ShopUrl/ShowTest.php b/tests/src/Feature/Api/V1/ShopUrl/ShowTest.php similarity index 93% rename from tests/src/Feature/Api/v1/ShopUrl/ShowTest.php rename to tests/src/Feature/Api/V1/ShopUrl/ShowTest.php index e879f6fcb..22f9ea704 100644 --- a/tests/src/Feature/Api/v1/ShopUrl/ShowTest.php +++ b/tests/src/Feature/Api/V1/ShopUrl/ShowTest.php @@ -4,9 +4,9 @@ use PrestaShop\Module\PsAccounts\Http\Controller\AbstractRestController; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; -use PrestaShop\Module\PsAccounts\Tests\Feature\FeatureTestCase; +use PrestaShop\Module\PsAccounts\Tests\Feature\TestCase; -class ShowTest extends FeatureTestCase +class ShowTest extends TestCase { /** * @inject @@ -15,6 +15,13 @@ class ShowTest extends FeatureTestCase */ protected $shopContext; + public function set_up() + { + parent::set_up(); + + $this->markTestSkipped(); + } + /** * @test * diff --git a/tests/src/Feature/Api/v2/ShopHealthCheck/ShowTest.php b/tests/src/Feature/Api/V2/ShopHealthCheck/ShowTest.php similarity index 54% rename from tests/src/Feature/Api/v2/ShopHealthCheck/ShowTest.php rename to tests/src/Feature/Api/V2/ShopHealthCheck/ShowTest.php index f7ccfa326..a3c6ad6c3 100644 --- a/tests/src/Feature/Api/v2/ShopHealthCheck/ShowTest.php +++ b/tests/src/Feature/Api/V2/ShopHealthCheck/ShowTest.php @@ -1,13 +1,12 @@ writeWellKnown(); - - $this->writeJwks(); - - parent::set_up(); - } - /** * @test * @@ -154,7 +68,7 @@ public function itShouldShowPrivateHealthCheck() $response = $this->client->get('/module/ps_accounts/apiV2ShopHealthCheck', [ 'headers' => [ - 'Authorization' => 'Bearer ' . $this->makeBearer([ + AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $this->makeBearer([ 'aud' => [ 'ps_accounts/' . $shop->uuid, ], @@ -179,7 +93,6 @@ public function itShouldShowPrivateHealthCheck() $this->assertIsString($json['cloudShopId']); $this->assertIsString($json['shopName']); $this->assertIsString($json['ownerEmail']); - $this->assertIsString($json['publicKey']); } /** @@ -191,7 +104,7 @@ public function itShouldFailWithInvalidBearer() { $response = $this->client->get('/module/ps_accounts/apiV2ShopHealthCheck', [ 'headers' => [ - 'Authorization' => 'Bearer: ' . 'some-invalid-bearer', + AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer: ' . 'some-invalid-bearer', ], 'query' => [ 'shop_id' => 1, @@ -217,7 +130,7 @@ public function itShouldFailWithInvalidAudience() { $response = $this->client->get('/module/ps_accounts/apiV2ShopHealthCheck', [ 'headers' => [ - 'Authorization' => 'Bearer ' . $this->makeBearer([ + AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $this->makeBearer([ 'aud' => [ 'ps_accounts/' . 'invalid_uid', ], @@ -246,7 +159,7 @@ public function itShouldFailWithInvalidScope() $response = $this->client->get('/module/ps_accounts/apiV2ShopHealthCheck', [ 'headers' => [ - 'Authorization' => 'Bearer ' . $this->makeBearer([ + AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $this->makeBearer([ 'aud' => [ 'ps_accounts/' . $shop->uuid, ], @@ -278,7 +191,7 @@ public function itShouldFailWithInvalidShopId() $response = $this->client->get('/module/ps_accounts/apiV2ShopHealthCheck', [ 'headers' => [ - 'Authorization' => 'Bearer ' . $this->makeBearer([ + AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $this->makeBearer([ 'aud' => [ 'ps_accounts/' . $shop->uuid, ], @@ -301,44 +214,4 @@ public function itShouldFailWithInvalidShopId() //'message' => 'Invalid audience', ], $json); } - - /** - * @param array $data - * - * @return string - */ - public function makeBearer(array $data) - { -// if (!isset($data['iat'])) { -// $data['iat'] = time(); -// } -// if (!isset($data['exp'])) { -// $data['exp'] = time() + 3600; -// } - return JWT::encode($data, $this->privateKey, 'RS256', 'public:hydra.jwt.access-token'); - } - - /** - * @return void - * @throws \Exception - */ - public function writeJwks() - { - if (! isset($this->cachedJwks)) { - $this->cachedJwks = new CachedFile(_PS_CACHE_DIR_ . 'ps_accounts' . '/jwks.json'); - } - $this->cachedJwks->write($this->jwks); - } - - /** - * @return void - * @throws \Exception - */ - public function writeWellKnown() - { - if (! isset($this->cachedWellKnown)) { - $this->cachedWellKnown = new CachedFile(_PS_CACHE_DIR_ . 'ps_accounts' . '/openid-configuration.json'); - } - $this->cachedWellKnown->write($this->wellKnown); - } } diff --git a/tests/src/Feature/Api/V2/ShopVerifyProof/ShowTest.php b/tests/src/Feature/Api/V2/ShopVerifyProof/ShowTest.php new file mode 100644 index 000000000..93c10ce9a --- /dev/null +++ b/tests/src/Feature/Api/V2/ShopVerifyProof/ShowTest.php @@ -0,0 +1,99 @@ +shopProvider->formatShopData((array) \Shop::getShop(1)); + + $proof = $this->manageProof->generateProof(); + + $response = $this->client->get('/module/ps_accounts/apiV2ShopProof', [ + 'headers' => [ + AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $this->makeBearer([ + 'aud' => [ + 'ps_accounts/' . $shop->uuid, + ], + 'scp' => [ + 'shop.proof.read', + ] + ]), + ], + 'query' => [ + 'shop_id' => $shop->id, + ] + ]); + + $json = $this->getResponseJson($response); + + $this->assertResponseOk($response); + + $this->assertEquals($proof, $json['proof']); + } + + /** + * @test + * + * @throws \Exception + */ + public function itShouldShowEmptyProof() + { + $shop = $this->shopProvider->formatShopData((array) \Shop::getShop(1)); + + //$proof = $this->manageProof->generateProof(); + + \Db::getInstance()->execute( + "DELETE FROM " . _DB_PREFIX_. "configuration " . + "WHERE name ='" . ConfigurationKeys::PS_ACCOUNTS_SHOP_PROOF. "'" + ); + + $response = $this->client->get('/module/ps_accounts/apiV2ShopProof', [ + 'headers' => [ + AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $this->makeBearer([ + 'aud' => [ + 'ps_accounts/' . $shop->uuid, + ], + 'scp' => [ + 'shop.proof.read', + ] + ]), + ], + 'query' => [ + 'shop_id' => $shop->id, + ] + ]); + + $json = $this->getResponseJson($response); + + $this->assertResponseOk($response); + + $this->assertFalse($json['proof']); + } +} diff --git a/tests/src/Feature/Api/V2/TestCase.php b/tests/src/Feature/Api/V2/TestCase.php new file mode 100644 index 000000000..937c877b6 --- /dev/null +++ b/tests/src/Feature/Api/V2/TestCase.php @@ -0,0 +1,134 @@ +writeWellKnown(); + + $this->writeJwks(); + + parent::set_up(); + } + + /** + * @param array $data + * + * @return string + */ + protected function makeBearer(array $data) + { +// if (!isset($data['iat'])) { +// $data['iat'] = time(); +// } +// if (!isset($data['exp'])) { +// $data['exp'] = time() + 3600; +// } + return JWT::encode($data, $this->privateKey, 'RS256', 'public:hydra.jwt.access-token'); + } + + /** + * @return void + * @throws \Exception + */ + protected function writeJwks() + { + if (! isset($this->cachedJwks)) { + $this->cachedJwks = new CachedFile(_PS_CACHE_DIR_ . 'ps_accounts' . '/jwks.json'); + } + $this->cachedJwks->write($this->jwks); + } + + /** + * @return void + * @throws \Exception + */ + protected function writeWellKnown() + { + if (! isset($this->cachedWellKnown)) { + $this->cachedWellKnown = new CachedFile(_PS_CACHE_DIR_ . 'ps_accounts' . '/openid-configuration.json'); + } + $this->cachedWellKnown->write($this->wellKnown); + } +} diff --git a/tests/src/Feature/Api/v1/DecodePayloadTest.php b/tests/src/Feature/Api/v1/DecodePayloadTest.php deleted file mode 100644 index 9ac7e0a51..000000000 --- a/tests/src/Feature/Api/v1/DecodePayloadTest.php +++ /dev/null @@ -1,81 +0,0 @@ - 'AOvuKvRgjx-ajJn9TU0yIAe7qQc5rEBmbnTfndKifCOV9XWKokdaCs1s_IQ1WxbwKfJ_eYhviCLBAYMqCXlVVNUYv3WHygzORqY-h8Pgt52CEq_u4QThl2nmB4a7wD_dgzv_GRmNIDgxkEC-IZMW3jG7xH0HHbPLXDDVAMHuDtupqos_07uXW60', - "jwt" => 'eyJhbGciOiJSUzI1NiIsImtpZCI6IjRlMDBlOGZlNWYyYzg4Y2YwYzcwNDRmMzA3ZjdlNzM5Nzg4ZTRmMWUiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vc2hvcHRlc3QtYzMyMzIiLCJhdWQiOiJzaG9wdGVzdC1jMzIzMiIsImF1dGhfdGltZSI6MTYxNjA2MzkzNywidXNlcl9pZCI6Ik1Ucm9MUVduaExlYUtxMGJzajNMdkFpWlRvOTIiLCJzdWIiOiJNVHJvTFFXbmhMZWFLcTBic2ozTHZBaVpUbzkyIiwiaWF0IjoxNjE2MDYzOTM3LCJleHAiOjE2MTYwNjc1MzcsImVtYWlsIjoiaHR0cHNsaW04MDgwMTYxNTgwMzUxNTVAc2hvcC5wcmVzdGFzaG9wLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbImh0dHBzbGltODA4MDE2MTU4MDM1MTU1QHNob3AucHJlc3Rhc2hvcC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJjdXN0b20ifX0.J6rX8H6roDa4Fq62vhlXtm702V9YnhqT2JLts31Jy2wvn9h5Qf-FxHInrGlQyHWqtPcM_mxFlgcTNYfZNNyuzF_5Iz-v6rKtCXK7tmtaw6qKSM3sDQAvGpPBRVuhxVxUUqgXkT6DeznfFTYOoD96P912jFF6KroObLtJfDJsfhvncaSqh3pcMbKUP6gwe05Xyg6g_psY48OpYjia6X9b0Hn1orgdOH15JE4SWM5nk0XXcbs98qlNKNu2SbmgrQqu9zv-3aiC44LWAp_7UTDLQvXTTpVk4GbmXNCoD26bOwPm75tm7b2X4yq5d4WAvkBQmTI5-ug1Kf7ZZyPtl1X7kw', - ]; - - /** @var RsaKeysProvider $shopKeysService */ - $shopKeysService = $this->module->getService(RsaKeysProvider::class); - - $jwt = (new Parser())->parse((string) $this->encodePayload($payload)); - - $this->assertTrue($jwt->verify(new Sha256(), new Key($shopKeysService->getPublicKey()))); - - $this->assertArraySubset($jwt->claims()->all(), $payload); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldDecodeMethod() - { - $response = $this->client->get('/module/ps_accounts/apiV1ShopUrl', [ -# $response = $this->client->get('/?module=ps_accounts&fc=module&controller=apiV1ShopUrl', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload([ - 'shop_id' => 1, - ]) - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseOk($response); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldDecodeMethodFromPayload() - { - $response = $this->client->post('/module/ps_accounts/apiV1ShopUrl', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload([ - 'method' => 'GET', - 'shop_id' => 1, - ]) - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseOk($response); - } -} diff --git a/tests/src/Feature/FeatureTestCase.php b/tests/src/Feature/TestCase.php similarity index 89% rename from tests/src/Feature/FeatureTestCase.php rename to tests/src/Feature/TestCase.php index 0c72f77e8..7e8092419 100644 --- a/tests/src/Feature/FeatureTestCase.php +++ b/tests/src/Feature/TestCase.php @@ -4,16 +4,13 @@ use Db; use PrestaShop\Module\PsAccounts\Http\Client\Response; -use PrestaShop\Module\PsAccounts\Provider\RsaKeysProvider; use PrestaShop\Module\PsAccounts\Repository\UserTokenRepository; use PrestaShop\Module\PsAccounts\Tests\HttpTestClient; -use PrestaShop\Module\PsAccounts\Tests\TestCase; use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Builder; use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Signer\Hmac\Sha256; -use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Signer\Key; use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Token; -class FeatureTestCase extends TestCase +class TestCase extends \PrestaShop\Module\PsAccounts\Tests\TestCase { /** * @var bool @@ -25,11 +22,6 @@ class FeatureTestCase extends TestCase */ protected $client; - /** - * @var RsaKeysProvider - */ - protected $rsaKeysProvider; - /** * @var UserTokenRepository */ @@ -64,9 +56,6 @@ public function set_up() $this->module->getLogger()->debug('Using ' . get_class($this->client)); - $this->rsaKeysProvider = $this->module->getService(RsaKeysProvider::class); - $this->rsaKeysProvider->regenerateKeys(); - $this->userTokenRepository = $this->module->getService(UserTokenRepository::class); $this->userTokenRepository->cleanupCredentials(); } @@ -75,6 +64,8 @@ public function set_up() * @param array $payload * * @return Token + * + * @depercated since v8.0.0 */ public function encodePayload(array $payload) { @@ -87,8 +78,8 @@ public function encodePayload(array $payload) } return $builder->getToken( - new Sha256(), - new Key($this->rsaKeysProvider->getPublicKey()) + new Sha256() + //new Key($this->rsaKeysProvider->getPublicKey()) ); } diff --git a/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php new file mode 100644 index 000000000..f4359bc7e --- /dev/null +++ b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php @@ -0,0 +1,153 @@ +shopId = $this->shopProvider->getShopContext()->getContext()->shop->id; + $this->client = $this->createMock(Client::class); + $this->accountsService->setClient($this->client); + } + + /** + * @test + */ + public function itShouldStoreIdentity() + { + $clientId = $this->faker->uuid; + $clientSecret = $this->faker->uuid; + $cloudShopId = $this->faker->uuid; + + $this->oauth2Client->delete(); + $this->statusManager->setCloudShopId(''); + + $this->client->method('post') + ->willReturnCallback(function ($route) use ($clientId, $clientSecret, $cloudShopId) { + if (preg_match('/v1\/shop-identities$/', $route)) { + return $this->createResponse([ + 'clientId' => $clientId, + 'clientSecret' => $clientSecret, + "cloudShopId" => $cloudShopId + ], 200, true); + } + return $this->createResponse([], 500, true); + }); + + $this->getHandler()->handle(new CreateIdentityCommand(1, [])); + + $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); + $this->assertEquals($clientId, $this->oauth2Client->getClientId()); + $this->assertEquals($clientSecret, $this->oauth2Client->getClientSecret()); + } + + /** + * @test + */ + public function itShouldNotChangeIdentityIfExists() + { + $clientId = $this->faker->uuid; + $clientSecret = $this->faker->uuid; + $cloudShopId = $this->faker->uuid; + + $id1 =$this->createResponse([ + 'clientId' => $clientId, + 'clientSecret' => $clientSecret, + "cloudShopId" => $cloudShopId + ], 200, true); + + $id2 =$this->createResponse([ + 'clientId' => $clientId . 'Foo', + 'clientSecret' => $clientSecret . 'Bar', + "cloudShopId" => $cloudShopId . 'Baz' + ], 200, true); + + $this->client + ->method('post') + ->willReturnCallback(function ($route) use ($id1, $id2) { + static $count = 1; + if (preg_match('/v1\/shop-identities$/', $route)) { + return $count++ === 1 ? $id1 : $id2; + } + return $this->createApiResponse([], 500, true); + }); + + $this->oauth2Client->delete(); + $this->statusManager->setCloudShopId(''); + + $this->getHandler()->handle(new CreateIdentityCommand(1, [])); + +// $this->oauth2Client->delete(); +// $this->statusManager->setCloudShopId(''); + + $this->getHandler()->handle(new CreateIdentityCommand(1, [])); + + $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); + $this->assertEquals($clientId, $this->oauth2Client->getClientId()); + $this->assertEquals($clientSecret, $this->oauth2Client->getClientSecret()); + } + + /** + * @return CreateIdentityHandler + */ + private function getHandler() + { + return new CreateIdentityHandler( + $this->accountsService, + $this->shopProvider, + $this->oauth2Client, + $this->statusManager + ); + } +} diff --git a/tests/src/Unit/Account/CommandHandler/UnlinkShopHandlerTest.php b/tests/src/Unit/Account/CommandHandler/UnlinkShopHandlerTest.php deleted file mode 100644 index fab13d4a6..000000000 --- a/tests/src/Unit/Account/CommandHandler/UnlinkShopHandlerTest.php +++ /dev/null @@ -1,70 +0,0 @@ -shopProvider->getShopContext()->getContext()->shop->id; - $shop = $this->shopProvider->formatShopData((array) \Shop::getShop($shopId)); - $this->analyticsService = $this->createMock(AnalyticsService::class); - $this->analyticsService - ->expects($this->once()) - ->method('trackShopUnlinkedOnError') - ->with( - $this->linkShop->getOwnerUuid(), - $this->linkShop->getOwnerEmail(), - $this->linkShop->getShopUuid(), - $shop->frontUrl, - $shop->url, - 'ps_accounts', - 'Unlinking Shop On Error' - ); - $this->getUnlinkShopHandler()->handle( - new UnlinkShopCommand($shopId, 'Unlinking Shop On Error') - ); - } - - /** - * @return UnlinkShopHandler - */ - private function getUnlinkShopHandler() - { - return new UnlinkShopHandler( - $this->linkShop, - $this->analyticsService, - $this->shopProvider - ); - } -} diff --git a/tests/src/Unit/Account/CommandHandler/UpgradeModuleHandlerTest.php b/tests/src/Unit/Account/CommandHandler/UpgradeModuleHandlerTest.php deleted file mode 100644 index a6b57f6a2..000000000 --- a/tests/src/Unit/Account/CommandHandler/UpgradeModuleHandlerTest.php +++ /dev/null @@ -1,253 +0,0 @@ -firebaseToken = new Token((string) $this->makeJwtToken(new \DateTimeImmutable()), 'not-fresh'); - $this->firebaseRefreshedToken = new Token((string) $this->makeJwtToken(new \DateTimeImmutable('+1hour')), 'not-fresh'); - - $this->accountsClient = $this->createMock(AccountsClient::class); - $this->accountsClient - ->method('upgradeShopModule') - ->willReturn($this->createApiResponse([], 200, true)); - $this->accountsClient - ->method('refreshShopToken') - ->with($this->firebaseToken->getRefreshToken(), $this->linkShop->getShopUuid()) - ->willReturn($this->createApiResponse([ - 'token' => (string) $this->firebaseRefreshedToken->getJwt(), - 'refresh_token' => $this->firebaseRefreshedToken->getRefreshToken(), - ], 200, true)); - - $this->shopSession = $this->createMock(ShopSession::class); - $this->shopSession->method('getValidToken')->willReturn($this->firebaseRefreshedToken); - $this->shopSession->method('getToken')->willReturn($this->firebaseToken); - - $this->shopId = $this->shopProvider->getShopContext()->getContext()->shop->id; - - $this->conf = $this->createMock(ConfigurationRepository::class); - $this->conf->method('getShopId')->willReturn($this->shopId); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldUpgradePrevModuleVersions() - { - $currentVersion = '6.3.2'; - $upgradeVersion = '7.0.0'; - - $this->conf->method('getLastUpgrade')->willReturn($currentVersion); - - $this->accountsClient - ->expects($this->once()) - ->method('upgradeShopModule'); - - $this->conf - ->expects($this->once()) - ->method('updateLastUpgrade') - ->with($upgradeVersion); - - $this->getUpgradeModuleHandler()->handle(new UpgradeModuleCommand(new UpgradeModule([ - 'version' => $upgradeVersion, - 'shopId' => $this->shopId, - ]))); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldNotUpgradeNextModuleVersions() - { - $currentVersion = '7.0.1'; - $upgradeVersion = '7.0.0'; - - $this->conf->method('getLastUpgrade')->willReturn($currentVersion); - - $this->accountsClient - ->expects($this->exactly(0)) - ->method('upgradeShopModule'); - - $this->getUpgradeModuleHandler()->handle(new UpgradeModuleCommand(new UpgradeModule([ - 'version' => $upgradeVersion, - 'shopId' => $this->shopId, - ]))); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldAttemptToRefreshTokenWithFirebaseRefreshToken() - { - $currentVersion = '6.3.2'; - $upgradeVersion = '7.0.0'; - - $this->conf->method('getLastUpgrade')->willReturn($currentVersion); - - $this->accountsClient - ->expects($this->once()) - ->method('upgradeShopModule'); - $this->accountsClient - ->expects($this->once()) - ->method('refreshShopToken'); - - $this->conf - ->expects($this->once()) - ->method('updateLastUpgrade') - ->with($upgradeVersion); - - $this->shopSession - ->expects($this->once()) - ->method('setToken') - ->with((string) $this->firebaseRefreshedToken->getJwt(), $this->firebaseRefreshedToken->getRefreshToken()); - - $this->getUpgradeModuleHandler()->handle(new UpgradeModuleCommand(new UpgradeModule([ - 'version' => $upgradeVersion, - 'shopId' => $this->shopId, - ]))); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldTriggerUnlinkShopCommandOnFailure() - { - $currentVersion = '6.3.2'; - $upgradeVersion = '7.0.0'; - - $this->conf->method('getLastUpgrade')->willReturn($currentVersion); - - $this->accountsClient = $this->createMock(AccountsClient::class); - $this->accountsClient - ->method('upgradeShopModule') - ->willReturn($this->createApiResponse([ - 'message' => 'Failed upgrading module', - ], 500, false)); - - $this->commandBus = $this->createMock(CommandBus::class); - $this->commandBus - ->expects($this->once()) - ->method('handle') - ->with(new UnlinkShopCommand($this->shopId, 500)); - - $this->getUpgradeModuleHandler()->handle(new UpgradeModuleCommand(new UpgradeModule([ - 'version' => $upgradeVersion, - 'shopId' => $this->shopId, - ]))); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldNotAttemptToRefreshTokenWithFirebaseRefreshToken() - { - $this->markTestSkipped('Not needed as long as we maintain refresh tokens for billing'); - - $currentVersion = '7.3.2'; - $upgradeVersion = '8.0.0'; - - $this->conf->method('getLastUpgrade')->willReturn($currentVersion); - - $this->accountsClient - ->expects($this->once()) - ->method('upgradeShopModule'); - $this->accountsClient - ->expects($this->exactly(0)) - ->method('refreshShopToken'); - - $this->conf - ->expects($this->once()) - ->method('updateLastUpgrade') - ->with($upgradeVersion); - - $this->shopSession - ->expects($this->once()) - ->method('setToken') - ->with((string) $this->firebaseRefreshedToken->getJwt(), $this->firebaseRefreshedToken->getRefreshToken()); - - $this->getUpgradeModuleHandler()->handle(new UpgradeModuleCommand(new UpgradeModule([ - 'version' => $upgradeVersion, - 'shopId' => $this->shopId, - ]))); - } - - public function itShouldDealWithConcurrentRequests() - { - // TODO implement test - } - - /** - * @return UpgradeModuleHandler - */ - private function getUpgradeModuleHandler() - { - return new UpgradeModuleHandler( - $this->accountsClient, - $this->linkShop, - $this->shopSession, - $this->shopContext, - $this->conf, - $this->commandBus - ); - } -} diff --git a/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php b/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php new file mode 100644 index 000000000..59378ea4c --- /dev/null +++ b/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php @@ -0,0 +1,147 @@ +shopId = $this->shopProvider->getShopContext()->getContext()->shop->id; + $this->client = $this->createMock(Client::class); + $this->accountsService->setClient($this->client); + $this->shopSession = $this->createMock(ShopSession::class); + } + + /** + * @test + */ + public function itShouldInvalidateCachedStatus() + { + $cloudShopId = $this->faker->uuid; + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $cloudShopId, + 'isVerified' => false, + ]) + ]))->toArray())); + + $this->client->method('post') + ->willReturnCallback(function ($route) use ($cloudShopId) { + if (preg_match('/v1\/shop-identities\/' . $cloudShopId . '\/verify$/', $route)) { + return $this->createResponse([], 200, true); + } + return $this->createResponse([], 500, true); + }); + + $this->getHandler()->handle(new VerifyIdentityCommand(1)); + + $this->assertTrue($this->statusManager->cacheInvalidated()); + } + + /** + * @test + */ + public function itShouldNotnvalidateCachedStatusOnHttpError() + { + $cloudShopId = $this->faker->uuid; + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $cloudShopId, + 'isVerified' => false, + ]) + ]))->toArray())); + + $this->client->method('post') + ->willReturnCallback(function ($route) use ($cloudShopId) { + if (preg_match('/v1\/shop-identities\/' . $cloudShopId . '\/verify$/', $route)) { + return $this->createResponse([], 500, true); + } + return $this->createResponse([], 500, true); + }); + + try { + $this->getHandler()->handle(new VerifyIdentityCommand(1)); + } catch (AccountsException $e) { + //$this->assertInstanceOf(AccountsException::class, $e); + } + + $this->assertFalse($this->statusManager->cacheInvalidated()); + } + + /** + * @return VerifyIdentityHandler + */ + private function getHandler() + { + return new VerifyIdentityHandler( + $this->accountsService, + $this->shopProvider, + $this->statusManager, + $this->shopSession, + $this->proofManager + ); + } +} diff --git a/tests/src/Unit/Account/Session/Firebase/OwnerSession/GetValidTokenTest.php b/tests/src/Unit/Account/Session/Firebase/OwnerSession/GetValidTokenTest.php index fb446d9f8..7845ae1e1 100644 --- a/tests/src/Unit/Account/Session/Firebase/OwnerSession/GetValidTokenTest.php +++ b/tests/src/Unit/Account/Session/Firebase/OwnerSession/GetValidTokenTest.php @@ -67,7 +67,7 @@ public function itShouldRefreshExpiredToken() $session = $this->getMockedFirebaseSession( Firebase\OwnerSession::class, - $this->createApiResponse([ + $this->createResponse([ 'userToken' => (string) $userRefreshedToken, 'userRefreshToken' => $userRefreshToken, 'shopToken' => (string) $shopRefreshedToken, @@ -108,7 +108,7 @@ public function itShouldNotRefreshValidToken() $session = $this->getMockedFirebaseSession( Firebase\OwnerSession::class, - $this->createApiResponse([ + $this->createResponse([ 'userToken' => (string) $userRefreshedToken, 'userRefreshToken' => $userRefreshToken, 'shopToken' => (string) $shopRefreshedToken, @@ -138,7 +138,7 @@ public function itShouldThrowRefreshTokenExceptionOnApiError() $session = $this->getMockedFirebaseSession( Firebase\OwnerSession::class, - $this->createApiResponse([ + $this->createResponse([ 'message' => 'Error !', ], 403, false), $this->getMockedShopSession(new Token($this->makeJwtToken(new \DateTimeImmutable()))) diff --git a/tests/src/Unit/Account/Session/Firebase/OwnerSession/RefreshTokenTest.php b/tests/src/Unit/Account/Session/Firebase/OwnerSession/RefreshTokenTest.php index 3697cb85e..508df6637 100644 --- a/tests/src/Unit/Account/Session/Firebase/OwnerSession/RefreshTokenTest.php +++ b/tests/src/Unit/Account/Session/Firebase/OwnerSession/RefreshTokenTest.php @@ -72,7 +72,7 @@ public function itShouldRefreshExpiredToken() $session = $this->getMockedFirebaseSession( Firebase\OwnerSession::class, - $this->createApiResponse([ + $this->createResponse([ 'userToken' => (string) $userRefreshedToken, 'userRefreshToken' => $userRefreshToken, 'shopToken' => (string) $shopRefreshedToken, @@ -105,7 +105,7 @@ public function itShouldKeepPreviousTokenOnApiError() $session = $this->getMockedFirebaseSession( Firebase\OwnerSession::class, - $this->createApiResponse([ + $this->createResponse([ 'message' => 'Error !', ], 403, false), $this->getMockedShopSession(new Token($this->makeJwtToken(new \DateTimeImmutable()))) diff --git a/tests/src/Unit/Account/Session/Firebase/ShopSession/GetValidTokenTest.php b/tests/src/Unit/Account/Session/Firebase/ShopSession/GetValidTokenTest.php index 3a664fce7..a92333d27 100644 --- a/tests/src/Unit/Account/Session/Firebase/ShopSession/GetValidTokenTest.php +++ b/tests/src/Unit/Account/Session/Firebase/ShopSession/GetValidTokenTest.php @@ -67,7 +67,7 @@ public function itShouldRefreshExpiredToken() $session = $this->getMockedFirebaseSession( Firebase\ShopSession::class, - $this->createApiResponse([ + $this->createResponse([ 'userToken' => (string) $userRefreshedToken, 'userRefreshToken' => $userRefreshToken, 'shopToken' => (string) $shopRefreshedToken, @@ -108,7 +108,7 @@ public function itShouldNotRefreshValidToken() $session = $this->getMockedFirebaseSession( Firebase\ShopSession::class, - $this->createApiResponse([ + $this->createResponse([ 'userToken' => (string) $userRefreshedToken, 'userRefreshToken' => $userRefreshToken, 'shopToken' => (string) $shopRefreshedToken, @@ -138,7 +138,7 @@ public function itShouldThrowRefreshTokenExceptionOnApiError() $session = $this->getMockedFirebaseSession( Firebase\ShopSession::class, - $this->createApiResponse([ + $this->createResponse([ 'message' => 'Error !', ], 403, false), $this->getMockedShopSession(new Token($this->makeJwtToken(new \DateTimeImmutable()))) diff --git a/tests/src/Unit/Account/Session/Firebase/ShopSession/RefreshTokenTest.php b/tests/src/Unit/Account/Session/Firebase/ShopSession/RefreshTokenTest.php index abdaaabc7..f40ded029 100644 --- a/tests/src/Unit/Account/Session/Firebase/ShopSession/RefreshTokenTest.php +++ b/tests/src/Unit/Account/Session/Firebase/ShopSession/RefreshTokenTest.php @@ -72,7 +72,7 @@ public function itShouldRefreshExpiredToken() $session = $this->getMockedFirebaseSession( Firebase\ShopSession::class, - $this->createApiResponse([ + $this->createResponse([ 'userToken' => (string) $userRefreshedToken, 'userRefreshToken' => $userRefreshToken, 'shopToken' => (string) $shopRefreshedToken, @@ -105,7 +105,7 @@ public function itShouldKeepPreviousTokenOnApiError() $session = $this->getMockedFirebaseSession( Firebase\ShopSession::class, - $this->createApiResponse([ + $this->createResponse([ 'message' => 'Error !', ], 403, false), $this->getMockedShopSession(new Token($this->makeJwtToken(new \DateTimeImmutable()))) diff --git a/tests/src/Unit/Account/Session/SessionHelpers.php b/tests/src/Unit/Account/Session/SessionHelpers.php index 3cb1dc415..5904fae62 100644 --- a/tests/src/Unit/Account/Session/SessionHelpers.php +++ b/tests/src/Unit/Account/Session/SessionHelpers.php @@ -2,10 +2,14 @@ namespace PrestaShop\Module\PsAccounts\Tests\Unit\Account\Session; +use PHPUnit\Framework\MockObject\MockObject; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\FirebaseSession; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; use PrestaShop\Module\PsAccounts\Account\Token\Token; -use PrestaShop\Module\PsAccounts\Api\Client\AccountsClient; +use PrestaShop\Module\PsAccounts\Http\Client\Curl\Client; +use PrestaShop\Module\PsAccounts\Http\Client\Response; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\FirebaseTokens; trait SessionHelpers { @@ -20,7 +24,7 @@ protected function getMockedShopSession(Token $token) ->setConstructorArgs([ $this->configurationRepository, $this->oAuth2Service, - $this->linkShop, + $this->statusManager, $this->commandBus ]) ->enableOriginalClone() @@ -32,25 +36,39 @@ protected function getMockedShopSession(Token $token) /** * @param string $firebaseSessionClass - * @param array $response + * @param Response $response * @param ShopSession $shopSession * - * @return \PHPUnit_Framework_MockObject_MockObject|FirebaseSession + * @return FirebaseSession&MockObject */ - protected function getMockedFirebaseSession($firebaseSessionClass, array $response, $shopSession) + protected function getMockedFirebaseSession($firebaseSessionClass, Response $response, $shopSession) { - $client = $this->createMock(AccountsClient::class); - $client->method('firebaseTokens')->willReturn($response); + $client = $this->createMock(Client::class); + /** @var AccountsService $accountsService */ + $accountsService = $this->module->getService(AccountsService::class); + $accountsService->setClient($client); + + $client->method('get') + ->willReturnCallback(function ($route) use ($response) { + if (preg_match('/v2\/shop\/firebase\/tokens$/', $route)) { + return $response; + } + return $this->createResponse([], 500, true); + }); + + /** @var FirebaseSession&MockObject $firebaseSession */ $firebaseSession = $this->getMockBuilder($firebaseSessionClass) ->setConstructorArgs([ $this->configurationRepository, $shopSession, ]) ->enableOriginalClone() - ->setMethods(['getAccountsClient']) + ->setMethods(['getAccountsService']) ->getMock(); - $firebaseSession->method('getAccountsClient')->willReturn($client); + $firebaseSession->method('getAccountsService') + ->willReturn($accountsService); + return $firebaseSession; } } diff --git a/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php b/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php index b491dbaee..755be6202 100644 --- a/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php +++ b/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php @@ -2,6 +2,7 @@ namespace PrestaShop\Module\PsAccounts\Tests\Unit\Account\Session\ShopSession; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; use PrestaShop\Module\PsAccounts\Account\Token\Token; use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\AccessToken; @@ -54,13 +55,10 @@ function set_up() $oAuth2Service->method('getOAuth2Client') ->willReturn($this->oauth2Client); - $commandBus = $this->createMock(CommandBus::class); - $this->shopSession = new ShopSession( $this->configurationRepository, $oAuth2Service, - $this->linkShop, - $commandBus + $this->module->getParameter('ps_accounts.accounts_api_url') ); $this->shopSession->cleanup(); @@ -76,72 +74,6 @@ public function tear_down() $this->shopSession->cleanup(); } - /** - * @test - */ - public function itShouldClearConfigurationAndThrowIfNoOauth() - { - $e = null; - try { - // OAuth2Client has been cleared - $this->oauth2Client->delete(); - - // Shop is linked - $this->linkShop->update(new \PrestaShop\Module\PsAccounts\Account\Dto\LinkShop([ - 'shopId' => 1, - 'uid' => $this->faker->uuid, - 'employeeId' => 5, - 'ownerUid' => $this->faker->uuid, - 'ownerEmail' => $this->faker->safeEmail, - ])); - - $this->shopSession->setOauth2ClientReceiptTimeout(1); - - sleep(2); - - $this->shopSession->refreshToken(); - } catch (RefreshTokenException $e) { - //$this->module->getLogger()->info($e->getMessage()); - } - - $this->assertInstanceOf(RefreshTokenException::class, $e); - $this->assertEquals(1, preg_match('/Invalid OAuth2 client/', $e->getMessage())); - $token = $this->shopSession->getToken(); - $this->assertEquals("", (string) $token->getJwt()); - $this->assertEquals("", (string) $token->getRefreshToken()); - } - - /** - * @test - */ - public function itShouldNotClearConfigurationAndThrowIfNoOauth() - { - $e = null; - try { - // Shop is linked - $this->linkShop->update(new \PrestaShop\Module\PsAccounts\Account\Dto\LinkShop([ - 'shopId' => 1, - 'uid' => $this->faker->uuid, - ])); - - // OAuth2Client has been cleared - $this->oauth2Client->delete(); - - $this->shopSession->setOauth2ClientReceiptTimeout(1); - - //sleep(2); - - $this->shopSession->refreshToken(); - } catch (RefreshTokenException $e) { - //$this->module->getLogger()->info($e->getMessage()); - } - - $this->assertNull($e); - $token = $this->shopSession->getToken(); - $this->assertEquals((string) $this->validAccessToken, (string) $token->getJwt()); - $this->assertEquals("", (string) $token->getRefreshToken()); - } - /** * @test */ @@ -150,10 +82,7 @@ public function itShouldRefreshToken() $e = null; try { // Shop is linked - $this->linkShop->update(new \PrestaShop\Module\PsAccounts\Account\Dto\LinkShop([ - 'shopId' => 1, - 'uid' => $this->faker->uuid, - ])); + $this->statusManager->setCloudShopId($this->faker->uuid); // OAuth2Client exists $this->oauth2Client->update( @@ -161,10 +90,6 @@ public function itShouldRefreshToken() $this->faker->password ); - $this->shopSession->setOauth2ClientReceiptTimeout(1); - - //sleep(2); - $token = $this->shopSession->refreshToken(); } catch (RefreshTokenException $e) { //$this->module->getLogger()->info($e->getMessage()); diff --git a/tests/src/Unit/Account/StatusManagerTest.php b/tests/src/Unit/Account/StatusManagerTest.php new file mode 100644 index 000000000..a49b053b3 --- /dev/null +++ b/tests/src/Unit/Account/StatusManagerTest.php @@ -0,0 +1,255 @@ +client = $this->createMock(Client::class); + $this->accountsService->setClient($this->client); + $this->shopSession = $this->createMock(ShopSession::class); + + $this->statusManager = new StatusManager( + $this->shopSession, + $this->accountsService, + $this->configurationRepository + ); + } + +// /** +// * @test +// */ +// public function itShouldSetCachedStatus() +// { +// $cloudShopId = $this->faker->uuid; +// +// $this->statusManager->setCachedStatus(new CachedShopStatus([ +// 'shopStatus' => new ShopStatus([ +// 'cloudShopId' => $cloudShopId, +// 'isVerified' => false, +// ]), +// ])); +// +// $cachedStatus = $this->statusManager->getStatus(StatusManager::CACHE_TTL_INFINITE); +// +// $this->assertEquals($cloudShopId, $cachedStatus->cloudShopId); +// $this->assertFalse($cachedStatus->isVerified); +// } +// +// /** +// * @test +// */ +// public function itShouldSetOrUpdateCachedStatus() +// { +// $cloudShopId = $this->faker->uuid; +// $pointOfContactEmail = $this->faker->email; +// +// $this->statusManager->setCachedStatus(new ShopStatus([ +// 'cloudShopId' => $cloudShopId, +// 'isVerified' => false, +// ])); +// +// $this->statusManager->upsetCachedStatus(new ShopStatus([ +// 'pointOfContactEmail' => $pointOfContactEmail, +// 'isVerified' => true, +// ])); +// +// $cachedStatus = $this->statusManager->getCachedStatus(); +// +// $this->assertEquals($cloudShopId, $cachedStatus->cloudShopId); +// $this->assertTrue($cachedStatus->isVerified); +// $this->assertEquals($pointOfContactEmail, $cachedStatus->pointOfContactEmail); +// } + + /** + * @test + */ + public function itShouldThrowOnUnsetCachedStatus() + { + $this->configurationRepository->updateCachedShopStatus(null); + + $this->expectException(UnknownStatusException::class); + + $this->statusManager->getStatus(StatusManager::CACHE_TTL_INFINITE); + } + + /** + * @test + */ + public function itShouldUpdateStatusFromCloudWhenTtlExpired() + { + $cloudShopId = $this->faker->uuid; + $pointOfContactEmail = $this->faker->email; + $pointOfContactUid = $this->faker->uuid; + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $cloudShopId, + 'isVerified' => false, + ]) + ]))->toArray())); + + $this->shopSession->method('getValidToken') + ->willReturn($this->faker->uuid); + + $this->client->method('get') + ->willReturnCallback(function ($route) use ($cloudShopId, $pointOfContactEmail, $pointOfContactUid) { + if (preg_match('/v1\/shop-identities\/' . $cloudShopId . '\/status$/', $route)) { + return $this->createResponse([ + "cloudShopId" => $cloudShopId, + "isVerified" => true, + "pointOfContactEmail" => $pointOfContactEmail, + "pointOfContactUid" => $pointOfContactUid, + ], 200, true); + } + return $this->createResponse([], 500, true); + }); + + sleep(1); + + $cachedStatus = $this->statusManager->getStatus(false, 1); + + $this->assertEquals($cloudShopId, $cachedStatus->cloudShopId); + $this->assertTrue($cachedStatus->isVerified); + $this->assertEquals($pointOfContactEmail, $cachedStatus->pointOfContactEmail); + } + + /** + * @test + */ + public function itShouldUpdateStatusFromCloudWhenCacheInvalidated() + { + $cloudShopId = $this->faker->uuid; + $pointOfContactEmail = $this->faker->email; + $pointOfContactUid = $this->faker->uuid; + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $cloudShopId, + 'isVerified' => false, + ]) + ]))->toArray())); + + $this->shopSession->method('getValidToken') + ->willReturn($this->faker->uuid); + + $this->client->method('get') + ->willReturnCallback(function ($route) use ($cloudShopId, $pointOfContactEmail, $pointOfContactUid) { + if (preg_match('/v1\/shop-identities\/' . $cloudShopId . '\/status$/', $route)) { + return $this->createResponse([ + "cloudShopId" => $cloudShopId, + "isVerified" => true, + "pointOfContactEmail" => $pointOfContactEmail, + "pointOfContactUid" => $pointOfContactUid, + ], 200, true); + } + return $this->createResponse([], 500, true); + }); + + $this->statusManager->invalidateCache(); + $cachedStatus = $this->statusManager->getStatus(); + + $this->assertEquals($cloudShopId, $cachedStatus->cloudShopId); + $this->assertTrue($cachedStatus->isVerified); + $this->assertEquals($pointOfContactEmail, $cachedStatus->pointOfContactEmail); + } + + /** + * @test + */ + public function itShouldNotUpdateStatusFromCloudIfTtlNotExpired() + { + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus(), + ]))->toArray())); + + $this->shopSession->method('getValidToken') + ->willReturn($this->faker->uuid); + + $this->client->method('get') + ->willReturnCallback(function ($route) { + if (preg_match('/v1\/shop-identities\/\d\/status$/', $route)) { + return $this->createResponse([ + "msg" => "Invalid request", + ], 400, true); + } + return $this->createResponse([], 500, true); + }); + + $cachedStatus = $this->statusManager->getStatus(false); + + $this->assertNull($cachedStatus->cloudShopId); + $this->assertFalse($cachedStatus->isVerified); + $this->assertNull($cachedStatus->pointOfContactEmail); + } + + /** + * @test + */ + public function itShouldNotUpdateStatusFromCloudOnError() + { + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus(), + ]))->toArray())); + + $this->shopSession->method('getValidToken') + ->willReturn($this->faker->uuid); + + $this->client->method('get') + ->willReturnCallback(function ($route) { + if (preg_match('/v1\/shop-identities\/\d\/status$/', $route)) { + return $this->createResponse([ + "msg" => "Invalid request", + ], 400, true); + } + return $this->createResponse([], 500, true); + }); + + sleep(1); + + $cachedStatus = $this->statusManager->getStatus(false, 1); + + $this->assertNull($cachedStatus->cloudShopId); + $this->assertFalse($cachedStatus->isVerified); + $this->assertNull($cachedStatus->pointOfContactEmail); + } +} diff --git a/tests/src/Unit/Hook/ActionObjectShopUpdateAfterTest.php b/tests/src/Unit/Hook/ActionObjectShopUpdateAfterTest.php index ae9c37800..c066cd5ca 100644 --- a/tests/src/Unit/Hook/ActionObjectShopUpdateAfterTest.php +++ b/tests/src/Unit/Hook/ActionObjectShopUpdateAfterTest.php @@ -2,12 +2,15 @@ namespace PrestaShop\Module\PsAccounts\Tests\Unit\Hook; +use PHPUnit\Framework\MockObject\MockObject; use PrestaShop\Module\PsAccounts\Account\CommandHandler\UpdateUserShopHandler; use PrestaShop\Module\PsAccounts\Account\Dto\UpdateShop; use PrestaShop\Module\PsAccounts\Account\Session\Firebase; use PrestaShop\Module\PsAccounts\Account\Token\Token; use PrestaShop\Module\PsAccounts\Adapter\Link; use PrestaShop\Module\PsAccounts\Api\Client\AccountsClient; +use PrestaShop\Module\PsAccounts\Http\Client\Response; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; use PrestaShop\Module\PsAccounts\Tests\TestCase; class Params { @@ -51,7 +54,7 @@ public function itShouldAttemptToUpdateShop() $ownerToken = new Token((string)$this->makeJwtToken(new \DateTimeImmutable('+1 hour'), [ 'sub' => $this->faker->uuid ])); - $updateUserShopResponse = $this->createApiResponse([ + $updateUserShopResponse = $this->createResponse([ 'message' => 'all good !', ], 200, true); @@ -95,7 +98,7 @@ public function itShouldAttemptToUpdateShopOnUrlUpdate() $ownerToken = new Token((string)$this->makeJwtToken(new \DateTimeImmutable('+1 hour'), [ 'sub' => $this->faker->uuid ])); - $updateUserShopResponse = $this->createApiResponse([ + $updateUserShopResponse = $this->createResponse([ 'message' => 'all good !', ], 200, true); @@ -152,13 +155,16 @@ public function itShouldAttemptToUpdateShopOnUrlUpdate() /** * @param Params|null $params - * @param array $response + * @param Response $response * * @return void + * @throws \ReflectionException */ - private function initResponse(&$params, array $response) + private function initResponse(&$params, Response $response) { - $accountsClient = $this->createMock(AccountsClient::class); + /** @var AccountsService&MockObject $accountsClient */ + $accountsClient = $this->createMock(AccountsService::class); + $accountsClient->expects($this->once())->method('updateUserShop')->willReturnCallback(function ( $ownerUid, $shopUid, $ownerToken, UpdateShop $shop ) use (&$params, $response) { @@ -170,7 +176,8 @@ private function initResponse(&$params, array $response) ]; return $response; }); - $this->replaceProperty($this->updateUserShopHandler, 'accountClient', $accountsClient); + + $this->replaceProperty($this->updateUserShopHandler, 'accountsService', $accountsClient); } /** diff --git a/tests/src/Unit/Provider/RsaKeysProvider/CreatePairTest.php b/tests/src/Unit/Provider/RsaKeysProvider/CreatePairTest.php deleted file mode 100644 index f25e67fe9..000000000 --- a/tests/src/Unit/Provider/RsaKeysProvider/CreatePairTest.php +++ /dev/null @@ -1,32 +0,0 @@ -rsaKeyService->createPair(); - - $this->assertArrayHasKey('privatekey', $key); - $this->assertArrayHasKey('publickey', $key); - - $this->assertEquals('string', gettype($key['privatekey'])); - $this->assertEquals('string', gettype($key['publickey'])); - } -} diff --git a/tests/src/Unit/Provider/RsaKeysProvider/GenerateKeysTest.php b/tests/src/Unit/Provider/RsaKeysProvider/GenerateKeysTest.php deleted file mode 100644 index 68bee3091..000000000 --- a/tests/src/Unit/Provider/RsaKeysProvider/GenerateKeysTest.php +++ /dev/null @@ -1,53 +0,0 @@ -getAccountsRsaPrivateKey() . "\n"; - - // Empty DB - $this->rsaKeysProvider->cleanupKeys(); - - $this->assertEmpty($this->rsaKeysProvider->getPrivateKey()); - $this->assertEmpty($this->rsaKeysProvider->getPublicKey()); - - $this->rsaKeysProvider->generateKeys(); - - //echo "B\n" . $configuration->getAccountsRsaPrivateKey() . "\n"; - - $this->assertNotEmpty($this->rsaKeysProvider->getPrivateKey()); - $this->assertNotEmpty($this->rsaKeysProvider->getPublicKey()); - - $data = $this->faker->sentence(); - $signedData = $this->rsaKeysProvider->signData($this->rsaKeysProvider->getPrivateKey(), $data); - - $this->assertTrue( - $this->rsaKeysProvider->verifySignature( - $this->rsaKeysProvider->getPublicKey(), - $signedData, - $data - ) - ); - } -} diff --git a/tests/src/Unit/Provider/RsaKeysProvider/VerifySignatureTest.php b/tests/src/Unit/Provider/RsaKeysProvider/VerifySignatureTest.php deleted file mode 100644 index c051d7166..000000000 --- a/tests/src/Unit/Provider/RsaKeysProvider/VerifySignatureTest.php +++ /dev/null @@ -1,30 +0,0 @@ -rsaKeysProvider->createPair(); - - $data = 'data-to-sign'; - - $signature = $this->rsaKeysProvider->signData($key['privatekey'], $data); - - $this->assertEquals(1, $this->rsaKeysProvider->verifySignature($key['publickey'], $signature, $data)); - } -} diff --git a/tests/src/Unit/Service/OAuth2/TestCase.php b/tests/src/Unit/Service/OAuth2/TestCase.php index 43f6aea82..7a5bf174d 100644 --- a/tests/src/Unit/Service/OAuth2/TestCase.php +++ b/tests/src/Unit/Service/OAuth2/TestCase.php @@ -14,39 +14,25 @@ class TestCase extends \PrestaShop\Module\PsAccounts\Tests\TestCase protected $oAuth2Service; /** - * @var \PHPUnit_Framework_MockObject_MockObject|Response|(Response&\PHPUnit_Framework_MockObject_MockObject) + * @var Response */ protected $wellKnownResponse; /** - * @var \PHPUnit_Framework_MockObject_MockObject|Response|(Response&\PHPUnit_Framework_MockObject_MockObject) + * @var Response */ protected $accessTokenResponse; /** - * @var \PHPUnit_Framework_MockObject_MockObject|Response|(Response&\PHPUnit_Framework_MockObject_MockObject) + * @var Response */ protected $resourceOwnerResponse; /** - * @var \PHPUnit_Framework_MockObject_MockObject|(\PHPUnit_Framework_MockObject_MockObject&Response)|Response + * @var Response */ protected $jwksResponse; - /** - * @param mixed $responseBody - * @param int $statusCode - * - * @return \PHPUnit_Framework_MockObject_MockObject|Response|(Response&\PHPUnit_Framework_MockObject_MockObject) - */ - protected function createResponse($responseBody, $statusCode = 200) - { - return new Response( - \json_decode($responseBody, true), - $statusCode - ); - } - /** * @return Client|(Client&\PHPUnit_Framework_MockObject_MockObject)|\PHPUnit_Framework_MockObject_MockObject */ diff --git a/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedTest.php b/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedTest.php index ad67b1e84..09ae618ed 100644 --- a/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedTest.php +++ b/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedTest.php @@ -2,6 +2,8 @@ namespace PrestaShop\Module\PsAccounts\Tests\Unit\Service\PsAccountsService; +use PrestaShop\Module\PsAccounts\Account\CachedShopStatus; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; use PrestaShop\Module\PsAccounts\Service\PsAccountsService; use PrestaShop\Module\PsAccounts\Tests\TestCase; @@ -14,6 +16,19 @@ class IsAccountLinkedTest extends TestCase */ protected $service; + public function set_up() + { + parent::set_up(); // TODO: Change the autogenerated stub + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $this->faker->uuid, + 'pointOfContactUuid' => $this->faker->uuid, + ]) + ]))->toArray())); + } + /** * @test * @@ -21,12 +36,19 @@ class IsAccountLinkedTest extends TestCase */ public function itShouldReturnTrue() { - $this->linkShop->delete(); + $this->assertTrue($this->service->isAccountLinked()); + } - $this->linkShop->setShopUuid($this->faker->uuid); - $this->linkShop->setOwnerEmail($this->faker->safeEmail); + /** + * @test + * + * @throws \Exception + */ + public function itShouldReturnFalseWithoutIdentity() + { + $this->statusManager->setCloudShopId(''); - $this->assertTrue($this->service->isAccountLinked()); + $this->assertFalse($this->service->isAccountLinked()); } /** @@ -34,9 +56,14 @@ public function itShouldReturnTrue() * * @throws \Exception */ - public function itShouldReturnFalse() + public function itShouldReturnFalseWithoutPointOfContact() { - $this->linkShop->delete(); + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus([ + 'pointOfContactUuid' => null, + ]) + ]))->toArray())); $this->assertFalse($this->service->isAccountLinked()); } diff --git a/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedV4Test.php b/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedV4Test.php index f67905bf9..7215ad792 100644 --- a/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedV4Test.php +++ b/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedV4Test.php @@ -15,6 +15,8 @@ class IsAccountLinkedV4Test extends TestCase */ public function itShouldReturnTrue() { + $this->markTestSkipped('Deprecated and considered FALSE'); + $token = $this->makeFirebaseToken(null, ['email_verified' => true]); /** @var ShopTokenRepository $tokenRepos */ @@ -37,6 +39,8 @@ public function itShouldReturnTrue() */ public function itShouldReturnFalse() { + $this->markTestSkipped('Deprecated and considered FALSE'); + $token = $this->makeFirebaseToken(null, ['email_verified' => true]); /** @var ShopTokenRepository $tokenRepos */ diff --git a/upgrade/upgrade-6.0.0.php b/upgrade/upgrade-6.0.0.php index baad9ce85..b1b622054 100644 --- a/upgrade/upgrade-6.0.0.php +++ b/upgrade/upgrade-6.0.0.php @@ -17,7 +17,7 @@ * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -require_once __DIR__ . '/../src/Module/Install.php'; + /** * @param Ps_accounts $module * @@ -27,6 +27,8 @@ */ function upgrade_module_6_0_0($module) { + require __DIR__ . '/../src/enforce_autoload.php'; + (new PrestaShop\Module\PsAccounts\Module\Install($module, Db::getInstance())) ->runMigration('create_employee_account_table'); diff --git a/upgrade/upgrade-6.1.4.php b/upgrade/upgrade-6.1.4.php index b2ca20fd6..bd439966b 100644 --- a/upgrade/upgrade-6.1.4.php +++ b/upgrade/upgrade-6.1.4.php @@ -1,5 +1,4 @@ registerHook($module->getHooksToRegister()); return true; diff --git a/upgrade/upgrade-6.1.6.php b/upgrade/upgrade-6.1.6.php index 76970ed11..80d0676e5 100644 --- a/upgrade/upgrade-6.1.6.php +++ b/upgrade/upgrade-6.1.6.php @@ -1,5 +1,4 @@ addCustomHooks($module->getCustomHooks()); $module->registerHook($module->getHooksToRegister()); diff --git a/upgrade/upgrade-7.0.0.php b/upgrade/upgrade-7.0.0.php index 70f2c0295..262fcae49 100644 --- a/upgrade/upgrade-7.0.0.php +++ b/upgrade/upgrade-7.0.0.php @@ -9,6 +9,8 @@ */ function upgrade_module_7_0_0($module) { + require __DIR__ . '/../src/enforce_autoload.php'; + foreach ([ //'displayBackOfficeHeader', //'actionAdminLoginControllerSetMedia', diff --git a/upgrade/upgrade-8.0.0.php b/upgrade/upgrade-8.0.0.php new file mode 100644 index 000000000..71de5456e --- /dev/null +++ b/upgrade/upgrade-8.0.0.php @@ -0,0 +1,32 @@ +getService(CommandBus::class); + $commandBus->handle(new CreateIdentitiesCommand()); + $commandBus->handle(new VerifyIdentitiesCommand()); + /* @phpstan-ignore-next-line */ + } catch (\Throwable $e) { + } catch (\Exception $e) { + Logger::getInstance()->error('error during upgrade : ' . $e->getMessage()); + } + + return true; +} From a38c18db4afb114ba52294a782b80764aba38c61 Mon Sep 17 00:00:00 2001 From: Antoine Date: Tue, 10 Jun 2025 10:16:19 +0200 Subject: [PATCH 03/46] fix: shop verification error (#496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: working identity creation * feat: refactor after review * feat: csfixer and test * feat: remove reonboard methods * feat: php cs fixer * feat: php stan + remove reonboard * fix: phpstan * feat: update phpunit * feat: phpstan fix * feat: phpunit test fix * Update config/command.yml Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> * Update config/command.yml Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> * fix: change endpoint path * fix: refactor multi handler * fix: refactor multi handler * fix: refactor multi handler * fix: remove abstract suffix * fix: injected dependencies * fix: unit tests, php-cs-fixer, phpstan * fix: unit tests, php-cs-fixer, phpstan * feat: improve scoping & hack autoloading for upgrade * feat: improve scoping & hack autoloading for upgrade * feat: verify url authenticity * fix: adapt command handler * fix: unlinked on error conf key * feat: improve scoping & hack autoloading for upgrade * feat: improve scoping & hack autoloading for upgrade * feat: add v2 of abstract rest controller * chore: reset prefix * refactor: return early * fix: more readable throwable catching * fix: more readable throwable catching * fix: php-cs-fixer * fix: scoper config * fix: phpstan * fix: inverted condition * fix: skip a few tests * feat: add x-multishop-enabled header * feat: cleanup handler and throw exception on failure * feat: cleanup handler and throw exception on failure * chore: adapt service container wiring * chore: post merge fixes * chore: rename to cloudShopId * refactor: some refactoring configure token verification test a header display hook * chore: some raw wiring * chore: some renaming * chore: some renaming * fix: php-cs-fixer * fix: post merge fixes * fix: cleanup tests * fix: cleanup tests * fix: cleanup tests * fix: cleanup tests * fix: cleanup tests * refactor: StatusManager & AccountsService replacement for AccountsClient * fix: create identity * fix: alpha debugging info * feat: more debugging errors * fix: use alternative Authorization header * fix: verify route name * fix: shop access token audience * fix: renaming * fix: disable scope and audience for now * fix: phpstan & php-cs-fixer * chore: activate display hook for alpha debugging * fix: alpha status manager cache * chore: some cleanup * fix: tests * fix: phpstan * chore: StatusManager & VerifyIdentityHandler tests and associated fixes * fix: phpstan & php-cs-fixer * fix: phpstan & php-cs-fixer * chore: remove useless query * fix: typo * fix: delete proof after verification & various renaming * fix: delete proof after verification & various renaming * fix: delete proof after verification & various renaming * fix: delete proof after verification & various renaming * refactor: audience configuration * fix: update bulle configuration * fix: disable php-scoper-update-prefix target * fix: update status based on verification response * fix: update status based on verification response * fix: update status based on verification response * feat: shop status route * feat: tracking v8 (#488) * fix: disable php-scoper-update-prefix target * Update src/Service/Accounts/AccountsService.php Thanks RV Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> * fix: disable php-scoper-update-prefix target * fix: anonymous id cookie on '/' --------- Co-authored-by: hschoenenberger Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> * fix: tests * fix: backend url generation * chore: update fixme * feat: attempt to get live status & fix empty datetime * feat: attempt to get live status & fix empty datetime * fix: shop verification error * fix: clear cached status * feat: invalidate cache (#498) * fix: serialized date format * fix: cached data for individual getters * fix: remove try catch block * fix: remove try catch block --------- Co-authored-by: atourneriePresta Co-authored-by: atourneriePresta <76098514+atourneriePresta@users.noreply.github.com> Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> Co-authored-by: hschoenenberger --- .../CommandHandler/VerifyIdentityHandler.php | 8 +------- src/Service/Accounts/AccountsService.php | 4 +--- .../CommandHandler/VerifyIdentityHandlerTest.php | 16 ++++++++++------ 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/Account/CommandHandler/VerifyIdentityHandler.php b/src/Account/CommandHandler/VerifyIdentityHandler.php index c3b3daf58..f1e5da126 100644 --- a/src/Account/CommandHandler/VerifyIdentityHandler.php +++ b/src/Account/CommandHandler/VerifyIdentityHandler.php @@ -94,21 +94,15 @@ public function handle(VerifyIdentityCommand $command) if ($cachedStatus->isVerified) { return; } + $shopId = $command->shopId ?: \Shop::getContextShopID(); - //try { $this->accountsService->verifyShopIdentity( $this->statusManager->getCloudShopId(), $this->shopSession->getValidToken(), $this->shopProvider->getUrl($shopId), $this->proofManager->generateProof() ); - $this->statusManager->invalidateCache(); - - $this->proofManager->deleteProof(); - //} catch (AccountsException $e) { - // // Status not verified - //} } } diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index 632b65e27..fe415f130 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -274,7 +274,7 @@ public function createShopIdentity(ShopUrl $shopUrl) * @param ShopUrl $shopUrl * @param string $proof * - * @return ShopStatus + * @return void * * @throws AccountsException */ @@ -298,8 +298,6 @@ public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $ if (!$response->isSuccessful) { throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to verify shop identity.')); } - - return new ShopStatus($response->body); } /** diff --git a/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php b/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php index 59378ea4c..e60c5abcd 100644 --- a/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php @@ -74,7 +74,7 @@ public function set_up() /** * @test */ - public function itShouldInvalidateCachedStatus() + public function itShouldInvalidateCachedStatusOnSuccess() { $cloudShopId = $this->faker->uuid; @@ -89,7 +89,9 @@ public function itShouldInvalidateCachedStatus() $this->client->method('post') ->willReturnCallback(function ($route) use ($cloudShopId) { if (preg_match('/v1\/shop-identities\/' . $cloudShopId . '\/verify$/', $route)) { - return $this->createResponse([], 200, true); + return $this->createResponse([ + "success" => true, + ], 200, true); } return $this->createResponse([], 500, true); }); @@ -102,7 +104,7 @@ public function itShouldInvalidateCachedStatus() /** * @test */ - public function itShouldNotnvalidateCachedStatusOnHttpError() + public function itShouldNotInvalidateCachedStatusOnHttpError() { $cloudShopId = $this->faker->uuid; @@ -117,15 +119,17 @@ public function itShouldNotnvalidateCachedStatusOnHttpError() $this->client->method('post') ->willReturnCallback(function ($route) use ($cloudShopId) { if (preg_match('/v1\/shop-identities\/' . $cloudShopId . '\/verify$/', $route)) { - return $this->createResponse([], 500, true); + return $this->createResponse([ + "error" => 'store-identity/proof-invalid', + "message" => 'Proof is invalid', + ], 400, true); } - return $this->createResponse([], 500, true); + return $this->createResponse([], 400, true); }); try { $this->getHandler()->handle(new VerifyIdentityCommand(1)); } catch (AccountsException $e) { - //$this->assertInstanceOf(AccountsException::class, $e); } $this->assertFalse($this->statusManager->cacheInvalidated()); From 803e259e2bf5114e0b17efb48d501371c3a4e449 Mon Sep 17 00:00:00 2001 From: Antoine Date: Tue, 10 Jun 2025 15:19:14 +0200 Subject: [PATCH 04/46] feat: verif implicit (#499) * feat: verif implicit * fix: trigger verify command if identity exists --------- Co-authored-by: hschoenenberger --- ps_accounts.php | 1 - .../CommandHandler/CreateIdentityHandler.php | 52 +++++++++++++----- src/Service/Accounts/AccountsService.php | 4 +- src/ServiceProvider/CommandProvider.php | 4 +- .../CreateIdentityHandlerTest.php | 54 ++++++++++++++++++- upgrade/upgrade-8.0.0.php | 2 - 6 files changed, 96 insertions(+), 21 deletions(-) diff --git a/ps_accounts.php b/ps_accounts.php index f16224d40..bf962abfa 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -459,7 +459,6 @@ public function onModuleReset() // Verification flow $commandBus->handle(new \PrestaShop\Module\PsAccounts\Account\Command\CreateIdentitiesCommand()); - $commandBus->handle(new \PrestaShop\Module\PsAccounts\Account\Command\VerifyIdentitiesCommand()); } /** diff --git a/src/Account/CommandHandler/CreateIdentityHandler.php b/src/Account/CommandHandler/CreateIdentityHandler.php index b219dce70..1a9d9d1d8 100644 --- a/src/Account/CommandHandler/CreateIdentityHandler.php +++ b/src/Account/CommandHandler/CreateIdentityHandler.php @@ -21,7 +21,12 @@ namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; use PrestaShop\Module\PsAccounts\Account\Command\CreateIdentityCommand; +use PrestaShop\Module\PsAccounts\Account\Command\VerifyIdentityCommand; +use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; +use PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException; +use PrestaShop\Module\PsAccounts\Account\ProofManager; use PrestaShop\Module\PsAccounts\Account\StatusManager; +use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; @@ -49,22 +54,38 @@ class CreateIdentityHandler */ private $statusManager; + /** + * @var ProofManager + */ + private $proofManager; + + /** + * @var CommandBus + */ + private $commandBus; + /** * @param AccountsService $accountsService * @param ShopProvider $shopProvider * @param OAuth2Client $oauth2Client * @param StatusManager $shopStatus + * @param ProofManager $proofManager + * @param CommandBus $commandBus */ public function __construct( AccountsService $accountsService, ShopProvider $shopProvider, OAuth2Client $oauth2Client, - StatusManager $shopStatus + StatusManager $shopStatus, + ProofManager $proofManager, + CommandBus $commandBus ) { $this->accountsService = $accountsService; $this->shopProvider = $shopProvider; $this->oAuth2Client = $oauth2Client; $this->statusManager = $shopStatus; + $this->proofManager = $proofManager; + $this->commandBus = $commandBus; } /** @@ -72,24 +93,27 @@ public function __construct( * * @return void * + * @throws RefreshTokenException + * @throws UnknownStatusException * @throws AccountsException */ public function handle(CreateIdentityCommand $command) { - if ($this->isAlreadyCreated()) { - return; - } + if (!$this->isAlreadyCreated()) { + $shopId = $command->shopId ?: \Shop::getContextShopID(); - $shopId = $command->shopId ?: \Shop::getContextShopID(); - - $identityCreated = $this->accountsService->createShopIdentity( - $this->shopProvider->getUrl($shopId) - ); - $this->oAuth2Client->update( - $identityCreated->clientId, - $identityCreated->clientSecret - ); - $this->statusManager->setCloudShopId($identityCreated->cloudShopId); + $identityCreated = $this->accountsService->createShopIdentity( + $this->shopProvider->getUrl($shopId), + $this->proofManager->generateProof() + ); + $this->oAuth2Client->update( + $identityCreated->clientId, + $identityCreated->clientSecret + ); + $this->statusManager->setCloudShopId($identityCreated->cloudShopId); + } else { + $this->commandBus->handle(new VerifyIdentityCommand($command->shopId)); + } } /** diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index fe415f130..766fc6528 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -243,12 +243,13 @@ public function healthCheck() /** * @param ShopUrl $shopUrl + * @param string $proof * * @return IdentityCreated * * @throws AccountsException */ - public function createShopIdentity(ShopUrl $shopUrl) + public function createShopIdentity(ShopUrl $shopUrl, $proof) { $response = $this->getClient()->post( '/v1/shop-identities', @@ -257,6 +258,7 @@ public function createShopIdentity(ShopUrl $shopUrl) 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), 'frontendUrl' => $shopUrl->getFrontendUrl(), 'multiShopId' => $shopUrl->getMultiShopId(), + 'proof' => $proof, ], ] ); diff --git a/src/ServiceProvider/CommandProvider.php b/src/ServiceProvider/CommandProvider.php index 5008a503b..aa21e9391 100644 --- a/src/ServiceProvider/CommandProvider.php +++ b/src/ServiceProvider/CommandProvider.php @@ -62,7 +62,9 @@ public function provide(ServiceContainer $container) $container->get(AccountsService::class), $container->get(ShopProvider::class), $container->get(OAuth2Client::class), - $container->get(StatusManager::class) + $container->get(StatusManager::class), + $container->get(ProofManager::class), + $container->get(CommandBus::class) ); }); $container->registerProvider(CreateIdentitiesHandler::class, static function () use ($container) { diff --git a/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php index f4359bc7e..c477c243b 100644 --- a/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php @@ -3,11 +3,16 @@ namespace PrestaShop\Module\PsAccounts\Tests\Unit\Account\CommandHandler; use PHPUnit\Framework\MockObject\MockObject; +use PrestaShop\Module\PsAccounts\Account\CachedShopStatus; use PrestaShop\Module\PsAccounts\Account\Command\CreateIdentityCommand; +use PrestaShop\Module\PsAccounts\Account\Command\VerifyIdentityCommand; use PrestaShop\Module\PsAccounts\Account\CommandHandler\CreateIdentityHandler; +use PrestaShop\Module\PsAccounts\Account\CommandHandler\VerifyIdentityHandler; +use PrestaShop\Module\PsAccounts\Account\ProofManager; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Http\Client\Curl\Client; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; use PrestaShop\Module\PsAccounts\Tests\TestCase; @@ -42,11 +47,23 @@ class CreateIdentityHandlerTest extends TestCase */ public $statusManager; + /** + * @inject + * + * @var ProofManager + */ + public $proofManager; + /** * @var Client&MockObject */ public $client; + /** + * @var VerifyIdentityHandler&MockObject + */ + public $verifyIdentityHandler; + /** * @var int */ @@ -59,6 +76,9 @@ public function set_up() $this->shopId = $this->shopProvider->getShopContext()->getContext()->shop->id; $this->client = $this->createMock(Client::class); $this->accountsService->setClient($this->client); + + $this->verifyIdentityHandler = $this->createMock(VerifyIdentityHandler::class); + $this->module->getServiceContainer()->set(VerifyIdentityHandler::class, $this->verifyIdentityHandler); } /** @@ -115,7 +135,7 @@ public function itShouldNotChangeIdentityIfExists() $this->client ->method('post') - ->willReturnCallback(function ($route) use ($id1, $id2) { + ->willReturnCallback(function ($route) use ($id1, $id2, $cloudShopId) { static $count = 1; if (preg_match('/v1\/shop-identities$/', $route)) { return $count++ === 1 ? $id1 : $id2; @@ -138,6 +158,34 @@ public function itShouldNotChangeIdentityIfExists() $this->assertEquals($clientSecret, $this->oauth2Client->getClientSecret()); } + /** + * @test + */ + public function itShouldTriggerVerifyIdentityIfAlreadyCreated() + { + $clientId = $this->faker->uuid; + $clientSecret = $this->faker->uuid; + $cloudShopId = $this->faker->uuid; + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $cloudShopId, + 'isVerified' => false, + ]) + ]))->toArray())); + + $this->oauth2Client->update($clientId, $clientSecret); + + $this->verifyIdentityHandler->expects($this->once()) + ->method('handle') + ->with( + $this->isInstanceOf(VerifyIdentityCommand::class) + ); + + $this->getHandler()->handle(new CreateIdentityCommand(1, [])); + } + /** * @return CreateIdentityHandler */ @@ -147,7 +195,9 @@ private function getHandler() $this->accountsService, $this->shopProvider, $this->oauth2Client, - $this->statusManager + $this->statusManager, + $this->proofManager, + $this->commandBus ); } } diff --git a/upgrade/upgrade-8.0.0.php b/upgrade/upgrade-8.0.0.php index 71de5456e..c027c33c0 100644 --- a/upgrade/upgrade-8.0.0.php +++ b/upgrade/upgrade-8.0.0.php @@ -1,7 +1,6 @@ getService(CommandBus::class); $commandBus->handle(new CreateIdentitiesCommand()); - $commandBus->handle(new VerifyIdentitiesCommand()); /* @phpstan-ignore-next-line */ } catch (\Throwable $e) { } catch (\Exception $e) { From 03f91ae95d4d3fd438525dce845e3dfa4f9f2576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:45:27 +0200 Subject: [PATCH 05/46] [ACCOUNT-2832] feat: identify contact (#481) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: unit tests, php-cs-fixer, phpstan * feat: improve scoping & hack autoloading for upgrade * feat: improve scoping & hack autoloading for upgrade * feat: verify url authenticity * fix: adapt command handler * fix: unlinked on error conf key * feat: improve scoping & hack autoloading for upgrade * feat: improve scoping & hack autoloading for upgrade * refactor: get rid of module-lib-service-container * refactor: remove useless deps * fix: phpstan, php-cs-fixer & feature tests * fix: phpstan, php-cs-fixer & feature tests * fix: phpstan, php-cs-fixer & feature tests * fix: phpstan, php-cs-fixer & feature tests * fix: php-cs-fixer * fix: cleanup * refactor: create provider files * refactor: create provider files * chore: empty services files * chore: empty services files * chore: empty services files * feat: add v2 of abstract rest controller * chore: empty services files * fix: cherry pick some fixes * fix: cherry pick some fixes * refactor: wip * chore: cleanup namespaces * chore: reset prefix * refactor: return early * fix: more readable throwable catching * fix: more readable throwable catching * fix: php-cs-fixer * fix: scoper config * fix: phpstan * fix: inverted condition * fix: skip a few tests * feat: add x-multishop-enabled header * feat: cleanup handler and throw exception on failure * feat: cleanup handler and throw exception on failure * chore: adapt service container wiring * chore: post merge fixes * chore: rename to cloudShopId * refactor: some refactoring configure token verification test a header display hook * chore: some raw wiring * chore: some renaming * chore: some renaming * feat: alpha proposal * refactor: move exceptions classes & instantiate real response alike objects * refactor: move exceptions classes & instantiate real response alike objects * refactor: phpstan & php-cs-fixer * fix: tests * fix: tests * fix: tests * fix: tests * fix: json post * fix: json post * fix: php-cs-fixer * fix: json post * fix: php-cs-fixer * fix: phpstan * fix: phpstan * chore: cleanup commented code * fix: default values much match legacy behaviour * fix: default values much match legacy behaviour * fix: default values much match legacy behaviour * refactor: userinfo without s * fix: update header-stamps * fix: php-cs-fixer * chore: bump to milestone * refactor: move responses objects * refactor: extract AccountLogin domain * fix: tests * fix: tests * refactor: response type legacy management * feat: token validator * feat: header-stamp * feat: unit tests * feat: unit tests * refactor: circuit breaker exceptions handling & http client exceptions * fix: header-stamps * fix: header-stamps * fix: http client post * fix: http client post * fix: http client post * feat: clear oauth2cache on reset * fix: safety check * fix: safety check * fix: check empty response * feat: secured healthcheck * feat: unit tests & fix token validator * feat: unit tests & fix token validator * feat: pollyfill for 56 * feat: querystring option * fix: set issued at by default * fix: query string usage with test http client * fix: query string usage with test http client * fix: query string usage with test http client * fix: query string usage with test http client * fix: phpstan * fix: audience in tests * fix: audience in tests * fix: audience in tests * fix: cached file broken null ttl * fix: don't write JWKS on error * feat: phpdoc for ttl parameter * feat: add default scopes and be sure we set redirect_uri everytime * feat: add default scopes and be sure we set redirect_uri everytime * fix: safety check * fix: safety check * fix: safety check * fix: enforce redirect_uri usage * fix: enforce redirect_uri usage * fix: default scopes for authorization uri only * fix: tests * feat: preserve service with deprecation notice * feat: preserve service with deprecation notice * feat: preserve service with deprecation notice * feat: preserve service with deprecation notice * fix: exception namespace * fix: dropped class * chore: rollback Installer * feat: client config classes * fix: header-stamps * fix: header-stamps * refactor: Dto class * refactor: Dto class * refactor: Dto class * refactor: Dto class * refactor: reorganize code * refactor: tests src directory * refactor: tests src directory * refactor: tests src directory * fix: restore tests composer.lock * fix: restore tests composer.lock * fix: restore tests composer.lock * refactor: move ConfigObject specific type * fix: post merge main * fix: post merge main * fix: post merge main * chore: todo * feat: query parameter * fix: post merge * fix: post merge * fix: ci php-cs-fixer to use makefile target * fix: ci php-cs-fixer to use makefile target * fix: makefile & ci * fix: symfony deprecation * fix: php-cs-fixer * refactor: some client refactoring * refactor: some client refactoring * refactor: some client refactoring * test: more tests * test: more tests * test: more tests * feat: implement contact identification * fix: php-cs-fixer * fix: php-cs-fixer * fix: php-cs-fixer * fix: post merge fixes * fix: cleanup tests * fix: cleanup tests * fix: cleanup tests * fix: cleanup tests * fix: cleanup tests * refactor: StatusManager & AccountsService replacement for AccountsClient * fix: create identity * fix: alpha debugging info * feat: more debugging errors * fix: use alternative Authorization header * fix: verify route name * fix: shop access token audience * fix: renaming * fix: disable scope and audience for now * fix: phpstan & php-cs-fixer * chore: activate display hook for alpha debugging * fix: alpha status manager cache * chore: some cleanup * fix: tests * fix: phpstan * chore: StatusManager & VerifyIdentityHandler tests and associated fixes * fix: phpstan & php-cs-fixer * fix: phpstan & php-cs-fixer * chore: remove useless query * fix: typo * fix: delete proof after verification & various renaming * fix: delete proof after verification & various renaming * fix: delete proof after verification & various renaming * fix: delete proof after verification & various renaming * refactor: audience configuration * fix: update bulle configuration * fix: disable php-scoper-update-prefix target * fix: update status based on verification response * fix: update status based on verification response * fix: update status based on verification response * fix: remove duplicate tests * feat: shop status route * feat: tracking v8 (#488) * fix: disable php-scoper-update-prefix target * Update src/Service/Accounts/AccountsService.php Thanks RV Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> * fix: disable php-scoper-update-prefix target * fix: anonymous id cookie on '/' --------- Co-authored-by: hschoenenberger Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> * fix: tests * fix: backend url generation * chore: update fixme * fix: getRedirectUri compat v9 * fix: getRedirectUri compat v9 * fix: getRedirectUri compat v9 * feat: attempt to get live status & fix empty datetime * feat: attempt to get live status & fix empty datetime * feat: invalidate cache (#498) * fix: serialized date format * fix: cached data for individual getters * refactor: more complex proof * fix: exception catching * [ACCOUNT-2832] feat: identify contact part 2 (#495) * feat: add IdentifyContact command and handler for setting point of contact * fix: php-cs-fixer * feat: add unit tests for identifyContactHandler * fix: lint code * fix: lint code * fix: cs-fixer * fix: simplify handle method in IdentifyContactHandler * fix: adapt test for simplified handle method * fix: implement IdentifyContactCommand in OAuth2Controller * fix: add CommandBus property to AdminOAuth2PsAccountsController and OAuth2Controller * fix: update return type of setPointOfContact to void in AccountsService * fix: update handle method to use getStatus instead of getCachedStatus in IdentifyContactHandler * fix: refacto IdentifyContactHandlerTest * fix: refactor php cs fixer / phpstan * feat: initialize command bus in constructor of AdminOAuth2PsAccountsController and OAuth2Controller * feat: invalidate cache after setting point of contact in IdentifyContactHandler * feat: delete unused class IdentifyContact * feat: update cached shop status handling in IdentifyContactHandlerTest * chore: cleanup POC vars * chore: cleanup POC vars * chore: cleanup POC vars * [ACCOUNT-2825] Get context query (#494) * feat: attempt to get live status & fix empty datetime * feat: attempt to get live status & fix empty datetime * feat: get context query ajax endpoint + query * fix: serialize datetime as iso 8601 string * feat: add identify url + ps version from file * feat: add identify url + ps version from file * feat: replace existing GetContext action * Component init (#497) C'est Guillaume-L <77913074+guillaume60240@users.noreply.github.com> qui m'a obligé à merge :gun: * feat: add component_params_init to ps accounts presenter * fix: phpcs * feat: add ini params in PsAccountsService and pass to vue component init * Pass settings as second params of init Co-authored-by: Guillaume-L <77913074+guillaume60240@users.noreply.github.com> * fix: let shopid and groupId as integer --------- Co-authored-by: Guillaume-L <77913074+guillaume60240@users.noreply.github.com> * fix: rename getAccountUrl to getAccountsUiUrl * fix: after review * fix: lint * fix: lint * fix: remove twice window ps accounts context --------- Co-authored-by: hschoenenberger Co-authored-by: Guillaume-L <77913074+guillaume60240@users.noreply.github.com> --------- Co-authored-by: Antoine Metifeu Co-authored-by: Sullivan Co-authored-by: Guillaume-L <77913074+guillaume60240@users.noreply.github.com> --- _dev/apps/configuration/App.vue | 2 +- _dev/apps/configuration/types/window.d.ts | 15 +- config.dist.php | 14 +- .../admin/AdminAjaxPsAccountsController.php | 48 ++++-- .../admin/AdminOAuth2PsAccountsController.php | 40 ++++- ps_accounts.php | 8 + .../Command/IdentifyContactCommand.php | 40 +++++ .../CommandHandler/IdentifyContactHandler.php | 82 ++++++++++ src/Account/Query/GetContextQuery.php | 52 ++++++ .../QueryHandler/GetContextHandler.php | 66 ++++++++ src/AccountLogin/OAuth2LoginTrait.php | 30 +++- src/Controller/Admin/OAuth2Controller.php | 38 ++++- src/Cqrs/QueryBus.php | 37 +++++ src/EventListener/Admin/LogoutSubscriber.php | 0 src/Presenter/PsAccountsPresenter.php | 2 + src/Provider/ShopProvider.php | 66 +++++++- src/Service/Accounts/AccountsService.php | 29 ++++ src/Service/OAuth2/OAuth2Client.php | 10 +- src/Service/PsAccountsService.php | 27 ++++ src/ServiceContainer/PsAccountsContainer.php | 1 + src/ServiceProvider/CommandProvider.php | 9 ++ src/ServiceProvider/DefaultProvider.php | 11 +- src/ServiceProvider/QueryProvider.php | 38 +++++ .../IdentifyContactHandlerTest.php | 153 ++++++++++++++++++ .../QueryHandler/GetContextHandlerTest.php | 69 ++++++++ tests/src/Unit/Cqrs/QueryBusTest.php | 66 ++++++++ 26 files changed, 911 insertions(+), 42 deletions(-) create mode 100644 src/Account/Command/IdentifyContactCommand.php create mode 100644 src/Account/CommandHandler/IdentifyContactHandler.php create mode 100644 src/Account/Query/GetContextQuery.php create mode 100644 src/Account/QueryHandler/GetContextHandler.php create mode 100755 src/Cqrs/QueryBus.php create mode 100644 src/EventListener/Admin/LogoutSubscriber.php create mode 100644 src/ServiceProvider/QueryProvider.php create mode 100644 tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php create mode 100644 tests/src/Unit/Account/QueryHandler/GetContextHandlerTest.php create mode 100644 tests/src/Unit/Cqrs/QueryBusTest.php diff --git a/_dev/apps/configuration/App.vue b/_dev/apps/configuration/App.vue index 85e7f0425..e6c62cf03 100644 --- a/_dev/apps/configuration/App.vue +++ b/_dev/apps/configuration/App.vue @@ -37,7 +37,7 @@ import { init } from "prestashop_accounts_vue_components" onMounted(async () => { if (window?.psaccountsVue) { - return window?.psaccountsVue?.init(); + return window?.psaccountsVue?.init(window.contextPsAccounts.component_params_init, "Settings"); } init(); }); diff --git a/_dev/apps/configuration/types/window.d.ts b/_dev/apps/configuration/types/window.d.ts index 5014dc859..ecfcd8703 100644 --- a/_dev/apps/configuration/types/window.d.ts +++ b/_dev/apps/configuration/types/window.d.ts @@ -1,5 +1,18 @@ +type ContextParamsInit = { + mode: 1 | 2 | 4; + shopId: number; + groupId: number; + getContextUrl: string; + manageAccountUrl: string; + psxName: string; +} + interface Window { psaccountsVue: any; signInComponent: any; storePsAccounts: any; -} \ No newline at end of file + contextPsAccounts: { + [key: string]: any; + component_params_init: ContextParamsInit; + }; +} diff --git a/config.dist.php b/config.dist.php index 1be28c78d..0bae66c9a 100644 --- a/config.dist.php +++ b/config.dist.php @@ -21,20 +21,20 @@ return [ 'ps_accounts.accounts_api_url' => 'https://accounts-api.prestashop.localhost/', 'ps_accounts.accounts_ui_url' => 'https://accounts.prestashop.localhost', - 'ps_accounts.sso_api_url' => 'https://auth-preprod.prestashop.com/api/v1/', - 'ps_accounts.sso_account_url' => 'https://authv2-preprod.prestashop.com/login', - 'ps_accounts.sso_resend_verification_email_url' => 'https://auth-preprod.prestashop.com/account/send-verification-email', - 'ps_accounts.billing_api_url' => 'https://billing-api.psessentials-integration.net', - 'ps_accounts.sentry_credentials' => 'https://4c7f6c8dd5aa405b8401a35f5cf26ada@o298402.ingest.sentry.io/5354585', + /* deprecated */ 'ps_accounts.sso_api_url' => 'https://auth-preprod.prestashop.com/api/v1/', + /* deprecated */ 'ps_accounts.sso_account_url' => 'https://authv2-preprod.prestashop.com/login', + /* deprecated */ 'ps_accounts.sso_resend_verification_email_url' => 'https://auth-preprod.prestashop.com/account/send-verification-email', + /* deprecated */ 'ps_accounts.billing_api_url' => 'https://billing-api.psessentials-integration.net', + /* deprecated */ 'ps_accounts.sentry_credentials' => 'https://4c7f6c8dd5aa405b8401a35f5cf26ada@o298402.ingest.sentry.io/5354585', 'ps_accounts.segment_write_key' => 'UITzSdsFTgYsXaiJG09hsCiupUPwgJQB', 'ps_accounts.check_api_ssl_cert' => false, 'ps_accounts.verify_account_tokens' => false, - 'ps_accounts.accounts_vue_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@3/dist/psaccountsVue.umd.min.js', + /* deprecated */ 'ps_accounts.accounts_vue_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@3/dist/psaccountsVue.umd.min.js', 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5', 'ps_accounts.environment' => 'development', // a page to display "Update Your Module" message - 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.psessentials-integration.net', + /* deprecated */ 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.psessentials-integration.net', // OAuth2 configuration url 'ps_accounts.oauth2_url' => 'https://oauth.prestashop.localhost', diff --git a/controllers/admin/AdminAjaxPsAccountsController.php b/controllers/admin/AdminAjaxPsAccountsController.php index bf290b9a9..f3225c0cc 100644 --- a/controllers/admin/AdminAjaxPsAccountsController.php +++ b/controllers/admin/AdminAjaxPsAccountsController.php @@ -20,13 +20,15 @@ require_once __DIR__ . '/../../src/Polyfill/Traits/Controller/AjaxRender.php'; use PrestaShop\Module\PsAccounts\Account\Command\DeleteUserShopCommand; +use PrestaShop\Module\PsAccounts\Account\Query\GetContextQuery; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\AccountLogin\OAuth2Session; use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; +use PrestaShop\Module\PsAccounts\Cqrs\QueryBus; use PrestaShop\Module\PsAccounts\Hook\ActionShopAccountUnlinkAfter; +use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Polyfill\Traits\Controller\AjaxRender; -use PrestaShop\Module\PsAccounts\Presenter\PsAccountsPresenter; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; use PrestaShop\Module\PsAccounts\Service\SentryService; @@ -47,6 +49,11 @@ class AdminAjaxPsAccountsController extends \ModuleAdminController */ private $commandBus; + /** + * @var QueryBus + */ + private $queryBus; + /** * AdminAjaxPsAccountsController constructor. * @@ -57,6 +64,7 @@ public function __construct() parent::__construct(); $this->commandBus = $this->module->getService(CommandBus::class); + $this->queryBus = $this->module->getService(QueryBus::class); $this->ajax = true; $this->content_only = true; @@ -146,17 +154,19 @@ public function ajaxProcessResetLinkAccount() * * @throws Exception */ - public function ajaxProcessGetContext() + public function ajaxProcessGetOrRefreshAccessToken() { try { - $psxName = Tools::getValue('psx_name'); - - /** @var PsAccountsPresenter $presenter */ - $presenter = $this->module->getService(PsAccountsPresenter::class); + /** @var OAuth2Session $oauth2Session */ + $oauth2Session = $this->module->getService(OAuth2Session::class); header('Content-Type: text/json'); - $this->ajaxRender((string) json_encode($presenter->present($psxName))); + $this->ajaxRender( + (string) json_encode([ + 'token' => (string) $oauth2Session->getOrRefreshAccessToken(), + ]) + ); } catch (Exception $e) { SentryService::captureAndRethrow($e); } @@ -167,21 +177,31 @@ public function ajaxProcessGetContext() * * @throws Exception */ - public function ajaxProcessGetOrRefreshAccessToken() + public function ajaxProcessGetContext() { + header('Content-Type: text/json'); + try { - /** @var OAuth2Session $oauth2Session */ - $oauth2Session = $this->module->getService(OAuth2Session::class); + $command = new GetContextQuery( + Tools::getValue('group_id', null), + Tools::getValue('shop_id', null), + filter_var(Tools::getValue('refresh', false), FILTER_VALIDATE_BOOLEAN) + ); - header('Content-Type: text/json'); + $this->ajaxRender( + (string) json_encode($this->queryBus->handle($command)) + ); + } catch (Exception $e) { + Logger::getInstance()->error($e->getMessage()); + + http_response_code(500); $this->ajaxRender( (string) json_encode([ - 'token' => (string) $oauth2Session->getOrRefreshAccessToken(), + 'error' => true, + 'message' => $e->getMessage(), ]) ); - } catch (Exception $e) { - SentryService::captureAndRethrow($e); } } } diff --git a/controllers/admin/AdminOAuth2PsAccountsController.php b/controllers/admin/AdminOAuth2PsAccountsController.php index 815e92fb4..b3e27d6c0 100644 --- a/controllers/admin/AdminOAuth2PsAccountsController.php +++ b/controllers/admin/AdminOAuth2PsAccountsController.php @@ -18,16 +18,18 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ +use PrestaShop\Module\PsAccounts\Account\Command\IdentifyContactCommand; use PrestaShop\Module\PsAccounts\AccountLogin\Exception\AccountLoginException; use PrestaShop\Module\PsAccounts\AccountLogin\Exception\EmailNotVerifiedException; use PrestaShop\Module\PsAccounts\AccountLogin\Exception\EmployeeNotFoundException; use PrestaShop\Module\PsAccounts\AccountLogin\OAuth2LoginTrait; use PrestaShop\Module\PsAccounts\AccountLogin\OAuth2Session; +use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Polyfill\Traits\AdminController\IsAnonymousAllowed; use PrestaShop\Module\PsAccounts\Service\AnalyticsService; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; -use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\UserInfo; +use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\AccessToken; use PrestaShop\Module\PsAccounts\Service\PsAccountsService; use Symfony\Component\HttpFoundation\Session\SessionInterface; @@ -51,6 +53,11 @@ class AdminOAuth2PsAccountsController extends \ModuleAdminController */ private $psAccountsService; + /** + * @var CommandBus + */ + private $commandBus; + /** * @throws PrestaShopException * @throws Exception @@ -61,6 +68,7 @@ public function __construct() $this->analyticsService = $this->module->getService(AnalyticsService::class); $this->psAccountsService = $this->module->getService(PsAccountsService::class); + $this->commandBus = $this->module->getService(CommandBus::class); $this->ajax = true; $this->content_only = true; @@ -105,16 +113,30 @@ public function init() } /** - * @param UserInfo $user + * @param AccessToken $accessToken * * @return bool * * @throws EmailNotVerifiedException * @throws EmployeeNotFoundException - * @throws Exception */ - protected function initUserSession(UserInfo $user) + protected function initUserSession(AccessToken $accessToken) { + $user = $this->getOAuth2Service()->getUserInfo($accessToken->access_token); + + Logger::getInstance()->info( + '[OAuth2] ' . (string) print_r($user, true) + ); + + if ($this->getOAuthAction() === 'identifyPointOfContact') { + $this->commandBus->handle(new IdentifyContactCommand($accessToken)); + + return true; + } + + $this->getOauth2Session()->setTokenProvider($accessToken); + //$user = $oauth2Session->getUserInfo(); + Logger::getInstance()->info( '[OAuth2] ' . (string) print_r($user, true) ); @@ -179,6 +201,16 @@ protected function getOAuth2Service() */ protected function redirectAfterLogin() { + if ($this->getOAuthAction() === 'identifyPointOfContact') { + // Refresh configuration page + echo << +window.opener.location.reload(); +window.close(); + +HTML; + exit; + } $returnTo = $this->getSessionReturnTo() ?: 'AdminDashboard'; if (preg_match('/^([A-Z][a-z0-9]+)+$/', $returnTo)) { $returnTo = $this->context->link->getAdminLink($returnTo); diff --git a/ps_accounts.php b/ps_accounts.php index bf962abfa..a29c9f66e 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -370,6 +370,14 @@ public function getContent() return $this->display(__FILE__, 'views/templates/admin/app.tpl'); } + /** + * @return string + */ + public function getAccountsUiUrl() + { + return $this->getParameter('ps_accounts.accounts_ui_url'); + } + /** * @return string */ diff --git a/src/Account/Command/IdentifyContactCommand.php b/src/Account/Command/IdentifyContactCommand.php new file mode 100644 index 000000000..a1d8d3bb3 --- /dev/null +++ b/src/Account/Command/IdentifyContactCommand.php @@ -0,0 +1,40 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\Command; + +use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\AccessToken; + +class IdentifyContactCommand +{ + /** + * @var AccessToken + */ + public $accessToken; + + /** + * @param AccessToken $accessToken + */ + public function __construct($accessToken) + { + $this->accessToken = $accessToken; + } +} diff --git a/src/Account/CommandHandler/IdentifyContactHandler.php b/src/Account/CommandHandler/IdentifyContactHandler.php new file mode 100644 index 000000000..9b79e1328 --- /dev/null +++ b/src/Account/CommandHandler/IdentifyContactHandler.php @@ -0,0 +1,82 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; + +use PrestaShop\Module\PsAccounts\Account\Command\IdentifyContactCommand; +use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; +use PrestaShop\Module\PsAccounts\Account\StatusManager; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; + +class IdentifyContactHandler +{ + /** + * @var AccountsService + */ + private $accountsService; + + /** + * @var StatusManager + */ + private $statusManager; + + /** + * @var ShopSession + */ + private $shopSession; + + /** + * @param AccountsService $accountsService + * @param StatusManager $statusManager + * @param ShopSession $shopSession + */ + public function __construct( + AccountsService $accountsService, + StatusManager $statusManager, + ShopSession $shopSession + ) { + $this->accountsService = $accountsService; + $this->statusManager = $statusManager; + $this->shopSession = $shopSession; + } + + /** + * @param IdentifyContactCommand $command + * + * @return void + * + * @throws AccountsException + */ + public function handle(IdentifyContactCommand $command) + { + $status = $this->statusManager->getStatus(); + if (!$status->isVerified) { + return; + } + + $this->accountsService->setPointOfContact( + $this->statusManager->getCloudShopId(), + $this->shopSession->getValidToken(), + $command->accessToken->access_token + ); + $this->statusManager->invalidateCache(); + } +} diff --git a/src/Account/Query/GetContextQuery.php b/src/Account/Query/GetContextQuery.php new file mode 100644 index 000000000..ba04b72bb --- /dev/null +++ b/src/Account/Query/GetContextQuery.php @@ -0,0 +1,52 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\Query; + +class GetContextQuery +{ + /** + * @var string|null + */ + public $groupId; + + /** + * @var string|null + */ + public $shopId; + + /** + * @var bool + */ + public $refresh; + + /** + * @param string|null $groupId + * @param string|null $shopId + * @param bool $refresh + */ + public function __construct($groupId = null, $shopId = null, $refresh = false) + { + $this->groupId = $groupId; + $this->shopId = $shopId; + $this->refresh = $refresh; + } +} diff --git a/src/Account/QueryHandler/GetContextHandler.php b/src/Account/QueryHandler/GetContextHandler.php new file mode 100644 index 000000000..0c9c77cdb --- /dev/null +++ b/src/Account/QueryHandler/GetContextHandler.php @@ -0,0 +1,66 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\QueryHandler; + +use PrestaShop\Module\PsAccounts\Account\Query\GetContextQuery; +use PrestaShop\Module\PsAccounts\Provider\ShopProvider; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; + +class GetContextHandler +{ + /** + * @var ShopProvider + */ + private $shopProvider; + + /** + * @param ShopProvider $shopProvider + */ + public function __construct( + ShopProvider $shopProvider + ) { + $this->shopProvider = $shopProvider; + } + + /** + * @param GetContextQuery $query + * + * @return array + * + * @throws AccountsException + */ + public function handle(GetContextQuery $query) + { + $psAccountsVersion = \Db::getInstance()->getValue('SELECT version FROM ' . _DB_PREFIX_ . 'module WHERE name = "ps_accounts"'); + + return [ + 'ps_accounts' => [ + 'last_succeeded_upgrade_version' => $psAccountsVersion, + 'module_version_from_files' => \Ps_accounts::VERSION, + ], + 'groups' => $this->shopProvider->getShops( + $query->groupId, + $query->shopId, + $query->refresh + ), + ]; + } +} diff --git a/src/AccountLogin/OAuth2LoginTrait.php b/src/AccountLogin/OAuth2LoginTrait.php index 5f2400b69..30a7a0551 100644 --- a/src/AccountLogin/OAuth2LoginTrait.php +++ b/src/AccountLogin/OAuth2LoginTrait.php @@ -31,6 +31,7 @@ use PrestaShop\Module\PsAccounts\Service\AnalyticsService; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Exception; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; +use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\AccessToken; use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\UserInfo; use PrestaShop\Module\PsAccounts\Service\PsAccountsService; use Symfony\Component\HttpFoundation\Session\SessionInterface; @@ -44,11 +45,11 @@ trait OAuth2LoginTrait abstract protected function getOAuth2Service(); /** - * @param UserInfo $user + * @param AccessToken $accessToken * * @return bool */ - abstract protected function initUserSession(UserInfo $user); + abstract protected function initUserSession(AccessToken $accessToken); /** * @return mixed @@ -104,6 +105,7 @@ public function oauth2Login() $error = Tools::getValue('error', ''); $state = Tools::getValue('state', ''); $code = Tools::getValue('code', ''); + $action = Tools::getValue('action', 'login'); if (!empty($error)) { // Got an error, probably user denied access @@ -113,6 +115,8 @@ public function oauth2Login() // cleanup existing accessToken $oauth2Session->clear(); + $this->setOAuthAction($action); + $this->setSessionReturnTo(Tools::getValue($this->getReturnToParam())); $this->oauth2Redirect(Tools::getValue('locale', 'en')); @@ -134,9 +138,7 @@ public function oauth2Login() throw new Oauth2LoginException($e->getMessage(), null, $e); } - $oauth2Session->setTokenProvider($accessToken); - - if ($this->initUserSession($oauth2Session->getUserInfo())) { + if ($this->initUserSession($accessToken)) { return $this->redirectAfterLogin(); } } @@ -227,6 +229,24 @@ private function getReturnToParam() return 'return_to'; } + /** + * @return string + */ + private function getOAuthAction() + { + return $this->getSession()->get('oauth2action'); + } + + /** + * @param string $action + * + * @return void + */ + private function setOAuthAction($action) + { + $this->getSession()->set('oauth2action', $action); + } + /** * @param string $uid * @param string $email diff --git a/src/Controller/Admin/OAuth2Controller.php b/src/Controller/Admin/OAuth2Controller.php index dcceaca16..245fbf292 100644 --- a/src/Controller/Admin/OAuth2Controller.php +++ b/src/Controller/Admin/OAuth2Controller.php @@ -22,6 +22,7 @@ //use PrestaShopBundle\Controller\Admin\PrestaShopAdminController; use Doctrine\ORM\EntityManagerInterface; +use PrestaShop\Module\PsAccounts\Account\Command\IdentifyContactCommand; use PrestaShop\Module\PsAccounts\AccountLogin\Exception\AccountLoginException; use PrestaShop\Module\PsAccounts\AccountLogin\Exception\EmailNotVerifiedException; use PrestaShop\Module\PsAccounts\AccountLogin\Exception\EmployeeNotFoundException; @@ -29,10 +30,11 @@ use PrestaShop\Module\PsAccounts\AccountLogin\OAuth2Session; use PrestaShop\Module\PsAccounts\Adapter\Link; use PrestaShop\Module\PsAccounts\Api\Client\ExternalAssetsClient; +use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Service\AnalyticsService; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; -use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\UserInfo; +use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\AccessToken; use PrestaShop\Module\PsAccounts\Service\PsAccountsService; use PrestaShopBundle\Controller\Admin\FrameworkBundleAdminController; use PrestaShopBundle\Entity\Employee\Employee as EmployeeEntity; @@ -92,6 +94,11 @@ class OAuth2Controller extends FrameworkBundleAdminController */ private $redirectResponse; + /** + * @var CommandBus + */ + private $commandBus; + public function __construct() { /** @var Ps_accounts $module */ @@ -101,6 +108,7 @@ public function __construct() $this->analyticsService = $this->module->getService(AnalyticsService::class); $this->psAccountsService = $this->module->getService(PsAccountsService::class); $this->externalAssetsClient = $this->module->getService(ExternalAssetsClient::class); + $this->commandBus = $this->module->getService(CommandBus::class); } /** @@ -174,19 +182,30 @@ protected function getOAuth2Service() } /** - * @param UserInfo $user + * @param AccessToken $accessToken * * @return bool * * @throws EmailNotVerifiedException * @throws EmployeeNotFoundException */ - protected function initUserSession(UserInfo $user) + protected function initUserSession(AccessToken $accessToken) { + $user = $this->getOAuth2Service()->getUserInfo($accessToken->access_token); + Logger::getInstance()->info( '[OAuth2] ' . (string) json_encode($user->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) ); + if ($this->getOAuthAction() === 'identifyPointOfContact') { + $this->commandBus->handle(new IdentifyContactCommand($accessToken)); + + return true; + } + + $this->getOauth2Session()->setTokenProvider($accessToken); + //$user = $oauth2Session->getUserInfo(); + //$context = $this->context; /** @var \Context $context */ $context = $this->module->getService('ps_accounts.context'); @@ -224,11 +243,20 @@ protected function initUserSession(UserInfo $user) } /** - * @return RedirectResponse + * @return Response */ protected function redirectAfterLogin() { - // FIXME: requires some testing + if ($this->getOAuthAction() === 'identifyPointOfContact') { + return (new Response())->setContent(<< +window.opener.location.reload(); +window.close(); + +HTML + ); + } + return $this->redirectResponse; } diff --git a/src/Cqrs/QueryBus.php b/src/Cqrs/QueryBus.php new file mode 100755 index 000000000..99e3c2284 --- /dev/null +++ b/src/Cqrs/QueryBus.php @@ -0,0 +1,37 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Cqrs; + +class QueryBus extends Bus +{ + /** + * @param string $className + * + * @return string + */ + public function resolveHandlerClass($className) + { + return preg_replace( + '/((Query)(\\\\([^\\\\]*?)(Query)?$))/', + '${2}Handler\\\\${4}Handler', + $className, 1); + } +} diff --git a/src/EventListener/Admin/LogoutSubscriber.php b/src/EventListener/Admin/LogoutSubscriber.php new file mode 100644 index 000000000..e69de29bb diff --git a/src/Presenter/PsAccountsPresenter.php b/src/Presenter/PsAccountsPresenter.php index 201392113..b666a9afe 100644 --- a/src/Presenter/PsAccountsPresenter.php +++ b/src/Presenter/PsAccountsPresenter.php @@ -163,6 +163,8 @@ public function present($psxName = 'ps_accounts') 'adminAjaxLink' => $this->psAccountsService->getAdminAjaxUrl(), 'accountsUiUrl' => $this->module->getParameter('ps_accounts.accounts_ui_url'), + + 'component_params_init' => $this->psAccountsService->getComponentInitParams($psxName), ], (new DependenciesPresenter())->present($psxName) ); diff --git a/src/Provider/ShopProvider.php b/src/Provider/ShopProvider.php index e8d2369b9..9191e6080 100644 --- a/src/Provider/ShopProvider.php +++ b/src/Provider/ShopProvider.php @@ -26,6 +26,7 @@ use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Adapter\Link; use PrestaShop\Module\PsAccounts\Context\ShopContext; +use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; class ShopProvider { @@ -39,18 +40,33 @@ class ShopProvider */ private $link; + /** + * @var StatusManager + */ + private $shopStatus; + + /** + * @var OAuth2Service + */ + private $oAuth2Service; + /** * ShopProvider constructor. * * @param ShopContext $shopContext * @param Link $link + * @param StatusManager $shopStatus */ public function __construct( ShopContext $shopContext, - Link $link + Link $link, + StatusManager $shopStatus, + OAuth2Service $oAuth2Service ) { $this->shopContext = $shopContext; $this->link = $link; + $this->shopStatus = $shopStatus; + $this->oAuth2Service = $oAuth2Service; } /** @@ -296,4 +312,52 @@ public function getUrl($shopId) return new ShopUrl($backOfficeUrl, $frontendUrl, $shopId); } + + /** + * @param string|null $groupId + * @param string|null $shopId + * @param bool $refresh + * + * @return array + */ + public function getShops($groupId = null, $shopId = null, $refresh = false) + { + $shopList = []; + + foreach (\Shop::getTree() as $groupData) { + if ($groupId !== null && $groupId !== $groupData['id']) { + continue; + } + + $shops = []; + foreach ($groupData['shops'] as $shopData) { + if ($shopId !== null && $shopId !== $shopData['id_shop']) { + continue; + } + + $shopUrl = $this->getUrl((int) $shopData['id_shop']); + $shopStatus = $this->shopStatus->getStatus($refresh); + $identifyUrl = $this->oAuth2Service->getOAuth2Client()->getRedirectUri([ + 'action' => 'identifyPointOfContact', + ]); + + $shops[] = [ + 'id' => (int) $shopData['id_shop'], + 'name' => $shopData['name'], + 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), + 'frontendUrl' => $shopUrl->getFrontendUrl(), + 'identifyUrl' => $identifyUrl, + 'shopStatus' => $shopStatus, + ]; + } + + $shopList[] = [ + 'id' => (int) $groupData['id'], + 'name' => $groupData['name'], + 'shops' => $shops, + ]; + } + + return $shopList; + } } diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index 766fc6528..0c7f9d183 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -329,6 +329,35 @@ public function shopStatus($cloudShopId, $shopToken) return new ShopStatus($response->body); } + /** + * @param string $cloudShopId + * @param string $shopToken + * @param string $userToken + * + * @return void + * + * @throws AccountsException + */ + public function setPointOfContact($cloudShopId, $shopToken, $userToken) + { + $response = $this->getClient()->post( + '/v1/shop-identities/' . $cloudShopId . '/point-of-contact', + [ + Request::HEADERS => $this->getHeaders([ + 'Authorization' => 'Bearer ' . $shopToken, + 'X-Shop-Id' => $cloudShopId, + ]), + Request::JSON => [ + 'pointOfContactJWT' => $userToken, + ], + ] + ); + + if (!$response->isSuccessful) { + throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to set point of contact')); + } + } + /** * @param Response $response * @param string $defaultMessage diff --git a/src/Service/OAuth2/OAuth2Client.php b/src/Service/OAuth2/OAuth2Client.php index fd2a47253..faf93e13c 100644 --- a/src/Service/OAuth2/OAuth2Client.php +++ b/src/Service/OAuth2/OAuth2Client.php @@ -121,16 +121,20 @@ public function setClientSecret($clientSecret) * @example http://my-shop.mydomain/admin-path/index.php?controller=AdminOAuth2PsAccounts * @example http://my-shop.mydomain/admin-path/modules/ps_accounts/oauth2 * + * @param array $query + * * @return string */ - public function getRedirectUri() + public function getRedirectUri($query = []) { if (defined('_PS_VERSION_') && version_compare(_PS_VERSION_, '9', '>=')) { - return $this->link->getAdminLink('SfAdminOAuth2PsAccounts', false); + return $this->link->getAdminLink('SfAdminOAuth2PsAccounts', false, array_merge([ + 'route' => 'ps_accounts_oauth2', + ], $query), $query, true); } - return $this->link->getAdminLink('AdminOAuth2PsAccounts', false, [], [], true); + return $this->link->getAdminLink('AdminOAuth2PsAccounts', false, [], $query, true); } /** diff --git a/src/Service/PsAccountsService.php b/src/Service/PsAccountsService.php index 9e191af59..99a2c43b7 100644 --- a/src/Service/PsAccountsService.php +++ b/src/Service/PsAccountsService.php @@ -241,6 +241,16 @@ public function getAdminAjaxUrl() return $this->link->getAdminLink('AdminAjaxPsAccounts', true, [], ['ajax' => 1]); } + /** + * @return string + * + * @throws \PrestaShopException + */ + public function getContextUrl() + { + return $this->link->getAdminLink('AdminAjaxPsAccounts', true, [], ['ajax' => 1, 'action' => 'getContext']); + } + /** * @return string * @@ -305,4 +315,21 @@ public function getEmployeeAccount() return null; } } + + /** + * @param string $psxName + * + * @return array + */ + public function getComponentInitParams($psxName = 'ps_accounts') + { + return [ + 'mode' => \Shop::getContext(), + 'shopId' => \Shop::getContextShopID(), + 'groupId' => \Shop::getContextShopGroupID(), + 'getContextUrl' => $this->getContextUrl(), + 'manageAccountUrl' => $this->module->getAccountsUiUrl(), + 'psxName' => $psxName, + ]; + } } diff --git a/src/ServiceContainer/PsAccountsContainer.php b/src/ServiceContainer/PsAccountsContainer.php index 1da345888..293adcb2d 100644 --- a/src/ServiceContainer/PsAccountsContainer.php +++ b/src/ServiceContainer/PsAccountsContainer.php @@ -40,6 +40,7 @@ class PsAccountsContainer extends ServiceContainer ServiceProvider\CommandProvider::class, ServiceProvider\DefaultProvider::class, ServiceProvider\OAuth2Provider::class, + ServiceProvider\QueryProvider::class, ServiceProvider\RepositoryProvider::class, ServiceProvider\SessionProvider::class, ]; diff --git a/src/ServiceProvider/CommandProvider.php b/src/ServiceProvider/CommandProvider.php index aa21e9391..b3c811a81 100644 --- a/src/ServiceProvider/CommandProvider.php +++ b/src/ServiceProvider/CommandProvider.php @@ -23,6 +23,7 @@ use PrestaShop\Module\PsAccounts\Account\CommandHandler\CreateIdentitiesHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\CreateIdentityHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\DeleteUserShopHandler; +use PrestaShop\Module\PsAccounts\Account\CommandHandler\IdentifyContactHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\UpdateUserShopHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\VerifyIdentitiesHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\VerifyIdentityHandler; @@ -88,5 +89,13 @@ public function provide(ServiceContainer $container) $container->get(CommandBus::class) ); }); + + $container->registerProvider(IdentifyContactHandler::class, static function () use ($container) { + return new IdentifyContactHandler( + $container->get(AccountsService::class), + $container->get(StatusManager::class), + $container->get(Session\ShopSession::class) + ); + }); } } diff --git a/src/ServiceProvider/DefaultProvider.php b/src/ServiceProvider/DefaultProvider.php index 52e7c19aa..c12e56a58 100644 --- a/src/ServiceProvider/DefaultProvider.php +++ b/src/ServiceProvider/DefaultProvider.php @@ -29,6 +29,7 @@ use PrestaShop\Module\PsAccounts\Api\Client\ServicesBillingClient; use PrestaShop\Module\PsAccounts\Context\ShopContext; use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; +use PrestaShop\Module\PsAccounts\Cqrs\QueryBus; use PrestaShop\Module\PsAccounts\Http\Client\CircuitBreaker; use PrestaShop\Module\PsAccounts\Installer\Installer; use PrestaShop\Module\PsAccounts\Presenter\PsAccountsPresenter; @@ -37,6 +38,7 @@ use PrestaShop\Module\PsAccounts\Repository\ShopTokenRepository; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; use PrestaShop\Module\PsAccounts\Service\AnalyticsService; +use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; use PrestaShop\Module\PsAccounts\Service\PsAccountsService; use PrestaShop\Module\PsAccounts\Service\PsBillingService; use PrestaShop\Module\PsAccounts\Service\SentryService; @@ -115,7 +117,9 @@ public function provide(ServiceContainer $container) $container->registerProvider(Provider\ShopProvider::class, static function () use ($container) { return new Provider\ShopProvider( $container->get(ShopContext::class), - $container->get(Link::class) + $container->get(Link::class), + $container->get(StatusManager::class), + $container->get(OAuth2Service::class) ); }); // Context @@ -131,6 +135,11 @@ public function provide(ServiceContainer $container) $container->get('ps_accounts.module') ); }); + $container->registerProvider(QueryBus::class, static function () use ($container) { + return new QueryBus( + $container->get('ps_accounts.module') + ); + }); // Factories $container->registerProvider(CircuitBreaker\Factory::class, static function () use ($container) { return new CircuitBreaker\Factory( diff --git a/src/ServiceProvider/QueryProvider.php b/src/ServiceProvider/QueryProvider.php new file mode 100644 index 000000000..89546df2f --- /dev/null +++ b/src/ServiceProvider/QueryProvider.php @@ -0,0 +1,38 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\ServiceProvider; + +use PrestaShop\Module\PsAccounts\Account\QueryHandler\GetContextHandler; +use PrestaShop\Module\PsAccounts\Provider\ShopProvider; +use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\Contract\IServiceProvider; +use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\ServiceContainer; + +class QueryProvider implements IServiceProvider +{ + public function provide(ServiceContainer $container) + { + $container->registerProvider(GetContextHandler::class, static function () use ($container) { + return new GetContextHandler( + $container->get(ShopProvider::class) + ); + }); + } +} diff --git a/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php b/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php new file mode 100644 index 000000000..17ae10782 --- /dev/null +++ b/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php @@ -0,0 +1,153 @@ +client = $this->createMock(Client::class); + $this->shopSession = $this->createMock(ShopSession::class); + $this->accountsService = $this->createMock(AccountsService::class); + $this->accountsService->setClient($this->client); + } + + /** + * @test + */ + public function itShouldSaveIdentityContact() + { + $pointOfContactEmail = $this->faker->email; + $pointOfContactUuid = $this->faker->uuid; + $cloudShopId = $this->faker->uuid; + + $this->statusManager->setCloudShopId($cloudShopId); + + $this->shopSession->method('getValidToken')->willReturn("valid_token"); + + $this->client->method('post') + ->willReturnCallback(function ($route) use ($pointOfContactEmail, $pointOfContactUuid, $cloudShopId) { + if (preg_match('/v1\/shop-identities\/' . $cloudShopId . '\/point-of-contact$/', $route)) { + return $this->createResponse([ + 'success' => true, + ], 200, true); + } + return $this->createResponse([], 500, true); + }); + + // Expected call to setPointOfContact with correct parameters + $this->accountsService->expects($this->once()) + ->method('setPointOfContact') + ->with( + $this->equalTo($cloudShopId), + $this->equalTo("valid_token"), + $this->equalTo("valid_access_token") + ); + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $cloudShopId, + 'isVerified' => true, + ]) + ]))->toArray())); + + $this->getHandler()->handle(new IdentifyContactCommand(new AccessToken( + [ + 'access_token' => 'valid_access_token', + ] + ))); + } + + /** + * @test + */ + public function itShouldNotSaveIdentityContactOnShopNotVerified() + { + $cloudShopId = $this->faker->uuid; + + $this->statusManager->setCloudShopId($cloudShopId); + + $this->shopSession->method('getValidToken')->willReturn("valid_token"); + + // Expected call to setPointOfContact with correct parameters + $this->accountsService->expects($this->exactly(0)) + ->method('setPointOfContact') + ->with( + $this->equalTo($cloudShopId), + $this->equalTo("valid_token"), + $this->equalTo("valid_access_token") + ); + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $cloudShopId, + 'isVerified' => false, + ]) + ]))->toArray())); + + $this->getHandler()->handle(new IdentifyContactCommand(new AccessToken( + [ + 'access_token' => 'valid_access_token', + ] + ))); + } + + /** + * @return IdentifyContactHandler + */ + private function getHandler() + { + return new IdentifyContactHandler( + $this->accountsService, + $this->statusManager, + $this->shopSession + ); + } +} diff --git a/tests/src/Unit/Account/QueryHandler/GetContextHandlerTest.php b/tests/src/Unit/Account/QueryHandler/GetContextHandlerTest.php new file mode 100644 index 000000000..b8d93ac81 --- /dev/null +++ b/tests/src/Unit/Account/QueryHandler/GetContextHandlerTest.php @@ -0,0 +1,69 @@ + 1, 'name' => 'Shop 1'], + ['id_shop' => 2, 'name' => 'Shop 2'], + ]; + + $this->shopProvider = $this->createMock(ShopProvider::class); + $this->shopProvider->method('getShops')->willReturn($expectedShops); + + $result = $this->getHandler()->handle(new GetContextQuery()); + + $this->assertArrayHasKey('ps_accounts', $result); + $this->assertArrayHasKey('groups', $result); + + $this->assertSame([ + 'last_succeeded_upgrade_version' => '8.0.0', + 'module_version_from_files' => '8.0.0', + ], $result['ps_accounts']); + + $this->assertSame($expectedShops, $result['groups']); + } + + /** + * @test + */ + public function itShouldThrowsUnknownStatusException() + { + $this->shopProvider = $this->createMock(ShopProvider::class); + $this->shopProvider->method('getShops')->will($this->throwException( + new \PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException('Unknown status') + )); + + $this->expectException(\PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException::class); + + $this->getHandler()->handle(new GetContextQuery()); + } + + + /** + * @return GetContextHandler + */ + private function getHandler() + { + return new GetContextHandler( + $this->shopProvider + ); + } +} diff --git a/tests/src/Unit/Cqrs/QueryBusTest.php b/tests/src/Unit/Cqrs/QueryBusTest.php new file mode 100644 index 000000000..a0c0b4347 --- /dev/null +++ b/tests/src/Unit/Cqrs/QueryBusTest.php @@ -0,0 +1,66 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Tests\Unit\Cqrs; + +use PrestaShop\Module\PsAccounts\Account\Query\GetContextQuery; +use PrestaShop\Module\PsAccounts\Account\QueryHandler\GetContextHandler; +use PrestaShop\Module\PsAccounts\Cqrs\QueryBus; +use PrestaShop\Module\PsAccounts\Tests\TestCase; + +class QueryBusTest extends TestCase +{ + /** + * @inject + * + * @var QueryBus + */ + public $queryBus; + + /** + * @test + * + * @throws \Exception + * + * @return void + */ + public function itShouldResolveHandler() + { + $query = 'PrestaShop\Module\PsAccounts\Domain\Account\Query\ListShopQuery'; + $handler = 'PrestaShop\Module\PsAccounts\Domain\Account\QueryHandler\ListShopHandler'; + + $this->assertEquals($handler, $this->queryBus->resolveHandlerClass($query)); + } + + /** + * @test + * + * @throws \Exception + * + * @return void + */ + public function itShouldResolveExistingHandler() + { + $query = GetContextQuery::class; + $handler = GetContextHandler::class; + + $this->assertEquals($handler, $this->queryBus->resolveHandlerClass($query)); + } +} From b78e25fa2bb0c11a1226395573cc3e623c58db3d Mon Sep 17 00:00:00 2001 From: Antoine Date: Thu, 12 Jun 2025 09:21:49 +0200 Subject: [PATCH 06/46] Add store audience (#501) * fix: add store/ scope * fix: token audience & multishop bo uri * fix: tests * fix: phpstan * fix: cleanup --------- Co-authored-by: hschoenenberger --- config.bulle.php | 3 ++- src/Account/Session/ShopSession.php | 18 +++++++++++++++++- src/Polyfill/Traits/Controller/AjaxRender.php | 2 +- src/Provider/ShopProvider.php | 14 +++++++++++--- src/ServiceProvider/DefaultProvider.php | 9 +++++++-- .../Session/ShopSession/RefreshTokenTest.php | 1 + 6 files changed, 39 insertions(+), 8 deletions(-) diff --git a/config.bulle.php b/config.bulle.php index fb8e92023..9806aedf6 100644 --- a/config.bulle.php +++ b/config.bulle.php @@ -29,7 +29,8 @@ 'ps_accounts.sentry_credentials' => 'https://a065bd1f092f8c849e6076fe0640d049@o298402.ingest.us.sentry.io/5354585', 'ps_accounts.segment_write_key' => 'eYODaH20rT1lMRTTUtAa15BKBlV1XUXQ', 'ps_accounts.accounts_vue_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@3/dist/psaccountsVue.umd.min.js', - 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5', + //'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5', + 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/@prestashopcorp/accounts-components@0.0.2-beta.1', 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.psessentials-integration.net', 'ps_accounts.oauth2_url' => 'https://oauth-integration.prestashop.com', 'ps_accounts.token_audience' => 'https://accounts-api.distribution-integration.prestashop.net', diff --git a/src/Account/Session/ShopSession.php b/src/Account/Session/ShopSession.php index ecc9b0122..c3e41e60c 100644 --- a/src/Account/Session/ShopSession.php +++ b/src/Account/Session/ShopSession.php @@ -21,6 +21,7 @@ namespace PrestaShop\Module\PsAccounts\Account\Session; use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Account\Token\Token; use PrestaShop\Module\PsAccounts\Hook\ActionShopAccessTokenRefreshAfter; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; @@ -45,6 +46,11 @@ class ShopSession extends Session implements SessionInterface */ protected $tokenAudience; + /** + * @var StatusManager + */ + protected $statusManager; + /** * @param ConfigurationRepository $configurationRepository * @param OAuth2Service $oAuth2Service @@ -71,7 +77,7 @@ public function refreshToken($refreshToken = null) { try { $accessToken = $this->getAccessToken([], [ - //'shop_' . $shopUuid, // FIXME: remove that audience + 'store/' . $this->statusManager->getCloudShopId(), $this->tokenAudience, ]); @@ -120,6 +126,16 @@ public function cleanup() $this->configurationRepository->updateAccessToken(''); } + /** + * @param StatusManager $statusManager + * + * @return void + */ + public function setStatusManager(StatusManager $statusManager) + { + $this->statusManager = $statusManager; + } + /** * @param array $scope * @param array $audience diff --git a/src/Polyfill/Traits/Controller/AjaxRender.php b/src/Polyfill/Traits/Controller/AjaxRender.php index 2cf5f423a..d4038d7bc 100644 --- a/src/Polyfill/Traits/Controller/AjaxRender.php +++ b/src/Polyfill/Traits/Controller/AjaxRender.php @@ -44,7 +44,7 @@ protected function ajaxRender($value = null, $controller = null, $method = null) if (method_exists($controllerBaseClass, 'ajaxRender')) { /* @phpstan-ignore-next-line */ parent::ajaxRender($value, $controller, $method); - //exit; + exit; } else { parent::ajaxDie($value, $controller, $method); } diff --git a/src/Provider/ShopProvider.php b/src/Provider/ShopProvider.php index 9191e6080..b7b67f8e1 100644 --- a/src/Provider/ShopProvider.php +++ b/src/Provider/ShopProvider.php @@ -288,16 +288,24 @@ public function getFrontendUrl($shopId) /** * @param int $shopId * - * @return string + * @return string|null */ public function getBackendUrl($shopId) { + $shop = new \Shop($shopId); + + if (!$shop->id) { + return null; + } + + $boBaseUri = ($shop->domain_ssl ? 'https://' : 'http://') . + ($shop->domain_ssl ?: $shop->domain) . $shop->physical_uri; + // FIXME: throw exception in wrong context // FIXME: unit tests - // FIXME: remove virtual uri ? $adminPath = defined('_PS_ADMIN_DIR_') ? basename(_PS_ADMIN_DIR_) : ''; - return rtrim($this->getFrontendUrl($shopId), '/') . '/' . $adminPath; + return rtrim($boBaseUri, '/') . '/' . $adminPath; } /** diff --git a/src/ServiceProvider/DefaultProvider.php b/src/ServiceProvider/DefaultProvider.php index c12e56a58..48f6e6654 100644 --- a/src/ServiceProvider/DefaultProvider.php +++ b/src/ServiceProvider/DefaultProvider.php @@ -64,11 +64,16 @@ public function provide(ServiceContainer $container) }); // Entities ? $container->registerProvider(StatusManager::class, static function () use ($container) { - return new StatusManager( - $container->get(ShopSession::class), + /** @var ShopSession $shopSession */ + $shopSession = $container->get(ShopSession::class); + $service = new StatusManager( + $shopSession, $container->get(AccountsService::class), $container->get(ConfigurationRepository::class) ); + $shopSession->setStatusManager($service); + + return $service; }); // Adapter $container->registerProvider(Adapter\Configuration::class, static function () use ($container) { diff --git a/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php b/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php index 755be6202..89505cd3d 100644 --- a/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php +++ b/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php @@ -60,6 +60,7 @@ function set_up() $oAuth2Service, $this->module->getParameter('ps_accounts.accounts_api_url') ); + $this->shopSession->setStatusManager($this->statusManager); $this->shopSession->cleanup(); } From 23ba011a8a04738ba7bfde41dd04e71cc1518288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:10:03 +0200 Subject: [PATCH 07/46] fix: don't disconnect bo when identify contact flow fails (#502) * fix: don't disconnect bo when identify contact flow fails * fix: don't disconnect bo when identify contact flow fails * fix: v9 oauth controller --- config.bulle.php | 2 +- .../admin/AdminOAuth2PsAccountsController.php | 28 +++++++++++++----- src/Controller/Admin/OAuth2Controller.php | 29 ++++++++++++++----- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/config.bulle.php b/config.bulle.php index 9806aedf6..49e58739b 100644 --- a/config.bulle.php +++ b/config.bulle.php @@ -30,7 +30,7 @@ 'ps_accounts.segment_write_key' => 'eYODaH20rT1lMRTTUtAa15BKBlV1XUXQ', 'ps_accounts.accounts_vue_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@3/dist/psaccountsVue.umd.min.js', //'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5', - 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/@prestashopcorp/accounts-components@0.0.2-beta.1', + 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/@prestashopcorp/accounts-components@0.0.2-beta.2', 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.psessentials-integration.net', 'ps_accounts.oauth2_url' => 'https://oauth-integration.prestashop.com', 'ps_accounts.token_audience' => 'https://accounts-api.distribution-integration.prestashop.net', diff --git a/controllers/admin/AdminOAuth2PsAccountsController.php b/controllers/admin/AdminOAuth2PsAccountsController.php index b3e27d6c0..9646b305e 100644 --- a/controllers/admin/AdminOAuth2PsAccountsController.php +++ b/controllers/admin/AdminOAuth2PsAccountsController.php @@ -202,14 +202,7 @@ protected function getOAuth2Service() protected function redirectAfterLogin() { if ($this->getOAuthAction() === 'identifyPointOfContact') { - // Refresh configuration page - echo << -window.opener.location.reload(); -window.close(); - -HTML; - exit; + $this->closePopup(); } $returnTo = $this->getSessionReturnTo() ?: 'AdminDashboard'; if (preg_match('/^([A-Z][a-z0-9]+)+$/', $returnTo)) { @@ -235,6 +228,9 @@ protected function logout() */ protected function onLoginFailedRedirect() { + if ($this->getOAuthAction() === 'identifyPointOfContact') { + $this->closePopup(false); + } $this->logout(); } @@ -269,4 +265,20 @@ protected function getPsAccountsService() { return $this->psAccountsService; } + + /** + * @param bool $refreshParent + * + * @return void + */ + protected function closePopup($refreshParent = true) + { + echo ' + +'; + exit; + } } diff --git a/src/Controller/Admin/OAuth2Controller.php b/src/Controller/Admin/OAuth2Controller.php index 245fbf292..ade716b48 100644 --- a/src/Controller/Admin/OAuth2Controller.php +++ b/src/Controller/Admin/OAuth2Controller.php @@ -248,13 +248,7 @@ protected function initUserSession(AccessToken $accessToken) protected function redirectAfterLogin() { if ($this->getOAuthAction() === 'identifyPointOfContact') { - return (new Response())->setContent(<< -window.opener.location.reload(); -window.close(); - -HTML - ); + return $this->closePopup(); } return $this->redirectResponse; @@ -273,10 +267,14 @@ protected function logout() } /** - * @return RedirectResponse + * @return RedirectResponse|Response */ protected function onLoginFailedRedirect() { + if ($this->getOAuthAction() === 'identifyPointOfContact') { + return $this->closePopup(false); + } + return $this->redirect( $this->getSessionReturnTo() ?: $this->link->getAdminLink('AdminDashboard') @@ -315,6 +313,21 @@ protected function getPsAccountsService() return $this->psAccountsService; } + /** + * @param bool $refreshParent + * + * @return Response + */ + protected function closePopup($refreshParent = true) + { + return (new Response())->setContent(' + +'); + } + /** * @return array */ From e41dca84e700290562f943ec5b8cfcb5309ac75f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Tue, 24 Jun 2025 10:32:44 +0200 Subject: [PATCH 08/46] [ACCOUNT-2945] feat: fallback session for ps16 (#506) * feat: fallback session for ps16 * feat: check expiration date from cached status * refactor: move session build into provider * fix: existing tests * fix: existing tests * feat: cleanup session * feat: distinct session by employee_id * fix: phpstan * feat: unit tests --- .../admin/AdminOAuth2PsAccountsController.php | 4 + ps_accounts.php | 27 +- src/Account/CachedShopStatus.php | 14 +- src/Account/StatusManager.php | 58 ++-- src/Http/Resource/Resource.php | 32 ++ src/Polyfill/ConfigurationStorageSession.php | 286 ++++++++++++++++++ src/Repository/ConfigurationRepository.php | 2 +- src/Service/Accounts/Resource/ShopStatus.php | 18 +- src/Service/OAuth2/Resource/UserInfo.php | 7 +- src/ServiceProvider/DefaultProvider.php | 47 ++- tests/phpstan/phpstan-PS-1.6.neon | 1 + tests/phpstan/phpstan-PS-1.7.neon | 1 + tests/src/Unit/Account/StatusManagerTest.php | 76 +++-- .../ConfigurationStorageSessionTest.php | 191 ++++++++++++ 14 files changed, 654 insertions(+), 110 deletions(-) create mode 100644 src/Polyfill/ConfigurationStorageSession.php create mode 100644 tests/src/Unit/Polyfill/ConfigurationStorageSessionTest.php diff --git a/controllers/admin/AdminOAuth2PsAccountsController.php b/controllers/admin/AdminOAuth2PsAccountsController.php index 9646b305e..d61b7b5cb 100644 --- a/controllers/admin/AdminOAuth2PsAccountsController.php +++ b/controllers/admin/AdminOAuth2PsAccountsController.php @@ -17,6 +17,8 @@ * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ +require_once __DIR__ . '/../../src/AccountLogin/OAuth2LoginTrait.php'; +require_once __DIR__ . '/../../src/Polyfill/Traits/AdminController/IsAnonymousAllowed.php'; use PrestaShop\Module\PsAccounts\Account\Command\IdentifyContactCommand; use PrestaShop\Module\PsAccounts\AccountLogin\Exception\AccountLoginException; @@ -109,6 +111,7 @@ public function init() } catch (Exception $e) { $this->onLoginFailed(new AccountLoginException($e->getMessage(), null, $e)); } + // why do this at the end of the method ? parent::init(); } @@ -202,6 +205,7 @@ protected function getOAuth2Service() protected function redirectAfterLogin() { if ($this->getOAuthAction() === 'identifyPointOfContact') { + $this->getSession()->clear(); $this->closePopup(); } $returnTo = $this->getSessionReturnTo() ?: 'AdminDashboard'; diff --git a/ps_accounts.php b/ps_accounts.php index a29c9f66e..1053157e8 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -412,31 +412,8 @@ public function isShopEdition() */ public function getSession() { - $container = $this->getCoreServiceContainer(); - if ($container) { - try { - /** - * @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session - * @phpstan-ignore-next-line - */ - $session = $container->get('session'); - /* @phpstan-ignore-next-line */ - } catch (\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException $e) { - try { - // FIXME: fix for 1.7.7.x - global $kernel; - $session = $kernel->getContainer()->get('session'); - /* @phpstan-ignore-next-line */ - } catch (\Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException $e) { - // FIXME: fix for 9.x - global $request; - $session = $request->getSession(); - } - } - - return $session; - } - throw new \Exception('Feature not available'); + // Class name must be literal here in case interface is not present (PrestaShop 1.6) + return $this->getService('\Symfony\Component\HttpFoundation\Session\SessionInterface'); } /** diff --git a/src/Account/CachedShopStatus.php b/src/Account/CachedShopStatus.php index c658a042e..ec657091b 100644 --- a/src/Account/CachedShopStatus.php +++ b/src/Account/CachedShopStatus.php @@ -70,12 +70,16 @@ public function __construct($values = []) */ public function toArray($all = true) { - $values = parent::toArray($all); + $array = parent::toArray($all); - if (isset($values['shopStatus'])) { - $values['shopStatus'] = $this->shopStatus->toArray(); - } + $this->uncastChildResource($array, ShopStatus::class, [ + 'shopStatus', + ]); + + $this->uncastDateTime($array, [ + 'updatedAt', + ]); - return $values; + return $array; } } diff --git a/src/Account/StatusManager.php b/src/Account/StatusManager.php index 4a720b7ad..bf2214fc4 100644 --- a/src/Account/StatusManager.php +++ b/src/Account/StatusManager.php @@ -34,7 +34,7 @@ class StatusManager /** * Status Cache TTL in seconds */ - const CACHE_TTL = 10; + const CACHE_TTL = 30; /** * Infinite Status Cache @@ -80,22 +80,30 @@ public function identityCreated() } /** - * @param bool $cachedStatus + * @param bool $cachedOnly * @param int $cacheTtl * * @return ShopStatus * * @throws UnknownStatusException */ - public function getStatus($cachedStatus = false, $cacheTtl = self::CACHE_TTL) + public function getStatus($cachedOnly = false, $cacheTtl = self::CACHE_TTL) { - if (!$cachedStatus) { - if ($this->cacheInvalidated() || - $this->cacheExpired($cacheTtl) + if (!$cachedOnly) { + try { + $cachedShopStatus = $this->getCachedStatus(); + } catch (UnknownStatusException $e) { + $cachedShopStatus = null; + } + + if (!$cachedShopStatus || + $this->cacheInvalidated($cachedShopStatus) || + $this->cacheExpired($cachedShopStatus, $cacheTtl) ) { try { $this->upsetCachedStatus(new CachedShopStatus([ 'isValid' => true, + 'updatedAt' => date('Y-m-d H:i:s'), 'shopStatus' => $this->accountsService->shopStatus( $this->getCloudShopId(), $this->shopSession->getValidToken() @@ -121,12 +129,15 @@ public function invalidateCache() } /** + * @param CachedShopStatus $cachedStatus + * * @return bool */ - public function cacheInvalidated() + public function cacheInvalidated(CachedShopStatus $cachedStatus = null) { try { - $isValid = $this->getCachedStatus()->isValid; + $cachedStatus = $cachedStatus ?: $this->getCachedStatus(); + $isValid = $cachedStatus->isValid; } catch (UnknownStatusException $e) { $isValid = false; } @@ -135,26 +146,33 @@ public function cacheInvalidated() } /** + * @param CachedShopStatus $cachedStatus * @param int $cacheTtl * * @return bool */ - public function cacheExpired($cacheTtl = self::CACHE_TTL) + public function cacheExpired(CachedShopStatus $cachedStatus = null, $cacheTtl = self::CACHE_TTL) { - $dateUpd = $this->getCacheDateUpd(); + try { + //$dateUpd = $this->getCacheDateUpd(); + $cachedStatus = $cachedStatus ?: $this->getCachedStatus(); + $dateUpd = $cachedStatus->updatedAt; - return $dateUpd instanceof DateTime && - $cacheTtl != self::CACHE_TTL_INFINITE && - time() - $dateUpd->getTimestamp() >= $cacheTtl; + return $dateUpd instanceof DateTime && + $cacheTtl != self::CACHE_TTL_INFINITE && + time() - $dateUpd->getTimestamp() >= $cacheTtl; + } catch (UnknownStatusException $e) { + return true; + } } - /** - * @return \DateTime|null - */ - public function getCacheDateUpd() - { - return $this->repository->getCachedShopStatusDateUpd(); - } +// /** +// * @return \DateTime|null +// */ +// public function getCacheDateUpd() +// { +// return $this->repository->getCachedShopStatusDateUpd(); +// } /** * @param bool $cachedStatus diff --git a/src/Http/Resource/Resource.php b/src/Http/Resource/Resource.php index a5c5c0962..40a83435d 100644 --- a/src/Http/Resource/Resource.php +++ b/src/Http/Resource/Resource.php @@ -46,6 +46,22 @@ protected function castChildResource(array & $values, $className, array $fields) } } + /** + * @param array $values + * @param string $className + * @param array $fields + * + * @return void + */ + protected function uncastChildResource(array & $values, $className, array $fields) + { + foreach ($fields as $field) { + if (isset($values[$field]) && is_a($values[$field], Resource::class, true)) { + $values[$field] = $this->$field->toArray(); + } + } + } + /** * @param array $values * @param array $fields @@ -75,4 +91,20 @@ protected function castDateTime(array & $values, array $fields) } } } + + /** + * @param array $values + * @param array $fields + * @param string $format + * + * @return void + */ + protected function uncastDateTime(array & $values, array $fields, $format = DateTime::ATOM) + { + foreach ($fields as $field) { + if (!empty($values[$field]) && $values[$field] instanceof DateTime) { + $values[$field] = $values[$field]->format($format); + } + } + } } diff --git a/src/Polyfill/ConfigurationStorageSession.php b/src/Polyfill/ConfigurationStorageSession.php new file mode 100644 index 000000000..a2ec8ae7a --- /dev/null +++ b/src/Polyfill/ConfigurationStorageSession.php @@ -0,0 +1,286 @@ +configuration = $configuration; + + $this->name .= '_' . \Context::getContext()->employee->id; + } + + /** + * Starts the session storage. + * + * @return bool True if session started + * + * @throws \RuntimeException if session fails to start + */ + public function start() + { + if (!$this->getId()) { + $this->cleanup(); + $this->setId(uniqid()); + } + + return true; + } + + /** + * Returns the session ID. + * + * @return string The session ID + */ + public function getId() + { + return \Context::getContext()->cookie->id_session; + } + + /** + * Sets the session ID. + * + * @param string $id + */ + public function setId($id) + { + \Context::getContext()->cookie->id_session = $id; + } + + /** + * Returns the session name. + * + * @return mixed The session name + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the session name. + * + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Invalidates the current session. + * + * Clears all session attributes and flashes and regenerates the + * session and deletes the old session from persistence. + * + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session invalidated, false if error + */ + public function invalidate($lifetime = null) + { + $this->notImplemented(); + } + + /** + * Migrates the current session to a new session id while maintaining all + * session attributes. + * + * @param bool $destroy Whether to delete the old session or leave it to garbage collection + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session migrated, false if error + */ + public function migrate($destroy = false, $lifetime = null) + { + $this->notImplemented(); + } + + /** + * Force the session to be saved and closed. + * + * This method is generally not required for real sessions as + * the session will be automatically saved at the end of + * code execution. + */ + public function save() + { + } + + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return bool true if the attribute is defined, false otherwise + */ + public function has($name) + { + return array_key_exists($name, $this->all()); + } + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get($name, $default = null) + { + $session = $this->all(); + + return array_key_exists($name, $session) ? $session[$name] : $default; + } + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value) + { + $session = $this->all(); + $session[$name] = $value; + + $this->configuration->set($this->getConfigurationName(), json_encode($session)); + } + + /** + * Returns attributes. + * + * @return array Attributes + */ + public function all() + { + $all = json_decode($this->configuration->getUncached($this->getConfigurationName()), true); + + if (is_array($all)) { + return $all; + } + + return []; + } + + /** + * Sets attributes. + * + * @param array $attributes Attributes + */ + public function replace(array $attributes) + { + $this->notImplemented(); + } + + /** + * Removes an attribute. + * + * @param string $name + * + * @return mixed The removed value or null when it does not exist + */ + public function remove($name) + { + $session = $this->all(); + unset($session[$name]); + + $this->configuration->set($name, json_encode($session)); + } + + /** + * Clears all attributes. + */ + public function clear() + { + $this->configuration->set($this->getConfigurationName(), json_encode([])); + } + + /** + * Checks if the session was started. + * + * @return bool + */ + public function isStarted() + { + return !empty($this->getId()); + } + + /** + * Registers a SessionBagInterface with the session. + */ + public function registerBag(SessionBagInterface $bag) + { + $this->notImplemented(); + } + + /** + * Gets a bag instance by name. + * + * @param string $name + * + * @return SessionBagInterface + */ + public function getBag($name) + { + $this->notImplemented(); + } + + /** + * Gets session meta. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + $this->notImplemented(); + } + + /** + * @return string + */ + private function getConfigurationName() + { + return $this->getName() . '_' . $this->getId(); + } + + /** + * @return void + */ + private function cleanup() + { + \Db::getInstance()->query( + 'DELETE FROM ' . _DB_PREFIX_ . "configuration WHERE name LIKE '" . $this->name . "_%'" + ); + } + + /** + * @return void + * + * @throws \Exception + */ + private function notImplemented() + { + throw new \Exception('Method not implemented : ' . __METHOD__); + } +} diff --git a/src/Repository/ConfigurationRepository.php b/src/Repository/ConfigurationRepository.php index 66d1bb532..2b73e7245 100644 --- a/src/Repository/ConfigurationRepository.php +++ b/src/Repository/ConfigurationRepository.php @@ -402,7 +402,7 @@ public function updateShopProof($proof) */ public function getCachedShopStatus() { - return $this->configuration->get(ConfigurationKeys::PS_ACCOUNTS_CACHED_SHOP_STATUS); + return $this->configuration->getUncached(ConfigurationKeys::PS_ACCOUNTS_CACHED_SHOP_STATUS); } /** diff --git a/src/Service/Accounts/Resource/ShopStatus.php b/src/Service/Accounts/Resource/ShopStatus.php index a34427212..818aa9dac 100644 --- a/src/Service/Accounts/Resource/ShopStatus.php +++ b/src/Service/Accounts/Resource/ShopStatus.php @@ -104,18 +104,12 @@ public function toArray($all = true) { $array = parent::toArray($all); - foreach ( - [ - 'createdAt', - 'updatedAt', - 'verifiedAt', - 'unverifiedAt', - ] as $dateField - ) { - if (!empty($array[$dateField])) { - $array[$dateField] = $array[$dateField]->format(DateTime::ATOM); - } - } + $this->uncastDateTime($array, [ + 'createdAt', + 'updatedAt', + 'verifiedAt', + 'unverifiedAt', + ]); return $array; } diff --git a/src/Service/OAuth2/Resource/UserInfo.php b/src/Service/OAuth2/Resource/UserInfo.php index 8d8020bf5..5ccca8905 100644 --- a/src/Service/OAuth2/Resource/UserInfo.php +++ b/src/Service/OAuth2/Resource/UserInfo.php @@ -89,9 +89,10 @@ class UserInfo extends Resource public function __construct(array $data = []) { - if (isset($data['email_verified'])) { - $data['email_verified'] = (bool) $data['email_verified']; - } + $this->castBool($data, [ + 'email_verified', + ]); + parent::__construct($data); } } diff --git a/src/ServiceProvider/DefaultProvider.php b/src/ServiceProvider/DefaultProvider.php index 48f6e6654..b8655d7d1 100644 --- a/src/ServiceProvider/DefaultProvider.php +++ b/src/ServiceProvider/DefaultProvider.php @@ -24,7 +24,6 @@ use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Adapter; -use PrestaShop\Module\PsAccounts\Adapter\Configuration; use PrestaShop\Module\PsAccounts\Adapter\Link; use PrestaShop\Module\PsAccounts\Api\Client\ServicesBillingClient; use PrestaShop\Module\PsAccounts\Context\ShopContext; @@ -32,6 +31,7 @@ use PrestaShop\Module\PsAccounts\Cqrs\QueryBus; use PrestaShop\Module\PsAccounts\Http\Client\CircuitBreaker; use PrestaShop\Module\PsAccounts\Installer\Installer; +use PrestaShop\Module\PsAccounts\Polyfill\ConfigurationStorageSession; use PrestaShop\Module\PsAccounts\Presenter\PsAccountsPresenter; use PrestaShop\Module\PsAccounts\Provider; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; @@ -44,6 +44,8 @@ use PrestaShop\Module\PsAccounts\Service\SentryService; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\Contract\IServiceProvider; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\ServiceContainer; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; class DefaultProvider implements IServiceProvider { @@ -148,7 +150,7 @@ public function provide(ServiceContainer $container) // Factories $container->registerProvider(CircuitBreaker\Factory::class, static function () use ($container) { return new CircuitBreaker\Factory( - $container->get(Configuration::class) + $container->get(Adapter\Configuration::class) ); }); // Installer @@ -164,5 +166,46 @@ public function provide(ServiceContainer $container) $container->get('ps_accounts.module') ); }); + // PHP Session + $container->registerProvider( + '\Symfony\Component\HttpFoundation\Session\SessionInterface', + static function () use ($container) { + $module = $container->get('ps_accounts.module'); + + $core = $module->getCoreServiceContainer(); + if ($core) { + try { + /** + * @var SessionInterface $session + * @phpstan-ignore-next-line + */ + $session = $core->get('session'); + /* @phpstan-ignore-next-line */ + } catch (ServiceNotFoundException $e) { + try { + // FIXME: fix for 1.7.7.x + global $kernel; + $session = $kernel->getContainer()->get('session'); + /* @phpstan-ignore-next-line */ + } catch (ServiceNotFoundException $e) { + // FIXME: fix for 9.x + global $request; + $session = $request->getSession(); + } + } + + return $session; + } + + // Fallback session object + // FIXME: create an interface for it + $session = new ConfigurationStorageSession( + $container->get(Adapter\Configuration::class) + ); + $session->start(); + + return $session; + } + ); } } diff --git a/tests/phpstan/phpstan-PS-1.6.neon b/tests/phpstan/phpstan-PS-1.6.neon index 4f94f8970..5fe48f8d7 100644 --- a/tests/phpstan/phpstan-PS-1.6.neon +++ b/tests/phpstan/phpstan-PS-1.6.neon @@ -9,6 +9,7 @@ parameters: - ../../ps_accounts.php excludePaths: - ../../src/Controller + - ../../src/Polyfill/ConfigurationStorageSession.php dynamicConstantNames: - _PS_VERSION_ ignoreErrors: diff --git a/tests/phpstan/phpstan-PS-1.7.neon b/tests/phpstan/phpstan-PS-1.7.neon index ac127688a..c3e7a8b92 100644 --- a/tests/phpstan/phpstan-PS-1.7.neon +++ b/tests/phpstan/phpstan-PS-1.7.neon @@ -10,6 +10,7 @@ parameters: - ../../ps_accounts.php excludePaths: - ../../src/Controller + - ../../src/Polyfill/ConfigurationStorageSession.php dynamicConstantNames: - _PS_VERSION_ ignoreErrors: diff --git a/tests/src/Unit/Account/StatusManagerTest.php b/tests/src/Unit/Account/StatusManagerTest.php index a49b053b3..2825c887e 100644 --- a/tests/src/Unit/Account/StatusManagerTest.php +++ b/tests/src/Unit/Account/StatusManagerTest.php @@ -119,6 +119,7 @@ public function itShouldUpdateStatusFromCloudWhenTtlExpired() $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ 'isValid' => true, + 'updatedAt' => (new \DateTime())->format(\DateTime::ATOM), 'shopStatus' => new ShopStatus([ 'cloudShopId' => $cloudShopId, 'isVerified' => false, @@ -129,17 +130,13 @@ public function itShouldUpdateStatusFromCloudWhenTtlExpired() ->willReturn($this->faker->uuid); $this->client->method('get') - ->willReturnCallback(function ($route) use ($cloudShopId, $pointOfContactEmail, $pointOfContactUid) { - if (preg_match('/v1\/shop-identities\/' . $cloudShopId . '\/status$/', $route)) { - return $this->createResponse([ - "cloudShopId" => $cloudShopId, - "isVerified" => true, - "pointOfContactEmail" => $pointOfContactEmail, - "pointOfContactUid" => $pointOfContactUid, - ], 200, true); - } - return $this->createResponse([], 500, true); - }); + ->with($this->matchesRegularExpression('/v1\/shop-identities\/' . $cloudShopId . '\/status$/')) + ->willReturn($this->createResponse([ + "cloudShopId" => $cloudShopId, + "isVerified" => true, + "pointOfContactEmail" => $pointOfContactEmail, + "pointOfContactUid" => $pointOfContactUid, + ], 200)); sleep(1); @@ -161,6 +158,7 @@ public function itShouldUpdateStatusFromCloudWhenCacheInvalidated() $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ 'isValid' => true, + 'updatedAt' => (new \DateTime())->format(\DateTime::ATOM), 'shopStatus' => new ShopStatus([ 'cloudShopId' => $cloudShopId, 'isVerified' => false, @@ -171,17 +169,13 @@ public function itShouldUpdateStatusFromCloudWhenCacheInvalidated() ->willReturn($this->faker->uuid); $this->client->method('get') - ->willReturnCallback(function ($route) use ($cloudShopId, $pointOfContactEmail, $pointOfContactUid) { - if (preg_match('/v1\/shop-identities\/' . $cloudShopId . '\/status$/', $route)) { - return $this->createResponse([ - "cloudShopId" => $cloudShopId, - "isVerified" => true, - "pointOfContactEmail" => $pointOfContactEmail, - "pointOfContactUid" => $pointOfContactUid, - ], 200, true); - } - return $this->createResponse([], 500, true); - }); + ->with($this->matchesRegularExpression('/v1\/shop-identities\/' . $cloudShopId . '\/status$/')) + ->willReturn($this->createResponse([ + "cloudShopId" => $cloudShopId, + "isVerified" => true, + "pointOfContactEmail" => $pointOfContactEmail, + "pointOfContactUid" => $pointOfContactUid, + ], 200)); $this->statusManager->invalidateCache(); $cachedStatus = $this->statusManager->getStatus(); @@ -198,25 +192,24 @@ public function itShouldNotUpdateStatusFromCloudIfTtlNotExpired() { $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ 'isValid' => true, - 'shopStatus' => new ShopStatus(), + 'updatedAt' => (new \DateTime())->format(\DateTime::ATOM), + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $this->faker->uuid, + ]), ]))->toArray())); $this->shopSession->method('getValidToken') ->willReturn($this->faker->uuid); $this->client->method('get') - ->willReturnCallback(function ($route) { - if (preg_match('/v1\/shop-identities\/\d\/status$/', $route)) { - return $this->createResponse([ - "msg" => "Invalid request", - ], 400, true); - } - return $this->createResponse([], 500, true); - }); + ->with($this->matchesRegularExpression('/v1\/shop-identities\/[^\/]+\/status$/')) + ->willReturn($this->createResponse([ + "msg" => "Invalid request", + ], 400)); $cachedStatus = $this->statusManager->getStatus(false); - $this->assertNull($cachedStatus->cloudShopId); + //$this->assertNull($cachedStatus->cloudShopId); $this->assertFalse($cachedStatus->isVerified); $this->assertNull($cachedStatus->pointOfContactEmail); } @@ -228,27 +221,26 @@ public function itShouldNotUpdateStatusFromCloudOnError() { $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ 'isValid' => true, - 'shopStatus' => new ShopStatus(), + 'updatedAt' => (new \DateTime())->format(\DateTime::ATOM), + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $this->faker->uuid, + ]), ]))->toArray())); $this->shopSession->method('getValidToken') ->willReturn($this->faker->uuid); $this->client->method('get') - ->willReturnCallback(function ($route) { - if (preg_match('/v1\/shop-identities\/\d\/status$/', $route)) { - return $this->createResponse([ - "msg" => "Invalid request", - ], 400, true); - } - return $this->createResponse([], 500, true); - }); + ->with($this->matchesRegularExpression('/v1\/shop-identities\/[^\/]+\/status$/')) + ->willReturn($this->createResponse([ + "msg" => "Invalid request", + ], 400, true)); sleep(1); $cachedStatus = $this->statusManager->getStatus(false, 1); - $this->assertNull($cachedStatus->cloudShopId); + //$this->assertNull($cachedStatus->cloudShopId); $this->assertFalse($cachedStatus->isVerified); $this->assertNull($cachedStatus->pointOfContactEmail); } diff --git a/tests/src/Unit/Polyfill/ConfigurationStorageSessionTest.php b/tests/src/Unit/Polyfill/ConfigurationStorageSessionTest.php new file mode 100644 index 000000000..bed6ba5db --- /dev/null +++ b/tests/src/Unit/Polyfill/ConfigurationStorageSessionTest.php @@ -0,0 +1,191 @@ +=')) { + $this->markTestSkipped('== PrestaShop 1.6 Only Test =='); + } + + // Login context + \Context::getContext()->employee = new \Employee(1); + \Context::getContext()->cookie = new \Cookie('cookie'); + \Context::getContext()->cookie->employee_id = 1; + + $this->session = new ConfigurationStorageSession($this->configuration); + } + + /** + * @test + */ + public function itShouldSetSessionName() + { + $this->session->start(); + + $this->assertEquals('PS_ACCOUNTS_SESSION_' . 1, $this->session->getName()); + } + + /** + * @test + */ + public function itShouldGetStartedStatus() + { + $this->assertFalse($this->session->isStarted()); + + $this->session->start(); + + $this->assertTrue($this->session->isStarted()); + } + + + /** + * @test + */ + public function itShouldGetSessionId() + { + $this->session->start(); + + $this->assertIsString($this->session->getId()); + } + + /** + * @test + */ + public function itShouldSetAndGetSessionProperty() + { + $this->session->start(); + + $this->session->set('foo', 'bar'); + + $this->assertEquals('bar', $this->session->get('foo')); + } + + /** + * @test + */ + public function itShouldGetPropertyDefault() + { + $this->session->start(); + + $this->assertEquals('default', $this->session->get('tata', 'default')); + } + + /** + * @test + */ + public function itShouldClearSession() + { + $this->session->start(); + + $this->session->set('foo', 'bar'); + + $this->session->clear(); + + $this->assertNull($this->session->get('foo')); + } + + /** + * @test + */ + public function itShouldCleanupSessionOnStart() + { + $this->session->start(); + + $this->session->set('foo', 'bar'); + + $all = $this->session->all(); + + $this->assertArrayHasKey('foo', $all); + + $this->session->setId(''); + + $this->session->start(); + + $this->session->set('bar', 'baz'); + + $all = $this->session->all(); + + $this->assertArrayHasKey('bar', $all); + $this->assertArrayNotHasKey('foo', $all); + } + + /** + * @test + */ + public function itShouldGetAllProperties() + { + $this->session->start(); + + $this->session->set('foo', 'bar'); + $this->session->set('bar', 'baz'); + + $this->assertArraySubset([ + 'foo' => 'bar', + 'bar' => 'baz', + ], $this->session->all()); + } + + /** + * @test + */ + public function itShouldMaintainOneSessionByEmployee() + { + $this->session->start(); + + $this->session->set('foo', 'bar'); + + $this->session->setId(''); + + $this->session->start(); + + $this->session->set('foo', 'bar'); + + $this->session->setId(''); + + $this->session->start(); + + $this->session->set('foo', 'bar'); + + $this->session->setId(''); + + $this->session->start(); + + $this->session->set('foo', 'bar'); + + $count = \Db::getInstance()->getValue( + 'SELECT COUNT(*) FROM ' . _DB_PREFIX_ . "configuration WHERE name LIKE '" . $this->session->getName() . "_%'" + ); + + $this->assertEquals(1, $count); + } + + /** + * @test + */ + public function itShouldThrowExceptionOnUnimplementedMethod() + { + $this->expectException(\Exception::class); + + $this->session->getBag('random'); + } + + public function tear_down() + { + parent::tear_down(); + + $this->session->clear(); + } +} From 5e7e291c77e43ad5f75b5e801e89be2bde1d95a0 Mon Sep 17 00:00:00 2001 From: Antoine Date: Fri, 27 Jun 2025 11:51:47 +0200 Subject: [PATCH 09/46] [ACCOUNT-2953] Compat firebase tokens (#505) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add get shop tokens query * test: fix * fix: move get cloud shop id in trycatch block * fix: add x-shop-id header * fix: legacy firebase tokens class * fix: legacy firebase tokens class * fix: remove useless x-shop-id header * Update src/Service/Accounts/Resource/FirebaseTokens.php --------- Co-authored-by: hschoenenberger Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> --- .../Session/Firebase/FirebaseSession.php | 56 +++++++++++-------- src/Service/Accounts/AccountsService.php | 10 ++-- .../Accounts/Resource/FirebaseToken.php | 36 ++++++++++++ .../Accounts/Resource/FirebaseTokens.php | 26 +++++---- .../Accounts/Resource/LegacyFirebaseToken.php | 41 ++++++++++++++ src/ServiceProvider/DefaultProvider.php | 5 ++ .../OwnerSession/GetValidTokenTest.php | 24 +++++--- .../OwnerSession/RefreshTokenTest.php | 12 ++-- .../ShopSession/GetValidTokenTest.php | 24 +++++--- .../Firebase/ShopSession/RefreshTokenTest.php | 12 ++-- .../Unit/Account/Session/SessionHelpers.php | 16 +++++- 11 files changed, 198 insertions(+), 64 deletions(-) create mode 100644 src/Service/Accounts/Resource/FirebaseToken.php create mode 100644 src/Service/Accounts/Resource/LegacyFirebaseToken.php diff --git a/src/Account/Session/Firebase/FirebaseSession.php b/src/Account/Session/Firebase/FirebaseSession.php index ce646f7c0..28f9ecfbd 100644 --- a/src/Account/Session/Firebase/FirebaseSession.php +++ b/src/Account/Session/Firebase/FirebaseSession.php @@ -25,11 +25,11 @@ use PrestaShop\Module\PsAccounts\Account\Session\Session; use PrestaShop\Module\PsAccounts\Account\Session\SessionInterface; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Account\Token\Token; use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; -use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\FirebaseTokens; abstract class FirebaseSession extends Session implements SessionInterface { @@ -43,6 +43,11 @@ abstract class FirebaseSession extends Session implements SessionInterface */ private $module; + /** + * @var StatusManager + */ + protected $statusManager; + public function __construct(ShopSession $shopSession) { $this->shopSession = $shopSession; @@ -84,10 +89,11 @@ public function getShopSession() */ public function refreshToken($refreshToken = null) { - $token = $this->shopSession->getValidToken(); - try { - $this->refreshFirebaseTokens($token); + $token = $this->shopSession->getValidToken(); + $cloudShopId = $this->statusManager->getCloudShopId(); + + $this->refreshFirebaseTokens($cloudShopId, $token); } catch (RefreshTokenException $e) { Logger::getInstance()->error('Unable to get or refresh owner/shop token : ' . $e->getMessage()); throw $e; @@ -97,43 +103,49 @@ public function refreshToken($refreshToken = null) } /** + * @param string $cloudShopId * @param Token $token * * @return void * * @throws RefreshTokenException */ - protected function refreshFirebaseTokens($token) + protected function refreshFirebaseTokens($cloudShopId, $token) { try { - $firebaseTokens = $this->getAccountsService()->firebaseTokens($token); + $firebaseTokens = $this->getAccountsService()->firebaseTokens($cloudShopId, $token); } catch (AccountsException $e) { throw new RefreshTokenException($e->getMessage()); } - $shopToken = $this->getFirebaseTokenFromResponse($firebaseTokens, 'shopToken', 'shopRefreshToken'); - $ownerToken = $this->getFirebaseTokenFromResponse($firebaseTokens, 'userToken', 'userRefreshToken'); + $shopToken = new Token( + $firebaseTokens->shop->token, + $firebaseTokens->shop->refreshToken + ); + + $pointOfContactToken = null; + if (isset($firebaseTokens->pointOfContact->token) && isset($firebaseTokens->pointOfContact->refreshToken)) { + $pointOfContactToken = new Token( + $firebaseTokens->pointOfContact->token, + $firebaseTokens->pointOfContact->refreshToken + ); + } // saving both tokens here $this->getShopSession()->setToken((string) $shopToken->getJwt(), $shopToken->getRefreshToken()); - $this->getOwnerSession()->setToken((string) $ownerToken->getJwt(), $ownerToken->getRefreshToken()); + + if (isset($pointOfContactToken)) { + $this->getOwnerSession()->setToken((string) $pointOfContactToken->getJwt(), $pointOfContactToken->getRefreshToken()); + } } /** - * @param FirebaseTokens $firebaseTokens - * @param string $name - * @param string $refreshName + * @param StatusManager $statusManager * - * @return Token + * @return void */ - protected function getFirebaseTokenFromResponse( - FirebaseTokens $firebaseTokens, - $name, - $refreshName - ) { - return new Token( - $firebaseTokens->$name, - $firebaseTokens->$refreshName - ); + public function setStatusManager(StatusManager $statusManager) + { + $this->statusManager = $statusManager; } } diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index 0c7f9d183..9e5115b5c 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -29,6 +29,7 @@ use PrestaShop\Module\PsAccounts\Http\Client\Response; use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\FirebaseTokens; use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\IdentityCreated; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\LegacyFirebaseToken; use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; use PrestaShop\Module\PsAccounts\Vendor\Ramsey\Uuid\Uuid; @@ -97,16 +98,17 @@ private function getHeaders($additionalHeaders = []) } /** + * @param string $cloudShopId * @param string $accessToken * * @return FirebaseTokens * * @throws AccountsException */ - public function firebaseTokens($accessToken) + public function firebaseTokens($cloudShopId, $accessToken) { $response = $this->getClient()->get( - 'v2/shop/firebase/tokens', + '/v1/shop-identities/' . $cloudShopId . '/tokens', [ Request::HEADERS => $this->getHeaders([ 'Authorization' => 'Bearer ' . $accessToken, @@ -124,7 +126,7 @@ public function firebaseTokens($accessToken) * @param string $refreshToken * @param string $cloudShopId * - * @return FirebaseTokens + * @return LegacyFirebaseToken * * @throws AccountsException */ @@ -146,7 +148,7 @@ public function refreshShopToken($refreshToken, $cloudShopId) throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to refresh token.')); } - return new FirebaseTokens($response->body); + return new LegacyFirebaseToken($response->body); } /** diff --git a/src/Service/Accounts/Resource/FirebaseToken.php b/src/Service/Accounts/Resource/FirebaseToken.php new file mode 100644 index 000000000..96832c0eb --- /dev/null +++ b/src/Service/Accounts/Resource/FirebaseToken.php @@ -0,0 +1,36 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Service\Accounts\Resource; + +use PrestaShop\Module\PsAccounts\Http\Resource\Resource; + +class FirebaseToken extends Resource +{ + /** + * @var string|null + */ + public $token; + + /** + * @var string|null + */ + public $refreshToken; +} diff --git a/src/Service/Accounts/Resource/FirebaseTokens.php b/src/Service/Accounts/Resource/FirebaseTokens.php index cb9b5c3a4..d4248cfed 100644 --- a/src/Service/Accounts/Resource/FirebaseTokens.php +++ b/src/Service/Accounts/Resource/FirebaseTokens.php @@ -25,22 +25,24 @@ class FirebaseTokens extends Resource { /** - * @var string + * @var FirebaseToken */ - public $userToken; + public $shop; /** - * @var string + * @var FirebaseToken */ - public $userRefreshToken; + public $pointOfContact; - /** - * @var string - */ - public $shopToken; + public function __construct($values = []) + { + $this->castChildResource($values, FirebaseToken::class, [ + 'shop', + ]); + $this->castChildResource($values, FirebaseToken::class, [ + 'pointOfContact', + ]); - /** - * @var string - */ - public $shopRefreshToken; + parent::__construct($values); + } } diff --git a/src/Service/Accounts/Resource/LegacyFirebaseToken.php b/src/Service/Accounts/Resource/LegacyFirebaseToken.php new file mode 100644 index 000000000..56b0073a9 --- /dev/null +++ b/src/Service/Accounts/Resource/LegacyFirebaseToken.php @@ -0,0 +1,41 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Service\Accounts\Resource; + +use PrestaShop\Module\PsAccounts\Http\Resource\Resource; + +class LegacyFirebaseToken extends Resource +{ + /** + * @var string + */ + public $token; + + /** + * @var string + */ + public $refresh_token; + + /** + * @var string + */ + public $id_token; +} diff --git a/src/ServiceProvider/DefaultProvider.php b/src/ServiceProvider/DefaultProvider.php index b8655d7d1..56e53fa14 100644 --- a/src/ServiceProvider/DefaultProvider.php +++ b/src/ServiceProvider/DefaultProvider.php @@ -21,6 +21,7 @@ namespace PrestaShop\Module\PsAccounts\ServiceProvider; use PrestaShop\Module\PsAccounts\Account\ProofManager; +use PrestaShop\Module\PsAccounts\Account\Session\Firebase; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Adapter; @@ -68,12 +69,16 @@ public function provide(ServiceContainer $container) $container->registerProvider(StatusManager::class, static function () use ($container) { /** @var ShopSession $shopSession */ $shopSession = $container->get(ShopSession::class); + $firebaseOwnerSession = $container->get(Firebase\OwnerSession::class); + $firebaseShopSession = $container->get(Firebase\ShopSession::class); $service = new StatusManager( $shopSession, $container->get(AccountsService::class), $container->get(ConfigurationRepository::class) ); $shopSession->setStatusManager($service); + $firebaseOwnerSession->setStatusManager($service); + $firebaseShopSession->setStatusManager($service); return $service; }); diff --git a/tests/src/Unit/Account/Session/Firebase/OwnerSession/GetValidTokenTest.php b/tests/src/Unit/Account/Session/Firebase/OwnerSession/GetValidTokenTest.php index 7845ae1e1..9115565f5 100644 --- a/tests/src/Unit/Account/Session/Firebase/OwnerSession/GetValidTokenTest.php +++ b/tests/src/Unit/Account/Session/Firebase/OwnerSession/GetValidTokenTest.php @@ -68,10 +68,14 @@ public function itShouldRefreshExpiredToken() $session = $this->getMockedFirebaseSession( Firebase\OwnerSession::class, $this->createResponse([ - 'userToken' => (string) $userRefreshedToken, - 'userRefreshToken' => $userRefreshToken, - 'shopToken' => (string) $shopRefreshedToken, - 'shopRefreshToken' => $shopRefreshToken, + 'shop' => [ + 'token' => (string) $shopRefreshedToken, + 'refreshToken' => $shopRefreshToken, + ], + 'pointOfContact' => [ + 'token' => (string) $userRefreshedToken, + 'refreshToken' => $userRefreshToken, + ] ], 200, true), $this->getMockedShopSession(new Token($this->makeJwtToken(new \DateTimeImmutable()))) ); @@ -109,10 +113,14 @@ public function itShouldNotRefreshValidToken() $session = $this->getMockedFirebaseSession( Firebase\OwnerSession::class, $this->createResponse([ - 'userToken' => (string) $userRefreshedToken, - 'userRefreshToken' => $userRefreshToken, - 'shopToken' => (string) $shopRefreshedToken, - 'shopRefreshToken' => $shopRefreshToken, + 'shop' => [ + 'token' => (string) $shopRefreshedToken, + 'refreshToken' => $shopRefreshToken, + ], + 'pointOfContact' => [ + 'token' => (string) $userRefreshedToken, + 'refreshToken' => $userRefreshToken, + ] ], 200, true), $this->getMockedShopSession(new Token('')) // Empty token ); diff --git a/tests/src/Unit/Account/Session/Firebase/OwnerSession/RefreshTokenTest.php b/tests/src/Unit/Account/Session/Firebase/OwnerSession/RefreshTokenTest.php index 508df6637..3bbc2fe9a 100644 --- a/tests/src/Unit/Account/Session/Firebase/OwnerSession/RefreshTokenTest.php +++ b/tests/src/Unit/Account/Session/Firebase/OwnerSession/RefreshTokenTest.php @@ -73,10 +73,14 @@ public function itShouldRefreshExpiredToken() $session = $this->getMockedFirebaseSession( Firebase\OwnerSession::class, $this->createResponse([ - 'userToken' => (string) $userRefreshedToken, - 'userRefreshToken' => $userRefreshToken, - 'shopToken' => (string) $shopRefreshedToken, - 'shopRefreshToken' => $shopRefreshToken, + 'shop' => [ + 'token' => (string) $shopRefreshedToken, + 'refreshToken' => $shopRefreshToken, + ], + 'pointOfContact' => [ + 'token' => (string) $userRefreshedToken, + 'refreshToken' => $userRefreshToken, + ] ], 200, true), $this->getMockedShopSession(new Token($this->makeJwtToken(new \DateTimeImmutable()))) ); diff --git a/tests/src/Unit/Account/Session/Firebase/ShopSession/GetValidTokenTest.php b/tests/src/Unit/Account/Session/Firebase/ShopSession/GetValidTokenTest.php index a92333d27..5f9205467 100644 --- a/tests/src/Unit/Account/Session/Firebase/ShopSession/GetValidTokenTest.php +++ b/tests/src/Unit/Account/Session/Firebase/ShopSession/GetValidTokenTest.php @@ -68,10 +68,14 @@ public function itShouldRefreshExpiredToken() $session = $this->getMockedFirebaseSession( Firebase\ShopSession::class, $this->createResponse([ - 'userToken' => (string) $userRefreshedToken, - 'userRefreshToken' => $userRefreshToken, - 'shopToken' => (string) $shopRefreshedToken, - 'shopRefreshToken' => $shopRefreshToken, + 'shop' => [ + 'token' => (string) $shopRefreshedToken, + 'refreshToken' => $shopRefreshToken, + ], + 'pointOfContact' => [ + 'token' => (string) $userRefreshedToken, + 'refreshToken' => $userRefreshToken, + ] ], 200, true), $this->getMockedShopSession(new Token($this->makeJwtToken(new \DateTimeImmutable()))) ); @@ -109,10 +113,14 @@ public function itShouldNotRefreshValidToken() $session = $this->getMockedFirebaseSession( Firebase\ShopSession::class, $this->createResponse([ - 'userToken' => (string) $userRefreshedToken, - 'userRefreshToken' => $userRefreshToken, - 'shopToken' => (string) $shopRefreshedToken, - 'shopRefreshToken' => $shopRefreshToken, + 'shop' => [ + 'token' => (string) $shopRefreshedToken, + 'refreshToken' => $shopRefreshToken, + ], + 'pointOfContact' => [ + 'token' => (string) $userRefreshedToken, + 'refreshToken' => $userRefreshToken, + ] ], 200, true), $this->getMockedShopSession(new Token('')) // Empty token ); diff --git a/tests/src/Unit/Account/Session/Firebase/ShopSession/RefreshTokenTest.php b/tests/src/Unit/Account/Session/Firebase/ShopSession/RefreshTokenTest.php index f40ded029..a55f5a33b 100644 --- a/tests/src/Unit/Account/Session/Firebase/ShopSession/RefreshTokenTest.php +++ b/tests/src/Unit/Account/Session/Firebase/ShopSession/RefreshTokenTest.php @@ -73,10 +73,14 @@ public function itShouldRefreshExpiredToken() $session = $this->getMockedFirebaseSession( Firebase\ShopSession::class, $this->createResponse([ - 'userToken' => (string) $userRefreshedToken, - 'userRefreshToken' => $userRefreshToken, - 'shopToken' => (string) $shopRefreshedToken, - 'shopRefreshToken' => $shopRefreshToken, + 'shop' => [ + 'token' => (string) $shopRefreshedToken, + 'refreshToken' => $shopRefreshToken, + ], + 'pointOfContact' => [ + 'token' => (string) $userRefreshedToken, + 'refreshToken' => $userRefreshToken, + ] ], 200, true), $this->getMockedShopSession(new Token($this->makeJwtToken(new \DateTimeImmutable()))) ); diff --git a/tests/src/Unit/Account/Session/SessionHelpers.php b/tests/src/Unit/Account/Session/SessionHelpers.php index 5904fae62..98f9ca6d6 100644 --- a/tests/src/Unit/Account/Session/SessionHelpers.php +++ b/tests/src/Unit/Account/Session/SessionHelpers.php @@ -3,13 +3,14 @@ namespace PrestaShop\Module\PsAccounts\Tests\Unit\Account\Session; use PHPUnit\Framework\MockObject\MockObject; +use PrestaShop\Module\PsAccounts\Account\CachedShopStatus; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\FirebaseSession; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; use PrestaShop\Module\PsAccounts\Account\Token\Token; use PrestaShop\Module\PsAccounts\Http\Client\Curl\Client; use PrestaShop\Module\PsAccounts\Http\Client\Response; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; -use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\FirebaseTokens; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; trait SessionHelpers { @@ -45,13 +46,22 @@ protected function getMockedFirebaseSession($firebaseSessionClass, Response $res { $client = $this->createMock(Client::class); + $cloudShopId = $this->faker->uuid; + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $cloudShopId, + ]) + ]))->toArray())); + /** @var AccountsService $accountsService */ $accountsService = $this->module->getService(AccountsService::class); $accountsService->setClient($client); $client->method('get') ->willReturnCallback(function ($route) use ($response) { - if (preg_match('/v2\/shop\/firebase\/tokens$/', $route)) { + if (preg_match('/v1\/shop-identities\/(.*)\/tokens$/', $route)) { return $response; } return $this->createResponse([], 500, true); @@ -69,6 +79,8 @@ protected function getMockedFirebaseSession($firebaseSessionClass, Response $res $firebaseSession->method('getAccountsService') ->willReturn($accountsService); + $firebaseSession->setStatusManager($this->statusManager); + return $firebaseSession; } } From 0b4fd033d70bb34ce47ab5461e9fe9539c7b0400 Mon Sep 17 00:00:00 2001 From: Guillaume-L <77913074+guillaume60240@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:05:37 +0200 Subject: [PATCH 10/46] [ACCOUNT-2853] feat: upgrade to v8 (#503) * feat: add command for migrate shop identity * fix: update MigrateShopIdentityHandler to use MigrateShopIdentityCommand and ConfigurationRepository * feat: add tokenAudience parameter to MigrateShopIdentitiesCommand and update handlers * fix: lint error * fix: remove unnecessary blank lines in CommandProvider * fix: remove unused dependency from and update CommandProvider * feat: happy path & TODO * feat: happy path & TODO * feat: happy path & TODO * feat: happy path & TODO * refactor: remove unused upgradeShopModule method * feat: migrate from v5 & v6 et set last upgrade * feat: migrate from v5 & v6 et set last upgrade * feat: handler basic tests * feat: handler basic tests * feat: handler basic tests * feat: handler basic tests * feat: handler basic tests * feat: refactor App.vue to use PuikTabNavigation and update ps_accounts.php for code consistency * feat: give proof on migration request * feat: fix style * feat: fix style * feat: commented tabNavigation * feat: test inputs (to be refactored) * feat: implement fromVersion * feat: CI fixes * refactor: rename command * feat: test audience * feat: test inputs * feat: test inputs * feat: rename handler * feat: CI fixes * feat: refactor upgrade logic to handle shop existence check * fix: remove unused property * feat: refactor identity migration on upgrade and create identity if necessary * feat: rename commands & call migrate on reset * fix: phpstan * fix: last upgraded version * fix: minor fixes & call singular command inside singular command * fix: renaming for clarity * fix: getLastUpgrade always return a version number (0 by default) * fix: shop contextualized data * fix: shop contextualized data & fixed uncached call * fix: shop contextualized data & fixed uncached call * fix: cut error output rendering * fix: simplify identity migration logic and improve version checks * fix: migrate route name * fix: unused import * fix: tests * fix: improve version upgrade logic and add debug/health check links in header hook * fix: improve version upgrade logic and add debug/health check links in header hook * fix: phpstan --------- Co-authored-by: hschoenenberger Co-authored-by: Sullivan --- _dev/apps/configuration/App.vue | 82 ++-- _dev/index.html | 2 +- ps_accounts.php | 2 +- .../MigrateOrCreateIdentitiesV8Command.php | 26 ++ .../MigrateOrCreateIdentityV8Command.php | 38 ++ .../Command/VerifyIdentitiesCommand.php | 3 - .../MigrateOrCreateIdentitiesV8Handler.php | 39 ++ .../MigrateOrCreateIdentityV8Handler.php | 203 ++++++++++ src/Hook/DisplayAdminAfterHeader.php | 37 +- .../Controller/AbstractRestController.php | 1 + src/Polyfill/ConfigurationStorageSession.php | 2 +- src/Provider/ShopProvider.php | 34 +- src/Repository/ConfigurationRepository.php | 4 +- src/Service/Accounts/AccountsService.php | 69 ++-- src/ServiceProvider/CommandProvider.php | 22 +- .../CreateIdentityHandlerTest.php | 40 +- .../IdentifyContactHandlerTest.php | 40 +- .../MigrateOrCreateIdentityV8HandlerTest.php | 364 ++++++++++++++++++ .../VerifyIdentityHandlerTest.php | 20 +- upgrade/upgrade-8.0.0.php | 7 +- 20 files changed, 902 insertions(+), 133 deletions(-) create mode 100644 src/Account/Command/MigrateOrCreateIdentitiesV8Command.php create mode 100644 src/Account/Command/MigrateOrCreateIdentityV8Command.php create mode 100644 src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php create mode 100644 src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php create mode 100644 tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php diff --git a/_dev/apps/configuration/App.vue b/_dev/apps/configuration/App.vue index e6c62cf03..260b1f15d 100644 --- a/_dev/apps/configuration/App.vue +++ b/_dev/apps/configuration/App.vue @@ -17,53 +17,83 @@ * International Registered Trademark & Property of PrestaShop SA *--> diff --git a/_dev/index.html b/_dev/index.html index 0c7aa0325..030a6ff51 100644 --- a/_dev/index.html +++ b/_dev/index.html @@ -10,4 +10,4 @@
- \ No newline at end of file + diff --git a/ps_accounts.php b/ps_accounts.php index 1053157e8..b09d5cc92 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -443,7 +443,7 @@ public function onModuleReset() $commandBus = $this->getService(\PrestaShop\Module\PsAccounts\Cqrs\CommandBus::class); // Verification flow - $commandBus->handle(new \PrestaShop\Module\PsAccounts\Account\Command\CreateIdentitiesCommand()); + $commandBus->handle(new \PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentitiesV8Command()); } /** diff --git a/src/Account/Command/MigrateOrCreateIdentitiesV8Command.php b/src/Account/Command/MigrateOrCreateIdentitiesV8Command.php new file mode 100644 index 000000000..ccbdb3764 --- /dev/null +++ b/src/Account/Command/MigrateOrCreateIdentitiesV8Command.php @@ -0,0 +1,26 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\Command; + +class MigrateOrCreateIdentitiesV8Command +{ +} diff --git a/src/Account/Command/MigrateOrCreateIdentityV8Command.php b/src/Account/Command/MigrateOrCreateIdentityV8Command.php new file mode 100644 index 000000000..9f3e10562 --- /dev/null +++ b/src/Account/Command/MigrateOrCreateIdentityV8Command.php @@ -0,0 +1,38 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\Command; + +class MigrateOrCreateIdentityV8Command +{ + /** + * @var int|null + */ + public $shopId; + + /** + * @param int|null $shopId + */ + public function __construct($shopId) + { + $this->shopId = $shopId; + } +} diff --git a/src/Account/Command/VerifyIdentitiesCommand.php b/src/Account/Command/VerifyIdentitiesCommand.php index 30fbe5790..dfd50215c 100644 --- a/src/Account/Command/VerifyIdentitiesCommand.php +++ b/src/Account/Command/VerifyIdentitiesCommand.php @@ -23,7 +23,4 @@ class VerifyIdentitiesCommand { - public function __construct() - { - } } diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php new file mode 100644 index 000000000..0fffd981f --- /dev/null +++ b/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php @@ -0,0 +1,39 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; + +use PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentitiesV8Command; +use PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentityV8Command; + +class MigrateOrCreateIdentitiesV8Handler extends MultiShopHandler +{ + /** + * @param MigrateOrCreateIdentitiesV8Command $command + * + * @return void + */ + public function handle(MigrateOrCreateIdentitiesV8Command $command) + { + $this->handleMulti(function ($multiShopId) { + $this->commandBus->handle(new MigrateOrCreateIdentityV8Command($multiShopId)); + }); + } +} diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php new file mode 100644 index 000000000..15ef6a35a --- /dev/null +++ b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php @@ -0,0 +1,203 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; + +use PrestaShop\Module\PsAccounts\Account\Command\CreateIdentityCommand; +use PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentityV8Command; +use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; +use PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException; +use PrestaShop\Module\PsAccounts\Account\ProofManager; +use PrestaShop\Module\PsAccounts\Account\StatusManager; +use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; +use PrestaShop\Module\PsAccounts\Log\Logger; +use PrestaShop\Module\PsAccounts\Provider\ShopProvider; +use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; +use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Exception; +use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; + +class MigrateOrCreateIdentityV8Handler +{ + /** + * @var AccountsService + */ + private $accountsService; + + /** + * @var OAuth2Service + */ + protected $oAuth2Service; + + /** + * @var ShopProvider + */ + private $shopProvider; + + /** + * @var StatusManager + */ + private $statusManager; + + /** + * @var ProofManager + */ + protected $proofManager; + + /** + * @var ConfigurationRepository + */ + private $configurationRepository; + + /** + * @var CommandBus + */ + private $commandBus; + + /** + * @param AccountsService $accountsService + * @param OAuth2Service $oAuth2Service + * @param ShopProvider $shopProvider + * @param StatusManager $shopStatus + * @param ProofManager $proofManager + * @param ConfigurationRepository $configurationRepository + * @param CommandBus $commandBus + */ + public function __construct( + AccountsService $accountsService, + OAuth2Service $oAuth2Service, + ShopProvider $shopProvider, + StatusManager $shopStatus, + ProofManager $proofManager, + ConfigurationRepository $configurationRepository, + CommandBus $commandBus + ) { + $this->accountsService = $accountsService; + $this->oAuth2Service = $oAuth2Service; + $this->shopProvider = $shopProvider; + $this->statusManager = $shopStatus; + $this->proofManager = $proofManager; + $this->configurationRepository = $configurationRepository; + $this->commandBus = $commandBus; + } + + /** + * @param MigrateOrCreateIdentityV8Command $command + * + * @return void + */ + public function handle(MigrateOrCreateIdentityV8Command $command) + { + $shopId = $command->shopId ?: \Shop::getContextShopID(); + $shopUuid = $this->configurationRepository->getShopUuid(); + $lastUpgradedVersion = $this->configurationRepository->getLastUpgrade(false); + + $e = null; + try { + if (!$shopUuid || version_compare($lastUpgradedVersion, '8', '>=')) { + $this->upgradeVersionNumber(); + + $this->commandBus->handle(new CreateIdentityCommand($command->shopId)); + + return; + } + + // migrate cloudShopId locally + $this->statusManager->setCloudShopId($shopUuid); + + if (version_compare($lastUpgradedVersion, '7', '>=')) { + $token = $this->getAccessTokenV7($shopUuid); + } else { + $token = $this->getFirebaseTokenV6($shopUuid); + } + + // FIXME getLastUpgradedVersion from PS Core ? + $identityCreated = $this->accountsService->migrateShopIdentity( + $shopUuid, + $token, + $this->shopProvider->getUrl($shopId), + $this->proofManager->generateProof(), + $lastUpgradedVersion + ); + + if (!empty($identityCreated->clientId) && + !empty($identityCreated->clientSecret)) { + $this->oAuth2Service->getOAuth2Client()->update( + $identityCreated->clientId, + $identityCreated->clientSecret + ); + } + + // cleanup obsolete token + $this->configurationRepository->updateAccessToken(''); + + // update ps_accounts upgraded version + $this->upgradeVersionNumber(); + } catch (OAuth2Exception $e) { + } catch (AccountsException $e) { + } catch (RefreshTokenException $e) { + } catch (UnknownStatusException $e) { + } + + if ($e) { + Logger::getInstance()->error($e->getMessage()); + } + } + + /** + * @param string $shopUuid + * + * @return string + * + * @throws OAuth2Exception + */ + protected function getAccessTokenV7($shopUuid) + { + return $this->oAuth2Service->getAccessTokenByClientCredentials([], [ + // audience v7 + 'shop_' . $shopUuid, + ])->access_token; + } + + /** + * @param string $shopUuid + * + * @return string + * + * @throws AccountsException + */ + protected function getFirebaseTokenV6($shopUuid) + { + return $this->accountsService->refreshShopToken( + $this->configurationRepository->getFirebaseRefreshToken(), + $shopUuid + )->token; + } + + /** + * @return void + */ + protected function upgradeVersionNumber() + { + $this->configurationRepository->updateLastUpgrade(\Ps_accounts::VERSION); + } +} diff --git a/src/Hook/DisplayAdminAfterHeader.php b/src/Hook/DisplayAdminAfterHeader.php index cf03e43ff..5440b8e8b 100644 --- a/src/Hook/DisplayAdminAfterHeader.php +++ b/src/Hook/DisplayAdminAfterHeader.php @@ -20,6 +20,9 @@ namespace PrestaShop\Module\PsAccounts\Hook; +use PrestaShop\Module\PsAccounts\Adapter\Link; +use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\Exception\ParameterNotFoundException; + class DisplayAdminAfterHeader extends Hook { /** @@ -27,18 +30,42 @@ class DisplayAdminAfterHeader extends Hook */ public function execute(array $params = []) { - $cloudShopId = $this->module->getCloudShopId(); - $verified = $this->module->getVerifiedStatus(); - $verifiedMsg = $verified ? 'verified' : 'NOT verified'; + try { + if ('ERROR' === $this->module->getParameter('ps_accounts.log_level')) { + return ''; + } + } catch (ParameterNotFoundException $e) { + } + + try { + $cloudShopId = $this->module->getCloudShopId(); + $verified = $this->module->getVerifiedStatus(); + $verifiedMsg = $verified ? 'verified' : 'NOT verified'; - return <<module->getService(Link::class); + $moduleLink = $link->getAdminLink('AdminModules', true, [], [ + 'configure' => 'ps_accounts', + ]); + $debugLink = $link->getAdminLink('AdminDebugPsAccounts'); + $healthCheckLink = $link->getLink()->getModuleLink('ps_accounts', 'apiV2ShopHealthCheck'); + + return << HTML; + } catch (\Throwable $e) { + /* @phpstan-ignore-next-line */ + } catch (\Exception $e) { + } + + return ''; } } diff --git a/src/Http/Controller/AbstractRestController.php b/src/Http/Controller/AbstractRestController.php index 21cd4c022..629eabe82 100644 --- a/src/Http/Controller/AbstractRestController.php +++ b/src/Http/Controller/AbstractRestController.php @@ -111,6 +111,7 @@ public function postProcess() */ public function dieWithResponseJson(array $response, $httpResponseCode = null) { + ob_end_flush(); ob_end_clean(); if (is_integer($httpResponseCode)) { diff --git a/src/Polyfill/ConfigurationStorageSession.php b/src/Polyfill/ConfigurationStorageSession.php index a2ec8ae7a..da5e12b07 100644 --- a/src/Polyfill/ConfigurationStorageSession.php +++ b/src/Polyfill/ConfigurationStorageSession.php @@ -174,7 +174,7 @@ public function set($name, $value) */ public function all() { - $all = json_decode($this->configuration->getUncached($this->getConfigurationName()), true); + $all = json_decode($this->configuration->get($this->getConfigurationName(), false, false), true); if (is_array($all)) { return $all; diff --git a/src/Provider/ShopProvider.php b/src/Provider/ShopProvider.php index b7b67f8e1..fbe63d871 100644 --- a/src/Provider/ShopProvider.php +++ b/src/Provider/ShopProvider.php @@ -331,7 +331,6 @@ public function getUrl($shopId) public function getShops($groupId = null, $shopId = null, $refresh = false) { $shopList = []; - foreach (\Shop::getTree() as $groupData) { if ($groupId !== null && $groupId !== $groupData['id']) { continue; @@ -343,20 +342,25 @@ public function getShops($groupId = null, $shopId = null, $refresh = false) continue; } - $shopUrl = $this->getUrl((int) $shopData['id_shop']); - $shopStatus = $this->shopStatus->getStatus($refresh); - $identifyUrl = $this->oAuth2Service->getOAuth2Client()->getRedirectUri([ - 'action' => 'identifyPointOfContact', - ]); - - $shops[] = [ - 'id' => (int) $shopData['id_shop'], - 'name' => $shopData['name'], - 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), - 'frontendUrl' => $shopUrl->getFrontendUrl(), - 'identifyUrl' => $identifyUrl, - 'shopStatus' => $shopStatus, - ]; + $this->getShopContext()->execInShopContext( + $shopData['id_shop'], + function () use (&$shops, $shopData, $refresh) { + $shopUrl = $this->getUrl((int) $shopData['id_shop']); + $shopStatus = $this->shopStatus->getStatus($refresh); + $identifyUrl = $this->oAuth2Service->getOAuth2Client()->getRedirectUri([ + 'action' => 'identifyPointOfContact', + ]); + + $shops[] = [ + 'id' => (int) $shopData['id_shop'], + 'name' => $shopData['name'], + 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), + 'frontendUrl' => $shopUrl->getFrontendUrl(), + 'identifyUrl' => $identifyUrl, + 'shopStatus' => $shopStatus, + ]; + } + ); } $shopList[] = [ diff --git a/src/Repository/ConfigurationRepository.php b/src/Repository/ConfigurationRepository.php index 2b73e7245..05508ba26 100644 --- a/src/Repository/ConfigurationRepository.php +++ b/src/Repository/ConfigurationRepository.php @@ -358,7 +358,7 @@ public function updateLastUpgrade($upgrade) */ public function getLastUpgrade($cached = true) { - return $this->configuration->get(ConfigurationKeys::PS_ACCOUNTS_LAST_UPGRADE, false, $cached); + return $this->configuration->get(ConfigurationKeys::PS_ACCOUNTS_LAST_UPGRADE, false, $cached) ?: '0'; } /** @@ -402,7 +402,7 @@ public function updateShopProof($proof) */ public function getCachedShopStatus() { - return $this->configuration->getUncached(ConfigurationKeys::PS_ACCOUNTS_CACHED_SHOP_STATUS); + return $this->configuration->get(ConfigurationKeys::PS_ACCOUNTS_CACHED_SHOP_STATUS, false, false); } /** diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index 9e5115b5c..06d0b4c7f 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -113,7 +113,8 @@ public function firebaseTokens($cloudShopId, $accessToken) Request::HEADERS => $this->getHeaders([ 'Authorization' => 'Bearer ' . $accessToken, ]), - ]); + ] + ); if (!$response->isSuccessful) { throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to refresh token.')); @@ -145,7 +146,7 @@ public function refreshShopToken($refreshToken, $cloudShopId) ); if (!$response->isSuccessful) { - throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to refresh token.')); + throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to refresh shop token.')); } return new LegacyFirebaseToken($response->body); @@ -194,27 +195,6 @@ public function updateUserShop($ownerUid, $cloudShopId, $ownerToken, UpdateShop ); } -// /** -// * @param string $cloudShopId -// * @param string $shopToken -// * @param UpgradeModule $data -// * -// * @return Response -// */ -// public function upgradeShopModule($cloudShopId, $shopToken, UpgradeModule $data) -// { -// return $this->getClient()->post( -// '/v2/shop/module/update', -// [ -// Request::HEADERS => $this->getHeaders([ -// 'Authorization' => 'Bearer ' . $shopToken, -// 'X-Shop-Id' => $cloudShopId, -// ]), -// Request::JSON => $data->jsonSerialize(), -// ] -// ); -// } - /** * @param string $idToken * @@ -285,7 +265,8 @@ public function createShopIdentity(ShopUrl $shopUrl, $proof) public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $proof) { $response = $this->getClient()->post( - '/v1/shop-identities/' . $cloudShopId . '/verify', [ + '/v1/shop-identities/' . $cloudShopId . '/verify', + [ Request::HEADERS => $this->getHeaders([ 'Authorization' => 'Bearer ' . $shopToken, 'X-Shop-Id' => $cloudShopId, @@ -360,6 +341,43 @@ public function setPointOfContact($cloudShopId, $shopToken, $userToken) } } + /** + * @param string $cloudShopId + * @param string $shopToken + * @param ShopUrl $shopUrl + * @param string $proof + * @param string $fromVersion + * + * @return IdentityCreated + * + * @throws AccountsException + */ + public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $proof, $fromVersion) + { + $response = $this->getClient()->put( + '/v1/shop-identities/' . $cloudShopId . '/migrate', + [ + Request::HEADERS => $this->getHeaders([ + 'Authorization' => 'Bearer ' . $shopToken, + 'X-Shop-Id' => $cloudShopId, + ]), + Request::JSON => [ + 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), + 'frontendUrl' => $shopUrl->getFrontendUrl(), + 'multiShopId' => $shopUrl->getMultiShopId(), + 'proof' => $proof, + 'fromVersion' => $fromVersion, + ], + ] + ); + + if (!$response->isSuccessful) { + throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to migrate shop identity.')); + } + + return new IdentityCreated($response->body); + } + /** * @param Response $response * @param string $defaultMessage @@ -370,7 +388,8 @@ protected function getResponseErrorMsg(Response $response, $defaultMessage = '') { $msg = $defaultMessage; $body = $response->body; - if (isset($body['error']) && + if ( + isset($body['error']) && isset($body['error_description']) ) { $msg = $body['error'] . ': ' . $body['error_description']; diff --git a/src/ServiceProvider/CommandProvider.php b/src/ServiceProvider/CommandProvider.php index b3c811a81..4103f8b68 100644 --- a/src/ServiceProvider/CommandProvider.php +++ b/src/ServiceProvider/CommandProvider.php @@ -24,6 +24,8 @@ use PrestaShop\Module\PsAccounts\Account\CommandHandler\CreateIdentityHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\DeleteUserShopHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\IdentifyContactHandler; +use PrestaShop\Module\PsAccounts\Account\CommandHandler\MigrateOrCreateIdentitiesV8Handler; +use PrestaShop\Module\PsAccounts\Account\CommandHandler\MigrateOrCreateIdentityV8Handler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\UpdateUserShopHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\VerifyIdentitiesHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\VerifyIdentityHandler; @@ -33,8 +35,10 @@ use PrestaShop\Module\PsAccounts\Context\ShopContext; use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; +use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; +use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\Contract\IServiceProvider; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\ServiceContainer; @@ -89,7 +93,6 @@ public function provide(ServiceContainer $container) $container->get(CommandBus::class) ); }); - $container->registerProvider(IdentifyContactHandler::class, static function () use ($container) { return new IdentifyContactHandler( $container->get(AccountsService::class), @@ -97,5 +100,22 @@ public function provide(ServiceContainer $container) $container->get(Session\ShopSession::class) ); }); + $container->registerProvider(MigrateOrCreateIdentitiesV8Handler::class, static function () use ($container) { + return new MigrateOrCreateIdentitiesV8Handler( + $container->get(ShopContext::class), + $container->get(CommandBus::class) + ); + }); + $container->registerProvider(MigrateOrCreateIdentityV8Handler::class, static function () use ($container) { + return new MigrateOrCreateIdentityV8Handler( + $container->get(AccountsService::class), + $container->get(OAuth2Service::class), + $container->get(ShopProvider::class), + $container->get(StatusManager::class), + $container->get(ProofManager::class), + $container->get(ConfigurationRepository::class), + $container->get(CommandBus::class) + ); + }); } } diff --git a/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php index c477c243b..a3e60dc40 100644 --- a/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php @@ -11,6 +11,7 @@ use PrestaShop\Module\PsAccounts\Account\ProofManager; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Http\Client\Curl\Client; +use PrestaShop\Module\PsAccounts\Http\Client\Request; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; @@ -94,15 +95,19 @@ public function itShouldStoreIdentity() $this->statusManager->setCloudShopId(''); $this->client->method('post') - ->willReturnCallback(function ($route) use ($clientId, $clientSecret, $cloudShopId) { - if (preg_match('/v1\/shop-identities$/', $route)) { - return $this->createResponse([ - 'clientId' => $clientId, - 'clientSecret' => $clientSecret, - "cloudShopId" => $cloudShopId - ], 200, true); - } - return $this->createResponse([], 500, true); + ->with($this->matchesRegularExpression('/v1\/shop-identities$/')) + ->willReturnCallback(function ($route, $options) use ($clientId, $clientSecret, $cloudShopId) { + + $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); + $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); + $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); + + return $this->createResponse([ + 'clientId' => $clientId, + 'clientSecret' => $clientSecret, + "cloudShopId" => $cloudShopId + ], 200, true); }); $this->getHandler()->handle(new CreateIdentityCommand(1, [])); @@ -133,14 +138,17 @@ public function itShouldNotChangeIdentityIfExists() "cloudShopId" => $cloudShopId . 'Baz' ], 200, true); - $this->client - ->method('post') - ->willReturnCallback(function ($route) use ($id1, $id2, $cloudShopId) { + $this->client->method('post') + ->with($this->matchesRegularExpression('/v1\/shop-identities$/')) + ->willReturnCallback(function ($route, $options) use ($id1, $id2, $cloudShopId) { + + $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); + $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); + $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); + static $count = 1; - if (preg_match('/v1\/shop-identities$/', $route)) { - return $count++ === 1 ? $id1 : $id2; - } - return $this->createApiResponse([], 500, true); + return $count++ === 1 ? $id1 : $id2; }); $this->oauth2Client->delete(); diff --git a/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php b/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php index 17ae10782..e05e40f3e 100644 --- a/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php @@ -9,7 +9,9 @@ use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Http\Client\Curl\Client; +use PrestaShop\Module\PsAccounts\Http\Client\Request; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\IdentityCreated; use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\AccessToken; use PrestaShop\Module\PsAccounts\Tests\TestCase; @@ -17,9 +19,7 @@ class IdentifyContactHandlerTest extends TestCase { /** - * @inject - * - * @var AccountsService + * @var AccountsService&MockObject */ protected $accountsService; @@ -31,9 +31,7 @@ class IdentifyContactHandlerTest extends TestCase public $statusManager; /** - * @inject - * - * @var ShopSession + * @var ShopSession&MockObject */ protected $shopSession; @@ -62,24 +60,12 @@ public function set_up() */ public function itShouldSaveIdentityContact() { - $pointOfContactEmail = $this->faker->email; - $pointOfContactUuid = $this->faker->uuid; $cloudShopId = $this->faker->uuid; $this->statusManager->setCloudShopId($cloudShopId); $this->shopSession->method('getValidToken')->willReturn("valid_token"); - $this->client->method('post') - ->willReturnCallback(function ($route) use ($pointOfContactEmail, $pointOfContactUuid, $cloudShopId) { - if (preg_match('/v1\/shop-identities\/' . $cloudShopId . '\/point-of-contact$/', $route)) { - return $this->createResponse([ - 'success' => true, - ], 200, true); - } - return $this->createResponse([], 500, true); - }); - // Expected call to setPointOfContact with correct parameters $this->accountsService->expects($this->once()) ->method('setPointOfContact') @@ -97,11 +83,11 @@ public function itShouldSaveIdentityContact() ]) ]))->toArray())); - $this->getHandler()->handle(new IdentifyContactCommand(new AccessToken( - [ - 'access_token' => 'valid_access_token', - ] - ))); + $this->getHandler()->handle(new IdentifyContactCommand(new AccessToken([ + 'access_token' => 'valid_access_token', + ]))); + + $this->assertTrue($this->statusManager->cacheInvalidated()); } /** @@ -132,11 +118,9 @@ public function itShouldNotSaveIdentityContactOnShopNotVerified() ]) ]))->toArray())); - $this->getHandler()->handle(new IdentifyContactCommand(new AccessToken( - [ - 'access_token' => 'valid_access_token', - ] - ))); + $this->getHandler()->handle(new IdentifyContactCommand(new AccessToken([ + 'access_token' => 'valid_access_token', + ]))); } /** diff --git a/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php new file mode 100644 index 000000000..c2be5ac86 --- /dev/null +++ b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php @@ -0,0 +1,364 @@ +shopId = $this->shopProvider->getShopContext()->getContext()->shop->id; + + $this->accountsClient = $this->createMock(Client::class); + $this->oAuth2Client = $this->createMock(Client::class); + $this->accountsService->setClient($this->accountsClient); + $this->oAuth2Service->setHttpClient($this->oAuth2Client); + +// $this->accountsService = $this->createMock(AccountsService::class); +// $this->oAuth2Service = $this->createMock(OAuth2Service::class); + + $this->wellKnownResponse = $this->createResponse($this->wellKnown); + } + + /** + * @test + */ + public function itShouldMigrateIdentityFromV7() + { + $cloudShopId = $this->faker->uuid; + $clientId = $this->faker->uuid; + $clientSecret = $this->faker->uuid; + $token = $this->faker->uuid; + $tokenAudience = 'shop_' . $cloudShopId; + + // introduced in v7 + $this->configurationRepository->updateLastUpgrade('7.2.0'); + + $this->configurationRepository->updateShopUuid($cloudShopId); + + // FIXME: test OAuth2Service in a dedicated Class + // preliminary check to require refresh token + $this->oAuth2Service->getOAuth2Client()->update($clientId, $clientSecret); + + $this->oAuth2Client->method('get') + ->with($this->matchesRegularExpression('/openid-configuration/')) + ->willReturn($this->wellKnownResponse); + + $this->oAuth2Client->method('post') + ->with($this->matchesRegularExpression('@' . $this->oAuth2Service->getWellKnown()->token_endpoint . '@')) + ->willReturnCallback(function ($route, $options) use ($cloudShopId, $clientId, $clientSecret, $token, $tokenAudience) { + + $this->assertTrue((bool) preg_match('/' . $tokenAudience . '/', $options[Request::FORM]['audience'])); + + return $this->createResponse([ + 'access_token' => $token, + ], 200, true); + }); + + // FIXME: test AccountsClient in a dedicated Class + $this->accountsClient->method('put') + ->with( + $this->matches('/v1/shop-identities/' . $cloudShopId . '/migrate'), + $this->isType('array') + ) + ->willReturnCallback(function ($route, $options) use ($cloudShopId, $clientId, $clientSecret, $token) { + + $this->assertEquals('Bearer ' . $token, $options[Request::HEADERS]['Authorization']); + $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); + $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); + $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); + $this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); + + return $this->createResponse([ + 'clientId' => $clientId, + 'clientSecret' => $clientSecret, + "cloudShopId" => $cloudShopId + ], 200, true); + }); + + $this->getHandler()->handle(new MigrateOrCreateIdentityV8Command($this->shopId)); + + $this->assertEmpty($this->configurationRepository->getAccessToken()); + $this->assertTrue($this->statusManager->cacheInvalidated()); + $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); + $this->assertEquals($clientId, $this->oAuth2Service->getOAuth2Client()->getClientId()); + $this->assertEquals($clientSecret, $this->oAuth2Service->getOAuth2Client()->getClientSecret()); + } + +// /** +// * @test +// */ +// public function itShouldMigrateIdentityFromV7() +// { +// $cloudShopId = $this->faker->uuid; +// $clientId = $this->faker->uuid; +// $clientSecret = $this->faker->uuid; +// +// // introduced in v7 +// $this->configurationRepository->updateLastUpgrade('7.2.0'); +// +// $this->configurationRepository->updateShopUuid($cloudShopId); +// +// /** @var OAuth2Client&MockObject $oAuth2Client */ +// $oAuth2Client = $this->createMock(OAuth2Client::class); +// +// $this->oAuth2Service +// ->method('getOAuth2Client') +// ->willReturn($oAuth2Client); +// +// $accessToken = new AccessToken([ +// 'access_token' => $this->faker->uuid, +// ]); +// +// $identityCreated = new IdentityCreated([ +// 'cloudShopId' => $cloudShopId, +// 'clientId' => $clientId, +// 'clientSecret' => $clientSecret, +// ]); +// +// $this->oAuth2Service +// ->expects($this->once()) +// ->method('getAccessTokenByClientCredentials') +// ->willReturn($accessToken); +// +// $this->accountsService +// ->expects($this->once()) +// ->method('migrateShopIdentity') +// ->with( +// $this->equalTo($cloudShopId), +// $this->equalTo($accessToken->access_token), +// $this->isInstanceOf(ShopUrl::class), +// $this->isType('string') +// ) +// ->willReturn($identityCreated); +// +// $oAuth2Client +// ->expects($this->once()) +// ->method('update') +// ->with($clientId, $clientSecret); +// +// $this->getHandler()->handle(new MigrateShopIdentityCommand($this->shopId, '')); +// +// $this->assertEmpty($this->configurationRepository->getAccessToken()); +// $this->assertTrue($this->statusManager->cacheInvalidated()); +// $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); +// } + + /** + * @test + */ + public function itShouldMigrateIdentityFromV5AndV6() + { + $cloudShopId = $this->faker->uuid; + $clientId = $this->faker->uuid; + $clientSecret = $this->faker->uuid; + $token = $this->faker->uuid; + + // introduced in v7 + $this->configurationRepository->updateLastUpgrade(null); + + $this->configurationRepository->updateShopUuid($cloudShopId); + + // FIXME: test AccountsClient in a dedicated Class + $this->accountsClient->method('post') + ->with($this->matchesRegularExpression('/v1\/shop\/token\/refresh/')) + ->willReturnCallback(function ($route, $options) use ($token) { + return $this->createResponse([ + 'token' => $token, + 'refresh_token' => $this->faker->uuid, + 'id_token' => $this->faker->uuid, + ], 200, true); + }); + + // FIXME: test AccountsClient in a dedicated Class + $this->accountsClient->method('put') + ->with( + $this->matches('/v1/shop-identities/' . $cloudShopId . '/migrate'), + $this->isType('array') + ) + ->willReturnCallback(function ($route, $options) use ($cloudShopId, $clientId, $clientSecret, $token) { + + $this->assertEquals('Bearer ' . $token, $options[Request::HEADERS]['Authorization']); + $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); + $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); + $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); + $this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); + + return $this->createResponse([ + 'clientId' => $clientId, + 'clientSecret' => $clientSecret, + "cloudShopId" => $cloudShopId + ], 200, true); + }); + + $this->getHandler()->handle(new MigrateOrCreateIdentityV8Command($this->shopId)); + + $this->assertTrue($this->statusManager->cacheInvalidated()); + $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); + $this->assertEquals($clientId, $this->oAuth2Service->getOAuth2Client()->getClientId()); + $this->assertEquals($clientSecret, $this->oAuth2Service->getOAuth2Client()->getClientSecret()); + } + + /** + * @test + */ + public function itShouldNotMigrateOnError() + { + $cloudShopId = $this->faker->uuid; + $clientId = $this->faker->uuid; + $clientSecret = $this->faker->uuid; + $token = $this->faker->uuid; + $tokenAudience = 'shop_' . $cloudShopId; + + // introduced in v7 + $this->configurationRepository->updateLastUpgrade('7.2.0'); + + $this->configurationRepository->updateShopUuid($cloudShopId); + + // FIXME: test OAuth2Service in a dedicated Class + // preliminary check to require refresh token + $this->oAuth2Service->getOAuth2Client()->update($clientId, $clientSecret); + + $this->oAuth2Client->method('get') + ->with($this->matchesRegularExpression('/openid-configuration/')) + ->willReturn($this->wellKnownResponse); + + $this->oAuth2Client->method('post') + ->with($this->matchesRegularExpression('@' . $this->oAuth2Service->getWellKnown()->token_endpoint . '@')) + ->willReturnCallback(function ($route, $options) use ($cloudShopId, $clientId, $clientSecret, $token, $tokenAudience) { + + $this->assertTrue((bool) preg_match('/' . $tokenAudience . '/', $options[Request::FORM]['audience'])); + + return $this->createResponse([ + 'access_token' => $token, + ], 200, true); + }); + + // FIXME: test AccountsClient in a dedicated Class + $this->accountsClient->method('put') + ->with( + $this->matches('/v1/shop-identities/' . $cloudShopId . '/migrate'), + $this->isType('array') + ) + ->willReturnCallback(function ($route, $options) use ($cloudShopId, $clientId, $clientSecret, $token) { + + $this->assertEquals('Bearer ' . $token, $options[Request::HEADERS]['Authorization']); + $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); + $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); + $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); + $this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); + + return $this->createResponse([ + "error" => 'store-identity/migration-failed', + "message" => 'Cannot migrate shop', + ], 400, true); + }); + + $this->getHandler()->handle(new MigrateOrCreateIdentityV8Command($this->shopId)); + + $this->assertTrue($this->statusManager->cacheInvalidated()); + $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); + + // FIXME: test something relevant + } + + /** + * @return MigrateOrCreateIdentityV8Handler + */ + private function getHandler() + { + return new MigrateOrCreateIdentityV8Handler( + $this->accountsService, + $this->oAuth2Service, + $this->shopProvider, + $this->statusManager, + $this->proofManager, + $this->configurationRepository, + $this->commandBus + ); + } +} diff --git a/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php b/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php index e60c5abcd..65b786071 100644 --- a/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php @@ -10,6 +10,7 @@ use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Http\Client\Curl\Client; +use PrestaShop\Module\PsAccounts\Http\Client\Request; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; @@ -87,13 +88,18 @@ public function itShouldInvalidateCachedStatusOnSuccess() ]))->toArray())); $this->client->method('post') - ->willReturnCallback(function ($route) use ($cloudShopId) { - if (preg_match('/v1\/shop-identities\/' . $cloudShopId . '\/verify$/', $route)) { - return $this->createResponse([ - "success" => true, - ], 200, true); - } - return $this->createResponse([], 500, true); + ->with($this->matchesRegularExpression('/v1\/shop-identities\/' . $cloudShopId . '\/verify$/')) + ->willReturnCallback(function ($route, $options) use ($cloudShopId) { + + $this->assertArrayHasKey('Authorization', $options[Request::HEADERS]); + $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); + $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); + $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); + + return $this->createResponse([ + "success" => true, + ], 200, true); }); $this->getHandler()->handle(new VerifyIdentityCommand(1)); diff --git a/upgrade/upgrade-8.0.0.php b/upgrade/upgrade-8.0.0.php index c027c33c0..b39e7798d 100644 --- a/upgrade/upgrade-8.0.0.php +++ b/upgrade/upgrade-8.0.0.php @@ -1,6 +1,6 @@ getService(CommandBus::class); - $commandBus->handle(new CreateIdentitiesCommand()); + + $commandBus->handle(new MigrateOrCreateIdentitiesV8Command()); + /* @phpstan-ignore-next-line */ } catch (\Throwable $e) { + Logger::getInstance()->error('error during upgrade : ' . $e->getMessage()); } catch (\Exception $e) { Logger::getInstance()->error('error during upgrade : ' . $e->getMessage()); } From 1d1a9c9cc2353fd8ce77222bb42584f18f4f2511 Mon Sep 17 00:00:00 2001 From: hschoenenberger Date: Fri, 27 Jun 2025 16:44:47 +0200 Subject: [PATCH 11/46] fix: debug hook display condition --- src/Hook/DisplayAdminAfterHeader.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Hook/DisplayAdminAfterHeader.php b/src/Hook/DisplayAdminAfterHeader.php index 5440b8e8b..f98a3d5f4 100644 --- a/src/Hook/DisplayAdminAfterHeader.php +++ b/src/Hook/DisplayAdminAfterHeader.php @@ -21,7 +21,6 @@ namespace PrestaShop\Module\PsAccounts\Hook; use PrestaShop\Module\PsAccounts\Adapter\Link; -use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\Exception\ParameterNotFoundException; class DisplayAdminAfterHeader extends Hook { @@ -34,10 +33,7 @@ public function execute(array $params = []) if ('ERROR' === $this->module->getParameter('ps_accounts.log_level')) { return ''; } - } catch (ParameterNotFoundException $e) { - } - try { $cloudShopId = $this->module->getCloudShopId(); $verified = $this->module->getVerifiedStatus(); $verifiedMsg = $verified ? 'verified' : 'NOT verified'; From 6e558e65a9e6e56a36919dc55e4309c8aa302481 Mon Sep 17 00:00:00 2001 From: hschoenenberger Date: Fri, 27 Jun 2025 17:07:25 +0200 Subject: [PATCH 12/46] fix: cache invalidation --- src/Account/CommandHandler/CreateIdentityHandler.php | 1 + src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/Account/CommandHandler/CreateIdentityHandler.php b/src/Account/CommandHandler/CreateIdentityHandler.php index 1a9d9d1d8..f58149f60 100644 --- a/src/Account/CommandHandler/CreateIdentityHandler.php +++ b/src/Account/CommandHandler/CreateIdentityHandler.php @@ -111,6 +111,7 @@ public function handle(CreateIdentityCommand $command) $identityCreated->clientSecret ); $this->statusManager->setCloudShopId($identityCreated->cloudShopId); + $this->statusManager->invalidateCache(); } else { $this->commandBus->handle(new VerifyIdentityCommand($command->shopId)); } diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php index 15ef6a35a..f09ff8102 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php @@ -150,6 +150,8 @@ public function handle(MigrateOrCreateIdentityV8Command $command) // cleanup obsolete token $this->configurationRepository->updateAccessToken(''); + $this->statusManager->invalidateCache(); + // update ps_accounts upgraded version $this->upgradeVersionNumber(); } catch (OAuth2Exception $e) { From 65129c000fda97a375f6b94d8dbcdb6a45773e44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Tue, 1 Jul 2025 13:52:42 +0200 Subject: [PATCH 13/46] feat: add shop status accessors into PsAccountsService and update readme (#508) * feat: add shop status accessors into PsAccountsService and update readme * feat: add shop identity methods and require shop.verified scope * feat: add shop identity methods and require shop.verified scope * test: include isVerified field in ShopStatus mock * test: replace shop data with shop UUID in ShopVerifyProof tests --- README.md | 38 +++++++++++++++++++ controllers/front/apiV2ShopProof.php | 2 + src/Account/Session/ShopSession.php | 10 ++++- src/Account/StatusManager.php | 14 +++++++ src/Service/PsAccountsService.php | 27 ++++++++++++- .../Api/V2/ShopVerifyProof/ShowTest.php | 12 +++--- .../PsAccountsService/IsAccountLinkedTest.php | 1 + 7 files changed, 94 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 21e6c5047..08fd1891a 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,44 @@ This module provides the following tokens: - **ShopAccessToken** (provided by [Prestashop OpenId Connect Provider](https://oauth.prestashop.com/.well-known/openid-configuration)) For machine to machine calls. (also used to keep up to date legacy Shop and Owner tokens +### How to get shop status + +#### Retrieving v8 Shop Status +```php +// /!\ TODO: Starting here you are responsible to check that the module is installed + +/** @var Ps_accounts $module */ +$module = \Module::getModuleIdByName('ps_accounts'); + +/** @var \PrestaShop\Module\PsAccounts\Service\PsAccountsService $service */ +$service = $module->getService(\PrestaShop\Module\PsAccounts\Service\PsAccountsService::class); + +// Starting from v8 status has been split into 3 distinct information +$service->isShopIdentityCreated(); +$service->isShopIdentityVerified(); +$service->isShopPointOfContactSet(); +``` + +#### Shop status compatibility with v7 +```php + +// strictly equivalent to: +// service->isShopIdentityCreated() && +// $service->isShopIdentityVerified() && +// $service->isShopPointOfContactSet() +$isShopLinked = $service->isAccountLinked(); +``` + +#### Tokens legacy compatibility table + +| | PrestaShop AccessToken | Scopes | Legacy
Firebase Shop Id Token | Legacy
Firebase User Id Token | +|-------------------------|------------------------|---------------|-----------------------------------|-----------------------------------| +| isShopIdentityCreated | Yes | | Yes | No | +| isShopIdentityVerified | Yes | shop.verified | Yes | No | +| isShopPointOfContactSet | Yes | shop.verified | Yes | Yes | +| isAccountLinked | Yes | shop.verified | Yes | Yes | + + ### How to get up-to-date JWT Shop Access Tokens ```php diff --git a/controllers/front/apiV2ShopProof.php b/controllers/front/apiV2ShopProof.php index 0a3ef4b39..bf28993ad 100644 --- a/controllers/front/apiV2ShopProof.php +++ b/controllers/front/apiV2ShopProof.php @@ -75,6 +75,8 @@ public function __construct() */ public function show(Shop $shop, array $payload) { + $this->statusManager->invalidateCache(); + return [ 'proof' => $this->proofManager->getProof(), ]; diff --git a/src/Account/Session/ShopSession.php b/src/Account/Session/ShopSession.php index c3e41e60c..22abb7fc2 100644 --- a/src/Account/Session/ShopSession.php +++ b/src/Account/Session/ShopSession.php @@ -76,10 +76,16 @@ public function __construct( public function refreshToken($refreshToken = null) { try { - $accessToken = $this->getAccessToken([], [ + $scopes = $this->statusManager->identityVerified() ? [ + 'shop.verified', + ] : []; + + $audience = [ 'store/' . $this->statusManager->getCloudShopId(), $this->tokenAudience, - ]); + ]; + + $accessToken = $this->getAccessToken($scopes, $audience); $this->setToken( $accessToken->access_token, diff --git a/src/Account/StatusManager.php b/src/Account/StatusManager.php index bf2214fc4..e0a8f9611 100644 --- a/src/Account/StatusManager.php +++ b/src/Account/StatusManager.php @@ -79,6 +79,20 @@ public function identityCreated() return !empty($this->getCloudShopId()); } + /** + * @param bool $cachedStatus + * + * @return bool + */ + public function identityVerified($cachedStatus = true) + { + try { + return $this->getStatus($cachedStatus)->isVerified; + } catch (UnknownStatusException $e) { + return false; + } + } + /** * @param bool $cachedOnly * @param int $cacheTtl diff --git a/src/Service/PsAccountsService.php b/src/Service/PsAccountsService.php index 99a2c43b7..dd05bab21 100644 --- a/src/Service/PsAccountsService.php +++ b/src/Service/PsAccountsService.php @@ -203,16 +203,39 @@ public function getEmail() /** * @return bool * - * @throws \Exception - * * @deprecated since v8.0.0 */ public function isAccountLinked() { return $this->statusManager->identityCreated() && + $this->statusManager->identityVerified() && $this->statusManager->getPointOfContactUuid(); } + /** + * @return bool + */ + public function isShopIdentityCreated() + { + return $this->statusManager->identityCreated(); + } + + /** + * @return bool + */ + public function isShopIdentityVerified() + { + return $this->statusManager->identityVerified(); + } + + /** + * @return bool + */ + public function isShopPointOfContactSet() + { + return (bool) $this->statusManager->getPointOfContactUuid(); + } + /** * @return bool * diff --git a/tests/src/Feature/Api/V2/ShopVerifyProof/ShowTest.php b/tests/src/Feature/Api/V2/ShopVerifyProof/ShowTest.php index 93c10ce9a..9d398d034 100644 --- a/tests/src/Feature/Api/V2/ShopVerifyProof/ShowTest.php +++ b/tests/src/Feature/Api/V2/ShopVerifyProof/ShowTest.php @@ -31,7 +31,7 @@ class ShowTest extends TestCase */ public function itShouldShowExpectedProof() { - $shop = $this->shopProvider->formatShopData((array) \Shop::getShop(1)); + $shopUuid = $this->configurationRepository->getShopUuid(); $proof = $this->manageProof->generateProof(); @@ -39,7 +39,7 @@ public function itShouldShowExpectedProof() 'headers' => [ AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $this->makeBearer([ 'aud' => [ - 'ps_accounts/' . $shop->uuid, + 'ps_accounts/' . $shopUuid, ], 'scp' => [ 'shop.proof.read', @@ -47,7 +47,7 @@ public function itShouldShowExpectedProof() ]), ], 'query' => [ - 'shop_id' => $shop->id, + 'shop_id' => 1, ] ]); @@ -65,7 +65,7 @@ public function itShouldShowExpectedProof() */ public function itShouldShowEmptyProof() { - $shop = $this->shopProvider->formatShopData((array) \Shop::getShop(1)); + $shopUuid = $this->configurationRepository->getShopUuid(); //$proof = $this->manageProof->generateProof(); @@ -78,7 +78,7 @@ public function itShouldShowEmptyProof() 'headers' => [ AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $this->makeBearer([ 'aud' => [ - 'ps_accounts/' . $shop->uuid, + 'ps_accounts/' . $shopUuid, ], 'scp' => [ 'shop.proof.read', @@ -86,7 +86,7 @@ public function itShouldShowEmptyProof() ]), ], 'query' => [ - 'shop_id' => $shop->id, + 'shop_id' => 1, ] ]); diff --git a/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedTest.php b/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedTest.php index 09ae618ed..1a5bd9e92 100644 --- a/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedTest.php +++ b/tests/src/Unit/Service/PsAccountsService/IsAccountLinkedTest.php @@ -24,6 +24,7 @@ public function set_up() 'isValid' => true, 'shopStatus' => new ShopStatus([ 'cloudShopId' => $this->faker->uuid, + 'isVerified' => true, 'pointOfContactUuid' => $this->faker->uuid, ]) ]))->toArray())); From 37718ac55296a2b21901150f18329e891d2ab500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:03:55 +0200 Subject: [PATCH 14/46] feat: refresh invalid token (#510) * feat: add shop status accessors into PsAccountsService and update readme * feat: add shop identity methods and require shop.verified scope * feat: add shop identity methods and require shop.verified scope * test: include isVerified field in ShopStatus mock * test: replace shop data with shop UUID in ShopVerifyProof tests * feat: replace the refresh token expiry check with an extended validity check * feat: add missing getValidTokenTests * docs: update token compatibility table in README --- README.md | 18 +- .../Session/Firebase/FirebaseSession.php | 4 +- src/Account/Session/Session.php | 8 +- src/Account/Session/SessionInterface.php | 8 +- src/Account/Session/ShopSession.php | 39 ++- src/Account/Token/Token.php | 69 +++++ tests/src/BaseTestCase.php | 5 + .../Session/ShopSession/GetValidTokenTest.php | 261 ++++++++++++++++++ .../PsAccountsService/GetShopTokenTest.php | 17 +- 9 files changed, 400 insertions(+), 29 deletions(-) create mode 100644 tests/src/Unit/Account/Session/ShopSession/GetValidTokenTest.php diff --git a/README.md b/README.md index 08fd1891a..fb30b1bf0 100644 --- a/README.md +++ b/README.md @@ -119,14 +119,16 @@ $service->isShopPointOfContactSet(); $isShopLinked = $service->isAccountLinked(); ``` -#### Tokens legacy compatibility table - -| | PrestaShop AccessToken | Scopes | Legacy
Firebase Shop Id Token | Legacy
Firebase User Id Token | -|-------------------------|------------------------|---------------|-----------------------------------|-----------------------------------| -| isShopIdentityCreated | Yes | | Yes | No | -| isShopIdentityVerified | Yes | shop.verified | Yes | No | -| isShopPointOfContactSet | Yes | shop.verified | Yes | Yes | -| isAccountLinked | Yes | shop.verified | Yes | Yes | +#### Tokens availability with legacy compatibility table + +| Method Name | PrestaShop AccessToken | Scopes | **_Legacy_**
Firebase Shop Id Token | **_Legacy_**
Firebase User Id Token | +|-------------------------|------------------------|---------------|-----------------------------------------|-----------------------------------------| +| **_>= v8.0.0_** | | | | | +| isShopIdentityCreated | Yes | | Yes | No | +| isShopIdentityVerified | Yes | shop.verified | Yes | No | +| isShopPointOfContactSet | Yes | shop.verified | Yes | Yes | +| **_< v8.0.0_** | | | | | +| isAccountLinked | Yes | shop.verified | Yes | Yes | ### How to get up-to-date JWT Shop Access Tokens diff --git a/src/Account/Session/Firebase/FirebaseSession.php b/src/Account/Session/Firebase/FirebaseSession.php index 28f9ecfbd..dd105513b 100644 --- a/src/Account/Session/Firebase/FirebaseSession.php +++ b/src/Account/Session/Firebase/FirebaseSession.php @@ -82,12 +82,14 @@ public function getShopSession() /** * @param string $refreshToken + * @param array $scope + * @param array $audience * * @return Token * * @throws RefreshTokenException */ - public function refreshToken($refreshToken = null) + public function refreshToken($refreshToken = null, array $scope = [], array $audience = []) { try { $token = $this->shopSession->getValidToken(); diff --git a/src/Account/Session/Session.php b/src/Account/Session/Session.php index 536e08811..e61fc9ce1 100644 --- a/src/Account/Session/Session.php +++ b/src/Account/Session/Session.php @@ -47,12 +47,14 @@ public function getOrRefreshToken($forceRefresh = false) /** * @param bool $forceRefresh * @param bool $throw + * @param array $scope + * @param array $audience * * @return Token * * @throws RefreshTokenException */ - public function getValidToken($forceRefresh = false, $throw = true) + public function getValidToken($forceRefresh = false, $throw = true, array $scope = [], array $audience = []) { /* * Avoid multiple refreshToken calls in the same runtime: @@ -68,9 +70,9 @@ public function getValidToken($forceRefresh = false, $throw = true) return $this->getToken(); } - if (true === $forceRefresh || $this->getToken()->isExpired()) { + if (true === $forceRefresh || false === $this->getToken()->isValid($scope, $audience)) { try { - $this->refreshToken(null); + $this->refreshToken(null, $scope, $audience); } catch (RefreshTokenException $e) { $this->setToken(''); $this->setRefreshTokenErrors(static::class, $e->getMessage()); diff --git a/src/Account/Session/SessionInterface.php b/src/Account/Session/SessionInterface.php index 2dc459bc5..1b8f2d02d 100644 --- a/src/Account/Session/SessionInterface.php +++ b/src/Account/Session/SessionInterface.php @@ -42,12 +42,14 @@ public function setToken($token, $refreshToken = null); * Refreshes and saves refreshed token * * @param string|null $refreshToken + * @param array $scope + * @param array $audience * * @return Token * * @throws RefreshTokenException */ - public function refreshToken($refreshToken = null); + public function refreshToken($refreshToken = null, array $scope = [], array $audience = []); /** * @deprecated use getValidToken instead @@ -65,12 +67,14 @@ public function getOrRefreshToken($forceRefresh = false); * * @param bool $forceRefresh * @param bool $throw + * @param array $scope + * @param array $audience * * @return Token * * @throws RefreshTokenException */ - public function getValidToken($forceRefresh = false, $throw = true); + public function getValidToken($forceRefresh = false, $throw = true, array $scope = [], array $audience = []); /** * @return void diff --git a/src/Account/Session/ShopSession.php b/src/Account/Session/ShopSession.php index 22abb7fc2..f1cf9ab64 100644 --- a/src/Account/Session/ShopSession.php +++ b/src/Account/Session/ShopSession.php @@ -67,25 +67,42 @@ public function __construct( } /** - * @param string $refreshToken + * @param bool $forceRefresh + * @param bool $throw + * @param array $scope + * @param array $audience * * @return Token * * @throws RefreshTokenException */ - public function refreshToken($refreshToken = null) + public function getValidToken($forceRefresh = false, $throw = true, array $scope = [], array $audience = []) { - try { - $scopes = $this->statusManager->identityVerified() ? [ - 'shop.verified', - ] : []; + $scp = $scope + ($this->statusManager->identityVerified() ? [ + 'shop.verified', + ] : []); - $audience = [ - 'store/' . $this->statusManager->getCloudShopId(), - $this->tokenAudience, - ]; + $aud = $audience + [ + 'store/' . $this->statusManager->getCloudShopId(), + $this->tokenAudience, + ]; - $accessToken = $this->getAccessToken($scopes, $audience); + return parent::getValidToken($forceRefresh, $throw, $scp, $aud); + } + + /** + * @param string $refreshToken + * @param array $scope + * @param array $audience + * + * @return Token + * + * @throws RefreshTokenException + */ + public function refreshToken($refreshToken = null, array $scope = [], array $audience = []) + { + try { + $accessToken = $this->getAccessToken($scope, $audience); $this->setToken( $accessToken->access_token, diff --git a/src/Account/Token/Token.php b/src/Account/Token/Token.php index da344c8c8..48938c2da 100644 --- a/src/Account/Token/Token.php +++ b/src/Account/Token/Token.php @@ -20,6 +20,7 @@ namespace PrestaShop\Module\PsAccounts\Account\Token; +use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Parser; use PrestaShop\Module\PsAccounts\Vendor\Lcobucci\JWT\Token\InvalidTokenStructure; @@ -73,6 +74,74 @@ public function isExpired() return $token->isExpired(new \DateTime()); } + /** + * @param array $scope + * + * @return bool + */ + public function hasScope(array $scope) + { + if ($scope === []) { + return true; + } + + $claims = $this->getJwt()->claims(); + if (!$claims->has('scp')) { + return false; + } + $scp = $claims->get('scp'); + + return count(array_intersect($scope, $scp)) == count($scope); + } + + /** + * @param array $audience + * + * @return bool + */ + public function hasAudience(array $audience) + { + if ($audience === []) { + return true; + } + + $claims = $this->getJwt()->claims(); + if (!$claims->has('aud')) { + return false; + } + $aud = $claims->get('aud'); + + return count(array_intersect($audience, $aud)) == count($audience); + } + + /** + * @param array $scope + * @param array $audience + * + * @return bool + */ + public function isValid(array $scope, array $audience) + { + $isValid = true; + + if ($this->isExpired()) { + Logger::getInstance()->error(__METHOD__ . ': token isExpired '); + $isValid = false; + } + + if ($isValid && !$this->hasScope($scope)) { + Logger::getInstance()->error(__METHOD__ . ': token scope invalid '); + $isValid = false; + } + + if ($isValid && !$this->hasAudience($audience)) { + Logger::getInstance()->error(__METHOD__ . ': token audience invalid '); + $isValid = false; + } + + return $isValid; + } + /** * @return string|null */ diff --git a/tests/src/BaseTestCase.php b/tests/src/BaseTestCase.php index 0aedf9c14..26ef102ed 100644 --- a/tests/src/BaseTestCase.php +++ b/tests/src/BaseTestCase.php @@ -129,6 +129,11 @@ public function makeJwtToken(\DateTimeImmutable $expiresAt = null, array $claims unset($claims['sub']); } + if (isset($claims['aud'])) { + $builder->permittedFor(...is_array($claims['aud']) ? $claims['aud'] : [$claims['aud']]); + unset($claims['aud']); + } + foreach ($claims as $claim => $value) { $builder->withClaim($claim, $value); } diff --git a/tests/src/Unit/Account/Session/ShopSession/GetValidTokenTest.php b/tests/src/Unit/Account/Session/ShopSession/GetValidTokenTest.php new file mode 100644 index 000000000..97f929e13 --- /dev/null +++ b/tests/src/Unit/Account/Session/ShopSession/GetValidTokenTest.php @@ -0,0 +1,261 @@ +cloudShopId = $this->faker->uuid; + + $this->validAccessToken = $this->makeJwtToken(new \DateTimeImmutable('tomorrow'), [ + 'scp' => [ + 'shop.verified', + ], + 'aud' => [ + $this->module->getParameter('ps_accounts.token_audience'), + 'store/' . $this->cloudShopId, + ], + ]); + + $this->oAuth2Service = $this->createMock(OAuth2Service::class); + $this->oAuth2Service->method('getAccessTokenByClientCredentials') + ->willReturn(new AccessToken([ + 'access_token' => (string)$this->validAccessToken + ])); + $this->oAuth2Service->method('getOAuth2Client') + ->willReturn($this->oauth2Client); + + $this->shopSession = new ShopSession( + $this->configurationRepository, + $this->oAuth2Service, + $this->module->getParameter('ps_accounts.accounts_api_url') + ); + $this->shopSession->setStatusManager($this->statusManager); + + $this->shopSession->cleanup(); + } + + /** + * @return void + */ + public function tear_down() + { + parent::tear_down(); + + $this->shopSession->cleanup(); + } + + /** + * @test + */ + public function itShouldReturnAValidIdentifiedShopToken() + { + $this->statusManager->setCloudShopId($this->cloudShopId);; + +// $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ +// 'isValid' => true, +// 'updatedAt' => (new \DateTime())->format(\DateTime::ATOM), +// 'shopStatus' => new ShopStatus([ +// 'cloudShopId' => $this->cloudShopId, +// 'isVerified' => true, +// ]) +// ]))->toArray())); + + $validAccessToken = $this->makeJwtToken(new \DateTimeImmutable('+1 hour'), [ + 'scp' => [ + //'shop.verified', + ], + 'aud' => [ + $this->module->getParameter('ps_accounts.token_audience') . '/', + 'store/' . $this->cloudShopId, + ], + ]); + + $this->shopSession->setToken((string) $validAccessToken); + + $this->assertEquals((string) $validAccessToken, (string) $this->shopSession->getValidToken()); + } + + /** + * @test + */ + public function itShouldReturnAValidVerifiedShopToken() + { + //$this->statusManager->setCloudShopId($cloudShopId); + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'updatedAt' => (new \DateTime())->format(\DateTime::ATOM), + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $this->cloudShopId, + 'isVerified' => true, + ]) + ]))->toArray())); + + $validAccessToken = $this->makeJwtToken(new \DateTimeImmutable('+1 hour'), [ + 'scp' => [ + 'shop.verified', + ], + 'aud' => [ + $this->module->getParameter('ps_accounts.token_audience') . '/', + 'store/' . $this->cloudShopId, + ], + ]); + + $this->shopSession->setToken((string) $validAccessToken); + + $this->assertEquals((string) $validAccessToken, (string) $this->shopSession->getValidToken()); + } + + public function provideInvalidTokens() + { + $module = $this->getModuleInstance(); + + return [ + 'expired token' => [ + $this->makeJwtToken(new \DateTimeImmutable('yesterday'), [ + 'scp' => [ + 'shop.verified', + ], + 'aud' => [ + $module->getParameter('ps_accounts.token_audience'), + 'store/' . $this->cloudShopId, + ] + ]), + ], + 'invalid scope' => [ + $this->makeJwtToken(new \DateTimeImmutable('tomorrow'), [ + 'scp' => [ + //'shop.verified', + ], + 'aud' => [ + $module->getParameter('ps_accounts.token_audience'), + 'store/' . $this->cloudShopId, + ] + ]), + ], + 'invalid audience' => [ + $this->makeJwtToken(new \DateTimeImmutable('tomorrow'), [ + 'scp' => [ + 'shop.verified', + ], + 'aud' => [ + //$module->getParameter('ps_accounts.token_audience'), + 'store/' . $this->cloudShopId, + ] + ]), + ], + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidTokens + */ + public function itShouldRefreshInvalidVerifiedShopToken(Token $invalidAccessToken) + { + //$this->statusManager->setCloudShopId($cloudShopId); + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'updatedAt' => (new \DateTime())->format(\DateTime::ATOM), + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $this->cloudShopId, + 'isVerified' => true, + ]) + ]))->toArray())); + + $this->shopSession->setToken((string) $invalidAccessToken); + + $this->assertEquals((string) $this->validAccessToken, (string) $this->shopSession->getValidToken()); + } + + /** + * @test + */ + public function itShouldThrowRefreshTokenExceptionOnOAuthClientError() + { + //$this->statusManager->setCloudShopId($cloudShopId); + + $this->oAuth2Service->method('getAccessTokenByClientCredentials') + ->willThrowException(new OAuth2Exception()); + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'updatedAt' => (new \DateTime())->format(\DateTime::ATOM), + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $this->cloudShopId, + 'isVerified' => true, + ]) + ]))->toArray())); + + $validAccessToken = $this->makeJwtToken(new \DateTimeImmutable('yesterday'), [ + 'scp' => [ + 'shop.verified', + ], + 'aud' => [ + $this->module->getParameter('ps_accounts.token_audience') . '/', + 'store/' . $this->cloudShopId, + ], + ]); + + $this->shopSession->setToken((string) $validAccessToken); + + $this->expectException(RefreshTokenException::class); + + $this->assertEquals((string) $validAccessToken, (string) $this->shopSession->getValidToken()); + } +} diff --git a/tests/src/Unit/Service/PsAccountsService/GetShopTokenTest.php b/tests/src/Unit/Service/PsAccountsService/GetShopTokenTest.php index 1e4e4bc2b..96830b57a 100644 --- a/tests/src/Unit/Service/PsAccountsService/GetShopTokenTest.php +++ b/tests/src/Unit/Service/PsAccountsService/GetShopTokenTest.php @@ -15,7 +15,7 @@ class GetShopTokenTest extends TestCase * * @var PsAccountsService */ - protected $service; + protected $psAccountsService; /** * @inject @@ -48,11 +48,20 @@ public function set_up() */ public function itShouldReturnAValidToken() { - $validToken = $this->makeJwtToken(new \DateTimeImmutable('+1 hour')); + $cloudShopId = $this->faker->uuid; + $this->statusManager->setCloudShopId($cloudShopId); + + $validToken = $this->makeJwtToken(new \DateTimeImmutable('+1 hour'), [ + 'scp' => [], + 'aud' => [ + $this->module->getParameter('ps_accounts.token_audience'), + 'store/' . $cloudShopId, + ], + ]); $this->shopSession->setToken((string) $validToken); - $this->assertEquals($validToken, $this->service->getShopToken()); + $this->assertEquals($validToken, $this->psAccountsService->getShopToken()); } /** @@ -67,6 +76,6 @@ public function itShouldThrowRefreshTokenExceptionOnError() $this->expectException(RefreshTokenException::class); - $this->service->getShopToken(); + $this->psAccountsService->getShopToken(); } } From 34fbd3876a5cb03422cbeb0bd2ecaec26f03d2a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Tue, 15 Jul 2025 17:41:34 +0200 Subject: [PATCH 15/46] fix: status manager circular dep (#517) * fix: lazy statusManager getter * fix: php-cs-fixer * fix: phpunit --- .../Session/Firebase/FirebaseSession.php | 28 ++----------------- src/Account/Session/Session.php | 20 +++++++++++++ src/Account/Session/ShopSession.php | 22 +++------------ src/ServiceProvider/DefaultProvider.php | 14 ++-------- .../Unit/Account/Session/SessionHelpers.php | 2 -- .../Session/ShopSession/GetValidTokenTest.php | 1 - .../Session/ShopSession/RefreshTokenTest.php | 1 - 7 files changed, 29 insertions(+), 59 deletions(-) diff --git a/src/Account/Session/Firebase/FirebaseSession.php b/src/Account/Session/Firebase/FirebaseSession.php index dd105513b..d97629353 100644 --- a/src/Account/Session/Firebase/FirebaseSession.php +++ b/src/Account/Session/Firebase/FirebaseSession.php @@ -25,7 +25,6 @@ use PrestaShop\Module\PsAccounts\Account\Session\Session; use PrestaShop\Module\PsAccounts\Account\Session\SessionInterface; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; -use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Account\Token\Token; use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; @@ -38,22 +37,11 @@ abstract class FirebaseSession extends Session implements SessionInterface */ protected $shopSession; - /** - * @var \Ps_accounts - */ - private $module; - - /** - * @var StatusManager - */ - protected $statusManager; - public function __construct(ShopSession $shopSession) { - $this->shopSession = $shopSession; + parent::__construct(); - /* @phpstan-ignore-next-line */ - $this->module = \Module::getInstanceByName('ps_accounts'); + $this->shopSession = $shopSession; } /** @@ -93,7 +81,7 @@ public function refreshToken($refreshToken = null, array $scope = [], array $aud { try { $token = $this->shopSession->getValidToken(); - $cloudShopId = $this->statusManager->getCloudShopId(); + $cloudShopId = $this->getStatusManager()->getCloudShopId(); $this->refreshFirebaseTokens($cloudShopId, $token); } catch (RefreshTokenException $e) { @@ -140,14 +128,4 @@ protected function refreshFirebaseTokens($cloudShopId, $token) $this->getOwnerSession()->setToken((string) $pointOfContactToken->getJwt(), $pointOfContactToken->getRefreshToken()); } } - - /** - * @param StatusManager $statusManager - * - * @return void - */ - public function setStatusManager(StatusManager $statusManager) - { - $this->statusManager = $statusManager; - } } diff --git a/src/Account/Session/Session.php b/src/Account/Session/Session.php index e61fc9ce1..171f7a392 100644 --- a/src/Account/Session/Session.php +++ b/src/Account/Session/Session.php @@ -21,6 +21,7 @@ namespace PrestaShop\Module\PsAccounts\Account\Session; use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; +use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Account\Token\NullToken; use PrestaShop\Module\PsAccounts\Account\Token\Token; use PrestaShop\Module\PsAccounts\Log\Logger; @@ -32,6 +33,17 @@ abstract class Session implements SessionInterface */ protected $refreshTokenErrors = []; + /** + * @var \Ps_accounts + */ + protected $module; + + public function __construct() + { + /* @phpstan-ignore-next-line */ + $this->module = \Module::getInstanceByName('ps_accounts'); + } + /** * @deprecated use getValidToken instead * @@ -138,4 +150,12 @@ protected function setRefreshTokenErrors($className, $message) { $this->refreshTokenErrors[$className] = $message; } + + /** + * @return StatusManager + */ + protected function getStatusManager() + { + return $this->module->getService(StatusManager::class); + } } diff --git a/src/Account/Session/ShopSession.php b/src/Account/Session/ShopSession.php index f1cf9ab64..9e4e6f9b1 100644 --- a/src/Account/Session/ShopSession.php +++ b/src/Account/Session/ShopSession.php @@ -21,7 +21,6 @@ namespace PrestaShop\Module\PsAccounts\Account\Session; use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; -use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Account\Token\Token; use PrestaShop\Module\PsAccounts\Hook\ActionShopAccessTokenRefreshAfter; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; @@ -46,11 +45,6 @@ class ShopSession extends Session implements SessionInterface */ protected $tokenAudience; - /** - * @var StatusManager - */ - protected $statusManager; - /** * @param ConfigurationRepository $configurationRepository * @param OAuth2Service $oAuth2Service @@ -61,6 +55,8 @@ public function __construct( OAuth2Service $oAuth2Service, $tokenAudience ) { + parent::__construct(); + $this->configurationRepository = $configurationRepository; $this->oAuth2Service = $oAuth2Service; $this->tokenAudience = $tokenAudience; @@ -78,12 +74,12 @@ public function __construct( */ public function getValidToken($forceRefresh = false, $throw = true, array $scope = [], array $audience = []) { - $scp = $scope + ($this->statusManager->identityVerified() ? [ + $scp = $scope + ($this->getStatusManager()->identityVerified() ? [ 'shop.verified', ] : []); $aud = $audience + [ - 'store/' . $this->statusManager->getCloudShopId(), + 'store/' . $this->getStatusManager()->getCloudShopId(), $this->tokenAudience, ]; @@ -149,16 +145,6 @@ public function cleanup() $this->configurationRepository->updateAccessToken(''); } - /** - * @param StatusManager $statusManager - * - * @return void - */ - public function setStatusManager(StatusManager $statusManager) - { - $this->statusManager = $statusManager; - } - /** * @param array $scope * @param array $audience diff --git a/src/ServiceProvider/DefaultProvider.php b/src/ServiceProvider/DefaultProvider.php index 56e53fa14..126a672d3 100644 --- a/src/ServiceProvider/DefaultProvider.php +++ b/src/ServiceProvider/DefaultProvider.php @@ -21,7 +21,6 @@ namespace PrestaShop\Module\PsAccounts\ServiceProvider; use PrestaShop\Module\PsAccounts\Account\ProofManager; -use PrestaShop\Module\PsAccounts\Account\Session\Firebase; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Adapter; @@ -67,20 +66,11 @@ public function provide(ServiceContainer $container) }); // Entities ? $container->registerProvider(StatusManager::class, static function () use ($container) { - /** @var ShopSession $shopSession */ - $shopSession = $container->get(ShopSession::class); - $firebaseOwnerSession = $container->get(Firebase\OwnerSession::class); - $firebaseShopSession = $container->get(Firebase\ShopSession::class); - $service = new StatusManager( - $shopSession, + return new StatusManager( + $container->get(ShopSession::class), $container->get(AccountsService::class), $container->get(ConfigurationRepository::class) ); - $shopSession->setStatusManager($service); - $firebaseOwnerSession->setStatusManager($service); - $firebaseShopSession->setStatusManager($service); - - return $service; }); // Adapter $container->registerProvider(Adapter\Configuration::class, static function () use ($container) { diff --git a/tests/src/Unit/Account/Session/SessionHelpers.php b/tests/src/Unit/Account/Session/SessionHelpers.php index 98f9ca6d6..24874389c 100644 --- a/tests/src/Unit/Account/Session/SessionHelpers.php +++ b/tests/src/Unit/Account/Session/SessionHelpers.php @@ -79,8 +79,6 @@ protected function getMockedFirebaseSession($firebaseSessionClass, Response $res $firebaseSession->method('getAccountsService') ->willReturn($accountsService); - $firebaseSession->setStatusManager($this->statusManager); - return $firebaseSession; } } diff --git a/tests/src/Unit/Account/Session/ShopSession/GetValidTokenTest.php b/tests/src/Unit/Account/Session/ShopSession/GetValidTokenTest.php index 97f929e13..59cf994f1 100644 --- a/tests/src/Unit/Account/Session/ShopSession/GetValidTokenTest.php +++ b/tests/src/Unit/Account/Session/ShopSession/GetValidTokenTest.php @@ -82,7 +82,6 @@ function set_up() $this->oAuth2Service, $this->module->getParameter('ps_accounts.accounts_api_url') ); - $this->shopSession->setStatusManager($this->statusManager); $this->shopSession->cleanup(); } diff --git a/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php b/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php index 89505cd3d..755be6202 100644 --- a/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php +++ b/tests/src/Unit/Account/Session/ShopSession/RefreshTokenTest.php @@ -60,7 +60,6 @@ function set_up() $oAuth2Service, $this->module->getParameter('ps_accounts.accounts_api_url') ); - $this->shopSession->setStatusManager($this->statusManager); $this->shopSession->cleanup(); } From 4e2f56c8ee309fd1c41d7f655f8991875f6e7bd0 Mon Sep 17 00:00:00 2001 From: hschoenenberger Date: Wed, 16 Jul 2025 10:20:49 +0200 Subject: [PATCH 16/46] fix: generic beta tag for component version --- config.bulle.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.bulle.php b/config.bulle.php index 49e58739b..f34ce4fd4 100644 --- a/config.bulle.php +++ b/config.bulle.php @@ -30,7 +30,7 @@ 'ps_accounts.segment_write_key' => 'eYODaH20rT1lMRTTUtAa15BKBlV1XUXQ', 'ps_accounts.accounts_vue_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@3/dist/psaccountsVue.umd.min.js', //'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5', - 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/@prestashopcorp/accounts-components@0.0.2-beta.2', + 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/@prestashopcorp/accounts-components@beta', 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.psessentials-integration.net', 'ps_accounts.oauth2_url' => 'https://oauth-integration.prestashop.com', 'ps_accounts.token_audience' => 'https://accounts-api.distribution-integration.prestashop.net', From 4b2ea02e9db2e36f071b06dde96b09bff1d65b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:20:43 +0200 Subject: [PATCH 17/46] feat: debugbar alert env (#518) * feat: use environment to include alert level for debug bar --- config.bulle.php | 1 + src/Hook/DisplayAdminAfterHeader.php | 30 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/config.bulle.php b/config.bulle.php index f34ce4fd4..e120f2c4e 100644 --- a/config.bulle.php +++ b/config.bulle.php @@ -39,4 +39,5 @@ 'ps_accounts.check_api_ssl_cert' => true, 'ps_accounts.verify_account_tokens' => true, + 'ps_accounts.log_level' => 'info', ]; diff --git a/src/Hook/DisplayAdminAfterHeader.php b/src/Hook/DisplayAdminAfterHeader.php index f98a3d5f4..33b78bc84 100644 --- a/src/Hook/DisplayAdminAfterHeader.php +++ b/src/Hook/DisplayAdminAfterHeader.php @@ -46,10 +46,15 @@ public function execute(array $params = []) $debugLink = $link->getAdminLink('AdminDebugPsAccounts'); $healthCheckLink = $link->getLink()->getModuleLink('ps_accounts', 'apiV2ShopHealthCheck'); + $environment = $this->module->getParameter('ps_accounts.environment'); + + $alertLevel = $this->getAlertLevel($environment); + return << -
+
+ {$environment} | {$cloudShopId} ({$verifiedMsg}) | Debug | @@ -64,4 +69,27 @@ public function execute(array $params = []) return ''; } + + /** + * @param string $environment + * + * @return string + */ + private function getAlertLevel($environment) + { + $alertLevel = 'info'; + switch ($environment) { + case 'production': + $alertLevel = 'info'; + break; + case 'integration': + $alertLevel = 'warning'; + break; + case 'development': + $alertLevel = 'danger'; + break; + } + + return $alertLevel; + } } From 898016a7f8b083c4e8be081199c379074a65ef37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:49:07 +0200 Subject: [PATCH 18/46] feat: get last upgraded version (#513) * feat: get last upgraded version from ps_module table * feat: introduce UpgradeService --- ps_accounts.php | 41 ++++++------ .../MigrateOrCreateIdentityV8Handler.php | 34 +++++----- src/Service/UpgradeService.php | 66 +++++++++++++++++++ .../MigrateOrCreateIdentityV8HandlerTest.php | 35 ++++++++-- upgrade/upgrade-8.0.0.php | 6 +- 5 files changed, 137 insertions(+), 45 deletions(-) create mode 100644 src/Service/UpgradeService.php diff --git a/ps_accounts.php b/ps_accounts.php index b09d5cc92..79f747788 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -21,11 +21,10 @@ exit; } require_once __DIR__ . '/vendor/autoload.php'; -//require __DIR__ . '/src/autoload_module.php'; -//if (!class_exists('\PrestaShop\Module\PsAccounts\Hook\HookableTrait')) { -// ps_accounts_fix_upgrade(); -//} +if (!class_exists('\PrestaShop\Module\PsAccounts\Hook\HookableTrait')) { + ps_accounts_fix_upgrade(); +} class Ps_accounts extends Module { @@ -475,20 +474,20 @@ public function getVerifiedStatus() return false; } } -// -///** -// * @return void -// */ -//function ps_accounts_fix_upgrade() -//{ -// $root = __DIR__; -// $requires = array_merge([ -// $root . '/src/Module/Install.php', -//// $root . '/src/Hook/Hook.php', -// $root . '/src/Hook/HookableTrait.php', -// ], []/*, glob($root . '/src/Hook/*.php')*/); -// -// foreach ($requires as $filename) { -// require_once $filename; -// } -//} + +/** + * @return void + */ +function ps_accounts_fix_upgrade() +{ + $root = __DIR__; + $requires = array_merge([ + $root . '/src/Module/Install.php', +// $root . '/src/Hook/Hook.php', + $root . '/src/Hook/HookableTrait.php', + ], []/*, glob($root . '/src/Hook/*.php')*/); + + foreach ($requires as $filename) { + require_once $filename; + } +} diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php index f09ff8102..ae90032e5 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php @@ -35,6 +35,7 @@ use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Exception; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; +use PrestaShop\Module\PsAccounts\Service\UpgradeService; class MigrateOrCreateIdentityV8Handler { @@ -68,6 +69,11 @@ class MigrateOrCreateIdentityV8Handler */ private $configurationRepository; + /** + * @var UpgradeService + */ + private $upgradeService; + /** * @var CommandBus */ @@ -98,6 +104,7 @@ public function __construct( $this->proofManager = $proofManager; $this->configurationRepository = $configurationRepository; $this->commandBus = $commandBus; + $this->upgradeService = new UpgradeService($configurationRepository); } /** @@ -109,12 +116,17 @@ public function handle(MigrateOrCreateIdentityV8Command $command) { $shopId = $command->shopId ?: \Shop::getContextShopID(); $shopUuid = $this->configurationRepository->getShopUuid(); - $lastUpgradedVersion = $this->configurationRepository->getLastUpgrade(false); + + $fromVersion = $this->upgradeService->getRegisteredVersion(); + if ($fromVersion === '0') { + $fromVersion = $this->upgradeService->getCoreRegisteredVersion(); + } $e = null; try { - if (!$shopUuid || version_compare($lastUpgradedVersion, '8', '>=')) { - $this->upgradeVersionNumber(); + // FIXME: shouldn't this condition be a specific flag + if (!$shopUuid || version_compare($fromVersion, '8', '>=')) { + $this->upgradeService->setRegisteredVersion(); $this->commandBus->handle(new CreateIdentityCommand($command->shopId)); @@ -124,19 +136,18 @@ public function handle(MigrateOrCreateIdentityV8Command $command) // migrate cloudShopId locally $this->statusManager->setCloudShopId($shopUuid); - if (version_compare($lastUpgradedVersion, '7', '>=')) { + if (version_compare($fromVersion, '7', '>=')) { $token = $this->getAccessTokenV7($shopUuid); } else { $token = $this->getFirebaseTokenV6($shopUuid); } - // FIXME getLastUpgradedVersion from PS Core ? $identityCreated = $this->accountsService->migrateShopIdentity( $shopUuid, $token, $this->shopProvider->getUrl($shopId), $this->proofManager->generateProof(), - $lastUpgradedVersion + $fromVersion ); if (!empty($identityCreated->clientId) && @@ -152,8 +163,7 @@ public function handle(MigrateOrCreateIdentityV8Command $command) $this->statusManager->invalidateCache(); - // update ps_accounts upgraded version - $this->upgradeVersionNumber(); + $this->upgradeService->setRegisteredVersion(); } catch (OAuth2Exception $e) { } catch (AccountsException $e) { } catch (RefreshTokenException $e) { @@ -194,12 +204,4 @@ protected function getFirebaseTokenV6($shopUuid) $shopUuid )->token; } - - /** - * @return void - */ - protected function upgradeVersionNumber() - { - $this->configurationRepository->updateLastUpgrade(\Ps_accounts::VERSION); - } } diff --git a/src/Service/UpgradeService.php b/src/Service/UpgradeService.php new file mode 100644 index 000000000..f8555371c --- /dev/null +++ b/src/Service/UpgradeService.php @@ -0,0 +1,66 @@ +repository = $configurationRepository; + } + + /** + * @return string + */ + public function getRegisteredVersion() + { + return $this->repository->getLastUpgrade(false); + } + + /** + * @param string $version + * + * @return void + */ + public function setRegisteredVersion($version = \Ps_accounts::VERSION) + { + $this->repository->updateLastUpgrade($version); + } + + /** + * @return string + */ + public function getCoreRegisteredVersion() + { + return \Db::getInstance()->getValue( + 'SELECT version FROM ' . _DB_PREFIX_ . 'module WHERE name = \'' . $this->moduleName . '\'' + ) ?: '0'; + } + + /** + * @param string $version + * + * @return void + */ + public function setCoreRegisteredVersion($version = \Ps_accounts::VERSION) + { + \Db::getInstance()->execute( + 'UPDATE ' . _DB_PREFIX_ . 'module SET version = \'' . $version . '\' WHERE name = \'' . $this->moduleName . '\'' + ); + } +} diff --git a/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php index c2be5ac86..c6625dea6 100644 --- a/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php @@ -13,6 +13,7 @@ use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; +use PrestaShop\Module\PsAccounts\Service\UpgradeService; use PrestaShop\Module\PsAccounts\Tests\TestCase; class MigrateOrCreateIdentityV8HandlerTest extends TestCase @@ -55,6 +56,11 @@ class MigrateOrCreateIdentityV8HandlerTest extends TestCase */ public $statusManager; + /** + * @var UpgradeService + */ + public $upgradeService; + /** * @inject * @@ -94,6 +100,7 @@ public function set_up() $this->oAuth2Client = $this->createMock(Client::class); $this->accountsService->setClient($this->accountsClient); $this->oAuth2Service->setHttpClient($this->oAuth2Client); + $this->upgradeService = new UpgradeService($this->configurationRepository); // $this->accountsService = $this->createMock(AccountsService::class); // $this->oAuth2Service = $this->createMock(OAuth2Service::class); @@ -113,7 +120,8 @@ public function itShouldMigrateIdentityFromV7() $tokenAudience = 'shop_' . $cloudShopId; // introduced in v7 - $this->configurationRepository->updateLastUpgrade('7.2.0'); + //$this->configurationRepository->updateLastUpgrade('7.2.0'); + $this->upgradeService->setRegisteredVersion('7.2.0'); $this->configurationRepository->updateShopUuid($cloudShopId); @@ -149,7 +157,8 @@ public function itShouldMigrateIdentityFromV7() $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); - $this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); + //$this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); + $this->assertEquals((string) $this->upgradeService->getRegisteredVersion(), $options[Request::JSON]['fromVersion']); return $this->createResponse([ 'clientId' => $clientId, @@ -160,6 +169,7 @@ public function itShouldMigrateIdentityFromV7() $this->getHandler()->handle(new MigrateOrCreateIdentityV8Command($this->shopId)); + $this->assertEquals(\Ps_accounts::VERSION, $this->upgradeService->getRegisteredVersion()); $this->assertEmpty($this->configurationRepository->getAccessToken()); $this->assertTrue($this->statusManager->cacheInvalidated()); $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); @@ -237,7 +247,12 @@ public function itShouldMigrateIdentityFromV5AndV6() $token = $this->faker->uuid; // introduced in v7 - $this->configurationRepository->updateLastUpgrade(null); + //$this->configurationRepository->updateLastUpgrade(null); + + $fromVersion = '5.6.2'; + $this->upgradeService->setCoreRegisteredVersion($fromVersion); + // FIXME: not working with null on v9 + $this->upgradeService->setRegisteredVersion('0'); $this->configurationRepository->updateShopUuid($cloudShopId); @@ -258,14 +273,15 @@ public function itShouldMigrateIdentityFromV5AndV6() $this->matches('/v1/shop-identities/' . $cloudShopId . '/migrate'), $this->isType('array') ) - ->willReturnCallback(function ($route, $options) use ($cloudShopId, $clientId, $clientSecret, $token) { + ->willReturnCallback(function ($route, $options) use ($cloudShopId, $clientId, $clientSecret, $token, $fromVersion) { $this->assertEquals('Bearer ' . $token, $options[Request::HEADERS]['Authorization']); $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); - $this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); + //$this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); + $this->assertEquals($fromVersion, $options[Request::JSON]['fromVersion']); return $this->createResponse([ 'clientId' => $clientId, @@ -276,6 +292,7 @@ public function itShouldMigrateIdentityFromV5AndV6() $this->getHandler()->handle(new MigrateOrCreateIdentityV8Command($this->shopId)); + $this->assertEquals(\Ps_accounts::VERSION, $this->upgradeService->getRegisteredVersion()); $this->assertTrue($this->statusManager->cacheInvalidated()); $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); $this->assertEquals($clientId, $this->oAuth2Service->getOAuth2Client()->getClientId()); @@ -294,7 +311,9 @@ public function itShouldNotMigrateOnError() $tokenAudience = 'shop_' . $cloudShopId; // introduced in v7 - $this->configurationRepository->updateLastUpgrade('7.2.0'); + //$this->configurationRepository->updateLastUpgrade('7.2.0'); + $fromVersion = '7.2.0'; + $this->upgradeService->setRegisteredVersion($fromVersion); $this->configurationRepository->updateShopUuid($cloudShopId); @@ -330,7 +349,8 @@ public function itShouldNotMigrateOnError() $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); - $this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); + //$this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); + $this->assertEquals((string) $this->upgradeService->getRegisteredVersion(), $options[Request::JSON]['fromVersion']); return $this->createResponse([ "error" => 'store-identity/migration-failed', @@ -340,6 +360,7 @@ public function itShouldNotMigrateOnError() $this->getHandler()->handle(new MigrateOrCreateIdentityV8Command($this->shopId)); + $this->assertEquals($fromVersion, $this->upgradeService->getRegisteredVersion()); $this->assertTrue($this->statusManager->cacheInvalidated()); $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); diff --git a/upgrade/upgrade-8.0.0.php b/upgrade/upgrade-8.0.0.php index b39e7798d..4b66c51f0 100644 --- a/upgrade/upgrade-8.0.0.php +++ b/upgrade/upgrade-8.0.0.php @@ -20,13 +20,17 @@ function upgrade_module_8_0_0($module) /** @var CommandBus $commandBus */ $commandBus = $module->getService(CommandBus::class); - $commandBus->handle(new MigrateOrCreateIdentitiesV8Command()); + $commandBus->handle(new MigrateOrCreateIdentitiesV8Command($module->getRegisteredVersion())); /* @phpstan-ignore-next-line */ } catch (\Throwable $e) { Logger::getInstance()->error('error during upgrade : ' . $e->getMessage()); + + return false; } catch (\Exception $e) { Logger::getInstance()->error('error during upgrade : ' . $e->getMessage()); + + return false; } return true; From 7ea8940a6f8d67e0ff333a59f5d95da9e3a2bb96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Thu, 31 Jul 2025 10:04:45 +0200 Subject: [PATCH 19/46] refactor: introduce header constants (#522) --- src/Service/Accounts/AccountsService.php | 43 ++++++++++++++---------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index 06d0b4c7f..4e11bbb93 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -35,6 +35,13 @@ class AccountsService { + const HEADER_AUTHORIZATION = 'Authorization'; + const HEADER_MODULE_VERSION = 'X-Module-Version'; + const HEADER_PRESTASHOP_VERSION = 'X-Prestashop-Version'; + const HEADER_MULTISHOP_ENABLED = 'X-Multishop-Enabled'; + const HEADER_REQUEST_ID = 'X-Request-ID'; + const HEADER_SHOP_ID = 'X-Shop-Id'; + /** * @var Client */ @@ -90,10 +97,10 @@ private function getHeaders($additionalHeaders = []) { return array_merge([ 'Accept' => 'application/json', - 'X-Module-Version' => \Ps_accounts::VERSION, - 'X-Prestashop-Version' => _PS_VERSION_, - 'X-Multishop-Enabled' => \Shop::isFeatureActive() ? 'true' : 'false', - 'X-Request-ID' => Uuid::uuid4()->toString(), + self::HEADER_MODULE_VERSION => \Ps_accounts::VERSION, + self::HEADER_PRESTASHOP_VERSION => _PS_VERSION_, + self::HEADER_MULTISHOP_ENABLED => \Shop::isFeatureActive() ? 'true' : 'false', + self::HEADER_REQUEST_ID => Uuid::uuid4()->toString(), ], $additionalHeaders); } @@ -111,7 +118,7 @@ public function firebaseTokens($cloudShopId, $accessToken) '/v1/shop-identities/' . $cloudShopId . '/tokens', [ Request::HEADERS => $this->getHeaders([ - 'Authorization' => 'Bearer ' . $accessToken, + self::HEADER_AUTHORIZATION => 'Bearer ' . $accessToken, ]), ] ); @@ -137,7 +144,7 @@ public function refreshShopToken($refreshToken, $cloudShopId) 'v1/shop/token/refresh', [ Request::HEADERS => $this->getHeaders([ - 'X-Shop-Id' => $cloudShopId, + self::HEADER_SHOP_ID => $cloudShopId, ]), Request::JSON => [ 'token' => $refreshToken, @@ -165,8 +172,8 @@ public function deleteUserShop($ownerUid, $cloudShopId, $ownerToken) 'v1/user/' . $ownerUid . '/shop/' . $cloudShopId, [ Request::HEADERS => $this->getHeaders([ - 'Authorization' => 'Bearer ' . $ownerToken, - 'X-Shop-Id' => $cloudShopId, + self::HEADER_AUTHORIZATION => 'Bearer ' . $ownerToken, + self::HEADER_SHOP_ID => $cloudShopId, ]), ] ); @@ -187,8 +194,8 @@ public function updateUserShop($ownerUid, $cloudShopId, $ownerToken, UpdateShop [ Request::HEADERS => $this->getHeaders([ // FIXME: use shop access token instead - 'Authorization' => 'Bearer ' . $ownerToken, - 'X-Shop-Id' => $cloudShopId, + self::HEADER_AUTHORIZATION => 'Bearer ' . $ownerToken, + self::HEADER_SHOP_ID => $cloudShopId, ]), Request::JSON => $shop->jsonSerialize(), ] @@ -268,8 +275,8 @@ public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $ '/v1/shop-identities/' . $cloudShopId . '/verify', [ Request::HEADERS => $this->getHeaders([ - 'Authorization' => 'Bearer ' . $shopToken, - 'X-Shop-Id' => $cloudShopId, + self::HEADER_AUTHORIZATION => 'Bearer ' . $shopToken, + self::HEADER_SHOP_ID => $cloudShopId, ]), Request::JSON => [ 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), @@ -299,8 +306,8 @@ public function shopStatus($cloudShopId, $shopToken) '/v1/shop-identities/' . $cloudShopId . '/status', [ Request::HEADERS => $this->getHeaders([ - 'Authorization' => 'Bearer ' . $shopToken, - 'X-Shop-Id' => $cloudShopId, + self::HEADER_AUTHORIZATION => 'Bearer ' . $shopToken, + self::HEADER_SHOP_ID => $cloudShopId, ]), ] ); @@ -327,8 +334,8 @@ public function setPointOfContact($cloudShopId, $shopToken, $userToken) '/v1/shop-identities/' . $cloudShopId . '/point-of-contact', [ Request::HEADERS => $this->getHeaders([ - 'Authorization' => 'Bearer ' . $shopToken, - 'X-Shop-Id' => $cloudShopId, + self::HEADER_AUTHORIZATION => 'Bearer ' . $shopToken, + self::HEADER_SHOP_ID => $cloudShopId, ]), Request::JSON => [ 'pointOfContactJWT' => $userToken, @@ -358,8 +365,8 @@ public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, '/v1/shop-identities/' . $cloudShopId . '/migrate', [ Request::HEADERS => $this->getHeaders([ - 'Authorization' => 'Bearer ' . $shopToken, - 'X-Shop-Id' => $cloudShopId, + self::HEADER_AUTHORIZATION => 'Bearer ' . $shopToken, + self::HEADER_SHOP_ID => $cloudShopId, ]), Request::JSON => [ 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), From fe16271e9b3652dba2df0671c0e61b7ccaa79172 Mon Sep 17 00:00:00 2001 From: Antoine Date: Wed, 6 Aug 2025 11:11:13 +0200 Subject: [PATCH 20/46] refactor: improve upgrade service (#524) --- .../MigrateOrCreateIdentityV8Handler.php | 23 +++++------ src/Service/UpgradeService.php | 41 +++++++++---------- src/ServiceProvider/CommandProvider.php | 4 +- src/ServiceProvider/DefaultProvider.php | 6 +++ .../MigrateOrCreateIdentityV8HandlerTest.php | 26 +++++++----- 5 files changed, 55 insertions(+), 45 deletions(-) diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php index ae90032e5..6c8bf2123 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php @@ -70,14 +70,14 @@ class MigrateOrCreateIdentityV8Handler private $configurationRepository; /** - * @var UpgradeService + * @var CommandBus */ - private $upgradeService; + private $commandBus; /** - * @var CommandBus + * @var UpgradeService */ - private $commandBus; + private $upgradeService; /** * @param AccountsService $accountsService @@ -87,6 +87,7 @@ class MigrateOrCreateIdentityV8Handler * @param ProofManager $proofManager * @param ConfigurationRepository $configurationRepository * @param CommandBus $commandBus + * @param UpgradeService $upgradeService */ public function __construct( AccountsService $accountsService, @@ -95,7 +96,8 @@ public function __construct( StatusManager $shopStatus, ProofManager $proofManager, ConfigurationRepository $configurationRepository, - CommandBus $commandBus + CommandBus $commandBus, + UpgradeService $upgradeService ) { $this->accountsService = $accountsService; $this->oAuth2Service = $oAuth2Service; @@ -104,7 +106,7 @@ public function __construct( $this->proofManager = $proofManager; $this->configurationRepository = $configurationRepository; $this->commandBus = $commandBus; - $this->upgradeService = new UpgradeService($configurationRepository); + $this->upgradeService = $upgradeService; } /** @@ -117,16 +119,13 @@ public function handle(MigrateOrCreateIdentityV8Command $command) $shopId = $command->shopId ?: \Shop::getContextShopID(); $shopUuid = $this->configurationRepository->getShopUuid(); - $fromVersion = $this->upgradeService->getRegisteredVersion(); - if ($fromVersion === '0') { - $fromVersion = $this->upgradeService->getCoreRegisteredVersion(); - } + $fromVersion = $this->upgradeService->getVersion(); $e = null; try { // FIXME: shouldn't this condition be a specific flag if (!$shopUuid || version_compare($fromVersion, '8', '>=')) { - $this->upgradeService->setRegisteredVersion(); + $this->upgradeService->setVersion(); $this->commandBus->handle(new CreateIdentityCommand($command->shopId)); @@ -163,7 +162,7 @@ public function handle(MigrateOrCreateIdentityV8Command $command) $this->statusManager->invalidateCache(); - $this->upgradeService->setRegisteredVersion(); + $this->upgradeService->setVersion(); } catch (OAuth2Exception $e) { } catch (AccountsException $e) { } catch (RefreshTokenException $e) { diff --git a/src/Service/UpgradeService.php b/src/Service/UpgradeService.php index f8555371c..632e8e1f7 100644 --- a/src/Service/UpgradeService.php +++ b/src/Service/UpgradeService.php @@ -6,16 +6,13 @@ class UpgradeService { + const MODULE_NAME = 'ps_accounts'; + /** * @var ConfigurationRepository */ private $repository; - /** - * @var string - */ - private $moduleName = 'ps_accounts'; - /** * @param ConfigurationRepository $configurationRepository */ @@ -27,7 +24,21 @@ public function __construct(ConfigurationRepository $configurationRepository) /** * @return string */ - public function getRegisteredVersion() + public function getVersion() + { + $version = $this->getRegisteredVersion(); + + if ($version === '0') { + $version = $this->getCoreRegisteredVersion(); + } + + return $version; + } + + /** + * @return string + */ + private function getRegisteredVersion() { return $this->repository->getLastUpgrade(false); } @@ -37,7 +48,7 @@ public function getRegisteredVersion() * * @return void */ - public function setRegisteredVersion($version = \Ps_accounts::VERSION) + public function setVersion($version = \Ps_accounts::VERSION) { $this->repository->updateLastUpgrade($version); } @@ -45,22 +56,10 @@ public function setRegisteredVersion($version = \Ps_accounts::VERSION) /** * @return string */ - public function getCoreRegisteredVersion() + private function getCoreRegisteredVersion() { return \Db::getInstance()->getValue( - 'SELECT version FROM ' . _DB_PREFIX_ . 'module WHERE name = \'' . $this->moduleName . '\'' + 'SELECT version FROM ' . _DB_PREFIX_ . 'module WHERE name = \'' . self::MODULE_NAME . '\'' ) ?: '0'; } - - /** - * @param string $version - * - * @return void - */ - public function setCoreRegisteredVersion($version = \Ps_accounts::VERSION) - { - \Db::getInstance()->execute( - 'UPDATE ' . _DB_PREFIX_ . 'module SET version = \'' . $version . '\' WHERE name = \'' . $this->moduleName . '\'' - ); - } } diff --git a/src/ServiceProvider/CommandProvider.php b/src/ServiceProvider/CommandProvider.php index 4103f8b68..2109fd7cb 100644 --- a/src/ServiceProvider/CommandProvider.php +++ b/src/ServiceProvider/CommandProvider.php @@ -39,6 +39,7 @@ use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; +use PrestaShop\Module\PsAccounts\Service\UpgradeService; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\Contract\IServiceProvider; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\ServiceContainer; @@ -114,7 +115,8 @@ public function provide(ServiceContainer $container) $container->get(StatusManager::class), $container->get(ProofManager::class), $container->get(ConfigurationRepository::class), - $container->get(CommandBus::class) + $container->get(CommandBus::class), + $container->get(UpgradeService::class) ); }); } diff --git a/src/ServiceProvider/DefaultProvider.php b/src/ServiceProvider/DefaultProvider.php index 126a672d3..f8af6955c 100644 --- a/src/ServiceProvider/DefaultProvider.php +++ b/src/ServiceProvider/DefaultProvider.php @@ -42,6 +42,7 @@ use PrestaShop\Module\PsAccounts\Service\PsAccountsService; use PrestaShop\Module\PsAccounts\Service\PsBillingService; use PrestaShop\Module\PsAccounts\Service\SentryService; +use PrestaShop\Module\PsAccounts\Service\UpgradeService; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\Contract\IServiceProvider; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\ServiceContainer; use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; @@ -110,6 +111,11 @@ public function provide(ServiceContainer $container) $container->get('ps_accounts.context') ); }); + $container->registerProvider(UpgradeService::class, static function () use ($container) { + return new UpgradeService( + $container->get(ConfigurationRepository::class) + ); + }); $container->registerProvider(ProofManager::class, static function () use ($container) { return new ProofManager( $container->get(ConfigurationRepository::class) diff --git a/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php index c6625dea6..896e34b02 100644 --- a/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php @@ -57,6 +57,8 @@ class MigrateOrCreateIdentityV8HandlerTest extends TestCase public $statusManager; /** + * @inject + * * @var UpgradeService */ public $upgradeService; @@ -100,7 +102,6 @@ public function set_up() $this->oAuth2Client = $this->createMock(Client::class); $this->accountsService->setClient($this->accountsClient); $this->oAuth2Service->setHttpClient($this->oAuth2Client); - $this->upgradeService = new UpgradeService($this->configurationRepository); // $this->accountsService = $this->createMock(AccountsService::class); // $this->oAuth2Service = $this->createMock(OAuth2Service::class); @@ -121,7 +122,7 @@ public function itShouldMigrateIdentityFromV7() // introduced in v7 //$this->configurationRepository->updateLastUpgrade('7.2.0'); - $this->upgradeService->setRegisteredVersion('7.2.0'); + $this->upgradeService->setVersion('7.2.0'); $this->configurationRepository->updateShopUuid($cloudShopId); @@ -158,7 +159,7 @@ public function itShouldMigrateIdentityFromV7() $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); //$this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); - $this->assertEquals((string) $this->upgradeService->getRegisteredVersion(), $options[Request::JSON]['fromVersion']); + $this->assertEquals((string) $this->upgradeService->getVersion(), $options[Request::JSON]['fromVersion']); return $this->createResponse([ 'clientId' => $clientId, @@ -169,7 +170,7 @@ public function itShouldMigrateIdentityFromV7() $this->getHandler()->handle(new MigrateOrCreateIdentityV8Command($this->shopId)); - $this->assertEquals(\Ps_accounts::VERSION, $this->upgradeService->getRegisteredVersion()); + $this->assertEquals(\Ps_accounts::VERSION, $this->upgradeService->getVersion()); $this->assertEmpty($this->configurationRepository->getAccessToken()); $this->assertTrue($this->statusManager->cacheInvalidated()); $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); @@ -250,9 +251,11 @@ public function itShouldMigrateIdentityFromV5AndV6() //$this->configurationRepository->updateLastUpgrade(null); $fromVersion = '5.6.2'; - $this->upgradeService->setCoreRegisteredVersion($fromVersion); + \Db::getInstance()->execute( + 'UPDATE ' . _DB_PREFIX_ . 'module SET version = \'' . $fromVersion . '\' WHERE name = \'' . UpgradeService::MODULE_NAME . '\'' + ); // FIXME: not working with null on v9 - $this->upgradeService->setRegisteredVersion('0'); + $this->upgradeService->setVersion('0'); $this->configurationRepository->updateShopUuid($cloudShopId); @@ -292,7 +295,7 @@ public function itShouldMigrateIdentityFromV5AndV6() $this->getHandler()->handle(new MigrateOrCreateIdentityV8Command($this->shopId)); - $this->assertEquals(\Ps_accounts::VERSION, $this->upgradeService->getRegisteredVersion()); + $this->assertEquals(\Ps_accounts::VERSION, $this->upgradeService->getVersion()); $this->assertTrue($this->statusManager->cacheInvalidated()); $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); $this->assertEquals($clientId, $this->oAuth2Service->getOAuth2Client()->getClientId()); @@ -313,7 +316,7 @@ public function itShouldNotMigrateOnError() // introduced in v7 //$this->configurationRepository->updateLastUpgrade('7.2.0'); $fromVersion = '7.2.0'; - $this->upgradeService->setRegisteredVersion($fromVersion); + $this->upgradeService->setVersion($fromVersion); $this->configurationRepository->updateShopUuid($cloudShopId); @@ -350,7 +353,7 @@ public function itShouldNotMigrateOnError() $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); //$this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); - $this->assertEquals((string) $this->upgradeService->getRegisteredVersion(), $options[Request::JSON]['fromVersion']); + $this->assertEquals((string) $this->upgradeService->getVersion(), $options[Request::JSON]['fromVersion']); return $this->createResponse([ "error" => 'store-identity/migration-failed', @@ -360,7 +363,7 @@ public function itShouldNotMigrateOnError() $this->getHandler()->handle(new MigrateOrCreateIdentityV8Command($this->shopId)); - $this->assertEquals($fromVersion, $this->upgradeService->getRegisteredVersion()); + $this->assertEquals($fromVersion, $this->upgradeService->getVersion()); $this->assertTrue($this->statusManager->cacheInvalidated()); $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); @@ -379,7 +382,8 @@ private function getHandler() $this->statusManager, $this->proofManager, $this->configurationRepository, - $this->commandBus + $this->commandBus, + $this->upgradeService ); } } From e7179cd4eb6d5ef292333adfae2249fd3126c86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Wed, 6 Aug 2025 11:25:56 +0200 Subject: [PATCH 21/46] fix: plugin upgrade service (#529) --- src/Account/QueryHandler/GetContextHandler.php | 14 ++++++++++---- src/Service/UpgradeService.php | 16 ++++++++-------- src/ServiceProvider/QueryProvider.php | 4 +++- .../QueryHandler/GetContextHandlerTest.php | 11 ++++++++++- 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/Account/QueryHandler/GetContextHandler.php b/src/Account/QueryHandler/GetContextHandler.php index 0c9c77cdb..188a9247b 100644 --- a/src/Account/QueryHandler/GetContextHandler.php +++ b/src/Account/QueryHandler/GetContextHandler.php @@ -23,6 +23,7 @@ use PrestaShop\Module\PsAccounts\Account\Query\GetContextQuery; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; +use PrestaShop\Module\PsAccounts\Service\UpgradeService; class GetContextHandler { @@ -31,13 +32,20 @@ class GetContextHandler */ private $shopProvider; + /** + * @var UpgradeService + */ + private $upgradeService; + /** * @param ShopProvider $shopProvider */ public function __construct( - ShopProvider $shopProvider + ShopProvider $shopProvider, + UpgradeService $upgradeService ) { $this->shopProvider = $shopProvider; + $this->upgradeService = $upgradeService; } /** @@ -49,11 +57,9 @@ public function __construct( */ public function handle(GetContextQuery $query) { - $psAccountsVersion = \Db::getInstance()->getValue('SELECT version FROM ' . _DB_PREFIX_ . 'module WHERE name = "ps_accounts"'); - return [ 'ps_accounts' => [ - 'last_succeeded_upgrade_version' => $psAccountsVersion, + 'last_succeeded_upgrade_version' => $this->upgradeService->getVersion(), 'module_version_from_files' => \Ps_accounts::VERSION, ], 'groups' => $this->shopProvider->getShops( diff --git a/src/Service/UpgradeService.php b/src/Service/UpgradeService.php index 632e8e1f7..0d4428ea3 100644 --- a/src/Service/UpgradeService.php +++ b/src/Service/UpgradeService.php @@ -35,14 +35,6 @@ public function getVersion() return $version; } - /** - * @return string - */ - private function getRegisteredVersion() - { - return $this->repository->getLastUpgrade(false); - } - /** * @param string $version * @@ -53,6 +45,14 @@ public function setVersion($version = \Ps_accounts::VERSION) $this->repository->updateLastUpgrade($version); } + /** + * @return string + */ + private function getRegisteredVersion() + { + return $this->repository->getLastUpgrade(false); + } + /** * @return string */ diff --git a/src/ServiceProvider/QueryProvider.php b/src/ServiceProvider/QueryProvider.php index 89546df2f..ae6376d79 100644 --- a/src/ServiceProvider/QueryProvider.php +++ b/src/ServiceProvider/QueryProvider.php @@ -22,6 +22,7 @@ use PrestaShop\Module\PsAccounts\Account\QueryHandler\GetContextHandler; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; +use PrestaShop\Module\PsAccounts\Service\UpgradeService; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\Contract\IServiceProvider; use PrestaShop\Module\PsAccounts\Vendor\PrestaShopCorp\LightweightContainer\ServiceContainer\ServiceContainer; @@ -31,7 +32,8 @@ public function provide(ServiceContainer $container) { $container->registerProvider(GetContextHandler::class, static function () use ($container) { return new GetContextHandler( - $container->get(ShopProvider::class) + $container->get(ShopProvider::class), + $container->get(UpgradeService::class) ); }); } diff --git a/tests/src/Unit/Account/QueryHandler/GetContextHandlerTest.php b/tests/src/Unit/Account/QueryHandler/GetContextHandlerTest.php index b8d93ac81..3d91b4d44 100644 --- a/tests/src/Unit/Account/QueryHandler/GetContextHandlerTest.php +++ b/tests/src/Unit/Account/QueryHandler/GetContextHandlerTest.php @@ -6,6 +6,7 @@ use PrestaShop\Module\PsAccounts\Account\Query\GetContextQuery; use PrestaShop\Module\PsAccounts\Account\QueryHandler\GetContextHandler; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; +use PrestaShop\Module\PsAccounts\Service\UpgradeService; use PrestaShop\Module\PsAccounts\Tests\TestCase; class GetContextHandlerTest extends TestCase @@ -15,6 +16,13 @@ class GetContextHandlerTest extends TestCase */ protected $shopProvider; + /** + * @inject + * + * @var UpgradeService + */ + protected $upgradeService; + /** * @test */ @@ -63,7 +71,8 @@ public function itShouldThrowsUnknownStatusException() private function getHandler() { return new GetContextHandler( - $this->shopProvider + $this->shopProvider, + $this->upgradeService ); } } From 9273463a3bbb49a946759f9eac7400536dbf7ae0 Mon Sep 17 00:00:00 2001 From: Sullivan Date: Wed, 6 Aug 2025 14:35:02 +0200 Subject: [PATCH 22/46] [ACCOUNT-2608] Manual verify shop (#519) * feat: add verify shop action on ajax controller & add verify shop url on context * fix: change verify to migrate or create * fix: remove unnecessary blank line in AdminAjaxPsAccountsController * feat: add migrateOrCreateIdentityV8Url to ShopProvider and remove from PsAccountsService * chore: cleanup commented code * refactor: rename migrateOrCreateIdentityV8 to fallbackCreateIdentity in AdminAjaxPsAccountsController and ShopProvider * fix: replace Error with Exception for better error handling in ajaxProcessGetOrRefreshToken * [ACCOUNT-3014] Let errors bubble up to ajax response (#523) * feat: let exception in single store command bubble up --------- Co-authored-by: hschoenenberger Co-authored-by: Antoine --- config.preprod.php | 3 +- .../admin/AdminAjaxPsAccountsController.php | 55 ++++++++++++- .../MigrateOrCreateIdentitiesV8Handler.php | 8 +- .../MigrateOrCreateIdentityV8Handler.php | 79 +++++++++---------- src/Provider/ShopProvider.php | 5 +- src/Service/Accounts/AccountsException.php | 59 ++++++++++++++ src/Service/Accounts/AccountsService.php | 34 ++------ .../MigrateOrCreateIdentityV8HandlerTest.php | 3 + 8 files changed, 169 insertions(+), 77 deletions(-) diff --git a/config.preprod.php b/config.preprod.php index 4ca636141..ca08eb659 100644 --- a/config.preprod.php +++ b/config.preprod.php @@ -30,7 +30,8 @@ 'ps_accounts.sso_resend_verification_email_url' => 'https://auth-preprod.prestashop.com/account/send-verification-email', 'ps_accounts.sentry_credentials' => 'https://a065bd1f092f8c849e6076fe0640d049@o298402.ingest.us.sentry.io/5354585', 'ps_accounts.accounts_vue_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components/dist/psaccountsVue.js', - 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components/dist/psaccountsVue.js', + //'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components/dist/psaccountsVue.js', + 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/@prestashopcorp/accounts-components@beta', 'ps_accounts.environment' => 'preprod', diff --git a/controllers/admin/AdminAjaxPsAccountsController.php b/controllers/admin/AdminAjaxPsAccountsController.php index f3225c0cc..e552300f1 100644 --- a/controllers/admin/AdminAjaxPsAccountsController.php +++ b/controllers/admin/AdminAjaxPsAccountsController.php @@ -20,6 +20,7 @@ require_once __DIR__ . '/../../src/Polyfill/Traits/Controller/AjaxRender.php'; use PrestaShop\Module\PsAccounts\Account\Command\DeleteUserShopCommand; +use PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentityV8Command; use PrestaShop\Module\PsAccounts\Account\Query\GetContextQuery; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; use PrestaShop\Module\PsAccounts\Account\StatusManager; @@ -30,6 +31,7 @@ use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Polyfill\Traits\Controller\AjaxRender; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; use PrestaShop\Module\PsAccounts\Service\SentryService; /** @@ -192,16 +194,63 @@ public function ajaxProcessGetContext() (string) json_encode($this->queryBus->handle($command)) ); } catch (Exception $e) { - Logger::getInstance()->error($e->getMessage()); + $this->handleError($e); + } + } + + /** + * @return void + * + * @throws Exception + */ + public function ajaxProcessFallbackCreateIdentity() + { + header('Content-Type: text/json'); + $shopId = Tools::getValue('shop_id', null); - http_response_code(500); + try { + if (!$shopId) { + throw new Exception('Shop ID is required for migration or creation.'); + } + $command = new MigrateOrCreateIdentityV8Command($shopId); + + $this->ajaxRender( + (string) json_encode($this->commandBus->handle($command)) + ); + } catch (Exception $e) { + $this->handleError($e); + } + } + + /** + * @param Exception $e + * + * @return void + */ + protected function handleError(Exception $e) + { + Logger::getInstance()->error($e); + + if ($e instanceof AccountsException) { + http_response_code(400); $this->ajaxRender( (string) json_encode([ - 'error' => true, 'message' => $e->getMessage(), + 'code' => $e->getErrorCode(), ]) ); + + return; } + + http_response_code(500); + + $this->ajaxRender( + (string) json_encode([ + 'message' => $e->getMessage() ? $e->getMessage() : 'Unknown Error', + 'code' => 'unknown-error', + ]) + ); } } diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php index 0fffd981f..78c5b30f5 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php @@ -20,8 +20,10 @@ namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; +use Exception; use PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentitiesV8Command; use PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentityV8Command; +use PrestaShop\Module\PsAccounts\Log\Logger; class MigrateOrCreateIdentitiesV8Handler extends MultiShopHandler { @@ -33,7 +35,11 @@ class MigrateOrCreateIdentitiesV8Handler extends MultiShopHandler public function handle(MigrateOrCreateIdentitiesV8Command $command) { $this->handleMulti(function ($multiShopId) { - $this->commandBus->handle(new MigrateOrCreateIdentityV8Command($multiShopId)); + try { + $this->commandBus->handle(new MigrateOrCreateIdentityV8Command($multiShopId)); + } catch (Exception $e) { + Logger::getInstance()->error($e->getMessage()); + } }); } } diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php index 6c8bf2123..d764b2580 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php @@ -28,7 +28,6 @@ use PrestaShop\Module\PsAccounts\Account\ProofManager; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; -use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; @@ -113,6 +112,11 @@ public function __construct( * @param MigrateOrCreateIdentityV8Command $command * * @return void + * + * @throws OAuth2Exception + * @throws AccountsException + * @throws RefreshTokenException + * @throws UnknownStatusException */ public function handle(MigrateOrCreateIdentityV8Command $command) { @@ -121,57 +125,46 @@ public function handle(MigrateOrCreateIdentityV8Command $command) $fromVersion = $this->upgradeService->getVersion(); - $e = null; - try { - // FIXME: shouldn't this condition be a specific flag - if (!$shopUuid || version_compare($fromVersion, '8', '>=')) { - $this->upgradeService->setVersion(); + // FIXME: shouldn't this condition be a specific flag + if (!$shopUuid || version_compare($fromVersion, '8', '>=')) { + $this->upgradeService->setVersion(); - $this->commandBus->handle(new CreateIdentityCommand($command->shopId)); + $this->commandBus->handle(new CreateIdentityCommand($command->shopId)); - return; - } + return; + } - // migrate cloudShopId locally - $this->statusManager->setCloudShopId($shopUuid); + // migrate cloudShopId locally + $this->statusManager->setCloudShopId($shopUuid); - if (version_compare($fromVersion, '7', '>=')) { - $token = $this->getAccessTokenV7($shopUuid); - } else { - $token = $this->getFirebaseTokenV6($shopUuid); - } + if (version_compare($fromVersion, '7', '>=')) { + $token = $this->getAccessTokenV7($shopUuid); + } else { + $token = $this->getFirebaseTokenV6($shopUuid); + } - $identityCreated = $this->accountsService->migrateShopIdentity( - $shopUuid, - $token, - $this->shopProvider->getUrl($shopId), - $this->proofManager->generateProof(), - $fromVersion + $identityCreated = $this->accountsService->migrateShopIdentity( + $shopUuid, + $token, + $this->shopProvider->getUrl($shopId), + $this->proofManager->generateProof(), + $fromVersion + ); + + if (!empty($identityCreated->clientId) && + !empty($identityCreated->clientSecret)) { + $this->oAuth2Service->getOAuth2Client()->update( + $identityCreated->clientId, + $identityCreated->clientSecret ); + } - if (!empty($identityCreated->clientId) && - !empty($identityCreated->clientSecret)) { - $this->oAuth2Service->getOAuth2Client()->update( - $identityCreated->clientId, - $identityCreated->clientSecret - ); - } - - // cleanup obsolete token - $this->configurationRepository->updateAccessToken(''); - - $this->statusManager->invalidateCache(); + // cleanup obsolete token + $this->configurationRepository->updateAccessToken(''); - $this->upgradeService->setVersion(); - } catch (OAuth2Exception $e) { - } catch (AccountsException $e) { - } catch (RefreshTokenException $e) { - } catch (UnknownStatusException $e) { - } + $this->statusManager->invalidateCache(); - if ($e) { - Logger::getInstance()->error($e->getMessage()); - } + $this->upgradeService->setVersion(); } /** diff --git a/src/Provider/ShopProvider.php b/src/Provider/ShopProvider.php index fbe63d871..0a89a20c2 100644 --- a/src/Provider/ShopProvider.php +++ b/src/Provider/ShopProvider.php @@ -347,7 +347,7 @@ public function getShops($groupId = null, $shopId = null, $refresh = false) function () use (&$shops, $shopData, $refresh) { $shopUrl = $this->getUrl((int) $shopData['id_shop']); $shopStatus = $this->shopStatus->getStatus($refresh); - $identifyUrl = $this->oAuth2Service->getOAuth2Client()->getRedirectUri([ + $identifyPointOfContactUrl = $this->oAuth2Service->getOAuth2Client()->getRedirectUri([ 'action' => 'identifyPointOfContact', ]); @@ -356,8 +356,9 @@ function () use (&$shops, $shopData, $refresh) { 'name' => $shopData['name'], 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), 'frontendUrl' => $shopUrl->getFrontendUrl(), - 'identifyUrl' => $identifyUrl, + 'identifyPointOfContactUrl' => $identifyPointOfContactUrl, 'shopStatus' => $shopStatus, + 'fallbackCreateIdentityUrl' => $this->link->getAdminLink('AdminAjaxPsAccounts', true, [], ['ajax' => 1, 'action' => 'fallbackCreateIdentity', 'shop_id' => $shopData['id_shop']]), ]; } ); diff --git a/src/Service/Accounts/AccountsException.php b/src/Service/Accounts/AccountsException.php index 9fb276adc..a801917ba 100644 --- a/src/Service/Accounts/AccountsException.php +++ b/src/Service/Accounts/AccountsException.php @@ -20,6 +20,65 @@ namespace PrestaShop\Module\PsAccounts\Service\Accounts; +use PrestaShop\Module\PsAccounts\Http\Client\Response; + class AccountsException extends \Exception { + /** + * @var string + */ + protected $errorCode; + + /** + * @param Response $response + * @param string $defaultMessage + * @param string $defaultErrorCode + */ + public function __construct($response, $defaultMessage = '', $defaultErrorCode = '') + { + $this->errorCode = $this->getErrorCodeFromResponse($response, $defaultErrorCode); + + $message = $this->getErrorMessageFromResponse($response, $defaultMessage); + parent::__construct($message); + } + + /** + * Get the error code. + * + * @return string + */ + public function getErrorCode() + { + return $this->errorCode; + } + + /** + * @param Response $response + * @param string $defaultMessage + * + * @return string + */ + protected function getErrorMessageFromResponse(Response $response, $defaultMessage = '') + { + if (!isset($response->body['message'])) { + return $defaultMessage; + } + + return $response->body['message']; + } + + /** + * @param Response $response + * @param string $defaultCode + * + * @return string + */ + protected function getErrorCodeFromResponse(Response $response, $defaultCode = '') + { + if (!isset($response->body['error'])) { + return $defaultCode; + } + + return $response->body['error']; + } } diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index 4e11bbb93..cda3796e5 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -124,7 +124,7 @@ public function firebaseTokens($cloudShopId, $accessToken) ); if (!$response->isSuccessful) { - throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to refresh token.')); + throw new AccountsException($response, 'Unable to get deprecated tokens', 'store-identity/unable-to-get-deprecated-tokens'); } return new FirebaseTokens($response->body); @@ -153,7 +153,7 @@ public function refreshShopToken($refreshToken, $cloudShopId) ); if (!$response->isSuccessful) { - throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to refresh shop token.')); + throw new AccountsException($response, 'Unable to refresh shop token', 'store/unable-to-refresh-shop-token'); } return new LegacyFirebaseToken($response->body); @@ -253,7 +253,7 @@ public function createShopIdentity(ShopUrl $shopUrl, $proof) ); if (!$response->isSuccessful) { - throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to create shop identity.')); + throw new AccountsException($response, 'Unable to create shop identity', 'store-identity/unable-to-create-shop-identity'); } return new IdentityCreated($response->body); @@ -288,7 +288,7 @@ public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $ ); if (!$response->isSuccessful) { - throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to verify shop identity.')); + throw new AccountsException($response, 'Unable to verify shop identity', 'store-identity/unable-to-verify-shop-identity'); } } @@ -313,7 +313,7 @@ public function shopStatus($cloudShopId, $shopToken) ); if (!$response->isSuccessful) { - throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to retrieve shop status')); + throw new AccountsException($response, 'Unable to retrieve shop status', 'store-identity/unable-to-retrieve-shop-status'); } return new ShopStatus($response->body); @@ -344,7 +344,7 @@ public function setPointOfContact($cloudShopId, $shopToken, $userToken) ); if (!$response->isSuccessful) { - throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to set point of contact')); + throw new AccountsException($response, 'Unable to set point of contact', 'store-identity/unable-to-set-point-of-contact'); } } @@ -379,29 +379,9 @@ public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, ); if (!$response->isSuccessful) { - throw new AccountsException($this->getResponseErrorMsg($response, 'Unable to migrate shop identity.')); + throw new AccountsException($response, 'Unable to migrate shop identity', 'store-identity/unable-to-migrate-shop-identity'); } return new IdentityCreated($response->body); } - - /** - * @param Response $response - * @param string $defaultMessage - * - * @return string - */ - protected function getResponseErrorMsg(Response $response, $defaultMessage = '') - { - $msg = $defaultMessage; - $body = $response->body; - if ( - isset($body['error']) && - isset($body['error_description']) - ) { - $msg = $body['error'] . ': ' . $body['error_description']; - } - - return $response->statusCode . ' - ' . $msg; - } } diff --git a/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php index 896e34b02..8062fc119 100644 --- a/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php @@ -12,6 +12,7 @@ use PrestaShop\Module\PsAccounts\Http\Client\Response; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; use PrestaShop\Module\PsAccounts\Service\UpgradeService; use PrestaShop\Module\PsAccounts\Tests\TestCase; @@ -361,6 +362,8 @@ public function itShouldNotMigrateOnError() ], 400, true); }); + $this->expectException(AccountsException::class); + $this->getHandler()->handle(new MigrateOrCreateIdentityV8Command($this->shopId)); $this->assertEquals($fromVersion, $this->upgradeService->getVersion()); From f195a68a701da39dd79e8d8d8e68c75f79f84ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Mon, 11 Aug 2025 11:43:51 +0200 Subject: [PATCH 23/46] [ACCOUNT-3038] feat: context with empty status (#531) * feat: return a context event with an empty status * feat: return a context event with an empty status * feat: add frontendUrl into empty status * fix: manage correctly refresh property --- src/Provider/ShopProvider.php | 11 ++++++++++- src/Service/Accounts/AccountsService.php | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Provider/ShopProvider.php b/src/Provider/ShopProvider.php index 0a89a20c2..43ac526e1 100644 --- a/src/Provider/ShopProvider.php +++ b/src/Provider/ShopProvider.php @@ -21,11 +21,13 @@ namespace PrestaShop\Module\PsAccounts\Provider; use PrestaShop\Module\PsAccounts\Account\Dto\Shop; +use PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; use PrestaShop\Module\PsAccounts\Account\ShopUrl; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Adapter\Link; use PrestaShop\Module\PsAccounts\Context\ShopContext; +use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; class ShopProvider @@ -346,7 +348,14 @@ public function getShops($groupId = null, $shopId = null, $refresh = false) $shopData['id_shop'], function () use (&$shops, $shopData, $refresh) { $shopUrl = $this->getUrl((int) $shopData['id_shop']); - $shopStatus = $this->shopStatus->getStatus($refresh); + try { + $cacheTtl = $refresh ? 0 : StatusManager::CACHE_TTL; + $shopStatus = $this->shopStatus->getStatus(false, $cacheTtl); + } catch (UnknownStatusException $e) { + $shopStatus = new ShopStatus([ + 'frontendUrl' => $shopUrl->getFrontendUrl(), + ]); + } $identifyPointOfContactUrl = $this->oAuth2Service->getOAuth2Client()->getRedirectUri([ 'action' => 'identifyPointOfContact', ]); diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index cda3796e5..1aa95bb82 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -124,7 +124,7 @@ public function firebaseTokens($cloudShopId, $accessToken) ); if (!$response->isSuccessful) { - throw new AccountsException($response, 'Unable to get deprecated tokens', 'store-identity/unable-to-get-deprecated-tokens'); + throw new AccountsException($response, 'Unable to get firebase tokens', 'store-identity/unable-to-get-deprecated-tokens'); } return new FirebaseTokens($response->body); @@ -153,7 +153,7 @@ public function refreshShopToken($refreshToken, $cloudShopId) ); if (!$response->isSuccessful) { - throw new AccountsException($response, 'Unable to refresh shop token', 'store/unable-to-refresh-shop-token'); + throw new AccountsException($response, 'Unable to refresh firebase shop token', 'store/unable-to-refresh-shop-token'); } return new LegacyFirebaseToken($response->body); From c881efc4c256bc7504de40dae4f38ad4af6692c2 Mon Sep 17 00:00:00 2001 From: Antoine Date: Tue, 12 Aug 2025 17:01:54 +0200 Subject: [PATCH 24/46] [ACCOUNT-3019] Manual verification tracking (#520) * feat: add source for tracking module usage * feat: add source everywhere * feat: add source on shop status (#525) * feat: add source on shop status * feat: add source everywhere * fix: lint * fix: keep simple source --- .../admin/AdminAjaxPsAccountsController.php | 1 + .../admin/AdminOAuth2PsAccountsController.php | 2 +- ps_accounts.php | 8 +++++--- .../Command/CreateIdentitiesCommand.php | 12 +++++++++++ src/Account/Command/CreateIdentityCommand.php | 9 ++++++++- .../Command/IdentifyContactCommand.php | 9 ++++++++- .../MigrateOrCreateIdentitiesV8Command.php | 12 +++++++++++ .../MigrateOrCreateIdentityV8Command.php | 9 ++++++++- .../Command/VerifyIdentitiesCommand.php | 12 +++++++++++ src/Account/Command/VerifyIdentityCommand.php | 9 ++++++++- .../CreateIdentitiesHandler.php | 4 ++-- .../CommandHandler/CreateIdentityHandler.php | 5 +++-- .../CommandHandler/IdentifyContactHandler.php | 5 +++-- .../MigrateOrCreateIdentitiesV8Handler.php | 4 ++-- .../MigrateOrCreateIdentityV8Handler.php | 5 +++-- .../VerifyIdentitiesHandler.php | 4 ++-- .../CommandHandler/VerifyIdentityHandler.php | 5 +++-- src/Account/Query/GetContextQuery.php | 9 ++++++++- .../QueryHandler/GetContextHandler.php | 1 + src/Account/StatusManager.php | 11 +++++----- src/AccountLogin/OAuth2LoginTrait.php | 20 +++++++++++++++++++ src/Controller/Admin/OAuth2Controller.php | 2 +- src/Hook/DisplayAdminAfterHeader.php | 2 +- src/Provider/ShopProvider.php | 10 ++++++---- src/Service/Accounts/AccountsService.php | 19 ++++++++++++++---- .../CreateIdentityHandlerTest.php | 8 ++++---- tests/src/Unit/Account/StatusManagerTest.php | 8 ++++---- upgrade/upgrade-8.0.0.php | 2 +- 28 files changed, 160 insertions(+), 47 deletions(-) diff --git a/controllers/admin/AdminAjaxPsAccountsController.php b/controllers/admin/AdminAjaxPsAccountsController.php index e552300f1..a12bad3ce 100644 --- a/controllers/admin/AdminAjaxPsAccountsController.php +++ b/controllers/admin/AdminAjaxPsAccountsController.php @@ -185,6 +185,7 @@ public function ajaxProcessGetContext() try { $command = new GetContextQuery( + Tools::getValue('source', 'ajax'), Tools::getValue('group_id', null), Tools::getValue('shop_id', null), filter_var(Tools::getValue('refresh', false), FILTER_VALIDATE_BOOLEAN) diff --git a/controllers/admin/AdminOAuth2PsAccountsController.php b/controllers/admin/AdminOAuth2PsAccountsController.php index d61b7b5cb..66f2743b0 100644 --- a/controllers/admin/AdminOAuth2PsAccountsController.php +++ b/controllers/admin/AdminOAuth2PsAccountsController.php @@ -132,7 +132,7 @@ protected function initUserSession(AccessToken $accessToken) ); if ($this->getOAuthAction() === 'identifyPointOfContact') { - $this->commandBus->handle(new IdentifyContactCommand($accessToken)); + $this->commandBus->handle(new IdentifyContactCommand($accessToken, $this->getSource())); return true; } diff --git a/ps_accounts.php b/ps_accounts.php index 79f747788..b076bf801 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -442,7 +442,7 @@ public function onModuleReset() $commandBus = $this->getService(\PrestaShop\Module\PsAccounts\Cqrs\CommandBus::class); // Verification flow - $commandBus->handle(new \PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentitiesV8Command()); + $commandBus->handle(new \PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentitiesV8Command('ps_accounts')); } /** @@ -457,15 +457,17 @@ public function getCloudShopId() } /** + * @param string $source + * * @return bool */ - public function getVerifiedStatus() + public function getVerifiedStatus($source = 'ps_accounts') { /** @var \PrestaShop\Module\PsAccounts\Account\StatusManager $statusManager */ $statusManager = $this->getService(\PrestaShop\Module\PsAccounts\Account\StatusManager::class); try { - if ($statusManager->getStatus()->isVerified) { + if ($statusManager->getStatus($source)->isVerified) { return true; } } catch (\PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException $e) { diff --git a/src/Account/Command/CreateIdentitiesCommand.php b/src/Account/Command/CreateIdentitiesCommand.php index 6b4fd1802..3c3edeaa9 100644 --- a/src/Account/Command/CreateIdentitiesCommand.php +++ b/src/Account/Command/CreateIdentitiesCommand.php @@ -23,4 +23,16 @@ class CreateIdentitiesCommand { + /** + * @var string|null + */ + public $source; + + /** + * @param string|null $source + */ + public function __construct($source = null) + { + $this->source = $source; + } } diff --git a/src/Account/Command/CreateIdentityCommand.php b/src/Account/Command/CreateIdentityCommand.php index 97ff8c55a..5844950fc 100644 --- a/src/Account/Command/CreateIdentityCommand.php +++ b/src/Account/Command/CreateIdentityCommand.php @@ -28,11 +28,18 @@ class CreateIdentityCommand */ public $shopId; + /** + * @var string|null + */ + public $source; + /** * @param int|null $shopId + * @param string|null $source */ - public function __construct($shopId) + public function __construct($shopId, $source = null) { $this->shopId = $shopId; + $this->source = $source; } } diff --git a/src/Account/Command/IdentifyContactCommand.php b/src/Account/Command/IdentifyContactCommand.php index a1d8d3bb3..6806f061d 100644 --- a/src/Account/Command/IdentifyContactCommand.php +++ b/src/Account/Command/IdentifyContactCommand.php @@ -30,11 +30,18 @@ class IdentifyContactCommand */ public $accessToken; + /** + * @var string|null + */ + public $source; + /** * @param AccessToken $accessToken + * @param string|null $source */ - public function __construct($accessToken) + public function __construct($accessToken, $source = 'ps_accounts') { $this->accessToken = $accessToken; + $this->source = $source; } } diff --git a/src/Account/Command/MigrateOrCreateIdentitiesV8Command.php b/src/Account/Command/MigrateOrCreateIdentitiesV8Command.php index ccbdb3764..b722c593e 100644 --- a/src/Account/Command/MigrateOrCreateIdentitiesV8Command.php +++ b/src/Account/Command/MigrateOrCreateIdentitiesV8Command.php @@ -23,4 +23,16 @@ class MigrateOrCreateIdentitiesV8Command { + /** + * @var string|null + */ + public $source; + + /** + * @param string|null $source + */ + public function __construct($source = null) + { + $this->source = $source; + } } diff --git a/src/Account/Command/MigrateOrCreateIdentityV8Command.php b/src/Account/Command/MigrateOrCreateIdentityV8Command.php index 9f3e10562..db82bb92e 100644 --- a/src/Account/Command/MigrateOrCreateIdentityV8Command.php +++ b/src/Account/Command/MigrateOrCreateIdentityV8Command.php @@ -28,11 +28,18 @@ class MigrateOrCreateIdentityV8Command */ public $shopId; + /** + * @var string|null + */ + public $source; + /** * @param int|null $shopId + * @param string|null $source */ - public function __construct($shopId) + public function __construct($shopId, $source = null) { $this->shopId = $shopId; + $this->source = $source; } } diff --git a/src/Account/Command/VerifyIdentitiesCommand.php b/src/Account/Command/VerifyIdentitiesCommand.php index dfd50215c..167696940 100644 --- a/src/Account/Command/VerifyIdentitiesCommand.php +++ b/src/Account/Command/VerifyIdentitiesCommand.php @@ -23,4 +23,16 @@ class VerifyIdentitiesCommand { + /** + * @var string|null + */ + public $source; + + /** + * @param string|null $source + */ + public function __construct($source = null) + { + $this->source = $source; + } } diff --git a/src/Account/Command/VerifyIdentityCommand.php b/src/Account/Command/VerifyIdentityCommand.php index 53535f27a..73d183573 100644 --- a/src/Account/Command/VerifyIdentityCommand.php +++ b/src/Account/Command/VerifyIdentityCommand.php @@ -28,11 +28,18 @@ class VerifyIdentityCommand */ public $shopId; + /** + * @var string|null + */ + public $source; + /** * @param int|null $shopId + * @param string|null $source */ - public function __construct($shopId) + public function __construct($shopId, $source = 'ps_accounts') { $this->shopId = $shopId; + $this->source = $source; } } diff --git a/src/Account/CommandHandler/CreateIdentitiesHandler.php b/src/Account/CommandHandler/CreateIdentitiesHandler.php index b1d45c309..b9877793f 100644 --- a/src/Account/CommandHandler/CreateIdentitiesHandler.php +++ b/src/Account/CommandHandler/CreateIdentitiesHandler.php @@ -35,9 +35,9 @@ class CreateIdentitiesHandler extends MultiShopHandler */ public function handle(CreateIdentitiesCommand $command) { - $this->handleMulti(function ($multiShopId) { + $this->handleMulti(function ($multiShopId) use ($command) { try { - $this->commandBus->handle(new CreateIdentityCommand($multiShopId)); + $this->commandBus->handle(new CreateIdentityCommand($multiShopId, $command->source)); } catch (RefreshTokenException $e) { Logger::getInstance()->error($e->getMessage()); } catch (AccountsException $e) { diff --git a/src/Account/CommandHandler/CreateIdentityHandler.php b/src/Account/CommandHandler/CreateIdentityHandler.php index f58149f60..a81734c0a 100644 --- a/src/Account/CommandHandler/CreateIdentityHandler.php +++ b/src/Account/CommandHandler/CreateIdentityHandler.php @@ -104,7 +104,8 @@ public function handle(CreateIdentityCommand $command) $identityCreated = $this->accountsService->createShopIdentity( $this->shopProvider->getUrl($shopId), - $this->proofManager->generateProof() + $this->proofManager->generateProof(), + $command->source ); $this->oAuth2Client->update( $identityCreated->clientId, @@ -113,7 +114,7 @@ public function handle(CreateIdentityCommand $command) $this->statusManager->setCloudShopId($identityCreated->cloudShopId); $this->statusManager->invalidateCache(); } else { - $this->commandBus->handle(new VerifyIdentityCommand($command->shopId)); + $this->commandBus->handle(new VerifyIdentityCommand($command->shopId, $command->source)); } } diff --git a/src/Account/CommandHandler/IdentifyContactHandler.php b/src/Account/CommandHandler/IdentifyContactHandler.php index 9b79e1328..1ea7b95f8 100644 --- a/src/Account/CommandHandler/IdentifyContactHandler.php +++ b/src/Account/CommandHandler/IdentifyContactHandler.php @@ -67,7 +67,7 @@ public function __construct( */ public function handle(IdentifyContactCommand $command) { - $status = $this->statusManager->getStatus(); + $status = $this->statusManager->getStatus($command->source); if (!$status->isVerified) { return; } @@ -75,7 +75,8 @@ public function handle(IdentifyContactCommand $command) $this->accountsService->setPointOfContact( $this->statusManager->getCloudShopId(), $this->shopSession->getValidToken(), - $command->accessToken->access_token + $command->accessToken->access_token, + $command->source ); $this->statusManager->invalidateCache(); } diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php index 78c5b30f5..4e0d370f2 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php @@ -34,9 +34,9 @@ class MigrateOrCreateIdentitiesV8Handler extends MultiShopHandler */ public function handle(MigrateOrCreateIdentitiesV8Command $command) { - $this->handleMulti(function ($multiShopId) { + $this->handleMulti(function ($multiShopId) use ($command) { try { - $this->commandBus->handle(new MigrateOrCreateIdentityV8Command($multiShopId)); + $this->commandBus->handle(new MigrateOrCreateIdentityV8Command($multiShopId, $command->source)); } catch (Exception $e) { Logger::getInstance()->error($e->getMessage()); } diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php index d764b2580..c9189c7db 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php @@ -129,7 +129,7 @@ public function handle(MigrateOrCreateIdentityV8Command $command) if (!$shopUuid || version_compare($fromVersion, '8', '>=')) { $this->upgradeService->setVersion(); - $this->commandBus->handle(new CreateIdentityCommand($command->shopId)); + $this->commandBus->handle(new CreateIdentityCommand($command->shopId, $command->source)); return; } @@ -148,7 +148,8 @@ public function handle(MigrateOrCreateIdentityV8Command $command) $token, $this->shopProvider->getUrl($shopId), $this->proofManager->generateProof(), - $fromVersion + $fromVersion, + $command->source ); if (!empty($identityCreated->clientId) && diff --git a/src/Account/CommandHandler/VerifyIdentitiesHandler.php b/src/Account/CommandHandler/VerifyIdentitiesHandler.php index 43c74314d..7456e486f 100644 --- a/src/Account/CommandHandler/VerifyIdentitiesHandler.php +++ b/src/Account/CommandHandler/VerifyIdentitiesHandler.php @@ -36,9 +36,9 @@ class VerifyIdentitiesHandler extends MultiShopHandler */ public function handle(VerifyIdentitiesCommand $command) { - $this->handleMulti(function ($multiShopId) { + $this->handleMulti(function ($multiShopId) use ($command) { try { - $this->commandBus->handle(new VerifyIdentityCommand($multiShopId)); + $this->commandBus->handle(new VerifyIdentityCommand($multiShopId, $command->source)); } catch (RefreshTokenException $e) { Logger::getInstance()->error($e->getMessage()); } catch (AccountsException $e) { diff --git a/src/Account/CommandHandler/VerifyIdentityHandler.php b/src/Account/CommandHandler/VerifyIdentityHandler.php index f1e5da126..683458283 100644 --- a/src/Account/CommandHandler/VerifyIdentityHandler.php +++ b/src/Account/CommandHandler/VerifyIdentityHandler.php @@ -89,7 +89,7 @@ public function __construct( */ public function handle(VerifyIdentityCommand $command) { - $cachedStatus = $this->statusManager->getStatus(); + $cachedStatus = $this->statusManager->getStatus($command->source); if ($cachedStatus->isVerified) { return; @@ -101,7 +101,8 @@ public function handle(VerifyIdentityCommand $command) $this->statusManager->getCloudShopId(), $this->shopSession->getValidToken(), $this->shopProvider->getUrl($shopId), - $this->proofManager->generateProof() + $this->proofManager->generateProof(), + $command->source ); $this->statusManager->invalidateCache(); } diff --git a/src/Account/Query/GetContextQuery.php b/src/Account/Query/GetContextQuery.php index ba04b72bb..0a47b0296 100644 --- a/src/Account/Query/GetContextQuery.php +++ b/src/Account/Query/GetContextQuery.php @@ -23,6 +23,11 @@ class GetContextQuery { + /** + * @var string|null + */ + public $source; + /** * @var string|null */ @@ -39,12 +44,14 @@ class GetContextQuery public $refresh; /** + * @param string|null $source * @param string|null $groupId * @param string|null $shopId * @param bool $refresh */ - public function __construct($groupId = null, $shopId = null, $refresh = false) + public function __construct($source = null, $groupId = null, $shopId = null, $refresh = false) { + $this->source = $source; $this->groupId = $groupId; $this->shopId = $shopId; $this->refresh = $refresh; diff --git a/src/Account/QueryHandler/GetContextHandler.php b/src/Account/QueryHandler/GetContextHandler.php index 188a9247b..bf5930f79 100644 --- a/src/Account/QueryHandler/GetContextHandler.php +++ b/src/Account/QueryHandler/GetContextHandler.php @@ -63,6 +63,7 @@ public function handle(GetContextQuery $query) 'module_version_from_files' => \Ps_accounts::VERSION, ], 'groups' => $this->shopProvider->getShops( + $query->source, $query->groupId, $query->shopId, $query->refresh diff --git a/src/Account/StatusManager.php b/src/Account/StatusManager.php index e0a8f9611..2477b2b25 100644 --- a/src/Account/StatusManager.php +++ b/src/Account/StatusManager.php @@ -87,13 +87,14 @@ public function identityCreated() public function identityVerified($cachedStatus = true) { try { - return $this->getStatus($cachedStatus)->isVerified; + return $this->getStatus(null, $cachedStatus)->isVerified; } catch (UnknownStatusException $e) { return false; } } /** + * @param string|null $source * @param bool $cachedOnly * @param int $cacheTtl * @@ -101,7 +102,7 @@ public function identityVerified($cachedStatus = true) * * @throws UnknownStatusException */ - public function getStatus($cachedOnly = false, $cacheTtl = self::CACHE_TTL) + public function getStatus($source = null, $cachedOnly = false, $cacheTtl = self::CACHE_TTL) { if (!$cachedOnly) { try { @@ -196,7 +197,7 @@ public function cacheExpired(CachedShopStatus $cachedStatus = null, $cacheTtl = public function getCloudShopId($cachedStatus = true) { try { - return $this->getStatus($cachedStatus)->cloudShopId; + return $this->getStatus(null, $cachedStatus)->cloudShopId; } catch (UnknownStatusException $e) { return null; } @@ -224,7 +225,7 @@ public function setCloudShopId($cloudShopId) public function getPointOfContactUuid($cachedStatus = true) { try { - return $this->getStatus($cachedStatus)->pointOfContactUuid; + return $this->getStatus(null, $cachedStatus)->pointOfContactUuid; } catch (UnknownStatusException $e) { return null; } @@ -238,7 +239,7 @@ public function getPointOfContactUuid($cachedStatus = true) public function getPointOfContactEmail($cachedStatus = true) { try { - return $this->getStatus($cachedStatus)->pointOfContactEmail; + return $this->getStatus(null, $cachedStatus)->pointOfContactEmail; } catch (UnknownStatusException $e) { return null; } diff --git a/src/AccountLogin/OAuth2LoginTrait.php b/src/AccountLogin/OAuth2LoginTrait.php index 30a7a0551..13b0cc9d2 100644 --- a/src/AccountLogin/OAuth2LoginTrait.php +++ b/src/AccountLogin/OAuth2LoginTrait.php @@ -106,6 +106,7 @@ public function oauth2Login() $state = Tools::getValue('state', ''); $code = Tools::getValue('code', ''); $action = Tools::getValue('action', 'login'); + $source = Tools::getValue('source', 'ps_accounts'); if (!empty($error)) { // Got an error, probably user denied access @@ -116,6 +117,7 @@ public function oauth2Login() $oauth2Session->clear(); $this->setOAuthAction($action); + $this->setSource($source); $this->setSessionReturnTo(Tools::getValue($this->getReturnToParam())); @@ -247,6 +249,24 @@ private function setOAuthAction($action) $this->getSession()->set('oauth2action', $action); } + /** + * @return string + */ + private function getSource() + { + return $this->getSession()->get('source'); + } + + /** + * @param string $source + * + * @return void + */ + private function setSource($source) + { + $this->getSession()->set('source', $source); + } + /** * @param string $uid * @param string $email diff --git a/src/Controller/Admin/OAuth2Controller.php b/src/Controller/Admin/OAuth2Controller.php index ade716b48..c0ccd1c36 100644 --- a/src/Controller/Admin/OAuth2Controller.php +++ b/src/Controller/Admin/OAuth2Controller.php @@ -198,7 +198,7 @@ protected function initUserSession(AccessToken $accessToken) ); if ($this->getOAuthAction() === 'identifyPointOfContact') { - $this->commandBus->handle(new IdentifyContactCommand($accessToken)); + $this->commandBus->handle(new IdentifyContactCommand($accessToken, $this->getSource())); return true; } diff --git a/src/Hook/DisplayAdminAfterHeader.php b/src/Hook/DisplayAdminAfterHeader.php index 33b78bc84..8dcf60aa7 100644 --- a/src/Hook/DisplayAdminAfterHeader.php +++ b/src/Hook/DisplayAdminAfterHeader.php @@ -35,7 +35,7 @@ public function execute(array $params = []) } $cloudShopId = $this->module->getCloudShopId(); - $verified = $this->module->getVerifiedStatus(); + $verified = $this->module->getVerifiedStatus('ps_accounts'); $verifiedMsg = $verified ? 'verified' : 'NOT verified'; /** @var Link $link */ diff --git a/src/Provider/ShopProvider.php b/src/Provider/ShopProvider.php index 43ac526e1..e4ce672b4 100644 --- a/src/Provider/ShopProvider.php +++ b/src/Provider/ShopProvider.php @@ -324,13 +324,14 @@ public function getUrl($shopId) } /** + * @param string|null $source * @param string|null $groupId * @param string|null $shopId * @param bool $refresh * * @return array */ - public function getShops($groupId = null, $shopId = null, $refresh = false) + public function getShops($source = null, $groupId = null, $shopId = null, $refresh = false) { $shopList = []; foreach (\Shop::getTree() as $groupData) { @@ -346,7 +347,7 @@ public function getShops($groupId = null, $shopId = null, $refresh = false) $this->getShopContext()->execInShopContext( $shopData['id_shop'], - function () use (&$shops, $shopData, $refresh) { + function () use (&$shops, $shopData, $source, $refresh) { $shopUrl = $this->getUrl((int) $shopData['id_shop']); try { $cacheTtl = $refresh ? 0 : StatusManager::CACHE_TTL; @@ -358,6 +359,7 @@ function () use (&$shops, $shopData, $refresh) { } $identifyPointOfContactUrl = $this->oAuth2Service->getOAuth2Client()->getRedirectUri([ 'action' => 'identifyPointOfContact', + 'source' => $source, ]); $shops[] = [ @@ -366,8 +368,8 @@ function () use (&$shops, $shopData, $refresh) { 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), 'frontendUrl' => $shopUrl->getFrontendUrl(), 'identifyPointOfContactUrl' => $identifyPointOfContactUrl, - 'shopStatus' => $shopStatus, - 'fallbackCreateIdentityUrl' => $this->link->getAdminLink('AdminAjaxPsAccounts', true, [], ['ajax' => 1, 'action' => 'fallbackCreateIdentity', 'shop_id' => $shopData['id_shop']]), + 'shopStatus' => $shopStatus->toArray(), + 'fallbackCreateIdentityUrl' => $this->link->getAdminLink('AdminAjaxPsAccounts', true, [], ['ajax' => 1, 'action' => 'fallbackCreateIdentity', 'shop_id' => $shopData['id_shop'], 'source' => $source]), ]; } ); diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index 1aa95bb82..6cfcdb360 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -36,6 +36,7 @@ class AccountsService { const HEADER_AUTHORIZATION = 'Authorization'; + const HEADER_MODULE_SOURCE = 'X-Module-Source'; const HEADER_MODULE_VERSION = 'X-Module-Version'; const HEADER_PRESTASHOP_VERSION = 'X-Prestashop-Version'; const HEADER_MULTISHOP_ENABLED = 'X-Multishop-Enabled'; @@ -233,16 +234,20 @@ public function healthCheck() /** * @param ShopUrl $shopUrl * @param string $proof + * @param string|null $source * * @return IdentityCreated * * @throws AccountsException */ - public function createShopIdentity(ShopUrl $shopUrl, $proof) + public function createShopIdentity(ShopUrl $shopUrl, $proof, $source = null) { $response = $this->getClient()->post( '/v1/shop-identities', [ + Request::HEADERS => $this->getHeaders([ + self::HEADER_MODULE_SOURCE => $source, + ]), Request::JSON => [ 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), 'frontendUrl' => $shopUrl->getFrontendUrl(), @@ -264,12 +269,13 @@ public function createShopIdentity(ShopUrl $shopUrl, $proof) * @param string $shopToken * @param ShopUrl $shopUrl * @param string $proof + * @param string|null $source * * @return void * * @throws AccountsException */ - public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $proof) + public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $proof, $source = null) { $response = $this->getClient()->post( '/v1/shop-identities/' . $cloudShopId . '/verify', @@ -277,6 +283,7 @@ public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $ Request::HEADERS => $this->getHeaders([ self::HEADER_AUTHORIZATION => 'Bearer ' . $shopToken, self::HEADER_SHOP_ID => $cloudShopId, + self::HEADER_MODULE_SOURCE => $source, ]), Request::JSON => [ 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), @@ -323,12 +330,13 @@ public function shopStatus($cloudShopId, $shopToken) * @param string $cloudShopId * @param string $shopToken * @param string $userToken + * @param string|null $source * * @return void * * @throws AccountsException */ - public function setPointOfContact($cloudShopId, $shopToken, $userToken) + public function setPointOfContact($cloudShopId, $shopToken, $userToken, $source = null) { $response = $this->getClient()->post( '/v1/shop-identities/' . $cloudShopId . '/point-of-contact', @@ -336,6 +344,7 @@ public function setPointOfContact($cloudShopId, $shopToken, $userToken) Request::HEADERS => $this->getHeaders([ self::HEADER_AUTHORIZATION => 'Bearer ' . $shopToken, self::HEADER_SHOP_ID => $cloudShopId, + self::HEADER_MODULE_SOURCE => $source, ]), Request::JSON => [ 'pointOfContactJWT' => $userToken, @@ -354,12 +363,13 @@ public function setPointOfContact($cloudShopId, $shopToken, $userToken) * @param ShopUrl $shopUrl * @param string $proof * @param string $fromVersion + * @param string|null $source * * @return IdentityCreated * * @throws AccountsException */ - public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $proof, $fromVersion) + public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $proof, $fromVersion, $source = null) { $response = $this->getClient()->put( '/v1/shop-identities/' . $cloudShopId . '/migrate', @@ -367,6 +377,7 @@ public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, Request::HEADERS => $this->getHeaders([ self::HEADER_AUTHORIZATION => 'Bearer ' . $shopToken, self::HEADER_SHOP_ID => $cloudShopId, + self::HEADER_MODULE_SOURCE => $source, ]), Request::JSON => [ 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), diff --git a/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php index a3e60dc40..a876d0635 100644 --- a/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php @@ -110,7 +110,7 @@ public function itShouldStoreIdentity() ], 200, true); }); - $this->getHandler()->handle(new CreateIdentityCommand(1, [])); + $this->getHandler()->handle(new CreateIdentityCommand(1)); $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); $this->assertEquals($clientId, $this->oauth2Client->getClientId()); @@ -154,12 +154,12 @@ public function itShouldNotChangeIdentityIfExists() $this->oauth2Client->delete(); $this->statusManager->setCloudShopId(''); - $this->getHandler()->handle(new CreateIdentityCommand(1, [])); + $this->getHandler()->handle(new CreateIdentityCommand(1)); // $this->oauth2Client->delete(); // $this->statusManager->setCloudShopId(''); - $this->getHandler()->handle(new CreateIdentityCommand(1, [])); + $this->getHandler()->handle(new CreateIdentityCommand(1)); $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); $this->assertEquals($clientId, $this->oauth2Client->getClientId()); @@ -191,7 +191,7 @@ public function itShouldTriggerVerifyIdentityIfAlreadyCreated() $this->isInstanceOf(VerifyIdentityCommand::class) ); - $this->getHandler()->handle(new CreateIdentityCommand(1, [])); + $this->getHandler()->handle(new CreateIdentityCommand(1)); } /** diff --git a/tests/src/Unit/Account/StatusManagerTest.php b/tests/src/Unit/Account/StatusManagerTest.php index 2825c887e..bce136247 100644 --- a/tests/src/Unit/Account/StatusManagerTest.php +++ b/tests/src/Unit/Account/StatusManagerTest.php @@ -105,7 +105,7 @@ public function itShouldThrowOnUnsetCachedStatus() $this->expectException(UnknownStatusException::class); - $this->statusManager->getStatus(StatusManager::CACHE_TTL_INFINITE); + $this->statusManager->getStatus(null, StatusManager::CACHE_TTL_INFINITE); } /** @@ -140,7 +140,7 @@ public function itShouldUpdateStatusFromCloudWhenTtlExpired() sleep(1); - $cachedStatus = $this->statusManager->getStatus(false, 1); + $cachedStatus = $this->statusManager->getStatus(null, false, 1); $this->assertEquals($cloudShopId, $cachedStatus->cloudShopId); $this->assertTrue($cachedStatus->isVerified); @@ -207,7 +207,7 @@ public function itShouldNotUpdateStatusFromCloudIfTtlNotExpired() "msg" => "Invalid request", ], 400)); - $cachedStatus = $this->statusManager->getStatus(false); + $cachedStatus = $this->statusManager->getStatus(null, false); //$this->assertNull($cachedStatus->cloudShopId); $this->assertFalse($cachedStatus->isVerified); @@ -238,7 +238,7 @@ public function itShouldNotUpdateStatusFromCloudOnError() sleep(1); - $cachedStatus = $this->statusManager->getStatus(false, 1); + $cachedStatus = $this->statusManager->getStatus(null, false, 1); //$this->assertNull($cachedStatus->cloudShopId); $this->assertFalse($cachedStatus->isVerified); diff --git a/upgrade/upgrade-8.0.0.php b/upgrade/upgrade-8.0.0.php index 4b66c51f0..18fa7e477 100644 --- a/upgrade/upgrade-8.0.0.php +++ b/upgrade/upgrade-8.0.0.php @@ -20,7 +20,7 @@ function upgrade_module_8_0_0($module) /** @var CommandBus $commandBus */ $commandBus = $module->getService(CommandBus::class); - $commandBus->handle(new MigrateOrCreateIdentitiesV8Command($module->getRegisteredVersion())); + $commandBus->handle(new MigrateOrCreateIdentitiesV8Command('ps_accounts')); /* @phpstan-ignore-next-line */ } catch (\Throwable $e) { From 459552576720535ceb8a286fe802edc6131fa611 Mon Sep 17 00:00:00 2001 From: hschoenenberger Date: Tue, 12 Aug 2025 17:41:37 +0200 Subject: [PATCH 25/46] fix: declare new admin controllers at upgrade time --- upgrade/upgrade-8.0.0.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/upgrade/upgrade-8.0.0.php b/upgrade/upgrade-8.0.0.php index 18fa7e477..1694171a6 100644 --- a/upgrade/upgrade-8.0.0.php +++ b/upgrade/upgrade-8.0.0.php @@ -17,6 +17,9 @@ function upgrade_module_8_0_0($module) require __DIR__ . '/../src/enforce_autoload.php'; try { + $installer = new PrestaShop\Module\PsAccounts\Module\Install($module, Db::getInstance()); + $installer->installInMenu(); + /** @var CommandBus $commandBus */ $commandBus = $module->getService(CommandBus::class); From a948434dc0492c1db6799d608e61a60a35802e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Tue, 19 Aug 2025 10:30:34 +0200 Subject: [PATCH 26/46] feat: finish the job providing source tracking (#537) * feat: finish providing missing source tracking * fix: tests * fix: missing source argument --- ps_accounts.php | 2 +- .../CommandHandler/IdentifyContactHandler.php | 2 +- .../CommandHandler/VerifyIdentityHandler.php | 2 +- src/Account/StatusManager.php | 15 ++++++++------- src/Provider/ShopProvider.php | 2 +- src/Service/Accounts/AccountsService.php | 4 +++- tests/src/Unit/Account/StatusManagerTest.php | 8 ++++---- 7 files changed, 19 insertions(+), 16 deletions(-) diff --git a/ps_accounts.php b/ps_accounts.php index b076bf801..052a6a9b0 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -467,7 +467,7 @@ public function getVerifiedStatus($source = 'ps_accounts') $statusManager = $this->getService(\PrestaShop\Module\PsAccounts\Account\StatusManager::class); try { - if ($statusManager->getStatus($source)->isVerified) { + if ($statusManager->getStatus(false, \PrestaShop\Module\PsAccounts\Account\StatusManager::CACHE_TTL, $source)->isVerified) { return true; } } catch (\PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException $e) { diff --git a/src/Account/CommandHandler/IdentifyContactHandler.php b/src/Account/CommandHandler/IdentifyContactHandler.php index 1ea7b95f8..b8aa2d31b 100644 --- a/src/Account/CommandHandler/IdentifyContactHandler.php +++ b/src/Account/CommandHandler/IdentifyContactHandler.php @@ -67,7 +67,7 @@ public function __construct( */ public function handle(IdentifyContactCommand $command) { - $status = $this->statusManager->getStatus($command->source); + $status = $this->statusManager->getStatus(false, StatusManager::CACHE_TTL, $command->source); if (!$status->isVerified) { return; } diff --git a/src/Account/CommandHandler/VerifyIdentityHandler.php b/src/Account/CommandHandler/VerifyIdentityHandler.php index 683458283..020c3fc27 100644 --- a/src/Account/CommandHandler/VerifyIdentityHandler.php +++ b/src/Account/CommandHandler/VerifyIdentityHandler.php @@ -89,7 +89,7 @@ public function __construct( */ public function handle(VerifyIdentityCommand $command) { - $cachedStatus = $this->statusManager->getStatus($command->source); + $cachedStatus = $this->statusManager->getStatus(false, StatusManager::CACHE_TTL, $command->source); if ($cachedStatus->isVerified) { return; diff --git a/src/Account/StatusManager.php b/src/Account/StatusManager.php index 2477b2b25..eb04bdc35 100644 --- a/src/Account/StatusManager.php +++ b/src/Account/StatusManager.php @@ -87,22 +87,22 @@ public function identityCreated() public function identityVerified($cachedStatus = true) { try { - return $this->getStatus(null, $cachedStatus)->isVerified; + return $this->getStatus($cachedStatus)->isVerified; } catch (UnknownStatusException $e) { return false; } } /** - * @param string|null $source * @param bool $cachedOnly * @param int $cacheTtl + * @param string|null $source * * @return ShopStatus * * @throws UnknownStatusException */ - public function getStatus($source = null, $cachedOnly = false, $cacheTtl = self::CACHE_TTL) + public function getStatus($cachedOnly = false, $cacheTtl = self::CACHE_TTL, $source = null) { if (!$cachedOnly) { try { @@ -121,7 +121,8 @@ public function getStatus($source = null, $cachedOnly = false, $cacheTtl = self: 'updatedAt' => date('Y-m-d H:i:s'), 'shopStatus' => $this->accountsService->shopStatus( $this->getCloudShopId(), - $this->shopSession->getValidToken() + $this->shopSession->getValidToken(), + $source ), ])); } catch (AccountsException $e) { @@ -197,7 +198,7 @@ public function cacheExpired(CachedShopStatus $cachedStatus = null, $cacheTtl = public function getCloudShopId($cachedStatus = true) { try { - return $this->getStatus(null, $cachedStatus)->cloudShopId; + return $this->getStatus($cachedStatus)->cloudShopId; } catch (UnknownStatusException $e) { return null; } @@ -225,7 +226,7 @@ public function setCloudShopId($cloudShopId) public function getPointOfContactUuid($cachedStatus = true) { try { - return $this->getStatus(null, $cachedStatus)->pointOfContactUuid; + return $this->getStatus($cachedStatus)->pointOfContactUuid; } catch (UnknownStatusException $e) { return null; } @@ -239,7 +240,7 @@ public function getPointOfContactUuid($cachedStatus = true) public function getPointOfContactEmail($cachedStatus = true) { try { - return $this->getStatus(null, $cachedStatus)->pointOfContactEmail; + return $this->getStatus($cachedStatus)->pointOfContactEmail; } catch (UnknownStatusException $e) { return null; } diff --git a/src/Provider/ShopProvider.php b/src/Provider/ShopProvider.php index e4ce672b4..fe03c2d88 100644 --- a/src/Provider/ShopProvider.php +++ b/src/Provider/ShopProvider.php @@ -351,7 +351,7 @@ function () use (&$shops, $shopData, $source, $refresh) { $shopUrl = $this->getUrl((int) $shopData['id_shop']); try { $cacheTtl = $refresh ? 0 : StatusManager::CACHE_TTL; - $shopStatus = $this->shopStatus->getStatus(false, $cacheTtl); + $shopStatus = $this->shopStatus->getStatus(false, $cacheTtl, $source); } catch (UnknownStatusException $e) { $shopStatus = new ShopStatus([ 'frontendUrl' => $shopUrl->getFrontendUrl(), diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index 6cfcdb360..168a9df3a 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -302,12 +302,13 @@ public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $ /** * @param string $cloudShopId * @param string $shopToken + * @param string|null $source * * @return ShopStatus * * @throws AccountsException */ - public function shopStatus($cloudShopId, $shopToken) + public function shopStatus($cloudShopId, $shopToken, $source = null) { $response = $this->getClient()->get( '/v1/shop-identities/' . $cloudShopId . '/status', @@ -315,6 +316,7 @@ public function shopStatus($cloudShopId, $shopToken) Request::HEADERS => $this->getHeaders([ self::HEADER_AUTHORIZATION => 'Bearer ' . $shopToken, self::HEADER_SHOP_ID => $cloudShopId, + self::HEADER_MODULE_SOURCE => $source, ]), ] ); diff --git a/tests/src/Unit/Account/StatusManagerTest.php b/tests/src/Unit/Account/StatusManagerTest.php index bce136247..784724e53 100644 --- a/tests/src/Unit/Account/StatusManagerTest.php +++ b/tests/src/Unit/Account/StatusManagerTest.php @@ -105,7 +105,7 @@ public function itShouldThrowOnUnsetCachedStatus() $this->expectException(UnknownStatusException::class); - $this->statusManager->getStatus(null, StatusManager::CACHE_TTL_INFINITE); + $this->statusManager->getStatus(true); } /** @@ -140,7 +140,7 @@ public function itShouldUpdateStatusFromCloudWhenTtlExpired() sleep(1); - $cachedStatus = $this->statusManager->getStatus(null, false, 1); + $cachedStatus = $this->statusManager->getStatus(false, 1); $this->assertEquals($cloudShopId, $cachedStatus->cloudShopId); $this->assertTrue($cachedStatus->isVerified); @@ -207,7 +207,7 @@ public function itShouldNotUpdateStatusFromCloudIfTtlNotExpired() "msg" => "Invalid request", ], 400)); - $cachedStatus = $this->statusManager->getStatus(null, false); + $cachedStatus = $this->statusManager->getStatus(); //$this->assertNull($cachedStatus->cloudShopId); $this->assertFalse($cachedStatus->isVerified); @@ -238,7 +238,7 @@ public function itShouldNotUpdateStatusFromCloudOnError() sleep(1); - $cachedStatus = $this->statusManager->getStatus(null, false, 1); + $cachedStatus = $this->statusManager->getStatus(false, 1); //$this->assertNull($cachedStatus->cloudShopId); $this->assertFalse($cachedStatus->isVerified); From a1577988f26d208ffb08d7980e6df24a86a55839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Tue, 19 Aug 2025 10:34:16 +0200 Subject: [PATCH 27/46] feat: remove admin debug controller (#534) * feat: remove admin debug controller * feat: remove debug link --- .../admin/AdminDebugPsAccountsController.php | 51 ++----------------- ps_accounts.php | 1 - src/Hook/DisplayAdminAfterHeader.php | 2 - upgrade/upgrade-8.0.0.php | 6 +++ 4 files changed, 9 insertions(+), 51 deletions(-) diff --git a/controllers/admin/AdminDebugPsAccountsController.php b/controllers/admin/AdminDebugPsAccountsController.php index 38aafee98..b4f0b22a0 100755 --- a/controllers/admin/AdminDebugPsAccountsController.php +++ b/controllers/admin/AdminDebugPsAccountsController.php @@ -18,54 +18,9 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; -use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; -use PrestaShop\Module\PsAccounts\Account\StatusManager; -use PrestaShop\Module\PsAccounts\Service\PsAccountsService; - +/** + * @deprecated removed starting ps_accounts 8.0.0 + */ class AdminDebugPsAccountsController extends \ModuleAdminController { - /** - * @var Ps_accounts - */ - public $module; - - /** - * @return void - * - * @throws SmartyException - * @throws Exception - */ - public function initContent() - { - /** @var OwnerSession $ownerSession */ - $ownerSession = $this->module->getService(OwnerSession::class); - - /** @var ShopSession $shopSession */ - $shopSession = $this->module->getService(ShopSession::class); - - /** @var PsAccountsService $psAccountsService */ - $psAccountsService = $this->module->getService(PsAccountsService::class); - - /** @var StatusManager $statusManager */ - $statusManager = $this->module->getService(StatusManager::class); - - $this->context->smarty->assign([ - 'config' => [ - 'shopId' => (int) $this->context->shop->id, - 'shopUuidV4' => $statusManager->getStatus()->isVerified, - 'moduleVersion' => \Ps_accounts::VERSION, - 'psVersion' => _PS_VERSION_, - 'phpVersion' => phpversion(), - 'firebase_email' => $ownerSession->getToken()->getEmail(), - 'firebase_email_is_verified' => $ownerSession->isEmailVerified(), - 'firebase_id_token' => (string) $shopSession->getToken(), - 'firebase_refresh_token' => '', - 'adminAjaxUrl' => $psAccountsService->getAdminAjaxUrl(), - 'isShopLinked' => $psAccountsService->isAccountLinked(), - ], - ]); - $this->content = $this->context->smarty->fetch($this->module->getLocalPath() . '/views/templates/admin/debug.tpl'); - parent::initContent(); - } } diff --git a/ps_accounts.php b/ps_accounts.php index 052a6a9b0..b8fd0dfe2 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -41,7 +41,6 @@ class Ps_accounts extends Module */ private $adminControllers = [ 'AdminAjaxPsAccountsController', - 'AdminDebugPsAccountsController', 'AdminOAuth2PsAccountsController', 'AdminLoginPsAccountsController', ]; diff --git a/src/Hook/DisplayAdminAfterHeader.php b/src/Hook/DisplayAdminAfterHeader.php index 8dcf60aa7..ec681ec0b 100644 --- a/src/Hook/DisplayAdminAfterHeader.php +++ b/src/Hook/DisplayAdminAfterHeader.php @@ -43,7 +43,6 @@ public function execute(array $params = []) $moduleLink = $link->getAdminLink('AdminModules', true, [], [ 'configure' => 'ps_accounts', ]); - $debugLink = $link->getAdminLink('AdminDebugPsAccounts'); $healthCheckLink = $link->getLink()->getModuleLink('ps_accounts', 'apiV2ShopHealthCheck'); $environment = $this->module->getParameter('ps_accounts.environment'); @@ -57,7 +56,6 @@ public function execute(array $params = []) {$environment} | {$cloudShopId} ({$verifiedMsg}) | - Debug | Health Check
diff --git a/upgrade/upgrade-8.0.0.php b/upgrade/upgrade-8.0.0.php index 1694171a6..7cad902d9 100644 --- a/upgrade/upgrade-8.0.0.php +++ b/upgrade/upgrade-8.0.0.php @@ -20,6 +20,12 @@ function upgrade_module_8_0_0($module) $installer = new PrestaShop\Module\PsAccounts\Module\Install($module, Db::getInstance()); $installer->installInMenu(); + $tabId = \Tab::getIdFromClassName('AdminDebugPsAccounts'); + if ($tabId) { + $tab = new \Tab($tabId); + $tab->delete(); + } + /** @var CommandBus $commandBus */ $commandBus = $module->getService(CommandBus::class); From 5362a4fec8f5413664c7de90403a62eaaa7a81f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Wed, 20 Aug 2025 18:09:34 +0200 Subject: [PATCH 28/46] feat: two steps creation & verification (#538) --- .../CommandHandler/CreateIdentityHandler.php | 14 +------ .../MigrateOrCreateIdentityV8Handler.php | 2 +- src/Service/Accounts/AccountsService.php | 38 ++++++++++--------- src/ServiceProvider/CommandProvider.php | 1 - .../CreateIdentityHandlerTest.php | 5 +-- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/src/Account/CommandHandler/CreateIdentityHandler.php b/src/Account/CommandHandler/CreateIdentityHandler.php index a81734c0a..e302e3d50 100644 --- a/src/Account/CommandHandler/CreateIdentityHandler.php +++ b/src/Account/CommandHandler/CreateIdentityHandler.php @@ -24,7 +24,6 @@ use PrestaShop\Module\PsAccounts\Account\Command\VerifyIdentityCommand; use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; use PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException; -use PrestaShop\Module\PsAccounts\Account\ProofManager; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; @@ -54,11 +53,6 @@ class CreateIdentityHandler */ private $statusManager; - /** - * @var ProofManager - */ - private $proofManager; - /** * @var CommandBus */ @@ -69,7 +63,6 @@ class CreateIdentityHandler * @param ShopProvider $shopProvider * @param OAuth2Client $oauth2Client * @param StatusManager $shopStatus - * @param ProofManager $proofManager * @param CommandBus $commandBus */ public function __construct( @@ -77,14 +70,12 @@ public function __construct( ShopProvider $shopProvider, OAuth2Client $oauth2Client, StatusManager $shopStatus, - ProofManager $proofManager, CommandBus $commandBus ) { $this->accountsService = $accountsService; $this->shopProvider = $shopProvider; $this->oAuth2Client = $oauth2Client; $this->statusManager = $shopStatus; - $this->proofManager = $proofManager; $this->commandBus = $commandBus; } @@ -104,7 +95,7 @@ public function handle(CreateIdentityCommand $command) $identityCreated = $this->accountsService->createShopIdentity( $this->shopProvider->getUrl($shopId), - $this->proofManager->generateProof(), + null, $command->source ); $this->oAuth2Client->update( @@ -113,9 +104,8 @@ public function handle(CreateIdentityCommand $command) ); $this->statusManager->setCloudShopId($identityCreated->cloudShopId); $this->statusManager->invalidateCache(); - } else { - $this->commandBus->handle(new VerifyIdentityCommand($command->shopId, $command->source)); } + $this->commandBus->handle(new VerifyIdentityCommand($command->shopId, $command->source)); } /** diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php index c9189c7db..bd065c953 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php @@ -147,8 +147,8 @@ public function handle(MigrateOrCreateIdentityV8Command $command) $shopUuid, $token, $this->shopProvider->getUrl($shopId), - $this->proofManager->generateProof(), $fromVersion, + $this->proofManager->generateProof(), $command->source ); diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index 168a9df3a..b9816f1d8 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -233,14 +233,14 @@ public function healthCheck() /** * @param ShopUrl $shopUrl - * @param string $proof + * @param string|null $proof * @param string|null $source * * @return IdentityCreated * * @throws AccountsException */ - public function createShopIdentity(ShopUrl $shopUrl, $proof, $source = null) + public function createShopIdentity(ShopUrl $shopUrl, $proof = null, $source = null) { $response = $this->getClient()->post( '/v1/shop-identities', @@ -248,12 +248,14 @@ public function createShopIdentity(ShopUrl $shopUrl, $proof, $source = null) Request::HEADERS => $this->getHeaders([ self::HEADER_MODULE_SOURCE => $source, ]), - Request::JSON => [ - 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), - 'frontendUrl' => $shopUrl->getFrontendUrl(), - 'multiShopId' => $shopUrl->getMultiShopId(), - 'proof' => $proof, - ], + Request::JSON => array_merge( + [ + 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), + 'frontendUrl' => $shopUrl->getFrontendUrl(), + 'multiShopId' => $shopUrl->getMultiShopId(), + ], + $proof ? ['proof' => $proof] : [] + ), ] ); @@ -363,15 +365,15 @@ public function setPointOfContact($cloudShopId, $shopToken, $userToken, $source * @param string $cloudShopId * @param string $shopToken * @param ShopUrl $shopUrl - * @param string $proof * @param string $fromVersion + * @param string|null $proof * @param string|null $source * * @return IdentityCreated * * @throws AccountsException */ - public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $proof, $fromVersion, $source = null) + public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $fromVersion, $proof = null, $source = null) { $response = $this->getClient()->put( '/v1/shop-identities/' . $cloudShopId . '/migrate', @@ -381,13 +383,15 @@ public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, self::HEADER_SHOP_ID => $cloudShopId, self::HEADER_MODULE_SOURCE => $source, ]), - Request::JSON => [ - 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), - 'frontendUrl' => $shopUrl->getFrontendUrl(), - 'multiShopId' => $shopUrl->getMultiShopId(), - 'proof' => $proof, - 'fromVersion' => $fromVersion, - ], + Request::JSON => array_merge( + [ + 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), + 'frontendUrl' => $shopUrl->getFrontendUrl(), + 'multiShopId' => $shopUrl->getMultiShopId(), + 'fromVersion' => $fromVersion, + ], + $proof ? ['proof' => $proof] : [] + ), ] ); diff --git a/src/ServiceProvider/CommandProvider.php b/src/ServiceProvider/CommandProvider.php index 2109fd7cb..4cf4c6872 100644 --- a/src/ServiceProvider/CommandProvider.php +++ b/src/ServiceProvider/CommandProvider.php @@ -69,7 +69,6 @@ public function provide(ServiceContainer $container) $container->get(ShopProvider::class), $container->get(OAuth2Client::class), $container->get(StatusManager::class), - $container->get(ProofManager::class), $container->get(CommandBus::class) ); }); diff --git a/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php index a876d0635..ffd3142aa 100644 --- a/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php @@ -101,7 +101,7 @@ public function itShouldStoreIdentity() $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); - $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); + //$this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); return $this->createResponse([ 'clientId' => $clientId, @@ -145,7 +145,7 @@ public function itShouldNotChangeIdentityIfExists() $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); - $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); + //$this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); static $count = 1; return $count++ === 1 ? $id1 : $id2; @@ -204,7 +204,6 @@ private function getHandler() $this->shopProvider, $this->oauth2Client, $this->statusManager, - $this->proofManager, $this->commandBus ); } From dfbdbe7d8b51d18f63e471446c8397989771e570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:16:26 +0200 Subject: [PATCH 29/46] [ACCOUNT-3009] fix: IFrame integration - CORS admin controller (#528) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: locales first iteration * feat: new back controller with cors * fix: return shops with null status if not exist * get context query params contextType and contextId * fix: popup should not reload parent page * fix: return type of postProcess * feat: check allowed origin for cors (#530) * Apply suggestion from @hschoenenberger Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> * Apply suggestion from @hschoenenberger Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> * fix: source on getStatus * [ACCOUNT-3051] feat: add new token to securize new admin controller (#532) * feat: add new token to securize new admin controller * feat: add new token to securize new admin controller * fix: specific constants for global configurations * [ACCOUNT-3038] feat: context with empty status (#531) * feat: return a context event with an empty status * feat: return a context event with an empty status * feat: add frontendUrl into empty status * fix: manage correctly refresh property * fix: specific constants for global configurations * fix: fallback and get context urls without token * fix: use openssl to generate signing key * fix: rename TokenService as AdminTokenService * fix: add missing @throws tags * fix: add protected method to generate signature and clean code * fix: default provider * fix: fallbackCreateIdentity * fix: PS v6 * fix: path * fix: remove token on urls * fix: declaration of AdminTokenService * fix: better random proof and signature * fix: cors header * fix: remove useless use of uuid in proof manager * fix: add source on get context url * fix: upgrade scripty don't return false * fix: pass source on manual identification / verification * fix: ps_accounts as default source * fix: common trait to read request headers * fix: require_once --------- Co-authored-by: hschoenenberger Co-authored-by: Hervé Schoenenberger <54308193+hschoenenberger@users.noreply.github.com> * feat: smoke test for get context * fix: missing require_once * fix: override init method for ps 1.6 * feat: add a more complete testsuite * feat: add a more complete testsuite * feat: add a more complete testsuite * feat: add a more complete testsuite --------- Co-authored-by: Antoine Metifeu --- _dev/apps/configuration/types/window.d.ts | 1 + config.bulle.php | 6 +- config.dist.php | 7 +- config.local.php | 8 +- config.preprod.php | 6 +- config.prod.php | 7 +- .../admin/AdminAjaxPsAccountsController.php | 92 -------- .../admin/AdminAjaxV2PsAccountsController.php | 126 +++++++++++ .../admin/AdminOAuth2PsAccountsController.php | 7 +- e2e-env/package-lock.json | 6 + ps_accounts.php | 1 + src/Account/ProofManager.php | 3 +- src/Account/Query/GetContextQuery.php | 18 +- .../QueryHandler/GetContextHandler.php | 4 +- src/Adapter/Configuration.php | 10 + src/Controller/Admin/OAuth2Controller.php | 7 +- src/Http/Client/Curl/Client.php | 46 +++- src/Http/Client/Response.php | 6 +- .../AbstractAdminAjaxCorsController.php | 189 +++++++++++++++++ .../Controller/AbstractRestController.php | 41 +--- src/Http/Controller/GetHeader.php | 46 ++++ src/Provider/ShopProvider.php | 12 +- src/Service/Accounts/AccountsService.php | 8 +- src/Service/AdminTokenService.php | 104 +++++++++ src/Service/PsAccountsService.php | 15 +- src/ServiceProvider/DefaultProvider.php | 4 + tests/bootstrap.php | 2 +- .../GetContextTest.php | 198 ++++++++++++++++++ upgrade/upgrade-8.0.0.php | 8 +- 29 files changed, 803 insertions(+), 185 deletions(-) create mode 100644 controllers/admin/AdminAjaxV2PsAccountsController.php create mode 100644 e2e-env/package-lock.json create mode 100644 src/Http/Controller/AbstractAdminAjaxCorsController.php create mode 100644 src/Http/Controller/GetHeader.php create mode 100644 src/Service/AdminTokenService.php create mode 100644 tests/src/Feature/AdminAjaxV2PsAccountsController/GetContextTest.php diff --git a/_dev/apps/configuration/types/window.d.ts b/_dev/apps/configuration/types/window.d.ts index ecfcd8703..e1cc6894e 100644 --- a/_dev/apps/configuration/types/window.d.ts +++ b/_dev/apps/configuration/types/window.d.ts @@ -4,6 +4,7 @@ type ContextParamsInit = { groupId: number; getContextUrl: string; manageAccountUrl: string; + token: string; psxName: string; } diff --git a/config.bulle.php b/config.bulle.php index e120f2c4e..9e118885c 100644 --- a/config.bulle.php +++ b/config.bulle.php @@ -29,8 +29,12 @@ 'ps_accounts.sentry_credentials' => 'https://a065bd1f092f8c849e6076fe0640d049@o298402.ingest.us.sentry.io/5354585', 'ps_accounts.segment_write_key' => 'eYODaH20rT1lMRTTUtAa15BKBlV1XUXQ', 'ps_accounts.accounts_vue_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@3/dist/psaccountsVue.umd.min.js', - //'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5', 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/@prestashopcorp/accounts-components@beta', + 'ps_accounts.cors_allowed_origins' => [ + 'https://integration-assets.prestashop3.com', + 'https://preproduction-assets.prestashop3.com', + 'https://assets.prestashop3.com', + ], 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.psessentials-integration.net', 'ps_accounts.oauth2_url' => 'https://oauth-integration.prestashop.com', 'ps_accounts.token_audience' => 'https://accounts-api.distribution-integration.prestashop.net', diff --git a/config.dist.php b/config.dist.php index 0bae66c9a..8900790ac 100644 --- a/config.dist.php +++ b/config.dist.php @@ -30,7 +30,12 @@ 'ps_accounts.check_api_ssl_cert' => false, 'ps_accounts.verify_account_tokens' => false, /* deprecated */ 'ps_accounts.accounts_vue_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@3/dist/psaccountsVue.umd.min.js', - 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5', + 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/@prestashopcorp/accounts-components@beta', + 'ps_accounts.cors_allowed_origins' => [ + 'https://integration-assets.prestashop3.com', + 'https://preproduction-assets.prestashop3.com', + 'https://assets.prestashop3.com', + ], 'ps_accounts.environment' => 'development', // a page to display "Update Your Module" message diff --git a/config.local.php b/config.local.php index e64e2fb90..e559bbd3f 100644 --- a/config.local.php +++ b/config.local.php @@ -33,9 +33,13 @@ 'ps_accounts.check_api_ssl_cert' => false, 'ps_accounts.verify_account_tokens' => false, 'ps_accounts.accounts_vue_cdn_url' => 'http://prestashop8.docker.localhost/upload/psaccountsVue.umd.min.js', - //ps_accounts.accounts_cdn_url' => 'http://prestashop8.docker.localhost/upload/psaccountsVue.js' - //ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5.1.0-test-1/dist/psaccountsVue.js' + // 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/@prestashopcorp/accounts-components@beta', 'ps_accounts.accounts_cdn_url' => 'http://127.0.0.1:3010/psaccountsVue.js', + 'ps_accounts.cors_allowed_origins' => [ + 'https://integration-assets.prestashop3.com', + 'https://preproduction-assets.prestashop3.com', + 'https://assets.prestashop3.com', + ], // a page to display "Update Your Module" message 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.prestashop.local/', diff --git a/config.preprod.php b/config.preprod.php index ca08eb659..dbfc06bf5 100644 --- a/config.preprod.php +++ b/config.preprod.php @@ -30,8 +30,12 @@ 'ps_accounts.sso_resend_verification_email_url' => 'https://auth-preprod.prestashop.com/account/send-verification-email', 'ps_accounts.sentry_credentials' => 'https://a065bd1f092f8c849e6076fe0640d049@o298402.ingest.us.sentry.io/5354585', 'ps_accounts.accounts_vue_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components/dist/psaccountsVue.js', - //'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components/dist/psaccountsVue.js', 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/@prestashopcorp/accounts-components@beta', + 'ps_accounts.cors_allowed_origins' => [ + 'https://integration-assets.prestashop3.com', + 'https://preproduction-assets.prestashop3.com', + 'https://assets.prestashop3.com', + ], 'ps_accounts.environment' => 'preprod', diff --git a/config.prod.php b/config.prod.php index af38c5940..1d278a4e0 100644 --- a/config.prod.php +++ b/config.prod.php @@ -32,7 +32,12 @@ 'ps_accounts.check_api_ssl_cert' => true, 'ps_accounts.verify_account_tokens' => true, 'ps_accounts.accounts_vue_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@3/dist/psaccountsVue.umd.min.js', - 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/prestashop_accounts_vue_components@5', + 'ps_accounts.accounts_cdn_url' => 'https://unpkg.com/@prestashopcorp/accounts-components@beta', + 'ps_accounts.cors_allowed_origins' => [ + 'https://integration-assets.prestashop3.com', + 'https://preproduction-assets.prestashop3.com', + 'https://assets.prestashop3.com', + ], 'ps_accounts.svc_accounts_ui_url' => 'https://accounts.psessentials.net', 'ps_accounts.oauth2_url' => 'https://oauth.prestashop.com', 'ps_accounts.token_audience' => 'https://accounts-api.distribution.prestashop.net', diff --git a/controllers/admin/AdminAjaxPsAccountsController.php b/controllers/admin/AdminAjaxPsAccountsController.php index a12bad3ce..090fae108 100644 --- a/controllers/admin/AdminAjaxPsAccountsController.php +++ b/controllers/admin/AdminAjaxPsAccountsController.php @@ -20,18 +20,13 @@ require_once __DIR__ . '/../../src/Polyfill/Traits/Controller/AjaxRender.php'; use PrestaShop\Module\PsAccounts\Account\Command\DeleteUserShopCommand; -use PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentityV8Command; -use PrestaShop\Module\PsAccounts\Account\Query\GetContextQuery; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\AccountLogin\OAuth2Session; use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; -use PrestaShop\Module\PsAccounts\Cqrs\QueryBus; use PrestaShop\Module\PsAccounts\Hook\ActionShopAccountUnlinkAfter; -use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Polyfill\Traits\Controller\AjaxRender; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; -use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; use PrestaShop\Module\PsAccounts\Service\SentryService; /** @@ -51,11 +46,6 @@ class AdminAjaxPsAccountsController extends \ModuleAdminController */ private $commandBus; - /** - * @var QueryBus - */ - private $queryBus; - /** * AdminAjaxPsAccountsController constructor. * @@ -66,7 +56,6 @@ public function __construct() parent::__construct(); $this->commandBus = $this->module->getService(CommandBus::class); - $this->queryBus = $this->module->getService(QueryBus::class); $this->ajax = true; $this->content_only = true; @@ -173,85 +162,4 @@ public function ajaxProcessGetOrRefreshAccessToken() SentryService::captureAndRethrow($e); } } - - /** - * @return void - * - * @throws Exception - */ - public function ajaxProcessGetContext() - { - header('Content-Type: text/json'); - - try { - $command = new GetContextQuery( - Tools::getValue('source', 'ajax'), - Tools::getValue('group_id', null), - Tools::getValue('shop_id', null), - filter_var(Tools::getValue('refresh', false), FILTER_VALIDATE_BOOLEAN) - ); - - $this->ajaxRender( - (string) json_encode($this->queryBus->handle($command)) - ); - } catch (Exception $e) { - $this->handleError($e); - } - } - - /** - * @return void - * - * @throws Exception - */ - public function ajaxProcessFallbackCreateIdentity() - { - header('Content-Type: text/json'); - $shopId = Tools::getValue('shop_id', null); - - try { - if (!$shopId) { - throw new Exception('Shop ID is required for migration or creation.'); - } - $command = new MigrateOrCreateIdentityV8Command($shopId); - - $this->ajaxRender( - (string) json_encode($this->commandBus->handle($command)) - ); - } catch (Exception $e) { - $this->handleError($e); - } - } - - /** - * @param Exception $e - * - * @return void - */ - protected function handleError(Exception $e) - { - Logger::getInstance()->error($e); - - if ($e instanceof AccountsException) { - http_response_code(400); - - $this->ajaxRender( - (string) json_encode([ - 'message' => $e->getMessage(), - 'code' => $e->getErrorCode(), - ]) - ); - - return; - } - - http_response_code(500); - - $this->ajaxRender( - (string) json_encode([ - 'message' => $e->getMessage() ? $e->getMessage() : 'Unknown Error', - 'code' => 'unknown-error', - ]) - ); - } } diff --git a/controllers/admin/AdminAjaxV2PsAccountsController.php b/controllers/admin/AdminAjaxV2PsAccountsController.php new file mode 100644 index 000000000..3df6cc76e --- /dev/null +++ b/controllers/admin/AdminAjaxV2PsAccountsController.php @@ -0,0 +1,126 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ +require_once __DIR__ . '/../../src/Http/Controller/AbstractAdminAjaxCorsController.php'; + +use PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentityV8Command; +use PrestaShop\Module\PsAccounts\Account\Query\GetContextQuery; +use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; +use PrestaShop\Module\PsAccounts\Cqrs\QueryBus; +use PrestaShop\Module\PsAccounts\Http\Controller\AbstractAdminAjaxCorsController; +use PrestaShop\Module\PsAccounts\Log\Logger; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; + +/** + * Controller for all ajax calls. + */ +class AdminAjaxV2PsAccountsController extends AbstractAdminAjaxCorsController +{ + /** + * @var CommandBus + */ + private $commandBus; + + /** + * @var QueryBus + */ + private $queryBus; + + /** + * AdminAjaxV2PsAccountsController constructor. + * + * @throws Exception + */ + public function __construct() + { + parent::__construct(); + + $this->commandBus = $this->module->getService(CommandBus::class); + $this->queryBus = $this->module->getService(QueryBus::class); + } + + /** + * @return void + * + * @throws Exception + */ + public function ajaxProcessGetContext() + { + $command = new GetContextQuery( + Tools::getValue('source', 'ps_accounts'), + Tools::getValue('context_type', null), + Tools::getValue('context_id', null), + filter_var(Tools::getValue('refresh', false), FILTER_VALIDATE_BOOLEAN) + ); + + $this->ajaxRender( + (string) json_encode($this->queryBus->handle($command)) + ); + } + + /** + * @return void + * + * @throws Exception + */ + public function ajaxProcessFallbackCreateIdentity() + { + $shopId = Tools::getValue('shop_id', null); + $source = Tools::getValue('source', 'ps_accounts'); + + if (!$shopId) { + throw new Exception('Shop ID is required for migration or creation.'); + } + + $command = new MigrateOrCreateIdentityV8Command($shopId, $source); + + $this->commandBus->handle($command); + + $this->ajaxRender( + (string) json_encode([ + 'success' => true, + ]) + ); + } + + /** + * @param \Throwable|\Exception $e + * + * @return void + */ + protected function handleError($e) + { + Logger::getInstance()->error($e); + + if ($e instanceof AccountsException) { + http_response_code(400); + + $this->ajaxRender( + (string) json_encode([ + 'message' => $e->getMessage(), + 'code' => $e->getErrorCode(), + ]) + ); + + return; + } + + parent::handleError($e); + } +} diff --git a/controllers/admin/AdminOAuth2PsAccountsController.php b/controllers/admin/AdminOAuth2PsAccountsController.php index 66f2743b0..77c1e662f 100644 --- a/controllers/admin/AdminOAuth2PsAccountsController.php +++ b/controllers/admin/AdminOAuth2PsAccountsController.php @@ -233,7 +233,7 @@ protected function logout() protected function onLoginFailedRedirect() { if ($this->getOAuthAction() === 'identifyPointOfContact') { - $this->closePopup(false); + $this->closePopup(); } $this->logout(); } @@ -271,15 +271,12 @@ protected function getPsAccountsService() } /** - * @param bool $refreshParent - * * @return void */ - protected function closePopup($refreshParent = true) + protected function closePopup() { echo ' '; diff --git a/e2e-env/package-lock.json b/e2e-env/package-lock.json new file mode 100644 index 000000000..936bda4c9 --- /dev/null +++ b/e2e-env/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "e2e-env", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/ps_accounts.php b/ps_accounts.php index b8fd0dfe2..04f43113b 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -41,6 +41,7 @@ class Ps_accounts extends Module */ private $adminControllers = [ 'AdminAjaxPsAccountsController', + 'AdminAjaxV2PsAccountsController', 'AdminOAuth2PsAccountsController', 'AdminLoginPsAccountsController', ]; diff --git a/src/Account/ProofManager.php b/src/Account/ProofManager.php index bdf1b7a60..3f16071a6 100644 --- a/src/Account/ProofManager.php +++ b/src/Account/ProofManager.php @@ -21,7 +21,6 @@ namespace PrestaShop\Module\PsAccounts\Account; use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; -use PrestaShop\Module\PsAccounts\Vendor\Ramsey\Uuid\Uuid; class ProofManager { @@ -46,7 +45,7 @@ public function __construct( */ public function generateProof() { - $proof = base64_encode(\hash_hmac('sha512', Uuid::uuid4()->toString(), uniqid())); + $proof = bin2hex(openssl_random_pseudo_bytes(32)); $this->configuration->updateShopProof($proof); diff --git a/src/Account/Query/GetContextQuery.php b/src/Account/Query/GetContextQuery.php index 0a47b0296..276cd09fe 100644 --- a/src/Account/Query/GetContextQuery.php +++ b/src/Account/Query/GetContextQuery.php @@ -29,14 +29,14 @@ class GetContextQuery public $source; /** - * @var string|null + * @var int */ - public $groupId; + public $contextType; /** - * @var string|null + * @var int|null */ - public $shopId; + public $contextId; /** * @var bool @@ -45,15 +45,15 @@ class GetContextQuery /** * @param string|null $source - * @param string|null $groupId - * @param string|null $shopId + * @param int|null $contextType + * @param int|null $contextId * @param bool $refresh */ - public function __construct($source = null, $groupId = null, $shopId = null, $refresh = false) + public function __construct($source = null, $contextType = \Shop::CONTEXT_ALL, $contextId = null, $refresh = false) { $this->source = $source; - $this->groupId = $groupId; - $this->shopId = $shopId; + $this->contextType = $contextType; + $this->contextId = $contextId; $this->refresh = $refresh; } } diff --git a/src/Account/QueryHandler/GetContextHandler.php b/src/Account/QueryHandler/GetContextHandler.php index bf5930f79..6e7c55f6f 100644 --- a/src/Account/QueryHandler/GetContextHandler.php +++ b/src/Account/QueryHandler/GetContextHandler.php @@ -64,8 +64,8 @@ public function handle(GetContextQuery $query) ], 'groups' => $this->shopProvider->getShops( $query->source, - $query->groupId, - $query->shopId, + $query->contextType, + $query->contextId, $query->refresh ), ]; diff --git a/src/Adapter/Configuration.php b/src/Adapter/Configuration.php index 14f4c4e2f..289d3e858 100644 --- a/src/Adapter/Configuration.php +++ b/src/Adapter/Configuration.php @@ -166,6 +166,16 @@ public function setRaw($key, $values, $html = false, $idShopGroup = null, $idSho return \Configuration::updateValue($key, $values, $html, $idShopGroup, $idShop); } + /** + * @param string $key + * + * @return mixed + */ + public function getGlobal($key) + { + return \Configuration::getGlobalValue($key); + } + /** * @param string $key * @param string|array $values diff --git a/src/Controller/Admin/OAuth2Controller.php b/src/Controller/Admin/OAuth2Controller.php index c0ccd1c36..6509fab5d 100644 --- a/src/Controller/Admin/OAuth2Controller.php +++ b/src/Controller/Admin/OAuth2Controller.php @@ -272,7 +272,7 @@ protected function logout() protected function onLoginFailedRedirect() { if ($this->getOAuthAction() === 'identifyPointOfContact') { - return $this->closePopup(false); + return $this->closePopup(); } return $this->redirect( @@ -314,15 +314,12 @@ protected function getPsAccountsService() } /** - * @param bool $refreshParent - * * @return Response */ - protected function closePopup($refreshParent = true) + protected function closePopup() { return (new Response())->setContent(' '); diff --git a/src/Http/Client/Curl/Client.php b/src/Http/Client/Curl/Client.php index 7f25336d9..904237aba 100644 --- a/src/Http/Client/Curl/Client.php +++ b/src/Http/Client/Curl/Client.php @@ -161,14 +161,24 @@ public function delete($route, array $options = []) */ protected function getResponse(Request $request) { - $res = curl_exec($request->handler); + $res = (string) curl_exec($request->handler); $this->handleError($request); + // Get the size of the header from the cURL info + $header_size = curl_getinfo($request->handler, CURLINFO_HEADER_SIZE); + + // Extract the headers from the response string + $headers = $this->parseHeaders(substr($res, 0, $header_size)); + + // Extract the body from the response string + $body = substr($res, $header_size); + $statusCode = curl_getinfo($request->handler, CURLINFO_RESPONSE_CODE); $response = new Response( - (string) $res, - $statusCode + (string) $body, + $statusCode, + $headers ); $response->request = $request; @@ -177,6 +187,35 @@ protected function getResponse(Request $request) return $response; } + /** + * @param string $headers + * + * @return array + */ + protected function parseHeaders($headers) + { + // Parse the header string into an array of lines + $headerLines = explode("\r\n", trim($headers)); + + // Initialize an empty array for the parsed headers + $parsedHeaders = []; + + foreach ($headerLines as $line) { + // Skip the status line (e.g., HTTP/1.1 200 OK) + if (strpos($line, ':') === false) { + continue; + } + + // Split the header line at the first colon + list($key, $value) = explode(':', $line, 2); + + // Trim and store the key-value pair + $parsedHeaders[trim($key)] = trim($value); + } + + return $parsedHeaders; + } + /** * @param Request $request * @@ -316,6 +355,7 @@ protected function initRequest(Request $request) curl_setopt($request->handler, CURLOPT_FOLLOWLOCATION, $this->config->allowRedirects); curl_setopt($request->handler, CURLOPT_POSTREDIR, $this->config->allowRedirects ? 3 : 0); curl_setopt($request->handler, CURLINFO_HEADER_OUT, true); + curl_setopt($request->handler, CURLOPT_HEADER, true); if (!empty($this->config->userAgent)) { curl_setopt($request->handler, CURLOPT_USERAGENT, $this->config->userAgent); diff --git a/src/Http/Client/Response.php b/src/Http/Client/Response.php index 1b820d1bb..ec3b797d2 100644 --- a/src/Http/Client/Response.php +++ b/src/Http/Client/Response.php @@ -22,6 +22,7 @@ /** * @property array $body + * @property array $headers * @property mixed $raw * @property int $statusCode * @property bool $isSuccessful @@ -30,6 +31,7 @@ class Response extends ConfigObject { const BODY = 'body'; + const HEADERS = 'headers'; const RAW = 'raw'; const STATUS_CODE = 'statusCode'; const IS_SUCCESSFUL = 'isSuccessful'; @@ -38,11 +40,13 @@ class Response extends ConfigObject /** * @param array|string $body * @param int $statusCode + * @param array $headers */ - public function __construct($body, $statusCode) + public function __construct($body, $statusCode, $headers = []) { parent::__construct([ self::RAW => $body, + self::HEADERS => $headers, self::BODY => $this->decodeBody($body), self::STATUS_CODE => (int) $statusCode, self::IS_SUCCESSFUL => '2' === substr((string) $statusCode, 0, 1), diff --git a/src/Http/Controller/AbstractAdminAjaxCorsController.php b/src/Http/Controller/AbstractAdminAjaxCorsController.php new file mode 100644 index 000000000..464ed2b80 --- /dev/null +++ b/src/Http/Controller/AbstractAdminAjaxCorsController.php @@ -0,0 +1,189 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Http\Controller; + +require_once __DIR__ . '/../../Polyfill/Traits/Controller/AjaxRender.php'; +require_once __DIR__ . '/../../Polyfill/Traits/AdminController/IsAnonymousAllowed.php'; +require_once __DIR__ . '/../../Http/Controller/GetHeader.php'; + +use ModuleAdminController; +use PrestaShop\Module\PsAccounts\Http\Exception\UnauthorizedException; +use PrestaShop\Module\PsAccounts\Log\Logger; +use PrestaShop\Module\PsAccounts\Polyfill\Traits\AdminController\IsAnonymousAllowed; +use PrestaShop\Module\PsAccounts\Polyfill\Traits\Controller\AjaxRender; +use PrestaShop\Module\PsAccounts\Service\AdminTokenService; + +abstract class AbstractAdminAjaxCorsController extends ModuleAdminController +{ + use AjaxRender; + use IsAnonymousAllowed; + use GetHeader; + + const HEADER_AUTHORIZATION = 'X-PrestaShop-Authorization'; + + /** + * @var \Ps_accounts + */ + public $module; + + /** + * @var AdminTokenService + */ + protected $tokenService; + + /** + * @var bool + */ + protected $authenticated = true; + + public function __construct() + { + parent::__construct(); + + $this->tokenService = $this->module->getService(AdminTokenService::class); + + $this->ajax = true; + $this->content_only = true; + } + + /** + * @return bool + */ + public function checkToken() + { + return true; + } + + /** + * All BO users can access the login page + * + * @param bool $disable + * + * @return bool + */ + public function viewAccess($disable = false) + { + return true; + } + + /** + * @return void + */ + public function init() + { + if (defined('_PS_VERSION_') + && version_compare(_PS_VERSION_, '1.7.0', '>=')) { + parent::init(); + } + } + + /** + * @return \ObjectModel|bool|void + */ + public function postProcess() + { + try { + if (isset($_SERVER['HTTP_ORIGIN']) && in_array($_SERVER['HTTP_ORIGIN'], $this->module->getParameter('ps_accounts.cors_allowed_origins'))) { + header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']); + } + + if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { + header('Access-Control-Allow-Headers: Content-Type, X-Prestashop-Authorization'); + // header('Access-Control-Allow-Private-Network: true'); + // header('Access-Control-Request-Credentials: true'); + header('Access-Control-Max-Age: 1728000'); + header('Content-Length: 0'); + header('Content-Type: text/plain'); + http_response_code(204); + exit; + } + + header('Content-Type: application/json'); + + if ($this->authenticated) { + $this->checkAuthorization(); + } + + return parent::postProcess(); + } catch (\Throwable $e) { + $this->handleError($e); + /* @phpstan-ignore-next-line */ + } catch (\Exception $e) { + $this->handleError($e); + } + } + + /** + * @return bool + * + * @throws UnauthorizedException + */ + protected function checkAuthorization() + { + $authorizationHeader = $this->getRequestHeader(self::HEADER_AUTHORIZATION); + if (!isset($authorizationHeader)) { + throw new UnauthorizedException('Authorization header is required.'); + } + + $jwtString = trim(str_replace('Bearer', '', $authorizationHeader)); + + $errorMsg = 'Invalid token'; + + try { + $this->tokenService->verifyToken($jwtString); + + return true; + } catch (\Exception $e) { + Logger::getInstance()->error($e); + throw new UnauthorizedException($errorMsg); + } + } + + /** + * @param \Throwable|\Exception $e + * + * @return void + */ + protected function handleError($e) + { + if ($e instanceof UnauthorizedException) { + http_response_code(401); + + $this->ajaxRender( + (string) json_encode([ + 'message' => $e->getMessage(), + 'code' => 'unauthorized', + ]) + ); + + return; + } + + http_response_code(500); + + $this->ajaxRender( + (string) json_encode([ + 'message' => $e->getMessage() ? $e->getMessage() : 'Unknown Error', + 'code' => 'unknown-error', + ]) + ); + } +} diff --git a/src/Http/Controller/AbstractRestController.php b/src/Http/Controller/AbstractRestController.php index 629eabe82..12864a8eb 100644 --- a/src/Http/Controller/AbstractRestController.php +++ b/src/Http/Controller/AbstractRestController.php @@ -34,6 +34,7 @@ abstract class AbstractRestController extends ModuleFrontController { use AjaxRender; + use GetHeader; const TOKEN_HEADER = 'X-PrestaShop-Signature'; @@ -275,46 +276,6 @@ protected function decodePayload($defaultShopId = null) throw new UnauthorizedException(); } - /** - * @param string $header - * - * @return string|null - */ - protected function getRequestHeader($header) - { - $headerValue = null; - - $headerKey = 'HTTP_' . strtoupper(str_replace('-', '_', $header)); - - if (array_key_exists($headerKey, $_SERVER)) { - $headerValue = $_SERVER[$headerKey]; - } - - if (null === $headerValue) { - $headerValue = $this->getApacheHeader($header); - } - - return $headerValue; - } - - /** - * @param string $header - * - * @return string|null - */ - protected function getApacheHeader($header) - { - if (function_exists('apache_request_headers')) { - $headers = getallheaders(); - //$header = preg_replace('/PrestaShop/', 'Prestashop', $header); - if (array_key_exists($header, $headers)) { - return $headers[$header]; - } - } - - return null; - } - /** * Force shop context * diff --git a/src/Http/Controller/GetHeader.php b/src/Http/Controller/GetHeader.php new file mode 100644 index 000000000..aa67706ac --- /dev/null +++ b/src/Http/Controller/GetHeader.php @@ -0,0 +1,46 @@ +getApacheHeader($header); + } + + return $headerValue; + } + + /** + * @param string $header + * + * @return string|null + */ + protected function getApacheHeader($header) + { + if (function_exists('apache_request_headers')) { + $headers = getallheaders(); + //$header = preg_replace('/PrestaShop/', 'Prestashop', $header); + if (array_key_exists($header, $headers)) { + return $headers[$header]; + } + } + + return null; + } +} diff --git a/src/Provider/ShopProvider.php b/src/Provider/ShopProvider.php index fe03c2d88..406c6012f 100644 --- a/src/Provider/ShopProvider.php +++ b/src/Provider/ShopProvider.php @@ -325,23 +325,23 @@ public function getUrl($shopId) /** * @param string|null $source - * @param string|null $groupId - * @param string|null $shopId + * @param int $contextType + * @param int|null $contextId * @param bool $refresh * * @return array */ - public function getShops($source = null, $groupId = null, $shopId = null, $refresh = false) + public function getShops($source = null, $contextType = \Shop::CONTEXT_ALL, $contextId = null, $refresh = false) { $shopList = []; foreach (\Shop::getTree() as $groupData) { - if ($groupId !== null && $groupId !== $groupData['id']) { + if ($contextType === \Shop::CONTEXT_GROUP && $contextId != $groupData['id']) { continue; } $shops = []; foreach ($groupData['shops'] as $shopData) { - if ($shopId !== null && $shopId !== $shopData['id_shop']) { + if ($contextType === \Shop::CONTEXT_SHOP && $contextId != $shopData['id_shop']) { continue; } @@ -369,7 +369,7 @@ function () use (&$shops, $shopData, $source, $refresh) { 'frontendUrl' => $shopUrl->getFrontendUrl(), 'identifyPointOfContactUrl' => $identifyPointOfContactUrl, 'shopStatus' => $shopStatus->toArray(), - 'fallbackCreateIdentityUrl' => $this->link->getAdminLink('AdminAjaxPsAccounts', true, [], ['ajax' => 1, 'action' => 'fallbackCreateIdentity', 'shop_id' => $shopData['id_shop'], 'source' => $source]), + 'fallbackCreateIdentityUrl' => $this->link->getAdminLink('AdminAjaxV2PsAccounts', false, [], ['ajax' => 1, 'action' => 'fallbackCreateIdentity', 'shop_id' => $shopData['id_shop'], 'source' => $source]), ]; } ); diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index b9816f1d8..58d4f8971 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -240,7 +240,7 @@ public function healthCheck() * * @throws AccountsException */ - public function createShopIdentity(ShopUrl $shopUrl, $proof = null, $source = null) + public function createShopIdentity(ShopUrl $shopUrl, $proof = null, $source = 'ps_accounts') { $response = $this->getClient()->post( '/v1/shop-identities', @@ -277,7 +277,7 @@ public function createShopIdentity(ShopUrl $shopUrl, $proof = null, $source = nu * * @throws AccountsException */ - public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $proof, $source = null) + public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $proof, $source = 'ps_accounts') { $response = $this->getClient()->post( '/v1/shop-identities/' . $cloudShopId . '/verify', @@ -340,7 +340,7 @@ public function shopStatus($cloudShopId, $shopToken, $source = null) * * @throws AccountsException */ - public function setPointOfContact($cloudShopId, $shopToken, $userToken, $source = null) + public function setPointOfContact($cloudShopId, $shopToken, $userToken, $source = 'ps_accounts') { $response = $this->getClient()->post( '/v1/shop-identities/' . $cloudShopId . '/point-of-contact', @@ -373,7 +373,7 @@ public function setPointOfContact($cloudShopId, $shopToken, $userToken, $source * * @throws AccountsException */ - public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $fromVersion, $proof = null, $source = null) + public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $fromVersion, $proof = null, $source = 'ps_accounts') { $response = $this->getClient()->put( '/v1/shop-identities/' . $cloudShopId . '/migrate', diff --git a/src/Service/AdminTokenService.php b/src/Service/AdminTokenService.php new file mode 100644 index 000000000..4f483764b --- /dev/null +++ b/src/Service/AdminTokenService.php @@ -0,0 +1,104 @@ +getTokenSignature(); + if (!$signature) { + $signature = $this->generateSignature(); + $this->setTokenSignature($signature); + } + + $configuration = Configuration::forSymmetricSigner( + new Sha256(), + InMemory::plainText($signature) + ); + + $issuedAt = new \DateTimeImmutable(); + + $builder = (new Builder()) + ->issuedAt($issuedAt) + ->expiresAt($issuedAt->modify('+1 hour')); + + return $builder->getToken( + $configuration->signer(), + $configuration->signingKey() + ); + } + + /** + * @param string $token + * + * @return bool + * + * @throws RequiredConstraintsViolated + * @throws NoConstraintsGiven + */ + public function verifyToken($token) + { + $signature = $this->getTokenSignature(); + + $configuration = Configuration::forSymmetricSigner( + new Sha256(), + InMemory::plainText($signature) + ); + + $configuration->setValidationConstraints( + new SignedWith($configuration->signer(), $configuration->signingKey()), + new ValidAt(new FrozenClock(new \DateTimeImmutable())) + ); + + $token = $configuration->parser()->parse($token); + + $constraints = $configuration->validationConstraints(); + + $configuration->validator()->assert($token, ...$constraints); + + return true; + } + + /** + * @return string + */ + protected function generateSignature() + { + return bin2hex(openssl_random_pseudo_bytes(32)); + } + + /** + * @return string + */ + protected function getTokenSignature() + { + return \Configuration::getGlobalValue(self::PS_ACCOUNTS_TOKEN_SIGNATURE); + } + + /** + * @param string $signature + * + * @return void + */ + protected function setTokenSignature($signature) + { + \Configuration::updateGlobalValue(self::PS_ACCOUNTS_TOKEN_SIGNATURE, $signature); + } +} diff --git a/src/Service/PsAccountsService.php b/src/Service/PsAccountsService.php index dd05bab21..02d57a343 100644 --- a/src/Service/PsAccountsService.php +++ b/src/Service/PsAccountsService.php @@ -64,6 +64,11 @@ class PsAccountsService */ private $statusManager; + /** + * @var AdminTokenService + */ + private $tokenService; + /** * @param \Ps_accounts $module * @@ -77,6 +82,7 @@ public function __construct(\Ps_accounts $module) $this->ownerSession = $this->module->getService(Firebase\OwnerSession::class); $this->link = $this->module->getService(Link::class); $this->statusManager = $module->getService(StatusManager::class); + $this->tokenService = $module->getService(AdminTokenService::class); } /** @@ -265,13 +271,15 @@ public function getAdminAjaxUrl() } /** + * @param string|null $source + * * @return string * * @throws \PrestaShopException */ - public function getContextUrl() + public function getContextUrl($source = null) { - return $this->link->getAdminLink('AdminAjaxPsAccounts', true, [], ['ajax' => 1, 'action' => 'getContext']); + return $this->link->getAdminLink('AdminAjaxV2PsAccounts', false, [], ['ajax' => 1, 'action' => 'getContext', 'source' => $source]); } /** @@ -350,8 +358,9 @@ public function getComponentInitParams($psxName = 'ps_accounts') 'mode' => \Shop::getContext(), 'shopId' => \Shop::getContextShopID(), 'groupId' => \Shop::getContextShopGroupID(), - 'getContextUrl' => $this->getContextUrl(), + 'getContextUrl' => $this->getContextUrl($psxName), 'manageAccountUrl' => $this->module->getAccountsUiUrl(), + 'token' => (string) $this->tokenService->getToken(), 'psxName' => $psxName, ]; } diff --git a/src/ServiceProvider/DefaultProvider.php b/src/ServiceProvider/DefaultProvider.php index f8af6955c..de2500807 100644 --- a/src/ServiceProvider/DefaultProvider.php +++ b/src/ServiceProvider/DefaultProvider.php @@ -37,6 +37,7 @@ use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; use PrestaShop\Module\PsAccounts\Repository\ShopTokenRepository; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; +use PrestaShop\Module\PsAccounts\Service\AdminTokenService; use PrestaShop\Module\PsAccounts\Service\AnalyticsService; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; use PrestaShop\Module\PsAccounts\Service\PsAccountsService; @@ -116,6 +117,9 @@ public function provide(ServiceContainer $container) $container->get(ConfigurationRepository::class) ); }); + $container->registerProvider(AdminTokenService::class, static function () { + return new AdminTokenService(); + }); $container->registerProvider(ProofManager::class, static function () use ($container) { return new ProofManager( $container->get(ConfigurationRepository::class) diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 389afe149..34ea5018b 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,6 +1,6 @@ shopProvider->formatShopData((array) \Shop::getShop(1)); + + $url = $this->psAccountsService->getContextUrl(); + //$url = str_replace('http://', 'https://', $url); + //$url = '/index.php?controller=AdminAjaxV2PsAccounts&ajax=1&action=getContext'; + //$url = '/admin-dev/?controller=AdminAjaxV2PsAccounts&ajax=1&action=getContext&source=ps_accounts'; + $token = (string)$this->tokenService->getToken(); + + $response = $this->client->get($url, [ + Request::HEADERS => [ + AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $token, + ], + ]); + + $this->assertResponseOk($response); + + $json = $this->getResponseJson($response); + + $this->assertIsArray($json['ps_accounts']); + + $this->assertIsArray($json['groups']); + $this->assertNotEmpty($json['groups']); + + $shops = $json['groups'][0]['shops'][0]; + + $this->assertEquals($shop->id, $shops['id']); + $this->assertEquals($shop->name, $shops['name']); + } + + /** + * @test + */ + public function itShouldFailWithoutToken() + { + $url = $this->psAccountsService->getContextUrl(); + + $response = $this->client->get($url); + + $this->assertResponseUnauthorized($response); + } + + /** + * @test + */ + public function itShouldFailWithWrongToken() + { + $url = $this->psAccountsService->getContextUrl(); + + $token = $this->makeJwtToken(new \DateTimeImmutable('+1 hour')); + + $response = $this->client->get($url, [ + Request::HEADERS => [ + AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $token, + ], + ]); + + $this->assertResponseUnauthorized($response); + } + + /** + * @test + */ + public function itShouldRespondWithAccessControlHeadersWithAllowedOrigin() + { + //$shop = $this->shopProvider->formatShopData((array) \Shop::getShop(1)); + + $url = $this->psAccountsService->getContextUrl(); + $token = (string)$this->tokenService->getToken(); + $allowedOrigin = $this->module->getParameter('ps_accounts.cors_allowed_origins')[0]; + + $response = $this->client->get($url, [ + Request::HEADERS => [ + "Origin" => $allowedOrigin, + //"Origin" => "https://foo.com", + AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $token, + ], + ]); + + //print_r($response->headers); + + $this->assertResponseOk($response); + + $json = $this->getResponseJson($response); + + $this->assertIsArray($json['ps_accounts']); + + $this->assertEquals($allowedOrigin, $response->headers['Access-Control-Allow-Origin']); + } + + + /** + * @test + */ + public function itShouldRespondWithoutAccessControlHeadersWithNotAllowedOrigin() + { + //$shop = $this->shopProvider->formatShopData((array) \Shop::getShop(1)); + + $url = $this->psAccountsService->getContextUrl(); + $token = (string)$this->tokenService->getToken(); + $NotAllowedOrigin = "https://foo.com"; + + $response = $this->client->get($url, [ + Request::HEADERS => [ + "Origin" => $NotAllowedOrigin, + AbstractV2RestController::HEADER_AUTHORIZATION => 'Bearer ' . $token, + ], + ]); + + //print_r($response->headers); + + $this->assertResponseOk($response); + + $json = $this->getResponseJson($response); + + $this->assertIsArray($json['ps_accounts']); + + $this->assertArrayNotHasKey($NotAllowedOrigin, $response->headers); + } +} diff --git a/upgrade/upgrade-8.0.0.php b/upgrade/upgrade-8.0.0.php index 7cad902d9..e2a8b4df4 100644 --- a/upgrade/upgrade-8.0.0.php +++ b/upgrade/upgrade-8.0.0.php @@ -33,13 +33,9 @@ function upgrade_module_8_0_0($module) /* @phpstan-ignore-next-line */ } catch (\Throwable $e) { - Logger::getInstance()->error('error during upgrade : ' . $e->getMessage()); - - return false; + Logger::getInstance()->error('error during upgrade : ' . $e); } catch (\Exception $e) { - Logger::getInstance()->error('error during upgrade : ' . $e->getMessage()); - - return false; + Logger::getInstance()->error('error during upgrade : ' . $e); } return true; From 09ddf7d50982bf36f4e08a61463a97823b41fca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Fri, 22 Aug 2025 09:19:36 +0200 Subject: [PATCH 30/46] feat: remove fallback vue component (#535) * feat: remove fallback vue component * feat: error message when CDN can't be loaded --- _dev/apps/configuration/App.vue | 13 ++- _dev/package.json | 1 - _dev/pnpm-lock.yaml | 152 -------------------------------- 3 files changed, 9 insertions(+), 157 deletions(-) diff --git a/_dev/apps/configuration/App.vue b/_dev/apps/configuration/App.vue index 260b1f15d..ac1a16791 100644 --- a/_dev/apps/configuration/App.vue +++ b/_dev/apps/configuration/App.vue @@ -27,7 +27,11 @@
- + + +
{$cloudShopId} ({$verifiedMsg}) | Health Check diff --git a/src/ServiceProvider/DefaultProvider.php b/src/ServiceProvider/DefaultProvider.php index de2500807..f11ed2b9e 100644 --- a/src/ServiceProvider/DefaultProvider.php +++ b/src/ServiceProvider/DefaultProvider.php @@ -202,6 +202,12 @@ static function () use ($container) { return $session; } + return $container->get(ConfigurationStorageSession::class); + } + ); + $container->registerProvider( + ConfigurationStorageSession::class, + static function () use ($container) { // Fallback session object // FIXME: create an interface for it $session = new ConfigurationStorageSession( From 62c6f5cbf4f413a3ea5113872e45adf4655fb2a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Thu, 28 Aug 2025 09:16:44 +0200 Subject: [PATCH 32/46] feat: improve upgrade script (#542) * feat: improve upgrade script --- upgrade/upgrade-8.0.0.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/upgrade/upgrade-8.0.0.php b/upgrade/upgrade-8.0.0.php index e2a8b4df4..f5a47e958 100644 --- a/upgrade/upgrade-8.0.0.php +++ b/upgrade/upgrade-8.0.0.php @@ -17,6 +17,9 @@ function upgrade_module_8_0_0($module) require __DIR__ . '/../src/enforce_autoload.php'; try { + $module->registerHook($module->getHooksToRegister()); + $module->unregisterHook('displayBackOfficeHeader'); + $installer = new PrestaShop\Module\PsAccounts\Module\Install($module, Db::getInstance()); $installer->installInMenu(); @@ -30,12 +33,10 @@ function upgrade_module_8_0_0($module) $commandBus = $module->getService(CommandBus::class); $commandBus->handle(new MigrateOrCreateIdentitiesV8Command('ps_accounts')); - - /* @phpstan-ignore-next-line */ - } catch (\Throwable $e) { - Logger::getInstance()->error('error during upgrade : ' . $e); } catch (\Exception $e) { Logger::getInstance()->error('error during upgrade : ' . $e); + } catch (\Throwable $e) { + Logger::getInstance()->error('error during upgrade : ' . $e); } return true; From 77da8bb1212a788b1e642287a0ca566b2269cadc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Mon, 1 Sep 2025 10:53:51 +0200 Subject: [PATCH 33/46] [ACCOUNT-3069] fix: reset migrate from v5 (#543) * fix: reset migrate from v5 * fix: unit test --- .../CommandHandler/MigrateOrCreateIdentityV8Handler.php | 3 ++- src/Service/UpgradeService.php | 4 ++-- .../CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php index bd065c953..c41ae8458 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php @@ -123,7 +123,8 @@ public function handle(MigrateOrCreateIdentityV8Command $command) $shopId = $command->shopId ?: \Shop::getContextShopID(); $shopUuid = $this->configurationRepository->getShopUuid(); - $fromVersion = $this->upgradeService->getVersion(); + // FIXME: command can hold that property depending on context + $fromVersion = $this->upgradeService->getRegisteredVersion(); // FIXME: shouldn't this condition be a specific flag if (!$shopUuid || version_compare($fromVersion, '8', '>=')) { diff --git a/src/Service/UpgradeService.php b/src/Service/UpgradeService.php index 0d4428ea3..140f4a926 100644 --- a/src/Service/UpgradeService.php +++ b/src/Service/UpgradeService.php @@ -48,7 +48,7 @@ public function setVersion($version = \Ps_accounts::VERSION) /** * @return string */ - private function getRegisteredVersion() + public function getRegisteredVersion() { return $this->repository->getLastUpgrade(false); } @@ -56,7 +56,7 @@ private function getRegisteredVersion() /** * @return string */ - private function getCoreRegisteredVersion() + public function getCoreRegisteredVersion() { return \Db::getInstance()->getValue( 'SELECT version FROM ' . _DB_PREFIX_ . 'module WHERE name = \'' . self::MODULE_NAME . '\'' diff --git a/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php index 8062fc119..2fbb79352 100644 --- a/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php @@ -251,7 +251,7 @@ public function itShouldMigrateIdentityFromV5AndV6() // introduced in v7 //$this->configurationRepository->updateLastUpgrade(null); - $fromVersion = '5.6.2'; + $fromVersion = '0'; \Db::getInstance()->execute( 'UPDATE ' . _DB_PREFIX_ . 'module SET version = \'' . $fromVersion . '\' WHERE name = \'' . UpgradeService::MODULE_NAME . '\'' ); From 27ad49b8495cd195f2fd6163ccfe2e5efa7ed94d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Mon, 1 Sep 2025 17:02:05 +0200 Subject: [PATCH 34/46] [ACCOUNT-3058] feat: manual verification flag (#544) * feat: manual verification flag * feat: force manualVerification at true when calling ajax action --- .../admin/AdminAjaxV2PsAccountsController.php | 2 +- src/Account/Command/CreateIdentityCommand.php | 9 ++++++++- .../Command/MigrateOrCreateIdentityV8Command.php | 9 ++++++++- src/Account/Command/VerifyIdentityCommand.php | 9 ++++++++- .../CommandHandler/CreateIdentitiesHandler.php | 2 +- src/Account/CommandHandler/CreateIdentityHandler.php | 6 +++++- .../MigrateOrCreateIdentitiesV8Handler.php | 2 +- .../MigrateOrCreateIdentityV8Handler.php | 6 +++++- .../CommandHandler/VerifyIdentitiesHandler.php | 2 +- src/Account/CommandHandler/VerifyIdentityHandler.php | 1 + src/Http/Client/ConfigObject.php | 1 + src/Service/Accounts/AccountsService.php | 12 ++++++++++-- tests/phpstan/phpstan-PS-1.6.neon | 1 + tests/phpstan/phpstan-PS-1.7.neon | 1 + 14 files changed, 52 insertions(+), 11 deletions(-) diff --git a/controllers/admin/AdminAjaxV2PsAccountsController.php b/controllers/admin/AdminAjaxV2PsAccountsController.php index 3df6cc76e..d92415409 100644 --- a/controllers/admin/AdminAjaxV2PsAccountsController.php +++ b/controllers/admin/AdminAjaxV2PsAccountsController.php @@ -88,7 +88,7 @@ public function ajaxProcessFallbackCreateIdentity() throw new Exception('Shop ID is required for migration or creation.'); } - $command = new MigrateOrCreateIdentityV8Command($shopId, $source); + $command = new MigrateOrCreateIdentityV8Command($shopId, true, $source); $this->commandBus->handle($command); diff --git a/src/Account/Command/CreateIdentityCommand.php b/src/Account/Command/CreateIdentityCommand.php index 5844950fc..551a4fd23 100644 --- a/src/Account/Command/CreateIdentityCommand.php +++ b/src/Account/Command/CreateIdentityCommand.php @@ -28,6 +28,11 @@ class CreateIdentityCommand */ public $shopId; + /** + * @var bool + */ + public $manualVerification; + /** * @var string|null */ @@ -35,11 +40,13 @@ class CreateIdentityCommand /** * @param int|null $shopId + * @param bool $manualVerification * @param string|null $source */ - public function __construct($shopId, $source = null) + public function __construct($shopId, $manualVerification = false, $source = null) { $this->shopId = $shopId; + $this->manualVerification = $manualVerification; $this->source = $source; } } diff --git a/src/Account/Command/MigrateOrCreateIdentityV8Command.php b/src/Account/Command/MigrateOrCreateIdentityV8Command.php index db82bb92e..6f0151656 100644 --- a/src/Account/Command/MigrateOrCreateIdentityV8Command.php +++ b/src/Account/Command/MigrateOrCreateIdentityV8Command.php @@ -28,6 +28,11 @@ class MigrateOrCreateIdentityV8Command */ public $shopId; + /** + * @var bool + */ + public $manualVerification; + /** * @var string|null */ @@ -35,11 +40,13 @@ class MigrateOrCreateIdentityV8Command /** * @param int|null $shopId + * @param bool $manualVerification * @param string|null $source */ - public function __construct($shopId, $source = null) + public function __construct($shopId, $manualVerification = false, $source = null) { $this->shopId = $shopId; + $this->manualVerification = $manualVerification; $this->source = $source; } } diff --git a/src/Account/Command/VerifyIdentityCommand.php b/src/Account/Command/VerifyIdentityCommand.php index 73d183573..22c2174e8 100644 --- a/src/Account/Command/VerifyIdentityCommand.php +++ b/src/Account/Command/VerifyIdentityCommand.php @@ -28,6 +28,11 @@ class VerifyIdentityCommand */ public $shopId; + /** + * @var bool + */ + public $manualVerification; + /** * @var string|null */ @@ -35,11 +40,13 @@ class VerifyIdentityCommand /** * @param int|null $shopId + * @param bool $manualVerification * @param string|null $source */ - public function __construct($shopId, $source = 'ps_accounts') + public function __construct($shopId, $manualVerification = false, $source = null) { $this->shopId = $shopId; + $this->manualVerification = $manualVerification; $this->source = $source; } } diff --git a/src/Account/CommandHandler/CreateIdentitiesHandler.php b/src/Account/CommandHandler/CreateIdentitiesHandler.php index b9877793f..91b87ed3c 100644 --- a/src/Account/CommandHandler/CreateIdentitiesHandler.php +++ b/src/Account/CommandHandler/CreateIdentitiesHandler.php @@ -37,7 +37,7 @@ public function handle(CreateIdentitiesCommand $command) { $this->handleMulti(function ($multiShopId) use ($command) { try { - $this->commandBus->handle(new CreateIdentityCommand($multiShopId, $command->source)); + $this->commandBus->handle(new CreateIdentityCommand($multiShopId, false, $command->source)); } catch (RefreshTokenException $e) { Logger::getInstance()->error($e->getMessage()); } catch (AccountsException $e) { diff --git a/src/Account/CommandHandler/CreateIdentityHandler.php b/src/Account/CommandHandler/CreateIdentityHandler.php index e302e3d50..c92b2f9fa 100644 --- a/src/Account/CommandHandler/CreateIdentityHandler.php +++ b/src/Account/CommandHandler/CreateIdentityHandler.php @@ -105,7 +105,11 @@ public function handle(CreateIdentityCommand $command) $this->statusManager->setCloudShopId($identityCreated->cloudShopId); $this->statusManager->invalidateCache(); } - $this->commandBus->handle(new VerifyIdentityCommand($command->shopId, $command->source)); + $this->commandBus->handle(new VerifyIdentityCommand( + $command->shopId, + $command->manualVerification, + $command->source + )); } /** diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php index 4e0d370f2..6885eec5e 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php @@ -36,7 +36,7 @@ public function handle(MigrateOrCreateIdentitiesV8Command $command) { $this->handleMulti(function ($multiShopId) use ($command) { try { - $this->commandBus->handle(new MigrateOrCreateIdentityV8Command($multiShopId, $command->source)); + $this->commandBus->handle(new MigrateOrCreateIdentityV8Command($multiShopId, false, $command->source)); } catch (Exception $e) { Logger::getInstance()->error($e->getMessage()); } diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php index c41ae8458..d0b7299c9 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php @@ -130,7 +130,11 @@ public function handle(MigrateOrCreateIdentityV8Command $command) if (!$shopUuid || version_compare($fromVersion, '8', '>=')) { $this->upgradeService->setVersion(); - $this->commandBus->handle(new CreateIdentityCommand($command->shopId, $command->source)); + $this->commandBus->handle(new CreateIdentityCommand( + $command->shopId, + $command->manualVerification, + $command->source + )); return; } diff --git a/src/Account/CommandHandler/VerifyIdentitiesHandler.php b/src/Account/CommandHandler/VerifyIdentitiesHandler.php index 7456e486f..885096c47 100644 --- a/src/Account/CommandHandler/VerifyIdentitiesHandler.php +++ b/src/Account/CommandHandler/VerifyIdentitiesHandler.php @@ -38,7 +38,7 @@ public function handle(VerifyIdentitiesCommand $command) { $this->handleMulti(function ($multiShopId) use ($command) { try { - $this->commandBus->handle(new VerifyIdentityCommand($multiShopId, $command->source)); + $this->commandBus->handle(new VerifyIdentityCommand($multiShopId, false, $command->source)); } catch (RefreshTokenException $e) { Logger::getInstance()->error($e->getMessage()); } catch (AccountsException $e) { diff --git a/src/Account/CommandHandler/VerifyIdentityHandler.php b/src/Account/CommandHandler/VerifyIdentityHandler.php index 020c3fc27..968165e91 100644 --- a/src/Account/CommandHandler/VerifyIdentityHandler.php +++ b/src/Account/CommandHandler/VerifyIdentityHandler.php @@ -102,6 +102,7 @@ public function handle(VerifyIdentityCommand $command) $this->shopSession->getValidToken(), $this->shopProvider->getUrl($shopId), $this->proofManager->generateProof(), + $command->manualVerification, $command->source ); $this->statusManager->invalidateCache(); diff --git a/src/Http/Client/ConfigObject.php b/src/Http/Client/ConfigObject.php index 8f2147a7e..045092df2 100644 --- a/src/Http/Client/ConfigObject.php +++ b/src/Http/Client/ConfigObject.php @@ -24,6 +24,7 @@ use PrestaShop\Module\PsAccounts\Http\Client\Exception\UndefinedPropertyException; use PrestaShop\Module\PsAccounts\Type\Enum; +#[\AllowDynamicProperties] class ConfigObject extends Enum { /** diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index 58d4f8971..ed1380b6c 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -271,14 +271,21 @@ public function createShopIdentity(ShopUrl $shopUrl, $proof = null, $source = 'p * @param string $shopToken * @param ShopUrl $shopUrl * @param string $proof + * @param bool $manualVerification * @param string|null $source * * @return void * * @throws AccountsException */ - public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $proof, $source = 'ps_accounts') - { + public function verifyShopIdentity( + $cloudShopId, + $shopToken, + ShopUrl $shopUrl, + $proof, + $manualVerification = false, + $source = 'ps_accounts' + ) { $response = $this->getClient()->post( '/v1/shop-identities/' . $cloudShopId . '/verify', [ @@ -292,6 +299,7 @@ public function verifyShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $ 'frontendUrl' => $shopUrl->getFrontendUrl(), 'multiShopId' => $shopUrl->getMultiShopId(), 'proof' => $proof, + 'manualVerification' => $manualVerification, ], ] ); diff --git a/tests/phpstan/phpstan-PS-1.6.neon b/tests/phpstan/phpstan-PS-1.6.neon index 95b047df5..3c78d617c 100644 --- a/tests/phpstan/phpstan-PS-1.6.neon +++ b/tests/phpstan/phpstan-PS-1.6.neon @@ -44,5 +44,6 @@ parameters: - '#Call to an undefined method object::generate\(\).#' - '#Symfony\\Component\\HttpFoundation\\Session\\SessionInterface#' - '#Property PrestaShop\\Module\\PsAccounts\\Entity\\EmployeeAccount::\$id is never written, only read#' + - '#Attribute class AllowDynamicProperties does not exist.#' level: 7 diff --git a/tests/phpstan/phpstan-PS-1.7.neon b/tests/phpstan/phpstan-PS-1.7.neon index dac7e4e53..891c7ab0f 100644 --- a/tests/phpstan/phpstan-PS-1.7.neon +++ b/tests/phpstan/phpstan-PS-1.7.neon @@ -46,5 +46,6 @@ parameters: - '#no value type specified in iterable type array#' - '#has Exception in PHPDoc @throws tag but it.s not thrown#' - '#Property PrestaShop\\Module\\PsAccounts\\Entity\\EmployeeAccount::\$id is never written, only read#' + - '#Attribute class AllowDynamicProperties does not exist.#' level: 7 From 461510521d6cfdb3c755902f2f65feb20ed87e43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Thu, 4 Sep 2025 10:59:26 +0200 Subject: [PATCH 35/46] [ACCOUNT-3078] feat: optimistic set point of contact (#545) * feat: optimistic set point of contact * feat: type in backOfficeUrl property * fix: upset cached status property set to merge * fix: unit tests and sf oauth controller for ps9 --- .../admin/AdminOAuth2PsAccountsController.php | 2 +- src/Account/CachedShopStatus.php | 2 +- .../Command/IdentifyContactCommand.php | 12 +++++- .../CommandHandler/IdentifyContactHandler.php | 5 ++- src/Account/StatusManager.php | 41 +++++++++++++++++-- src/Controller/Admin/OAuth2Controller.php | 2 +- src/Http/Resource/Resource.php | 5 ++- src/Service/Accounts/Resource/ShopStatus.php | 2 +- .../IdentifyContactHandlerTest.php | 18 ++++++-- 9 files changed, 73 insertions(+), 16 deletions(-) diff --git a/controllers/admin/AdminOAuth2PsAccountsController.php b/controllers/admin/AdminOAuth2PsAccountsController.php index 4d18c4f08..57791a1e8 100644 --- a/controllers/admin/AdminOAuth2PsAccountsController.php +++ b/controllers/admin/AdminOAuth2PsAccountsController.php @@ -133,7 +133,7 @@ protected function initUserSession(AccessToken $accessToken) ); if ($this->getOAuthAction() === 'identifyPointOfContact') { - $this->commandBus->handle(new IdentifyContactCommand($accessToken, $this->getSource())); + $this->commandBus->handle(new IdentifyContactCommand($accessToken, $user, $this->getSource())); return true; } diff --git a/src/Account/CachedShopStatus.php b/src/Account/CachedShopStatus.php index ec657091b..2062c91c7 100644 --- a/src/Account/CachedShopStatus.php +++ b/src/Account/CachedShopStatus.php @@ -74,7 +74,7 @@ public function toArray($all = true) $this->uncastChildResource($array, ShopStatus::class, [ 'shopStatus', - ]); + ], $all); $this->uncastDateTime($array, [ 'updatedAt', diff --git a/src/Account/Command/IdentifyContactCommand.php b/src/Account/Command/IdentifyContactCommand.php index 6806f061d..961d68f84 100644 --- a/src/Account/Command/IdentifyContactCommand.php +++ b/src/Account/Command/IdentifyContactCommand.php @@ -22,6 +22,7 @@ namespace PrestaShop\Module\PsAccounts\Account\Command; use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\AccessToken; +use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\UserInfo; class IdentifyContactCommand { @@ -35,13 +36,20 @@ class IdentifyContactCommand */ public $source; + /** + * @var UserInfo + */ + public $userInfo; + /** * @param AccessToken $accessToken - * @param string|null $source + * @param UserInfo $userInfo + * @param string $source */ - public function __construct($accessToken, $source = 'ps_accounts') + public function __construct($accessToken, $userInfo, $source = 'ps_accounts') { $this->accessToken = $accessToken; + $this->userInfo = $userInfo; $this->source = $source; } } diff --git a/src/Account/CommandHandler/IdentifyContactHandler.php b/src/Account/CommandHandler/IdentifyContactHandler.php index b8aa2d31b..9fa9ca9de 100644 --- a/src/Account/CommandHandler/IdentifyContactHandler.php +++ b/src/Account/CommandHandler/IdentifyContactHandler.php @@ -78,6 +78,9 @@ public function handle(IdentifyContactCommand $command) $command->accessToken->access_token, $command->source ); - $this->statusManager->invalidateCache(); + + // optimistic update cached status + $this->statusManager->setPointOfContactUuid($command->userInfo->sub); + $this->statusManager->setPointOfContactEmail($command->userInfo->email); } } diff --git a/src/Account/StatusManager.php b/src/Account/StatusManager.php index eb04bdc35..60e3aa836 100644 --- a/src/Account/StatusManager.php +++ b/src/Account/StatusManager.php @@ -212,9 +212,9 @@ public function getCloudShopId($cachedStatus = true) public function setCloudShopId($cloudShopId) { $this->upsetCachedStatus(new CachedShopStatus([ - 'shopStatus' => new ShopStatus([ + 'shopStatus' => [ 'cloudShopId' => $cloudShopId, - ]), + ], ])); } @@ -232,6 +232,20 @@ public function getPointOfContactUuid($cachedStatus = true) } } + /** + * @param string $pointOfContactUuid + * + * @return void + */ + public function setPointOfContactUuid($pointOfContactUuid) + { + $this->upsetCachedStatus(new CachedShopStatus([ + 'shopStatus' => [ + 'pointOfContactUuid' => $pointOfContactUuid, + ], + ])); + } + /** * @param bool $cachedStatus * @@ -246,6 +260,20 @@ public function getPointOfContactEmail($cachedStatus = true) } } + /** + * @param string $pointOfContactEmail + * + * @return void + */ + public function setPointOfContactEmail($pointOfContactEmail) + { + $this->upsetCachedStatus(new CachedShopStatus([ + 'shopStatus' => [ + 'pointOfContactEmail' => $pointOfContactEmail, + ], + ])); + } + /** * @return CachedShopStatus * @@ -263,6 +291,8 @@ protected function getCachedStatus() } /** + * @param CachedShopStatus $cachedShopStatus + * * @return void */ protected function setCachedStatus(CachedShopStatus $cachedShopStatus) @@ -273,14 +303,17 @@ protected function setCachedStatus(CachedShopStatus $cachedShopStatus) } /** + * @param CachedShopStatus $cachedShopStatus + * @param bool $all all fields or only explicitly initialized fields + * * @return void */ - protected function upsetCachedStatus(CachedShopStatus $cachedShopStatus) + protected function upsetCachedStatus(CachedShopStatus $cachedShopStatus, $all = false) { try { $this->setCachedStatus(new CachedShopStatus(array_replace_recursive( $this->getCachedStatus()->toArray(), - $cachedShopStatus->toArray(false) + $cachedShopStatus->toArray($all) ))); } catch (UnknownStatusException $e) { $this->setCachedStatus($cachedShopStatus); diff --git a/src/Controller/Admin/OAuth2Controller.php b/src/Controller/Admin/OAuth2Controller.php index 6509fab5d..7a969ecb7 100644 --- a/src/Controller/Admin/OAuth2Controller.php +++ b/src/Controller/Admin/OAuth2Controller.php @@ -198,7 +198,7 @@ protected function initUserSession(AccessToken $accessToken) ); if ($this->getOAuthAction() === 'identifyPointOfContact') { - $this->commandBus->handle(new IdentifyContactCommand($accessToken, $this->getSource())); + $this->commandBus->handle(new IdentifyContactCommand($accessToken, $user, $this->getSource())); return true; } diff --git a/src/Http/Resource/Resource.php b/src/Http/Resource/Resource.php index 40a83435d..837fa6d91 100644 --- a/src/Http/Resource/Resource.php +++ b/src/Http/Resource/Resource.php @@ -50,14 +50,15 @@ protected function castChildResource(array & $values, $className, array $fields) * @param array $values * @param string $className * @param array $fields + * @param bool $all * * @return void */ - protected function uncastChildResource(array & $values, $className, array $fields) + protected function uncastChildResource(array & $values, $className, array $fields, $all = true) { foreach ($fields as $field) { if (isset($values[$field]) && is_a($values[$field], Resource::class, true)) { - $values[$field] = $this->$field->toArray(); + $values[$field] = $this->$field->toArray($all); } } } diff --git a/src/Service/Accounts/Resource/ShopStatus.php b/src/Service/Accounts/Resource/ShopStatus.php index 818aa9dac..61d4b81d2 100644 --- a/src/Service/Accounts/Resource/ShopStatus.php +++ b/src/Service/Accounts/Resource/ShopStatus.php @@ -43,7 +43,7 @@ class ShopStatus extends Resource /** * @var string */ - public $backofficeUrl; + public $backOfficeUrl; /** * @var string diff --git a/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php b/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php index e05e40f3e..7808cae46 100644 --- a/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php @@ -14,6 +14,7 @@ use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\IdentityCreated; use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\AccessToken; +use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\UserInfo; use PrestaShop\Module\PsAccounts\Tests\TestCase; class IdentifyContactHandlerTest extends TestCase @@ -83,11 +84,17 @@ public function itShouldSaveIdentityContact() ]) ]))->toArray())); + $userInfo = new UserInfo([ + 'sub' => $this->faker->uuid, + 'email' => $this->faker->safeEmail, + ]); + $this->getHandler()->handle(new IdentifyContactCommand(new AccessToken([ 'access_token' => 'valid_access_token', - ]))); + ]), $userInfo)); - $this->assertTrue($this->statusManager->cacheInvalidated()); + $this->assertEquals($userInfo->sub, $this->statusManager->getPointOfContactUuid()); + $this->assertEquals($userInfo->email, $this->statusManager->getPointOfContactEmail()); } /** @@ -118,9 +125,14 @@ public function itShouldNotSaveIdentityContactOnShopNotVerified() ]) ]))->toArray())); + $userInfo = new UserInfo([ + 'sub' => $this->faker->uuid, + 'email' => $this->faker->safeEmail, + ]); + $this->getHandler()->handle(new IdentifyContactCommand(new AccessToken([ 'access_token' => 'valid_access_token', - ]))); + ]), $userInfo)); } /** From 2c1d33d777e5830b8df78a516944fcee744a7529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Thu, 4 Sep 2025 11:06:56 +0200 Subject: [PATCH 36/46] feat: detect url change alert (#540) * feat: working js version * feat: improve upgrade script * feat: add a proper header-alert app * feat: cleanup obsolete hooks * feat: js notification system & cleanup deprecated hooks * chore: cleanup FIXME * chore: update error code * chore: add fixme * chore: remove tests on deleted code * fix: retrim final slash before comparing * feat: trigger verification command after identity update * feat: add origin tracking property & remove unused commands * chore: remove commented code * feat: origin property everywhere * feat: missing origin and source headers * feat: add a "force" property to VerifyIdentityComman * feat: alert style * fix: use the available btn-warning class for legacy context * feat: call translation helper on notification strings * fix: call php-cs-fixer * feat: update master (en) translation file --- .gitignore | 2 + _dev/apps/notifications/index.ts | 56 +++++ _dev/package.json | 3 +- _dev/vite.notifications.config.ts | 30 +++ .../admin/AdminAjaxPsAccountsController.php | 183 +++++++++++----- .../admin/AdminAjaxV2PsAccountsController.php | 65 +++++- ps_accounts.php | 40 ++-- .../Command/CreateIdentitiesCommand.php | 15 +- src/Account/Command/CreateIdentityCommand.php | 27 ++- .../MigrateOrCreateIdentitiesV8Command.php | 15 +- .../MigrateOrCreateIdentityV8Command.php | 16 +- src/Account/Command/UpdateUserShopCommand.php | 36 ---- .../Command/VerifyIdentitiesCommand.php | 15 +- src/Account/Command/VerifyIdentityCommand.php | 19 +- .../CreateIdentitiesHandler.php | 7 +- .../CommandHandler/CreateIdentityHandler.php | 6 +- .../CommandHandler/DeleteUserShopHandler.php | 91 -------- .../MigrateOrCreateIdentitiesV8Handler.php | 6 +- .../MigrateOrCreateIdentityV8Handler.php | 3 +- .../CommandHandler/UpdateUserShopHandler.php | 92 -------- .../VerifyIdentitiesHandler.php | 7 +- .../CommandHandler/VerifyIdentityHandler.php | 4 +- src/Hook/ActionAdminControllerInitBefore.php | 75 +------ .../ActionAdminControllerSetMedia.php} | 14 +- src/Hook/ActionObjectEmployeeDeleteAfter.php | 12 +- src/Hook/ActionObjectShopDeleteBefore.php | 27 +-- src/Hook/ActionObjectShopUpdateAfter.php | 55 +---- src/Hook/ActionObjectShopUrlUpdateAfter.php | 17 +- src/Hook/ActionShopAccountLinkAfter.php | 12 +- src/Hook/ActionShopAccountUnlinkAfter.php | 24 +-- src/Hook/DisplayAccountUpdateWarning.php | 29 +-- src/Hook/DisplayAdminAfterHeader.php | 93 -------- src/Hook/DisplayBackOfficeHeader.php | 60 ++++++ src/Hook/DisplayDashboardTop.php | 111 +--------- src/Provider/ShopProvider.php | 30 ++- src/Service/Accounts/AccountsService.php | 74 ++----- src/ServiceProvider/CommandProvider.php | 18 -- .../Hook/ActionObjectShopUpdateAfterTest.php | 199 ------------------ translations/en.php | 34 ++- upgrade/upgrade-8.0.0.php | 20 +- .../Blocks/shop_urls_configuration.html.twig | 25 --- views/templates/admin/debug.tpl | 75 ------- 42 files changed, 577 insertions(+), 1165 deletions(-) create mode 100644 _dev/apps/notifications/index.ts create mode 100644 _dev/vite.notifications.config.ts delete mode 100644 src/Account/Command/UpdateUserShopCommand.php delete mode 100644 src/Account/CommandHandler/DeleteUserShopHandler.php delete mode 100644 src/Account/CommandHandler/UpdateUserShopHandler.php rename src/{Account/Command/DeleteUserShopCommand.php => Hook/ActionAdminControllerSetMedia.php} (78%) delete mode 100644 src/Hook/DisplayAdminAfterHeader.php create mode 100644 src/Hook/DisplayBackOfficeHeader.php delete mode 100644 tests/src/Unit/Hook/ActionObjectShopUpdateAfterTest.php delete mode 100644 views/PrestaShop/Admin/Configure/ShopParameters/TrafficSeo/Meta/Blocks/shop_urls_configuration.html.twig delete mode 100644 views/templates/admin/debug.tpl diff --git a/.gitignore b/.gitignore index 2e922738a..63dc7cee3 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,8 @@ views/files/* /views/js/settings*.js.map /views/js/settingsOnBoarding*.js /views/js/settingsOnBoarding*.js.map +/views/js/notifications*.js +/views/js/notifications*.js.map /views/js/login*.js /views/js/login*.js.map /views/css/login*.css diff --git a/_dev/apps/notifications/index.ts b/_dev/apps/notifications/index.ts new file mode 100644 index 000000000..7545ac775 --- /dev/null +++ b/_dev/apps/notifications/index.ts @@ -0,0 +1,56 @@ + +function getParams() { + const scriptFilename = 'ps_accounts/views/js/notifications.js'; + const scripts = document.querySelectorAll('script'); + + let currentScript: HTMLScriptElement|null = null; + scripts.forEach(script => { + if (script.src.includes(scriptFilename)) { + currentScript = script; + } + }); + + if (currentScript) { + // Get the full URL of the script + const scriptSrc = currentScript['src']; + const url = new URL(scriptSrc); + + return url.searchParams; + } + return null; +} + +async function getNotifications(uri: string) { + return await fetch(uri, { + method: 'GET' + }) + .then(response => response.json()) + .then(data => { + return data; + }) + .catch(error => { + console.error('Error:', error); + return null; + }); +} + +function injectNotifications(notifications: [], container: Element) +{ + notifications.forEach((notif: any) => { + const alert = document.createElement('div'); + alert.innerHTML = notif?.html; + container.prepend(alert); + }); +} + +document.addEventListener('DOMContentLoaded', async function() { + + const container = document.querySelector('#main-div .content-div, #main #content'); + if (! container) return; + + const params = getParams(); + if (!params) return; + + injectNotifications(await getNotifications(params.get('ctx') || ''), container); +}); + diff --git a/_dev/package.json b/_dev/package.json index 294ef4732..d934638ad 100644 --- a/_dev/package.json +++ b/_dev/package.json @@ -4,9 +4,10 @@ "private": true, "scripts": { "dev": "vite", - "build": "vue-tsc --noEmit && vite build && vite build --config=vite.login.config.ts", + "build": "vue-tsc --noEmit && vite build && vite build --config=vite.login.config.ts && vite build --config=vite.notifications.config.ts", "build:vue": "vue-tsc --noEmit && vite build", "build:login": "vite build --config=vite.login.config.ts", + "build:notifications": "vite build --config=vite.notifications.config.ts", "lint": "eslint --ext .js,.ts,.vue .", "lint:fix": "eslint --ext .js,.ts,.vue . --fix" }, diff --git a/_dev/vite.notifications.config.ts b/_dev/vite.notifications.config.ts new file mode 100644 index 000000000..a3b394d43 --- /dev/null +++ b/_dev/vite.notifications.config.ts @@ -0,0 +1,30 @@ +import { defineConfig } from "vite"; +import path from "path"; + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + lib: { + entry: path.resolve(__dirname, "apps/notifications/index.ts"), + name: "AccountsHeaderAlert", + formats: ["es"], + fileName: () => `js/notifications.js`, + }, + outDir: "../views", + assetsDir: "../views/css", + emptyOutDir: false, + rollupOptions: { + output: { + assetFileNames: `css/notifications.[ext]`, + }, + }, + }, + resolve: { + alias: [ + { + find: "@", + replacement: path.resolve(__dirname, "/apps"), + }, + ], + }, +}); diff --git a/controllers/admin/AdminAjaxPsAccountsController.php b/controllers/admin/AdminAjaxPsAccountsController.php index 090fae108..1e1bf537e 100644 --- a/controllers/admin/AdminAjaxPsAccountsController.php +++ b/controllers/admin/AdminAjaxPsAccountsController.php @@ -19,15 +19,16 @@ */ require_once __DIR__ . '/../../src/Polyfill/Traits/Controller/AjaxRender.php'; -use PrestaShop\Module\PsAccounts\Account\Command\DeleteUserShopCommand; +use PrestaShop\Module\PsAccounts\Account\Exception\UnknownStatusException; use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\AccountLogin\OAuth2Session; -use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; -use PrestaShop\Module\PsAccounts\Hook\ActionShopAccountUnlinkAfter; +use PrestaShop\Module\PsAccounts\Adapter\Link as AccountsLink; +use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Polyfill\Traits\Controller\AjaxRender; -use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; +use PrestaShop\Module\PsAccounts\Provider\ShopProvider; use PrestaShop\Module\PsAccounts\Service\SentryService; +use PrestaShop\Module\PsAccounts\Service\UpgradeService; /** * Controller for all ajax calls. @@ -41,11 +42,6 @@ class AdminAjaxPsAccountsController extends \ModuleAdminController */ public $module; - /** - * @var CommandBus - */ - private $commandBus; - /** * AdminAjaxPsAccountsController constructor. * @@ -55,8 +51,6 @@ public function __construct() { parent::__construct(); - $this->commandBus = $this->module->getService(CommandBus::class); - $this->ajax = true; $this->content_only = true; } @@ -92,21 +86,19 @@ public function ajaxProcessGetOrRefreshToken() * * @throws Exception */ - public function ajaxProcessUnlinkShop() + public function ajaxProcessGetOrRefreshAccessToken() { try { - /** @var ConfigurationRepository $configurationRepository */ - $configurationRepository = $this->module->getService(ConfigurationRepository::class); - - $response = $this->commandBus->handle(new DeleteUserShopCommand( - $configurationRepository->getShopId() - )); - - http_response_code($response['httpCode']); + /** @var OAuth2Session $oauth2Session */ + $oauth2Session = $this->module->getService(OAuth2Session::class); header('Content-Type: text/json'); - $this->ajaxRender((string) json_encode($response['body'])); + $this->ajaxRender( + (string) json_encode([ + 'token' => (string) $oauth2Session->getOrRefreshAccessToken(), + ]) + ); } catch (Exception $e) { SentryService::captureAndRethrow($e); } @@ -114,52 +106,139 @@ public function ajaxProcessUnlinkShop() /** * @return void - * - * @throws Exception */ - public function ajaxProcessResetLinkAccount() + public function ajaxProcessGetNotifications() { + $notifications = []; try { - /** @var StatusManager $statusManager */ - $statusManager = $this->module->getService(StatusManager::class); + $notifications = array_merge( + $this->getNotificationsUpgradeFailed(), + $this->getNotificationsUrlMismatch() + ); + } catch (\Exception $e) { + Logger::getInstance()->error($e->getMessage()); + } catch (\Throwable $e) { + Logger::getInstance()->error($e->getMessage()); + } + $this->ajaxRender( + (string) json_encode($notifications ? [$notifications] : []) + ); + } - $status = $statusManager->getStatus(); + /** + * @return array|string[] + * + * @throws UnknownStatusException + */ + protected function getNotificationsUrlMismatch() + { + /** @var StatusManager $statusManager */ + $statusManager = $this->module->getService(StatusManager::class); - $statusManager->invalidateCache(); + if (!$statusManager->identityCreated()) { + return []; + } - Hook::exec(ActionShopAccountUnlinkAfter::getName(), [ - 'cloudShopId' => $status->cloudShopId, - 'shopId' => \Context::getContext()->shop->id, - ]); + $status = $statusManager->getStatus(); - header('Content-Type: text/json'); + /** @var ShopProvider $shopProvider */ + $shopProvider = $this->module->getService(ShopProvider::class); + $shopUrl = $shopProvider->getUrl($this->context->shop->id); - $this->ajaxRender((string) json_encode(['message' => 'success'])); - } catch (Exception $e) { - SentryService::captureAndRethrow($e); + $cloudFrontendUrl = rtrim($status->frontendUrl, '/'); + $localFrontendUrl = rtrim($shopUrl->getFrontendUrl(), '/'); + + if ($localFrontendUrl === $cloudFrontendUrl) { + return []; } + + /** @var AccountsLink $link */ + $link = $this->module->getService(AccountsLink::class); + $moduleLink = $link->getAdminLink('AdminModules', true, [], [ + 'configure' => 'ps_accounts', + ]); + + return [ + 'html' => ' + +
+
+
+ ' . $this->module->l('Action required: confirm your store URL') . ' +
+

' . $this->module->l('We\'ve noticed that your store\'s URL no longer matches the one registered in your PrestaShop Account. For your services to function properly, you must either confirm this change or create a new identity for your store.') . '

+ +
+
+ +
+
+ +', + ]; } /** - * @return void - * - * @throws Exception + * @return array|string[] */ - public function ajaxProcessGetOrRefreshAccessToken() + protected function getNotificationsUpgradeFailed() { - try { - /** @var OAuth2Session $oauth2Session */ - $oauth2Session = $this->module->getService(OAuth2Session::class); - - header('Content-Type: text/json'); + /** @var UpgradeService $upgradeService */ + $upgradeService = $this->module->getService(UpgradeService::class); - $this->ajaxRender( - (string) json_encode([ - 'token' => (string) $oauth2Session->getOrRefreshAccessToken(), - ]) - ); - } catch (Exception $e) { - SentryService::captureAndRethrow($e); + if ($upgradeService->getCoreRegisteredVersion() === \Ps_accounts::VERSION) { + return []; } + + return [ + 'html' => ' +
+ + ' . $this->module->l('Warning!') . ' ' . $this->module->l('PrestaShop Account module wasn\'t upgraded properly.') . ' +
+ ' . $this->module->l('Please reset the module') . ' +
+', + ]; } } diff --git a/controllers/admin/AdminAjaxV2PsAccountsController.php b/controllers/admin/AdminAjaxV2PsAccountsController.php index d92415409..fcde2c5a0 100644 --- a/controllers/admin/AdminAjaxV2PsAccountsController.php +++ b/controllers/admin/AdminAjaxV2PsAccountsController.php @@ -19,13 +19,16 @@ */ require_once __DIR__ . '/../../src/Http/Controller/AbstractAdminAjaxCorsController.php'; +use PrestaShop\Module\PsAccounts\Account\Command\CreateIdentityCommand; use PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentityV8Command; +use PrestaShop\Module\PsAccounts\Account\Command\VerifyIdentityCommand; use PrestaShop\Module\PsAccounts\Account\Query\GetContextQuery; use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; use PrestaShop\Module\PsAccounts\Cqrs\QueryBus; use PrestaShop\Module\PsAccounts\Http\Controller\AbstractAdminAjaxCorsController; use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; /** * Controller for all ajax calls. @@ -88,7 +91,67 @@ public function ajaxProcessFallbackCreateIdentity() throw new Exception('Shop ID is required for migration or creation.'); } - $command = new MigrateOrCreateIdentityV8Command($shopId, true, $source); + $command = new MigrateOrCreateIdentityV8Command($shopId, AccountsService::ORIGIN_FALLBACK, $source); + + $this->commandBus->handle($command); + + $this->ajaxRender( + (string) json_encode([ + 'success' => true, + ]) + ); + } + + /** + * @return void + * + * @throws Exception + */ + public function ajaxProcessRenewIdentity() + { + $shopId = Tools::getValue('shop_id', null); + $source = Tools::getValue('source', 'ps_accounts'); + + if (!$shopId) { + throw new Exception('Shop ID is required for renew.'); + } + + $command = new CreateIdentityCommand( + $shopId, + true, + AccountsService::ORIGIN_MISMATCH_CREATE, + $source + ); + + $this->commandBus->handle($command); + + $this->ajaxRender( + (string) json_encode([ + 'success' => true, + ]) + ); + } + + /** + * @return void + * + * @throws Exception + */ + public function ajaxProcessUpdateIdentity() + { + $shopId = Tools::getValue('shop_id', null); + $source = Tools::getValue('source', 'ps_accounts'); + + if (!$shopId) { + throw new Exception('Shop ID is required for update.'); + } + + $command = new VerifyIdentityCommand( + $shopId, + true, + AccountsService::ORIGIN_MISMATCH_UPDATE, + $source + ); $this->commandBus->handle($command); diff --git a/ps_accounts.php b/ps_accounts.php index 05b7e6091..a1632e04e 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -52,13 +52,6 @@ class Ps_accounts extends Module * @var array */ private $customHooks = [ - [ - 'name' => 'displayAccountUpdateWarning', - 'title' => 'Display account update warning', - 'description' => 'Show a warning message when the user wants to' - . ' update his shop configuration', - 'position' => 1, - ], [ 'name' => 'actionShopAccountLinkAfter', 'title' => 'Shop linked event', @@ -87,28 +80,14 @@ class Ps_accounts extends Module private $hooks = [ //\PrestaShop\Module\PsAccounts\Hook\ActionAdminLoginControllerLoginAfter::class, 'actionAdminLoginControllerLoginAfter', + 'actionAdminLoginControllerSetMedia', + //'actionAdminControllerSetMedia', + 'displayBackOfficeHeader', 'actionObjectEmployeeDeleteAfter', 'actionObjectShopAddAfter', 'actionObjectShopDeleteAfter', - 'actionObjectShopDeleteBefore', - 'actionObjectShopUpdateAfter', - 'actionObjectShopUrlUpdateAfter', - 'actionShopAccountLinkAfter', - 'actionShopAccountUnlinkAfter', - 'displayAccountUpdateWarning', + 'actionShopAccessTokenRefreshAfter', 'displayBackOfficeEmployeeMenu', - 'displayDashboardTop', - - // toggle single/multi-shop - //'actionObjectShopAddAfter', - //'actionObjectShopDeleteAfter', - - // Login/Logout OAuth - // PS 1.6 - 1.7 - //'displayAdminAfterHeader', // FIXME: for alpha version only - 'actionAdminLoginControllerSetMedia', - // PS >= 8 - //'actionAdminControllerInitBefore', ]; /** @@ -185,6 +164,7 @@ public function install() && $this->addCustomHooks($this->customHooks) && $this->registerHook($this->getHooksToRegister()); + // FIXME: implement safe "reset" method $this->onModuleReset(); return $status; @@ -441,8 +421,16 @@ public function onModuleReset() /** @var \PrestaShop\Module\PsAccounts\Cqrs\CommandBus $commandBus */ $commandBus = $this->getService(\PrestaShop\Module\PsAccounts\Cqrs\CommandBus::class); + /** @var \PrestaShop\Module\PsAccounts\Service\UpgradeService $upgradeService */ + $upgradeService = $this->getService(\PrestaShop\Module\PsAccounts\Service\UpgradeService::class); + // Verification flow - $commandBus->handle(new \PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentitiesV8Command('ps_accounts')); + $commandBus->handle(new \PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentitiesV8Command( + 'ps_accounts', + version_compare($upgradeService->getCoreRegisteredVersion(), '0', '>') ? + \PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService::ORIGIN_RESET : + \PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService::ORIGIN_INSTALL + )); } /** diff --git a/src/Account/Command/CreateIdentitiesCommand.php b/src/Account/Command/CreateIdentitiesCommand.php index 3c3edeaa9..98023a5c9 100644 --- a/src/Account/Command/CreateIdentitiesCommand.php +++ b/src/Account/Command/CreateIdentitiesCommand.php @@ -21,18 +21,27 @@ namespace PrestaShop\Module\PsAccounts\Account\Command; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; + class CreateIdentitiesCommand { /** - * @var string|null + * @var string + */ + public $origin; + + /** + * @var string */ public $source; /** - * @param string|null $source + * @param string $origin + * @param string $source */ - public function __construct($source = null) + public function __construct($origin = AccountsService::ORIGIN_INSTALL, $source = 'ps_accounts') { + $this->origin = $origin; $this->source = $source; } } diff --git a/src/Account/Command/CreateIdentityCommand.php b/src/Account/Command/CreateIdentityCommand.php index 551a4fd23..43e9270fe 100644 --- a/src/Account/Command/CreateIdentityCommand.php +++ b/src/Account/Command/CreateIdentityCommand.php @@ -21,6 +21,8 @@ namespace PrestaShop\Module\PsAccounts\Account\Command; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; + class CreateIdentityCommand { /** @@ -31,22 +33,33 @@ class CreateIdentityCommand /** * @var bool */ - public $manualVerification; + public $renew; + + /** + * @var string + */ + public $origin; /** - * @var string|null + * @var string */ public $source; /** * @param int|null $shopId - * @param bool $manualVerification - * @param string|null $source + * @param bool $renew + * @param string $origin + * @param string $source */ - public function __construct($shopId, $manualVerification = false, $source = null) - { + public function __construct( + $shopId, + $renew = false, + $origin = AccountsService::ORIGIN_INSTALL, + $source = 'ps_accounts' + ) { $this->shopId = $shopId; - $this->manualVerification = $manualVerification; + $this->renew = $renew; + $this->origin = $origin; $this->source = $source; } } diff --git a/src/Account/Command/MigrateOrCreateIdentitiesV8Command.php b/src/Account/Command/MigrateOrCreateIdentitiesV8Command.php index b722c593e..472fd2ae8 100644 --- a/src/Account/Command/MigrateOrCreateIdentitiesV8Command.php +++ b/src/Account/Command/MigrateOrCreateIdentitiesV8Command.php @@ -21,18 +21,27 @@ namespace PrestaShop\Module\PsAccounts\Account\Command; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; + class MigrateOrCreateIdentitiesV8Command { /** - * @var string|null + * @var string + */ + public $origin; + + /** + * @var string */ public $source; /** - * @param string|null $source + * @param string $origin + * @param string $source */ - public function __construct($source = null) + public function __construct($origin = AccountsService::ORIGIN_INSTALL, $source = 'ps_accounts') { + $this->origin = $origin; $this->source = $source; } } diff --git a/src/Account/Command/MigrateOrCreateIdentityV8Command.php b/src/Account/Command/MigrateOrCreateIdentityV8Command.php index 6f0151656..d5193cf81 100644 --- a/src/Account/Command/MigrateOrCreateIdentityV8Command.php +++ b/src/Account/Command/MigrateOrCreateIdentityV8Command.php @@ -21,6 +21,8 @@ namespace PrestaShop\Module\PsAccounts\Account\Command; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; + class MigrateOrCreateIdentityV8Command { /** @@ -29,24 +31,24 @@ class MigrateOrCreateIdentityV8Command public $shopId; /** - * @var bool + * @var string */ - public $manualVerification; + public $origin; /** - * @var string|null + * @var string */ public $source; /** * @param int|null $shopId - * @param bool $manualVerification - * @param string|null $source + * @param string $origin + * @param string $source */ - public function __construct($shopId, $manualVerification = false, $source = null) + public function __construct($shopId, $origin = AccountsService::ORIGIN_INSTALL, $source = 'ps_accounts') { $this->shopId = $shopId; - $this->manualVerification = $manualVerification; + $this->origin = $origin; $this->source = $source; } } diff --git a/src/Account/Command/UpdateUserShopCommand.php b/src/Account/Command/UpdateUserShopCommand.php deleted file mode 100644 index c7824d32f..000000000 --- a/src/Account/Command/UpdateUserShopCommand.php +++ /dev/null @@ -1,36 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Account\Command; - -use PrestaShop\Module\PsAccounts\Account\Dto\UpdateShop; - -class UpdateUserShopCommand -{ - /** - * @var UpdateShop - */ - public $payload; - - public function __construct(UpdateShop $payload) - { - $this->payload = $payload; - } -} diff --git a/src/Account/Command/VerifyIdentitiesCommand.php b/src/Account/Command/VerifyIdentitiesCommand.php index 167696940..fdb9783c2 100644 --- a/src/Account/Command/VerifyIdentitiesCommand.php +++ b/src/Account/Command/VerifyIdentitiesCommand.php @@ -21,18 +21,27 @@ namespace PrestaShop\Module\PsAccounts\Account\Command; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; + class VerifyIdentitiesCommand { /** - * @var string|null + * @var string + */ + public $origin; + + /** + * @var string */ public $source; /** - * @param string|null $source + * @param string $origin + * @param string $source */ - public function __construct($source = null) + public function __construct($origin = AccountsService::ORIGIN_INSTALL, $source = 'ps_accounts') { + $this->origin = $origin; $this->source = $source; } } diff --git a/src/Account/Command/VerifyIdentityCommand.php b/src/Account/Command/VerifyIdentityCommand.php index 22c2174e8..f40414cd6 100644 --- a/src/Account/Command/VerifyIdentityCommand.php +++ b/src/Account/Command/VerifyIdentityCommand.php @@ -21,6 +21,8 @@ namespace PrestaShop\Module\PsAccounts\Account\Command; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; + class VerifyIdentityCommand { /** @@ -31,7 +33,12 @@ class VerifyIdentityCommand /** * @var bool */ - public $manualVerification; + public $force; + + /** + * @var string + */ + public $origin; /** * @var string|null @@ -40,13 +47,15 @@ class VerifyIdentityCommand /** * @param int|null $shopId - * @param bool $manualVerification - * @param string|null $source + * @param bool $force + * @param string $origin + * @param string $source */ - public function __construct($shopId, $manualVerification = false, $source = null) + public function __construct($shopId, $force = false, $origin = AccountsService::ORIGIN_INSTALL, $source = 'ps_accounts') { $this->shopId = $shopId; - $this->manualVerification = $manualVerification; + $this->force = $force; + $this->origin = $origin; $this->source = $source; } } diff --git a/src/Account/CommandHandler/CreateIdentitiesHandler.php b/src/Account/CommandHandler/CreateIdentitiesHandler.php index 91b87ed3c..5c7a2e738 100644 --- a/src/Account/CommandHandler/CreateIdentitiesHandler.php +++ b/src/Account/CommandHandler/CreateIdentitiesHandler.php @@ -37,7 +37,12 @@ public function handle(CreateIdentitiesCommand $command) { $this->handleMulti(function ($multiShopId) use ($command) { try { - $this->commandBus->handle(new CreateIdentityCommand($multiShopId, false, $command->source)); + $this->commandBus->handle(new CreateIdentityCommand( + $multiShopId, + false, + $command->origin, + $command->source + )); } catch (RefreshTokenException $e) { Logger::getInstance()->error($e->getMessage()); } catch (AccountsException $e) { diff --git a/src/Account/CommandHandler/CreateIdentityHandler.php b/src/Account/CommandHandler/CreateIdentityHandler.php index c92b2f9fa..c6e523c1a 100644 --- a/src/Account/CommandHandler/CreateIdentityHandler.php +++ b/src/Account/CommandHandler/CreateIdentityHandler.php @@ -90,12 +90,13 @@ public function __construct( */ public function handle(CreateIdentityCommand $command) { - if (!$this->isAlreadyCreated()) { + if ($command->renew || !$this->isAlreadyCreated()) { $shopId = $command->shopId ?: \Shop::getContextShopID(); $identityCreated = $this->accountsService->createShopIdentity( $this->shopProvider->getUrl($shopId), null, + $command->origin, $command->source ); $this->oAuth2Client->update( @@ -107,7 +108,8 @@ public function handle(CreateIdentityCommand $command) } $this->commandBus->handle(new VerifyIdentityCommand( $command->shopId, - $command->manualVerification, + false, + $command->origin, $command->source )); } diff --git a/src/Account/CommandHandler/DeleteUserShopHandler.php b/src/Account/CommandHandler/DeleteUserShopHandler.php deleted file mode 100644 index 3dd4c1532..000000000 --- a/src/Account/CommandHandler/DeleteUserShopHandler.php +++ /dev/null @@ -1,91 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; - -use PrestaShop\Module\PsAccounts\Account\Command\DeleteUserShopCommand; -use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; -use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; -use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; -use PrestaShop\Module\PsAccounts\Context\ShopContext; -use PrestaShop\Module\PsAccounts\Http\Client\Response; -use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; - -class DeleteUserShopHandler -{ - /** - * @var AccountsService - */ - private $accountsService; - - /** - * @var ShopContext - */ - private $shopContext; - - /** - * @var ShopSession - */ - private $shopSession; - - /** - * @var OwnerSession - */ - private $ownerSession; - - /** - * @param AccountsService $accountsService - * @param ShopContext $shopContext - * @param ShopSession $shopSession - * @param OwnerSession $ownerSession - */ - public function __construct( - AccountsService $accountsService, - ShopContext $shopContext, - ShopSession $shopSession, - OwnerSession $ownerSession - ) { - $this->accountsService = $accountsService; - $this->shopContext = $shopContext; - $this->shopSession = $shopSession; - $this->ownerSession = $ownerSession; - } - - /** - * @param DeleteUserShopCommand $command - * - * @return Response - * - * @throws RefreshTokenException - */ - public function handle(DeleteUserShopCommand $command) - { - return $this->shopContext->execInShopContext((int) $command->shopId, function () { - $ownerToken = $this->ownerSession->getValidToken(); - $shopToken = $this->shopSession->getValidToken(); - - return $this->accountsService->deleteUserShop( - $ownerToken->getUuid(), - $shopToken->getUuid(), - $ownerToken->getJwt() - ); - }); - } -} diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php index 6885eec5e..ef629c0ec 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentitiesV8Handler.php @@ -36,7 +36,11 @@ public function handle(MigrateOrCreateIdentitiesV8Command $command) { $this->handleMulti(function ($multiShopId) use ($command) { try { - $this->commandBus->handle(new MigrateOrCreateIdentityV8Command($multiShopId, false, $command->source)); + $this->commandBus->handle(new MigrateOrCreateIdentityV8Command( + $multiShopId, + $command->origin, + $command->source + )); } catch (Exception $e) { Logger::getInstance()->error($e->getMessage()); } diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php index d0b7299c9..3f664ec82 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php @@ -132,7 +132,8 @@ public function handle(MigrateOrCreateIdentityV8Command $command) $this->commandBus->handle(new CreateIdentityCommand( $command->shopId, - $command->manualVerification, + false, + $command->origin, $command->source )); diff --git a/src/Account/CommandHandler/UpdateUserShopHandler.php b/src/Account/CommandHandler/UpdateUserShopHandler.php deleted file mode 100644 index 92b2d730e..000000000 --- a/src/Account/CommandHandler/UpdateUserShopHandler.php +++ /dev/null @@ -1,92 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; - -use PrestaShop\Module\PsAccounts\Account\Command\UpdateUserShopCommand; -use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; -use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; -use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; -use PrestaShop\Module\PsAccounts\Context\ShopContext; -use PrestaShop\Module\PsAccounts\Http\Client\Response; -use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; - -class UpdateUserShopHandler -{ - /** - * @var AccountsService - */ - private $accountsService; - - /** - * @var ShopContext - */ - private $shopContext; - - /** - * @var ShopSession - */ - private $shopSession; - - /** - * @var OwnerSession - */ - private $ownerSession; - - /** - * @param AccountsService $accountsService - * @param ShopContext $shopContext - * @param ShopSession $shopSession - * @param OwnerSession $ownerSession - */ - public function __construct( - AccountsService $accountsService, - ShopContext $shopContext, - ShopSession $shopSession, - OwnerSession $ownerSession - ) { - $this->accountsService = $accountsService; - $this->shopContext = $shopContext; - $this->shopSession = $shopSession; - $this->ownerSession = $ownerSession; - } - - /** - * @param UpdateUserShopCommand $command - * - * @return Response - * - * @throws RefreshTokenException - */ - public function handle(UpdateUserShopCommand $command) - { - return $this->shopContext->execInShopContext((int) $command->payload->shopId, function () use ($command) { - $shopToken = $this->shopSession->getValidToken(); - $ownerToken = $this->ownerSession->getValidToken(); - - return $this->accountsService->updateUserShop( - $ownerToken->getUuid(), - $shopToken->getUuid(), - $ownerToken->getJwt(), - $command->payload - ); - }); - } -} diff --git a/src/Account/CommandHandler/VerifyIdentitiesHandler.php b/src/Account/CommandHandler/VerifyIdentitiesHandler.php index 885096c47..c33c9467d 100644 --- a/src/Account/CommandHandler/VerifyIdentitiesHandler.php +++ b/src/Account/CommandHandler/VerifyIdentitiesHandler.php @@ -38,7 +38,12 @@ public function handle(VerifyIdentitiesCommand $command) { $this->handleMulti(function ($multiShopId) use ($command) { try { - $this->commandBus->handle(new VerifyIdentityCommand($multiShopId, false, $command->source)); + $this->commandBus->handle(new VerifyIdentityCommand( + $multiShopId, + false, + $command->origin, + $command->source + )); } catch (RefreshTokenException $e) { Logger::getInstance()->error($e->getMessage()); } catch (AccountsException $e) { diff --git a/src/Account/CommandHandler/VerifyIdentityHandler.php b/src/Account/CommandHandler/VerifyIdentityHandler.php index 968165e91..c7c3e8114 100644 --- a/src/Account/CommandHandler/VerifyIdentityHandler.php +++ b/src/Account/CommandHandler/VerifyIdentityHandler.php @@ -91,7 +91,7 @@ public function handle(VerifyIdentityCommand $command) { $cachedStatus = $this->statusManager->getStatus(false, StatusManager::CACHE_TTL, $command->source); - if ($cachedStatus->isVerified) { + if (!$command->force && $cachedStatus->isVerified) { return; } @@ -102,7 +102,7 @@ public function handle(VerifyIdentityCommand $command) $this->shopSession->getValidToken(), $this->shopProvider->getUrl($shopId), $this->proofManager->generateProof(), - $command->manualVerification, + $command->origin, $command->source ); $this->statusManager->invalidateCache(); diff --git a/src/Hook/ActionAdminControllerInitBefore.php b/src/Hook/ActionAdminControllerInitBefore.php index a0aaffb41..ac9624f6f 100644 --- a/src/Hook/ActionAdminControllerInitBefore.php +++ b/src/Hook/ActionAdminControllerInitBefore.php @@ -20,83 +20,18 @@ namespace PrestaShop\Module\PsAccounts\Hook; -use AdminLoginPsAccountsController; -use Exception; -use PrestaShop\Module\PsAccounts\Log\Logger; -use PrestaShop\Module\PsAccounts\Service\AnalyticsService; -use PrestaShop\Module\PsAccounts\Service\PsAccountsService; -use Tools; - +/** + * @deprecated + */ class ActionAdminControllerInitBefore extends Hook { - /** - * @var PsAccountsService - */ - private $accountsService; - - /** - * @var AnalyticsService - */ - private $analytics; - - public function __construct(\Ps_accounts $module) - { - parent::__construct($module); - - $this->accountsService = $this->module->getService(PsAccountsService::class); - $this->analytics = $this->module->getService(AnalyticsService::class); - } - /** * @param array $params * - * @return void - * - * @throws Exception + * @return string */ public function execute(array $params = []) { -// $controller = $params['controller']; -// -// $this->module->getOauth2Middleware()->execute(); -// -// $className = preg_replace('/^.*\\\\/', '', get_class($controller)); -//// Logger::getInstance()->error('########################### ' . __CLASS__ . ' ' . $className); -// -// if ($className === 'AdminLoginController') { -// $local = Tools::getValue('mode') === AdminLoginPsAccountsController::PARAM_MODE_LOCAL || -// !$this->accountsService->getLoginActivated(); -// -// $this->trackLoginPage($local); -// -// if ($this->module->getShopContext()->isShop17() && !$local) { -//// /** @var \PrestaShop\Module\PsAccounts\Adapter\Link $link */ -//// $link = $this->module->getService(\PrestaShop\Module\PsAccounts\Adapter\Link::class); -//// Tools::redirectLink($link->getAdminLink('AdminLoginPsAccounts', false)); -// (new AdminLoginPsAccountsController())->run(); -// exit; -// } -// } - } - - /** - * @param bool $local - * - * @return void - * - * @throws Exception - */ - protected function trackLoginPage($local = false) - { - if ($this->module->isShopEdition()) { - $account = $this->accountsService->getEmployeeAccount(); - $userId = $account ? $account->getUid() : null; - - if (!$local) { - $this->analytics->pageAccountsBoLogin($userId); - } else { - $this->analytics->pageLocalBoLogin($userId); - } - } + return ''; } } diff --git a/src/Account/Command/DeleteUserShopCommand.php b/src/Hook/ActionAdminControllerSetMedia.php similarity index 78% rename from src/Account/Command/DeleteUserShopCommand.php rename to src/Hook/ActionAdminControllerSetMedia.php index 833668ca7..29c82003d 100644 --- a/src/Account/Command/DeleteUserShopCommand.php +++ b/src/Hook/ActionAdminControllerSetMedia.php @@ -18,20 +18,14 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -namespace PrestaShop\Module\PsAccounts\Account\Command; +namespace PrestaShop\Module\PsAccounts\Hook; -class DeleteUserShopCommand +class ActionAdminControllerSetMedia extends Hook { /** - * @var int + * @return void */ - public $shopId; - - /** - * @param int $shopId - */ - public function __construct($shopId) + public function execute(array $params = []) { - $this->shopId = $shopId; } } diff --git a/src/Hook/ActionObjectEmployeeDeleteAfter.php b/src/Hook/ActionObjectEmployeeDeleteAfter.php index cf5267438..2cb35cf85 100644 --- a/src/Hook/ActionObjectEmployeeDeleteAfter.php +++ b/src/Hook/ActionObjectEmployeeDeleteAfter.php @@ -33,18 +33,18 @@ class ActionObjectEmployeeDeleteAfter extends Hook */ public function execute(array $params = []) { - /** @var \Employee $employee */ - $employee = $params['object']; - - $repository = new EmployeeAccountRepository(); try { + /** @var \Employee $employee */ + $employee = $params['object']; + + $repository = new EmployeeAccountRepository(); + $employeeAccount = $repository->findByEmployeeId($employee->id); if ($employeeAccount) { $repository->delete($employeeAccount); } - } catch (\Throwable $e) { - /* @phpstan-ignore-next-line */ } catch (\Exception $e) { + } catch (\Throwable $e) { } } } diff --git a/src/Hook/ActionObjectShopDeleteBefore.php b/src/Hook/ActionObjectShopDeleteBefore.php index ec672429e..719de2af3 100644 --- a/src/Hook/ActionObjectShopDeleteBefore.php +++ b/src/Hook/ActionObjectShopDeleteBefore.php @@ -20,39 +20,18 @@ namespace PrestaShop\Module\PsAccounts\Hook; -use Exception; -use PrestaShop\Module\PsAccounts\Account\Command\DeleteUserShopCommand; - +/** + * @deprecated + */ class ActionObjectShopDeleteBefore extends Hook { /** * @param array $params * * @return bool - * - * @throws Exception */ public function execute(array $params = []) { - try { - $response = $this->commandBus->handle(new DeleteUserShopCommand($params['object']->id)); - - if (!$response) { - $this->module->getLogger()->error( - 'Error trying to DELETE shop : No $response object' - ); - } elseif (true !== $response['status']) { - $this->module->getLogger()->error( - 'Error trying to DELETE shop : ' . $response['httpCode'] . - ' ' . print_r($response['body']['message'], true) - ); - } - } catch (Exception $e) { - $this->module->getLogger()->error( - 'Error while trying to DELETE shop : ' . print_r($e->getMessage(), true) - ); - } - return true; } } diff --git a/src/Hook/ActionObjectShopUpdateAfter.php b/src/Hook/ActionObjectShopUpdateAfter.php index 232917274..b1943b472 100644 --- a/src/Hook/ActionObjectShopUpdateAfter.php +++ b/src/Hook/ActionObjectShopUpdateAfter.php @@ -20,26 +20,11 @@ namespace PrestaShop\Module\PsAccounts\Hook; -use PrestaShop\Module\PsAccounts\Account\Command\UpdateUserShopCommand; -use PrestaShop\Module\PsAccounts\Account\Dto\UpdateShop; -use PrestaShop\Module\PsAccounts\Adapter\Link; -use PrestaShop\Module\PsAccounts\Http\Client\Response; -use Ps_accounts; - +/** + * @deprecated + */ class ActionObjectShopUpdateAfter extends Hook { - /** - * @var Link - */ - private $link; - - public function __construct(Ps_accounts $module) - { - parent::__construct($module); - - $this->link = $this->module->getService(Link::class); - } - /** * @param array $params * @@ -47,40 +32,6 @@ public function __construct(Ps_accounts $module) */ public function execute(array $params = []) { - $this->updateUserShop($params['object']); - return true; } - - /** - * @param \Shop $shop - * - * @return void - */ - protected function updateUserShop(\Shop $shop) - { - try { - /** @var Response $response */ - $response = $this->commandBus->handle(new UpdateUserShopCommand(new UpdateShop([ - 'shopId' => (string) $shop->id, - 'name' => $shop->name, - 'domain' => 'http://' . $shop->domain, - 'sslDomain' => 'https://' . $shop->domain_ssl, - 'physicalUri' => $shop->physical_uri, - 'virtualUri' => $shop->virtual_uri, - 'boBaseUrl' => $this->link->fixAdminLink($this->link->getDashboardLink(), $shop), - ]))); - - if (!$response->isSuccessful) { - $this->module->getLogger()->error('Error trying to PATCH shop : ' . $response->statusCode . - ' ' . print_r(isset($response->body['message']) ? $response->body['message'] : '', true) - ); - } - } catch (\Throwable $e) { - $this->module->getLogger()->error('Error trying to PATCH shop: ' . $e->getMessage()); - /* @phpstan-ignore-next-line */ - } catch (\Exception $e) { - $this->module->getLogger()->error('Error trying to PATCH shop: ' . $e->getMessage()); - } - } } diff --git a/src/Hook/ActionObjectShopUrlUpdateAfter.php b/src/Hook/ActionObjectShopUrlUpdateAfter.php index 659b1865f..8f043d171 100644 --- a/src/Hook/ActionObjectShopUrlUpdateAfter.php +++ b/src/Hook/ActionObjectShopUrlUpdateAfter.php @@ -20,9 +20,9 @@ namespace PrestaShop\Module\PsAccounts\Hook; -use Cache; -use ShopUrl; - +/** + * @deprecated + */ class ActionObjectShopUrlUpdateAfter extends ActionObjectShopUpdateAfter { /** @@ -32,17 +32,6 @@ class ActionObjectShopUrlUpdateAfter extends ActionObjectShopUpdateAfter */ public function execute(array $params = []) { - /** @var ShopUrl $shopUrl */ - $shopUrl = $params['object']; - - if ($shopUrl->main) { - // Mandatory to get up to date urls - // Cache::clear(); - Cache::clean('Shop::setUrl_' . (int) $shopUrl->id); - - $this->updateUserShop(new \Shop($shopUrl->id_shop)); - } - return true; } } diff --git a/src/Hook/ActionShopAccountLinkAfter.php b/src/Hook/ActionShopAccountLinkAfter.php index a84b5c2bd..6bb5e3eac 100644 --- a/src/Hook/ActionShopAccountLinkAfter.php +++ b/src/Hook/ActionShopAccountLinkAfter.php @@ -20,23 +20,17 @@ namespace PrestaShop\Module\PsAccounts\Hook; -use PrestaShop\Module\PsAccounts\Service\PsAccountsService; - +/** + * @deprecated + */ class ActionShopAccountLinkAfter extends Hook { /** * @param array $params * * @return void - * - * @throws \Exception */ public function execute(array $params = []) { -// if ($this->module->isShopEdition()) { -// /** @var PsAccountsService $service */ -// $service = $this->module->getService(PsAccountsService::class); -// $service->enableLogin(true); -// } } } diff --git a/src/Hook/ActionShopAccountUnlinkAfter.php b/src/Hook/ActionShopAccountUnlinkAfter.php index 8754a8633..ccfe7dbcd 100644 --- a/src/Hook/ActionShopAccountUnlinkAfter.php +++ b/src/Hook/ActionShopAccountUnlinkAfter.php @@ -20,35 +20,17 @@ namespace PrestaShop\Module\PsAccounts\Hook; -use PrestaShop\Module\PsAccounts\Account\Session\Firebase; -use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; -use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Client; - +/** + * @deprecated + */ class ActionShopAccountUnlinkAfter extends Hook { /** * @param array $params * * @return void - * - * @throws \Exception */ public function execute(array $params = []) { - /** @var OAuth2Client $oauth2Client */ - $oauth2Client = $this->module->getService(OAuth2Client::class); - $oauth2Client->delete(); - - /** @var Firebase\ShopSession $shopSession */ - $shopSession = $this->module->getService(Firebase\ShopSession::class); - $shopSession->cleanup(); - - /** @var Firebase\OwnerSession $ownerSession */ - $ownerSession = $this->module->getService(Firebase\OwnerSession::class); - $ownerSession->cleanup(); - - /** @var ShopSession $session */ - $session = $this->module->getService(ShopSession::class); - $session->cleanup(); } } diff --git a/src/Hook/DisplayAccountUpdateWarning.php b/src/Hook/DisplayAccountUpdateWarning.php index 744af5c06..bade1b116 100644 --- a/src/Hook/DisplayAccountUpdateWarning.php +++ b/src/Hook/DisplayAccountUpdateWarning.php @@ -20,8 +20,9 @@ namespace PrestaShop\Module\PsAccounts\Hook; -use PrestaShop\Module\PsAccounts\Service\PsAccountsService; - +/** + * @deprecated + */ class DisplayAccountUpdateWarning extends Hook { /** @@ -31,30 +32,6 @@ class DisplayAccountUpdateWarning extends Hook */ public function execute(array $params = []) { - /** @var PsAccountsService $accountsService */ - $accountsService = $this->module->getService(PsAccountsService::class); - - if ($accountsService->isAccountLinked() && - !$this->module->getShopContext()->isMultishopActive()) { - $msg = $this->module->l( - 'This shop is linked to your PrestaShop account. ' . - 'Unlink your shop if you do not want to impact your live settings.', - 'ps_accounts' - ); - - return << -
- -
- -HTML; - } - return ''; } } diff --git a/src/Hook/DisplayAdminAfterHeader.php b/src/Hook/DisplayAdminAfterHeader.php deleted file mode 100644 index 62606df6b..000000000 --- a/src/Hook/DisplayAdminAfterHeader.php +++ /dev/null @@ -1,93 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Hook; - -use PrestaShop\Module\PsAccounts\Adapter\Link; - -class DisplayAdminAfterHeader extends Hook -{ - /** - * @return string - */ - public function execute(array $params = []) - { - try { - if ('ERROR' === $this->module->getParameter('ps_accounts.log_level')) { - return ''; - } - - $cloudShopId = $this->module->getCloudShopId(); - $verified = $this->module->getVerifiedStatus('ps_accounts'); - $verifiedMsg = $verified ? 'verified' : 'NOT verified'; - - /** @var Link $link */ - $link = $this->module->getService(Link::class); - $moduleLink = $link->getAdminLink('AdminModules', true, [], [ - 'configure' => 'ps_accounts', - ]); - $healthCheckLink = $link->getLink()->getModuleLink('ps_accounts', 'apiV2ShopHealthCheck'); - - $environment = $this->module->getParameter('ps_accounts.environment'); - - $alertLevel = $this->getAlertLevel($environment); - - return << -
- - PsAccount ({$environment}) | - - {$cloudShopId} ({$verifiedMsg}) | - Health Check -
- -HTML; - } catch (\Throwable $e) { - /* @phpstan-ignore-next-line */ - } catch (\Exception $e) { - } - - return ''; - } - - /** - * @param string $environment - * - * @return string - */ - private function getAlertLevel($environment) - { - $alertLevel = 'info'; - switch ($environment) { - case 'production': - $alertLevel = 'info'; - break; - case 'integration': - $alertLevel = 'warning'; - break; - case 'development': - $alertLevel = 'danger'; - break; - } - - return $alertLevel; - } -} diff --git a/src/Hook/DisplayBackOfficeHeader.php b/src/Hook/DisplayBackOfficeHeader.php new file mode 100644 index 000000000..1c30f50b8 --- /dev/null +++ b/src/Hook/DisplayBackOfficeHeader.php @@ -0,0 +1,60 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PsAccounts\Hook; + +use Exception; +use PrestaShop\Module\PsAccounts\Service\PsAccountsService; + +class DisplayBackOfficeHeader extends Hook +{ + /** + * @return string + * + * @throws Exception + */ + public function execute(array $params = []) + { + try { + /** @var PsAccountsService $psAccountsService */ + $psAccountsService = $this->module->getService(PsAccountsService::class); + + if (preg_match('/controller=AdminModules/', $_SERVER['REQUEST_URI']) && + preg_match('/configure=ps_accounts/', $_SERVER['REQUEST_URI']) || + preg_match('@modules/manage/action/configure/ps_accounts@', $_SERVER['REQUEST_URI']) + ) { + return ''; + } + +// if (!$psAccountsService->isShopIdentityCreated()) { +// return; +// } + + $this->module->getContext()->controller->addJs( + $this->module->getLocalPath() . 'views/js/notifications.js?' . + 'ctx=' . urlencode($psAccountsService->getAdminAjaxUrl() . '&action=getNotifications') + ); + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + + return ''; + } +} diff --git a/src/Hook/DisplayDashboardTop.php b/src/Hook/DisplayDashboardTop.php index 5cb6036cb..57e933c6e 100644 --- a/src/Hook/DisplayDashboardTop.php +++ b/src/Hook/DisplayDashboardTop.php @@ -20,121 +20,18 @@ namespace PrestaShop\Module\PsAccounts\Hook; -use PrestaShop\Module\PsAccounts\Context\ShopContext; -use PrestaShop\Module\PsAccounts\Provider\ShopProvider; -use PrestaShop\Module\PsAccounts\Service\PsAccountsService; - +/** + * @deprecated + */ class DisplayDashboardTop extends Hook { /** * @param array $params * - * @return mixed - */ - public function execute(array $params = []) - { - $shopContext = $this->module->getShopContext(); - - /** @var PsAccountsService $accountsService */ - $accountsService = $this->module->getService(PsAccountsService::class); - - $controller = isset($_GET['controller']) ? $_GET['controller'] : ''; - - if ('AdminShopUrl' === $controller) { - return $this->renderAdminShopUrlWarningIfLinked($shopContext, $accountsService); - } - - if ('AdminShop' === $controller) { - return $this->renderAdminShopWarningIfLinked($shopContext, $accountsService); - } - } - - /** - * @param ShopContext $shopContext - * @param PsAccountsService $accountsService - * * @return string */ - protected function renderAdminShopWarningIfLinked($shopContext, $accountsService) + public function execute(array $params = []) { - if (isset($_GET['addshop'])) { - return ''; - } - - if (isset($_GET['updateshop'])) { - return ''; - } - - /** @var ShopProvider $shopProvider */ - $shopProvider = $this->module->getService(ShopProvider::class); - - $shopsTree = $shopProvider->getShopsTree('ps_accounts'); - foreach ($shopsTree as $shopGroup) { - foreach ($shopGroup['shops'] as $shop) { - $isLink = $shopContext->execInShopContext($shop['id'], function () use ($accountsService) { - return $accountsService->isAccountLinked(); - }); - if ($isLink) { - $msg = $this->module->l( - 'Some shops are linked to your PrestaShop account. ' . - 'Delete these shops will impact your live settings.', - 'Modules.ps_accounts' - ); - - return << -
- -
- -HTML; - } - } - } - return ''; } - - /** - * @param ShopContext $shopContext - * @param PsAccountsService $accountsService - * - * @return string - */ - protected function renderAdminShopUrlWarningIfLinked($shopContext, $accountsService) - { - if (!isset($_GET['updateshop_url'])) { - return ''; - } - - $shopId = $shopContext->getShopIdFromShopUrlId((int) $_GET['id_shop_url']); - - return $shopContext->execInShopContext($shopId, function () use ($accountsService) { - if ($accountsService->isAccountLinked()) { - $msg = $this->module->l( - 'This shop is linked to your PrestaShop account. ' . - 'Unlink your shop if you do not want to impact your live settings.', - 'ps_accounts' - ); - - return << -
- -
- -HTML; - } - - return ''; - }); - } } diff --git a/src/Provider/ShopProvider.php b/src/Provider/ShopProvider.php index 406c6012f..4e73a3f8a 100644 --- a/src/Provider/ShopProvider.php +++ b/src/Provider/ShopProvider.php @@ -357,19 +357,35 @@ function () use (&$shops, $shopData, $source, $refresh) { 'frontendUrl' => $shopUrl->getFrontendUrl(), ]); } - $identifyPointOfContactUrl = $this->oAuth2Service->getOAuth2Client()->getRedirectUri([ - 'action' => 'identifyPointOfContact', - 'source' => $source, - ]); - $shops[] = [ 'id' => (int) $shopData['id_shop'], 'name' => $shopData['name'], 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), 'frontendUrl' => $shopUrl->getFrontendUrl(), - 'identifyPointOfContactUrl' => $identifyPointOfContactUrl, 'shopStatus' => $shopStatus->toArray(), - 'fallbackCreateIdentityUrl' => $this->link->getAdminLink('AdminAjaxV2PsAccounts', false, [], ['ajax' => 1, 'action' => 'fallbackCreateIdentity', 'shop_id' => $shopData['id_shop'], 'source' => $source]), + 'identifyPointOfContactUrl' => $this->oAuth2Service->getOAuth2Client()->getRedirectUri([ + 'action' => 'identifyPointOfContact', + 'source' => $source, + ]), + // FIXME: rename to "createIdentityUrl" + 'fallbackCreateIdentityUrl' => $this->link->getAdminLink('AdminAjaxV2PsAccounts', false, [], [ + 'ajax' => 1, + 'action' => 'fallbackCreateIdentity', + 'shop_id' => $shopData['id_shop'], + 'source' => $source, + ]), + 'renewIdentityUrl' => $this->link->getAdminLink('AdminAjaxV2PsAccounts', false, [], [ + 'ajax' => 1, + 'action' => 'renewIdentity', + 'shop_id' => $shopData['id_shop'], + 'source' => $source, + ]), + 'updateIdentityUrl' => $this->link->getAdminLink('AdminAjaxV2PsAccounts', false, [], [ + 'ajax' => 1, + 'action' => 'updateIdentity', + 'shop_id' => $shopData['id_shop'], + 'source' => $source, + ]), ]; } ); diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index ed1380b6c..8a382333d 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -20,7 +20,6 @@ namespace PrestaShop\Module\PsAccounts\Service\Accounts; -use PrestaShop\Module\PsAccounts\Account\Dto\UpdateShop; use PrestaShop\Module\PsAccounts\Account\ShopUrl; use PrestaShop\Module\PsAccounts\Http\Client\ClientConfig; use PrestaShop\Module\PsAccounts\Http\Client\Curl\Client; @@ -35,7 +34,9 @@ class AccountsService { + // Common headers const HEADER_AUTHORIZATION = 'Authorization'; + const HEADER_ACTION_ORIGIN = 'X-Action-Origin'; const HEADER_MODULE_SOURCE = 'X-Module-Source'; const HEADER_MODULE_VERSION = 'X-Module-Version'; const HEADER_PRESTASHOP_VERSION = 'X-Prestashop-Version'; @@ -43,6 +44,14 @@ class AccountsService const HEADER_REQUEST_ID = 'X-Request-ID'; const HEADER_SHOP_ID = 'X-Shop-Id'; + // tracking origin + const ORIGIN_INSTALL = 'install'; + const ORIGIN_FALLBACK = 'fallback'; + const ORIGIN_MISMATCH_CREATE = 'mismatch_create'; + const ORIGIN_MISMATCH_UPDATE = 'mismatch_update'; + const ORIGIN_RESET = 'reset'; + const ORIGIN_UPGRADE = 'upgrade'; + /** * @var Client */ @@ -160,49 +169,6 @@ public function refreshShopToken($refreshToken, $cloudShopId) return new LegacyFirebaseToken($response->body); } - /** - * @param string $ownerUid - * @param string $cloudShopId - * @param string $ownerToken - * - * @return Response - */ - public function deleteUserShop($ownerUid, $cloudShopId, $ownerToken) - { - return $this->getClient()->delete( - 'v1/user/' . $ownerUid . '/shop/' . $cloudShopId, - [ - Request::HEADERS => $this->getHeaders([ - self::HEADER_AUTHORIZATION => 'Bearer ' . $ownerToken, - self::HEADER_SHOP_ID => $cloudShopId, - ]), - ] - ); - } - - /** - * @param string $ownerUid - * @param string $cloudShopId - * @param string $ownerToken - * @param UpdateShop $shop - * - * @return Response - */ - public function updateUserShop($ownerUid, $cloudShopId, $ownerToken, UpdateShop $shop) - { - return $this->getClient()->patch( - 'v1/user/' . $ownerUid . '/shop/' . $cloudShopId, - [ - Request::HEADERS => $this->getHeaders([ - // FIXME: use shop access token instead - self::HEADER_AUTHORIZATION => 'Bearer ' . $ownerToken, - self::HEADER_SHOP_ID => $cloudShopId, - ]), - Request::JSON => $shop->jsonSerialize(), - ] - ); - } - /** * @param string $idToken * @@ -234,18 +200,24 @@ public function healthCheck() /** * @param ShopUrl $shopUrl * @param string|null $proof - * @param string|null $source + * @param string $origin UX origin triggering call + * @param string $source source module triggering call * * @return IdentityCreated * * @throws AccountsException */ - public function createShopIdentity(ShopUrl $shopUrl, $proof = null, $source = 'ps_accounts') - { + public function createShopIdentity( + ShopUrl $shopUrl, + $proof = null, + $origin = self::ORIGIN_INSTALL, + $source = 'ps_accounts' + ) { $response = $this->getClient()->post( '/v1/shop-identities', [ Request::HEADERS => $this->getHeaders([ + self::HEADER_ACTION_ORIGIN => $origin, self::HEADER_MODULE_SOURCE => $source, ]), Request::JSON => array_merge( @@ -271,8 +243,8 @@ public function createShopIdentity(ShopUrl $shopUrl, $proof = null, $source = 'p * @param string $shopToken * @param ShopUrl $shopUrl * @param string $proof - * @param bool $manualVerification - * @param string|null $source + * @param string $origin UX origin triggering call + * @param string $source source module triggering call * * @return void * @@ -283,7 +255,7 @@ public function verifyShopIdentity( $shopToken, ShopUrl $shopUrl, $proof, - $manualVerification = false, + $origin = self::ORIGIN_INSTALL, $source = 'ps_accounts' ) { $response = $this->getClient()->post( @@ -292,6 +264,7 @@ public function verifyShopIdentity( Request::HEADERS => $this->getHeaders([ self::HEADER_AUTHORIZATION => 'Bearer ' . $shopToken, self::HEADER_SHOP_ID => $cloudShopId, + self::HEADER_ACTION_ORIGIN => $origin, self::HEADER_MODULE_SOURCE => $source, ]), Request::JSON => [ @@ -299,7 +272,6 @@ public function verifyShopIdentity( 'frontendUrl' => $shopUrl->getFrontendUrl(), 'multiShopId' => $shopUrl->getMultiShopId(), 'proof' => $proof, - 'manualVerification' => $manualVerification, ], ] ); diff --git a/src/ServiceProvider/CommandProvider.php b/src/ServiceProvider/CommandProvider.php index 4cf4c6872..a8fb7a658 100644 --- a/src/ServiceProvider/CommandProvider.php +++ b/src/ServiceProvider/CommandProvider.php @@ -22,11 +22,9 @@ use PrestaShop\Module\PsAccounts\Account\CommandHandler\CreateIdentitiesHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\CreateIdentityHandler; -use PrestaShop\Module\PsAccounts\Account\CommandHandler\DeleteUserShopHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\IdentifyContactHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\MigrateOrCreateIdentitiesV8Handler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\MigrateOrCreateIdentityV8Handler; -use PrestaShop\Module\PsAccounts\Account\CommandHandler\UpdateUserShopHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\VerifyIdentitiesHandler; use PrestaShop\Module\PsAccounts\Account\CommandHandler\VerifyIdentityHandler; use PrestaShop\Module\PsAccounts\Account\ProofManager; @@ -47,22 +45,6 @@ class CommandProvider implements IServiceProvider { public function provide(ServiceContainer $container) { - $container->registerProvider(DeleteUserShopHandler::class, static function () use ($container) { - return new DeleteUserShopHandler( - $container->get(AccountsService::class), - $container->get(ShopContext::class), - $container->get(Session\Firebase\ShopSession::class), - $container->get(Session\Firebase\OwnerSession::class) - ); - }); - $container->registerProvider(UpdateUserShopHandler::class, static function () use ($container) { - return new UpdateUserShopHandler( - $container->get(AccountsService::class), - $container->get(ShopContext::class), - $container->get(Session\Firebase\ShopSession::class), - $container->get(Session\Firebase\OwnerSession::class) - ); - }); $container->registerProvider(CreateIdentityHandler::class, static function () use ($container) { return new CreateIdentityHandler( $container->get(AccountsService::class), diff --git a/tests/src/Unit/Hook/ActionObjectShopUpdateAfterTest.php b/tests/src/Unit/Hook/ActionObjectShopUpdateAfterTest.php deleted file mode 100644 index c066cd5ca..000000000 --- a/tests/src/Unit/Hook/ActionObjectShopUpdateAfterTest.php +++ /dev/null @@ -1,199 +0,0 @@ -makeJwtToken(new \DateTimeImmutable('+1 hour'), [ - 'sub' => $this->faker->uuid, - ])); - $ownerToken = new Token((string)$this->makeJwtToken(new \DateTimeImmutable('+1 hour'), [ - 'sub' => $this->faker->uuid - ])); - $updateUserShopResponse = $this->createResponse([ - 'message' => 'all good !', - ], 200, true); - - $this->initResponse($params, $updateUserShopResponse); - $this->initTokens($shopToken, $ownerToken); - - $newName = str_split($this->faker->slug(), 64)[0]; - - $shop = new \Shop(1); - $shop->name = $newName; - $shop->update(); - - $this->module->getLogger()->info(json_encode($params)); - - // FIXME: test data exhaustively - $this->assertEquals($shop->id, $params->shop->shopId); - $this->assertEquals($newName, $params->shop->name); - $this->assertEquals('http://' . $shop->domain, $params->shop->domain); - $this->assertEquals('https://' . $shop->domain_ssl, $params->shop->sslDomain); - $this->assertEquals((string) $ownerToken, $params->ownerToken); - $this->assertEquals($shopToken->getJwt()->claims()->get('sub'), $params->shopUid); - $this->assertEquals($ownerToken->getJwt()->claims()->get('sub'), $params->ownerUid); - } - - /** - * @test - */ - public function itShouldAttemptToUpdateShopOnUrlUpdate() - { - $shopUrls = \ShopUrl::getShopUrls(1); - - /** @var \ShopUrl $shopUrl */ - $shopUrl = $shopUrls->getFirst(); - - /** @var Params $params */ - $params = null; - - $shopToken = new Token((string)$this->makeJwtToken(new \DateTimeImmutable('+1 hour'), [ - 'sub' => $this->faker->uuid, - ])); - $ownerToken = new Token((string)$this->makeJwtToken(new \DateTimeImmutable('+1 hour'), [ - 'sub' => $this->faker->uuid - ])); - $updateUserShopResponse = $this->createResponse([ - 'message' => 'all good !', - ], 200, true); - - $this->initResponse($params, $updateUserShopResponse); - $this->initTokens($shopToken, $ownerToken); - - $domain = $this->faker->domainName; - $domainSsl = $this->faker->domainName; - $physicalUri = $this->faker->slug(1); - $virtualUri = $this->faker->slug(1); - $dashboardLink = $this->link->getDashboardLink(); - $trailingSlash = $this->link->getTrailingSlash($dashboardLink); - $index = $this->link->getScript($dashboardLink); - - echo $dashboardLink . PHP_EOL; - - $shopUrl->domain = $domain; - $shopUrl->domain_ssl = $domainSsl; - $shopUrl->physical_uri = $physicalUri; - $shopUrl->virtual_uri = $virtualUri; - - $shopUrl->update(); - - #\Cache::clear(); - #\Cache::clean('Shop::setUrl_' . (int) $shopUrl->id); - #$shopUrl->clearCache(); - $shop = new \Shop(1); - - $this->module->getLogger()->info(json_encode($params)); - - // FIXME: test data exhaustively - $this->assertEquals($shop->id, $params->shop->shopId); - $this->assertEquals($shop->name, $params->shop->name); - - $this->assertEquals('/' . $physicalUri . '/', $params->shop->physicalUri); - $this->assertEquals($virtualUri . '/', $params->shop->virtualUri); - - $this->assertEquals('http://' . $domain, $params->shop->domain); - $this->assertEquals('https://' . $domainSsl, $params->shop->sslDomain); - - $this->assertEquals((string) $ownerToken, $params->ownerToken); - $this->assertEquals($shopToken->getJwt()->claims()->get('sub'), $params->shopUid); - $this->assertEquals($ownerToken->getJwt()->claims()->get('sub'), $params->ownerUid); - - $parsedBoBaseUrl = parse_url($params->shop->boBaseUrl); - $this->assertEquals($domain, $parsedBoBaseUrl['host']); - $this->assertEquals( - $this->link->cleanSlashes( - '/' . $physicalUri . _PS_ADMIN_DIR_ . ($index ? '/' . $index : '/') . $trailingSlash - ), - $parsedBoBaseUrl['path'] - ); - } - - /** - * @param Params|null $params - * @param Response $response - * - * @return void - * @throws \ReflectionException - */ - private function initResponse(&$params, Response $response) - { - /** @var AccountsService&MockObject $accountsClient */ - $accountsClient = $this->createMock(AccountsService::class); - - $accountsClient->expects($this->once())->method('updateUserShop')->willReturnCallback(function ( - $ownerUid, $shopUid, $ownerToken, UpdateShop $shop - ) use (&$params, $response) { - $params = (object)[ - 'ownerUid' => $ownerUid, - 'shopUid' => $shopUid, - 'ownerToken' => (string)$ownerToken, - 'shop' => $shop, - ]; - return $response; - }); - - $this->replaceProperty($this->updateUserShopHandler, 'accountsService', $accountsClient); - } - - /** - * @param string $shopToken - * @param string $ownerToken - * - * @return void - */ - private function initTokens($shopToken, $ownerToken) - { - $shopSession = $this->createMock(Firebase\ShopSession::class); - $shopSession->method('getValidToken')->willReturn($shopToken); - $this->replaceProperty($this->updateUserShopHandler, 'shopSession', $shopSession); - - $ownerSession = $this->createMock(Firebase\OwnerSession::class); - $ownerSession->method('getValidToken')->willReturn($ownerToken); - $this->replaceProperty($this->updateUserShopHandler, 'ownerSession', $ownerSession); - } -} diff --git a/translations/en.php b/translations/en.php index 5ede6ffb6..3b14548a2 100644 --- a/translations/en.php +++ b/translations/en.php @@ -20,27 +20,25 @@ global $_MODULE; $_MODULE = []; $_MODULE['<{ps_accounts}prestashop>ps_accounts_40047b296fb8a4952e0512c7184e1ca3'] = 'PrestaShop Account'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_248ed4a0f71472c36d19bb0f1bcb68a0'] = 'Link your store to your PrestaShop account to activate and manage your subscriptions in your back office. Do not uninstall this module if you have a current subscription.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_ceeadaf870d9eaa81cf1fbe32614af64'] = 'This action will prevent immediately your PrestaShop services and Community services from working as they are using PrestaShop Accounts module for authentication.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_01a84505e1f42ba1b80ffed4f9f21221'] = 'This shop is linked to your PrestaShop account. Unlink your shop if you do not want to impact your live settings.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_d82864f800210969f401370615e30568'] = 'Some shops are linked to your PrestaShop account. Delete these shops will impact your live settings.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_aa8c92c02e922a0fff679d9f66fce2d0'] = 'Link your store to your PrestaShop account to activate and manage your subscriptions in your \' . \'back office. Do not uninstall this module if you have a current subscription.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_05eeaea4d4c380b44bbcca3b1341f811'] = 'This action will prevent immediately your PrestaShop services and Community services from \' . \'working as they are using PrestaShop Accounts module for authentication.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a6589b17d51617df13a46421dc86e004'] = 'Action required: confirm your store URL'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_950c32d1a4f7f717bb4b738cbbc54fe2'] = 'We\'ve noticed that your store\'s URL no longer matches the one registered in your PrestaShop Account. For your services to function properly, you must either confirm this change or create a new identity for your store.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'Current store URL'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'URL registered in PrestaShop Account'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Review settings'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_dc3fd488f03d423a04da27ce66274c1b'] = 'Warning!'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a1183bc21e7a9a0dc3410b1e88497cce'] = 'PrestaShop Account module wasn\'t upgraded properly.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_790360932d7b6d0f61cbd2611c9303b1'] = 'Please reset the module'; +$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'An error occured during login, please contact PrestaShop support'; +$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Welcome,'; +$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Access your back office to manage your store.'; +$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Go to the back office'; +$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Connect with another method'; +$_MODULE['<{ps_accounts}prestashop>displaybackofficeemployeemenu_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Manage your PrestaShop account'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_f4f70727dc34561dfde1a3c529b6205c'] = 'Settings'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Help'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_b7ca62f12bc3b8e0d6422c028473a265'] = 'Your PrestaShop account'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_53c4c7fe12fffd658daacfa5b41eb495'] = 'One account to manage all your PrestaShop stores'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_655aa4af96bbb20cf78e14b560affb38'] = 'Create your PrestaShop account or login to your existing account'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_a60d78ec73c0c133ce16b10419a3fdf6'] = 'Link your store to your account'; -$_MODULE['<{ps_accounts}prestashop>login_23feb5280da1e3a86dc0fcc048f1a58d'] = 'An easy-to-use back office'; -$_MODULE['<{ps_accounts}prestashop>login_e661ffe463ad9b9bcea4c49d744058e7'] = 'Manage your entire business in one place: product catalog, orders, payments, deliveries and much more.'; -$_MODULE['<{ps_accounts}prestashop>login_8ea4efcd82809db344ff0d7a76695448'] = 'All the essentials for your business'; -$_MODULE['<{ps_accounts}prestashop>login_f27ec0e8d78513e2e60830c62a3a13b8'] = 'Marketing, payment and performance analysis: the PrestaShop Essentials suite includes all the features you need to make your store successful.'; -$_MODULE['<{ps_accounts}prestashop>login_f2d32f40a4060af2595051d0ab0278cb'] = 'A 100% customizable solution'; -$_MODULE['<{ps_accounts}prestashop>login_9b4e2d0b927094565d6ce673644868a4'] = 'PrestaShop accompanies your growth. Find our modules and those of our partners on PrestaShop Addons Marketplace to customize and develop your store'; -$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Welcome,'; -$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Access your back office to manage your store.'; -$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Go to the back office'; -$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Connect with another method'; -$_MODULE['<{ps_accounts}prestashop>login_729f27762821b8ec88f1a656be7dff72'] = 'You need to activate your account first by clicking the link in the email. If you need to receive a new activation link,[1]please click here[/1]'; -$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'An error occured during login, please contact PrestaShop support'; -$_MODULE['<{ps_accounts}prestashop>login_fd359ea79740b865e45c2bf6fe5d3ab1'] = 'You cannot access the back office with this account. Try another account or contact your administrator.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Manage your PrestaShop account'; diff --git a/upgrade/upgrade-8.0.0.php b/upgrade/upgrade-8.0.0.php index f5a47e958..f4b997643 100644 --- a/upgrade/upgrade-8.0.0.php +++ b/upgrade/upgrade-8.0.0.php @@ -3,6 +3,7 @@ use PrestaShop\Module\PsAccounts\Account\Command\MigrateOrCreateIdentitiesV8Command; use PrestaShop\Module\PsAccounts\Cqrs\CommandBus; use PrestaShop\Module\PsAccounts\Log\Logger; +use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; /** * @param Ps_accounts $module @@ -17,11 +18,14 @@ function upgrade_module_8_0_0($module) require __DIR__ . '/../src/enforce_autoload.php'; try { - $module->registerHook($module->getHooksToRegister()); - $module->unregisterHook('displayBackOfficeHeader'); + $module->unregisterHook('actionObjectShopDeleteBefore'); + $module->unregisterHook('actionObjectShopUpdateAfter'); + $module->unregisterHook('actionObjectShopUrlUpdateAfter'); + $module->unregisterHook('actionShopAccountLinkAfter'); + $module->unregisterHook('actionShopAccountUnlinkAfter'); + $module->unregisterHook('displayAccountUpdateWarning'); - $installer = new PrestaShop\Module\PsAccounts\Module\Install($module, Db::getInstance()); - $installer->installInMenu(); + $module->registerHook($module->getHooksToRegister()); $tabId = \Tab::getIdFromClassName('AdminDebugPsAccounts'); if ($tabId) { @@ -29,10 +33,16 @@ function upgrade_module_8_0_0($module) $tab->delete(); } + $installer = new PrestaShop\Module\PsAccounts\Module\Install($module, Db::getInstance()); + $installer->installInMenu(); + /** @var CommandBus $commandBus */ $commandBus = $module->getService(CommandBus::class); - $commandBus->handle(new MigrateOrCreateIdentitiesV8Command('ps_accounts')); + $commandBus->handle(new MigrateOrCreateIdentitiesV8Command( + 'ps_accounts', + AccountsService::ORIGIN_UPGRADE + )); } catch (\Exception $e) { Logger::getInstance()->error('error during upgrade : ' . $e); } catch (\Throwable $e) { diff --git a/views/PrestaShop/Admin/Configure/ShopParameters/TrafficSeo/Meta/Blocks/shop_urls_configuration.html.twig b/views/PrestaShop/Admin/Configure/ShopParameters/TrafficSeo/Meta/Blocks/shop_urls_configuration.html.twig deleted file mode 100644 index 48369a48f..000000000 --- a/views/PrestaShop/Admin/Configure/ShopParameters/TrafficSeo/Meta/Blocks/shop_urls_configuration.html.twig +++ /dev/null @@ -1,25 +0,0 @@ -{#** - * Copyright since 2007 PrestaShop SA and Contributors - * PrestaShop is an International Registered Trademark & Property of PrestaShop SA - * - * NOTICE OF LICENSE - * - * This source file is subject to the Academic Free License version 3.0 - * that is bundled with this package in the file LICENSE.md. - * It is also available through the world-wide-web at this URL: - * https://opensource.org/licenses/AFL-3.0 - * If you did not receive a copy of the license and are unable to - * obtain it through the world-wide-web, please send an email - * to license@prestashop.com so we can send you a copy immediately. - * - * @author PrestaShop SA and Contributors - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - *#} - -{#** % extends 'PrestaShopBundle:Admin/Configure/ShopParameters/TrafficSeo/Meta/Blocks:shop_urls_configuration.html.twig' % *#} -{% extends '@!PrestaShop/Admin/Configure/ShopParameters/TrafficSeo/Meta/Blocks/shop_urls_configuration.html.twig' %} -{% block shop_urls_configuration %} - {{ renderhook('displayAccountUpdateWarning') }} - {{ parent() }} -{% endblock %} diff --git a/views/templates/admin/debug.tpl b/views/templates/admin/debug.tpl deleted file mode 100644 index 1672daa31..000000000 --- a/views/templates/admin/debug.tpl +++ /dev/null @@ -1,75 +0,0 @@ -{** - * Copyright since 2007 PrestaShop SA and Contributors - * PrestaShop is an International Registered Trademark & Property of PrestaShop SA - * - * NOTICE OF LICENSE - * - * This source file is subject to the Academic Free License version 3.0 - * that is bundled with this package in the file LICENSE.md. - * It is also available through the world-wide-web at this URL: - * https://opensource.org/licenses/AFL-3.0 - * If you did not receive a copy of the license and are unable to - * obtain it through the world-wide-web, please send an email - * to license@prestashop.com so we can send you a copy immediately. - * - * @author PrestaShop SA and Contributors - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - *} -
-

Shop & module information

-
    -
  • Shop ID : {$config.shopId}
  • -
  • Module version : {$config.moduleVersion}
  • -
  • Prestashop version : {$config.psVersion}
  • -
  • PHP version : {$config.phpVersion}
  • -
  • Shop UID : {$config.shopUuidV4}
  • -
  • Firebase email : {$config.firebase_email}
  • -
  • Is Firebase email verified : {$config.firebase_email_is_verified}
  • -
  • Firebase ID token : {$config.firebase_id_token}
  • -
  • Firebase refresh token : {$config.firebase_refresh_token}
  • -
  • Shop Linked : {if $config.isShopLinked}YES{else}NO{/if}
  • -
-
- - -
-
-
- - - - From 196aadbf40ccacad3ebf681968d141d872055d3b Mon Sep 17 00:00:00 2001 From: Guillaume Lepoetre Date: Thu, 4 Sep 2025 14:30:24 +0200 Subject: [PATCH 37/46] feat: add isVerified status management in CreateIdentityHandler and StatusManager --- .../CommandHandler/CreateIdentityHandler.php | 2 ++ src/Account/StatusManager.php | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/Account/CommandHandler/CreateIdentityHandler.php b/src/Account/CommandHandler/CreateIdentityHandler.php index c6e523c1a..e8dd81f70 100644 --- a/src/Account/CommandHandler/CreateIdentityHandler.php +++ b/src/Account/CommandHandler/CreateIdentityHandler.php @@ -1,4 +1,5 @@ clientSecret ); $this->statusManager->setCloudShopId($identityCreated->cloudShopId); + $this->statusManager->setIsVerified(false); $this->statusManager->invalidateCache(); } $this->commandBus->handle(new VerifyIdentityCommand( diff --git a/src/Account/StatusManager.php b/src/Account/StatusManager.php index 60e3aa836..302699a69 100644 --- a/src/Account/StatusManager.php +++ b/src/Account/StatusManager.php @@ -274,6 +274,20 @@ public function setPointOfContactEmail($pointOfContactEmail) ])); } + /** + * @param bool $isVerified + * + * @return void + */ + public function setIsVerified($isVerified) + { + $this->upsetCachedStatus(new CachedShopStatus([ + 'shopStatus' => [ + 'isVerified' => (bool) $isVerified, + ], + ])); + } + /** * @return CachedShopStatus * From a0c2ad536281d1bbe9e7ef00c6f737edacda116c Mon Sep 17 00:00:00 2001 From: Antoine Date: Thu, 4 Sep 2025 17:23:10 +0200 Subject: [PATCH 38/46] [ACCOUNT-3070] Add store name (#546) * feat: add store name * feat: unit tests for name property & missing test for renew identity --------- Co-authored-by: hschoenenberger --- .../CommandHandler/CreateIdentityHandler.php | 1 + .../MigrateOrCreateIdentityV8Handler.php | 1 + .../CommandHandler/VerifyIdentityHandler.php | 1 + src/Provider/ShopProvider.php | 12 +++++ src/Service/Accounts/AccountsService.php | 10 ++++- .../CreateIdentityHandlerTest.php | 45 +++++++++++++++++++ .../MigrateOrCreateIdentityV8HandlerTest.php | 3 ++ .../VerifyIdentityHandlerTest.php | 1 + 8 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/Account/CommandHandler/CreateIdentityHandler.php b/src/Account/CommandHandler/CreateIdentityHandler.php index e8dd81f70..44d9d58ac 100644 --- a/src/Account/CommandHandler/CreateIdentityHandler.php +++ b/src/Account/CommandHandler/CreateIdentityHandler.php @@ -96,6 +96,7 @@ public function handle(CreateIdentityCommand $command) $identityCreated = $this->accountsService->createShopIdentity( $this->shopProvider->getUrl($shopId), + $this->shopProvider->getName($shopId), null, $command->origin, $command->source diff --git a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php index 3f664ec82..c297d4883 100644 --- a/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php +++ b/src/Account/CommandHandler/MigrateOrCreateIdentityV8Handler.php @@ -153,6 +153,7 @@ public function handle(MigrateOrCreateIdentityV8Command $command) $shopUuid, $token, $this->shopProvider->getUrl($shopId), + $this->shopProvider->getName($shopId), $fromVersion, $this->proofManager->generateProof(), $command->source diff --git a/src/Account/CommandHandler/VerifyIdentityHandler.php b/src/Account/CommandHandler/VerifyIdentityHandler.php index c7c3e8114..e2fd1c843 100644 --- a/src/Account/CommandHandler/VerifyIdentityHandler.php +++ b/src/Account/CommandHandler/VerifyIdentityHandler.php @@ -101,6 +101,7 @@ public function handle(VerifyIdentityCommand $command) $this->statusManager->getCloudShopId(), $this->shopSession->getValidToken(), $this->shopProvider->getUrl($shopId), + $this->shopProvider->getName($shopId), $this->proofManager->generateProof(), $command->origin, $command->source diff --git a/src/Provider/ShopProvider.php b/src/Provider/ShopProvider.php index 4e73a3f8a..1badef27b 100644 --- a/src/Provider/ShopProvider.php +++ b/src/Provider/ShopProvider.php @@ -323,6 +323,18 @@ public function getUrl($shopId) return new ShopUrl($backOfficeUrl, $frontendUrl, $shopId); } + /** + * @param int $shopId + * + * @return string + */ + public function getName($shopId) + { + $shop = (array) \Shop::getShop($shopId); + + return $shop['name']; + } + /** * @param string|null $source * @param int $contextType diff --git a/src/Service/Accounts/AccountsService.php b/src/Service/Accounts/AccountsService.php index 8a382333d..474ac76aa 100644 --- a/src/Service/Accounts/AccountsService.php +++ b/src/Service/Accounts/AccountsService.php @@ -199,6 +199,7 @@ public function healthCheck() /** * @param ShopUrl $shopUrl + * @param string $shopName * @param string|null $proof * @param string $origin UX origin triggering call * @param string $source source module triggering call @@ -209,6 +210,7 @@ public function healthCheck() */ public function createShopIdentity( ShopUrl $shopUrl, + $shopName, $proof = null, $origin = self::ORIGIN_INSTALL, $source = 'ps_accounts' @@ -225,6 +227,7 @@ public function createShopIdentity( 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), 'frontendUrl' => $shopUrl->getFrontendUrl(), 'multiShopId' => $shopUrl->getMultiShopId(), + 'name' => $shopName, ], $proof ? ['proof' => $proof] : [] ), @@ -242,6 +245,7 @@ public function createShopIdentity( * @param string $cloudShopId * @param string $shopToken * @param ShopUrl $shopUrl + * @param string $shopName * @param string $proof * @param string $origin UX origin triggering call * @param string $source source module triggering call @@ -254,6 +258,7 @@ public function verifyShopIdentity( $cloudShopId, $shopToken, ShopUrl $shopUrl, + $shopName, $proof, $origin = self::ORIGIN_INSTALL, $source = 'ps_accounts' @@ -271,6 +276,7 @@ public function verifyShopIdentity( 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), 'frontendUrl' => $shopUrl->getFrontendUrl(), 'multiShopId' => $shopUrl->getMultiShopId(), + 'name' => $shopName, 'proof' => $proof, ], ] @@ -345,6 +351,7 @@ public function setPointOfContact($cloudShopId, $shopToken, $userToken, $source * @param string $cloudShopId * @param string $shopToken * @param ShopUrl $shopUrl + * @param string $shopName * @param string $fromVersion * @param string|null $proof * @param string|null $source @@ -353,7 +360,7 @@ public function setPointOfContact($cloudShopId, $shopToken, $userToken, $source * * @throws AccountsException */ - public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $fromVersion, $proof = null, $source = 'ps_accounts') + public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, $shopName, $fromVersion, $proof = null, $source = 'ps_accounts') { $response = $this->getClient()->put( '/v1/shop-identities/' . $cloudShopId . '/migrate', @@ -368,6 +375,7 @@ public function migrateShopIdentity($cloudShopId, $shopToken, ShopUrl $shopUrl, 'backOfficeUrl' => $shopUrl->getBackOfficeUrl(), 'frontendUrl' => $shopUrl->getFrontendUrl(), 'multiShopId' => $shopUrl->getMultiShopId(), + 'name' => $shopName, 'fromVersion' => $fromVersion, ], $proof ? ['proof' => $proof] : [] diff --git a/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php index ffd3142aa..6048ac716 100644 --- a/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/CreateIdentityHandlerTest.php @@ -101,6 +101,7 @@ public function itShouldStoreIdentity() $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertArrayHasKey('name', $options[Request::JSON]); //$this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); return $this->createResponse([ @@ -145,6 +146,7 @@ public function itShouldNotChangeIdentityIfExists() $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertArrayHasKey('name', $options[Request::JSON]); //$this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); static $count = 1; @@ -194,6 +196,49 @@ public function itShouldTriggerVerifyIdentityIfAlreadyCreated() $this->getHandler()->handle(new CreateIdentityCommand(1)); } + /** + * @test + */ + public function itShouldRenewExistingIdentity() + { + $clientId = $this->faker->uuid; + $clientSecret = $this->faker->uuid; + $cloudShopId = $this->faker->uuid; + + $this->configurationRepository->updateCachedShopStatus(json_encode((new CachedShopStatus([ + 'isValid' => true, + 'shopStatus' => new ShopStatus([ + 'cloudShopId' => $this->faker->uuid, + 'isVerified' => true, + ]) + ]))->toArray())); + + $this->oauth2Client->update($this->faker->uuid, $this->faker->uuid); + + $this->client->method('post') + ->with($this->matchesRegularExpression('/v1\/shop-identities$/')) + ->willReturnCallback(function ($route, $options) use ($clientId, $clientSecret, $cloudShopId) { + + $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); + $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); + $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertArrayHasKey('name', $options[Request::JSON]); + //$this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); + + return $this->createResponse([ + 'clientId' => $clientId, + 'clientSecret' => $clientSecret, + "cloudShopId" => $cloudShopId + ], 200, true); + }); + + $this->getHandler()->handle(new CreateIdentityCommand(1, true)); + + $this->assertEquals($cloudShopId, $this->statusManager->getCloudShopId()); + $this->assertEquals($clientId, $this->oauth2Client->getClientId()); + $this->assertEquals($clientSecret, $this->oauth2Client->getClientSecret()); + } + /** * @return CreateIdentityHandler */ diff --git a/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php index 2fbb79352..96b816adf 100644 --- a/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/MigrateOrCreateIdentityV8HandlerTest.php @@ -158,6 +158,7 @@ public function itShouldMigrateIdentityFromV7() $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertArrayHasKey('name', $options[Request::JSON]); $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); //$this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); $this->assertEquals((string) $this->upgradeService->getVersion(), $options[Request::JSON]['fromVersion']); @@ -283,6 +284,7 @@ public function itShouldMigrateIdentityFromV5AndV6() $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertArrayHasKey('name', $options[Request::JSON]); $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); //$this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); $this->assertEquals($fromVersion, $options[Request::JSON]['fromVersion']); @@ -352,6 +354,7 @@ public function itShouldNotMigrateOnError() $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertArrayHasKey('name', $options[Request::JSON]); $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); //$this->assertEquals((string) $this->configurationRepository->getLastUpgrade(), $options[Request::JSON]['fromVersion']); $this->assertEquals((string) $this->upgradeService->getVersion(), $options[Request::JSON]['fromVersion']); diff --git a/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php b/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php index 65b786071..1d8261a8f 100644 --- a/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/VerifyIdentityHandlerTest.php @@ -95,6 +95,7 @@ public function itShouldInvalidateCachedStatusOnSuccess() $this->assertArrayHasKey('backOfficeUrl', $options[Request::JSON]); $this->assertArrayHasKey('frontendUrl', $options[Request::JSON]); $this->assertArrayHasKey('multiShopId', $options[Request::JSON]); + $this->assertArrayHasKey('name', $options[Request::JSON]); $this->assertEquals($this->proofManager->getProof(), $options[Request::JSON]['proof']); return $this->createResponse([ From 5622e004304fc26698cd7fac964a09aca08ce9b8 Mon Sep 17 00:00:00 2001 From: Guillaume-L <77913074+guillaume60240@users.noreply.github.com> Date: Fri, 5 Sep 2025 16:13:33 +0200 Subject: [PATCH 39/46] fix: admin ajax controller alert style (#549) * feat: improve style in AdminAjaxPsAccountsController * feat: two lines * fix: correct button onclick syntax in AdminAjaxPsAccountsController * style: update warning button and alert colors for improved visibility * style: remove border-radius from warning button and alert styles for consistency * fix: split translation --------- Co-authored-by: hschoenenberger --- .../admin/AdminAjaxPsAccountsController.php | 49 +++++++++++++++++-- translations/en.php | 3 +- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/controllers/admin/AdminAjaxPsAccountsController.php b/controllers/admin/AdminAjaxPsAccountsController.php index 1e1bf537e..bd02b1a3d 100644 --- a/controllers/admin/AdminAjaxPsAccountsController.php +++ b/controllers/admin/AdminAjaxPsAccountsController.php @@ -1,4 +1,5 @@ -
+
' . $this->module->l('Action required: confirm your store URL') . '
-

' . $this->module->l('We\'ve noticed that your store\'s URL no longer matches the one registered in your PrestaShop Account. For your services to function properly, you must either confirm this change or create a new identity for your store.') . '

+

' . $this->module->l('We\'ve noticed that your store\'s URL no longer matches the one registered in your PrestaShop Account.') . '
' . $this->module->l('For your services to function properly, you must either confirm this change or create a new identity for your store.') . '

    -
  • - ' . $this->module->l('Current store URL') . ': ' . $localFrontendUrl . '
  • -
  • - ' . $this->module->l('URL registered in PrestaShop Account') . ': ' . $cloudFrontendUrl . '
  • +
  • - ' . $this->module->l('Current store URL') . ': ' . $localFrontendUrl . '
  • +
  • - ' . $this->module->l('URL registered in PrestaShop Account') . ': ' . $cloudFrontendUrl . '
-
diff --git a/translations/en.php b/translations/en.php index 3b14548a2..29d23e600 100644 --- a/translations/en.php +++ b/translations/en.php @@ -23,7 +23,8 @@ $_MODULE['<{ps_accounts}prestashop>ps_accounts_aa8c92c02e922a0fff679d9f66fce2d0'] = 'Link your store to your PrestaShop account to activate and manage your subscriptions in your \' . \'back office. Do not uninstall this module if you have a current subscription.'; $_MODULE['<{ps_accounts}prestashop>ps_accounts_05eeaea4d4c380b44bbcca3b1341f811'] = 'This action will prevent immediately your PrestaShop services and Community services from \' . \'working as they are using PrestaShop Accounts module for authentication.'; $_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a6589b17d51617df13a46421dc86e004'] = 'Action required: confirm your store URL'; -$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_950c32d1a4f7f717bb4b738cbbc54fe2'] = 'We\'ve noticed that your store\'s URL no longer matches the one registered in your PrestaShop Account. For your services to function properly, you must either confirm this change or create a new identity for your store.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_97c826995e093d19f3c940c389515195'] = 'We\'ve noticed that your store\'s URL no longer matches the one registered in your PrestaShop Account.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_33fba3aba5e5d575db9f041232e64d2b'] = 'For your services to function properly, you must either confirm this change or create a new identity for your store.'; $_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'Current store URL'; $_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'URL registered in PrestaShop Account'; $_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Review settings'; From dff567cfec0ef371987237134d39cbb36570be55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Mon, 8 Sep 2025 16:11:45 +0200 Subject: [PATCH 40/46] fix: preserve bo login session usage (#550) * fix: preserve bo login session usage & rename id_fallback_session for collision name safety * fix: check for empty mismatched urls --- controllers/admin/AdminAjaxPsAccountsController.php | 5 ++++- controllers/admin/AdminOAuth2PsAccountsController.php | 8 ++++++-- src/Polyfill/ConfigurationStorageSession.php | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/controllers/admin/AdminAjaxPsAccountsController.php b/controllers/admin/AdminAjaxPsAccountsController.php index bd02b1a3d..9f7b61fe2 100644 --- a/controllers/admin/AdminAjaxPsAccountsController.php +++ b/controllers/admin/AdminAjaxPsAccountsController.php @@ -149,7 +149,10 @@ protected function getNotificationsUrlMismatch() $cloudFrontendUrl = rtrim($status->frontendUrl, '/'); $localFrontendUrl = rtrim($shopUrl->getFrontendUrl(), '/'); - if ($localFrontendUrl === $cloudFrontendUrl) { + if (empty($localFrontendUrl) || + empty($cloudFrontendUrl) || + $localFrontendUrl === $cloudFrontendUrl + ) { return []; } diff --git a/controllers/admin/AdminOAuth2PsAccountsController.php b/controllers/admin/AdminOAuth2PsAccountsController.php index 57791a1e8..77c32ac01 100644 --- a/controllers/admin/AdminOAuth2PsAccountsController.php +++ b/controllers/admin/AdminOAuth2PsAccountsController.php @@ -244,8 +244,12 @@ protected function onLoginFailedRedirect() */ protected function getSession() { - //return $this->module->getSession(); - return $this->module->getService(ConfigurationStorageSession::class); + if (\Context::getContext()->employee->id) { + // FIXME: fallback only for setPointOfContact + return $this->module->getService(ConfigurationStorageSession::class); + } + + return $this->module->getSession(); } /** diff --git a/src/Polyfill/ConfigurationStorageSession.php b/src/Polyfill/ConfigurationStorageSession.php index da5e12b07..fe3a1731e 100644 --- a/src/Polyfill/ConfigurationStorageSession.php +++ b/src/Polyfill/ConfigurationStorageSession.php @@ -47,7 +47,7 @@ public function start() */ public function getId() { - return \Context::getContext()->cookie->id_session; + return \Context::getContext()->cookie->id_fallback_session; } /** @@ -57,7 +57,7 @@ public function getId() */ public function setId($id) { - \Context::getContext()->cookie->id_session = $id; + \Context::getContext()->cookie->id_fallback_session = $id; } /** From d70e53974ed2d5be36120eb77dc951045ac91a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:46:00 +0200 Subject: [PATCH 41/46] fix: cleanup owner session after changing the point of contact (#551) * fix: cleanup owner session after changing the point of contact * fix: unit tests cleanup owner token --- .../CommandHandler/IdentifyContactHandler.php | 14 +++++++++++++- src/ServiceProvider/CommandProvider.php | 3 ++- .../IdentifyContactHandlerTest.php | 17 ++++++++++++++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/Account/CommandHandler/IdentifyContactHandler.php b/src/Account/CommandHandler/IdentifyContactHandler.php index 9fa9ca9de..dd2240475 100644 --- a/src/Account/CommandHandler/IdentifyContactHandler.php +++ b/src/Account/CommandHandler/IdentifyContactHandler.php @@ -21,6 +21,7 @@ namespace PrestaShop\Module\PsAccounts\Account\CommandHandler; use PrestaShop\Module\PsAccounts\Account\Command\IdentifyContactCommand; +use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsException; @@ -43,19 +44,27 @@ class IdentifyContactHandler */ private $shopSession; + /** + * @var OwnerSession + */ + private $ownerSession; + /** * @param AccountsService $accountsService * @param StatusManager $statusManager * @param ShopSession $shopSession + * @param OwnerSession $ownerSession */ public function __construct( AccountsService $accountsService, StatusManager $statusManager, - ShopSession $shopSession + ShopSession $shopSession, + OwnerSession $ownerSession ) { $this->accountsService = $accountsService; $this->statusManager = $statusManager; $this->shopSession = $shopSession; + $this->ownerSession = $ownerSession; } /** @@ -79,6 +88,9 @@ public function handle(IdentifyContactCommand $command) $command->source ); + // cleanup user token + $this->ownerSession->cleanup(); + // optimistic update cached status $this->statusManager->setPointOfContactUuid($command->userInfo->sub); $this->statusManager->setPointOfContactEmail($command->userInfo->email); diff --git a/src/ServiceProvider/CommandProvider.php b/src/ServiceProvider/CommandProvider.php index a8fb7a658..6bf5b3565 100644 --- a/src/ServiceProvider/CommandProvider.php +++ b/src/ServiceProvider/CommandProvider.php @@ -79,7 +79,8 @@ public function provide(ServiceContainer $container) return new IdentifyContactHandler( $container->get(AccountsService::class), $container->get(StatusManager::class), - $container->get(Session\ShopSession::class) + $container->get(Session\ShopSession::class), + $container->get(Session\Firebase\OwnerSession::class) ); }); $container->registerProvider(MigrateOrCreateIdentitiesV8Handler::class, static function () use ($container) { diff --git a/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php b/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php index 7808cae46..7a1339c98 100644 --- a/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php +++ b/tests/src/Unit/Account/CommandHandler/IdentifyContactHandlerTest.php @@ -6,12 +6,12 @@ use PrestaShop\Module\PsAccounts\Account\CachedShopStatus; use PrestaShop\Module\PsAccounts\Account\Command\IdentifyContactCommand; use PrestaShop\Module\PsAccounts\Account\CommandHandler\IdentifyContactHandler; +use PrestaShop\Module\PsAccounts\Account\Session\Firebase\OwnerSession; use PrestaShop\Module\PsAccounts\Account\Session\ShopSession; use PrestaShop\Module\PsAccounts\Account\StatusManager; +use PrestaShop\Module\PsAccounts\Account\Token\NullToken; use PrestaShop\Module\PsAccounts\Http\Client\Curl\Client; -use PrestaShop\Module\PsAccounts\Http\Client\Request; use PrestaShop\Module\PsAccounts\Service\Accounts\AccountsService; -use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\IdentityCreated; use PrestaShop\Module\PsAccounts\Service\Accounts\Resource\ShopStatus; use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\AccessToken; use PrestaShop\Module\PsAccounts\Service\OAuth2\Resource\UserInfo; @@ -36,6 +36,13 @@ class IdentifyContactHandlerTest extends TestCase */ protected $shopSession; + /** + * @inject + * + * @var OwnerSession + */ + protected $ownerSession; + /** * @var Client&MockObject */ @@ -67,6 +74,8 @@ public function itShouldSaveIdentityContact() $this->shopSession->method('getValidToken')->willReturn("valid_token"); + $this->ownerSession->setToken($this->faker->uuid, $this->faker->uuid); + // Expected call to setPointOfContact with correct parameters $this->accountsService->expects($this->once()) ->method('setPointOfContact') @@ -93,6 +102,7 @@ public function itShouldSaveIdentityContact() 'access_token' => 'valid_access_token', ]), $userInfo)); + $this->assertInstanceOf(NullToken::class, $this->ownerSession->getToken()->getJwt()); $this->assertEquals($userInfo->sub, $this->statusManager->getPointOfContactUuid()); $this->assertEquals($userInfo->email, $this->statusManager->getPointOfContactEmail()); } @@ -143,7 +153,8 @@ private function getHandler() return new IdentifyContactHandler( $this->accountsService, $this->statusManager, - $this->shopSession + $this->shopSession, + $this->ownerSession ); } } From 0d280f4bd4eea533724238194a6ce4089348d382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:16:28 +0200 Subject: [PATCH 42/46] [ACCOUNT-3087] fix: danger banner alert (#552) * fix: danger alert for banner notifications * fix: improve condition for upgrade failure banner --- .../admin/AdminAjaxPsAccountsController.php | 222 +++++++++++------- 1 file changed, 132 insertions(+), 90 deletions(-) diff --git a/controllers/admin/AdminAjaxPsAccountsController.php b/controllers/admin/AdminAjaxPsAccountsController.php index 9f7b61fe2..4e62c55ce 100644 --- a/controllers/admin/AdminAjaxPsAccountsController.php +++ b/controllers/admin/AdminAjaxPsAccountsController.php @@ -43,6 +43,111 @@ class AdminAjaxPsAccountsController extends \ModuleAdminController */ public $module; + /** + * @var string + */ + private $alertCss = ' + +'; + /** * AdminAjaxPsAccountsController constructor. * @@ -122,12 +227,12 @@ public function ajaxProcessGetNotifications() Logger::getInstance()->error($e->getMessage()); } $this->ajaxRender( - (string) json_encode($notifications ? [$notifications] : []) + (string) json_encode($notifications ?: []) ); } /** - * @return array|string[] + * @return array|array[] * * @throws UnknownStatusException */ @@ -162,82 +267,8 @@ protected function getNotificationsUrlMismatch() 'configure' => 'ps_accounts', ]); - return [ - 'html' => ' - + return [[ + 'html' => $this->alertCss . '
@@ -255,32 +286,43 @@ protected function getNotificationsUrlMismatch()
- ', - ]; + ]]; } /** - * @return array|string[] + * @return array|array[] */ protected function getNotificationsUpgradeFailed() { /** @var UpgradeService $upgradeService */ $upgradeService = $this->module->getService(UpgradeService::class); - if ($upgradeService->getCoreRegisteredVersion() === \Ps_accounts::VERSION) { + if ($upgradeService->getCoreRegisteredVersion() === \Ps_accounts::VERSION && + $upgradeService->getRegisteredVersion() === \Ps_accounts::VERSION) { return []; } - return [ - 'html' => ' -
- - ' . $this->module->l('Warning!') . ' ' . $this->module->l('PrestaShop Account module wasn\'t upgraded properly.') . ' -
- ' . $this->module->l('Please reset the module') . ' + /** @var AccountsLink $link */ + $link = $this->module->getService(AccountsLink::class); + $moduleManagerLink = $link->getAdminLink('AdminModules'); + + return [[ + 'html' => $this->alertCss . ' +
+
+
+ ' . $this->module->l('PrestaShop Account module wasn\'t upgraded properly.') . ' +
+

' . $this->module->l('Please reset or reinstall the module') . '

+
+
+ +
', - ]; + ]]; } } From 644c2f8b71c4844f8dd5dbb453e73dac56c9b463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Wed, 10 Sep 2025 14:56:07 +0200 Subject: [PATCH 43/46] feat: banner reset link (#553) * fix: add a direct reset link inside the alert banner * fix: scope notification js * fix: upgrade failed condition --- _dev/apps/notifications/index.ts | 79 +++++++++---------- .../admin/AdminAjaxPsAccountsController.php | 35 +++++++- src/Hook/DisplayBackOfficeHeader.php | 5 +- src/Installer/Installer.php | 28 ++++++- 4 files changed, 98 insertions(+), 49 deletions(-) diff --git a/_dev/apps/notifications/index.ts b/_dev/apps/notifications/index.ts index 7545ac775..e73997f46 100644 --- a/_dev/apps/notifications/index.ts +++ b/_dev/apps/notifications/index.ts @@ -1,49 +1,47 @@ +document.addEventListener('DOMContentLoaded', async function() { + function getParams() { + const scriptFilename = 'ps_accounts/views/js/notifications.js'; + const scripts = document.querySelectorAll('script'); + + let currentScript: HTMLScriptElement|null = null; + scripts.forEach(script => { + if (script.src.includes(scriptFilename)) { + currentScript = script; + } + }); -function getParams() { - const scriptFilename = 'ps_accounts/views/js/notifications.js'; - const scripts = document.querySelectorAll('script'); + if (currentScript) { + // Get the full URL of the script + const scriptSrc = currentScript['src']; + const url = new URL(scriptSrc); - let currentScript: HTMLScriptElement|null = null; - scripts.forEach(script => { - if (script.src.includes(scriptFilename)) { - currentScript = script; + return url.searchParams; } - }); - - if (currentScript) { - // Get the full URL of the script - const scriptSrc = currentScript['src']; - const url = new URL(scriptSrc); - - return url.searchParams; + return null; } - return null; -} - -async function getNotifications(uri: string) { - return await fetch(uri, { - method: 'GET' - }) - .then(response => response.json()) - .then(data => { - return data; - }) - .catch(error => { - console.error('Error:', error); - return null; - }); -} -function injectNotifications(notifications: [], container: Element) -{ - notifications.forEach((notif: any) => { - const alert = document.createElement('div'); - alert.innerHTML = notif?.html; - container.prepend(alert); - }); -} + async function getNotifications(uri: string) { + return await fetch(uri, { + method: 'GET' + }) + .then(response => response.json()) + .then(data => { + return data; + }) + .catch(error => { + console.error('Error:', error); + return null; + }); + } -document.addEventListener('DOMContentLoaded', async function() { + function injectNotifications(notifications: [], container: Element) + { + notifications.forEach((notif: any) => { + const alert = document.createElement('div'); + alert.innerHTML = notif?.html; + container.prepend(alert); + }); + } const container = document.querySelector('#main-div .content-div, #main #content'); if (! container) return; @@ -53,4 +51,3 @@ document.addEventListener('DOMContentLoaded', async function() { injectNotifications(await getNotifications(params.get('ctx') || ''), container); }); - diff --git a/controllers/admin/AdminAjaxPsAccountsController.php b/controllers/admin/AdminAjaxPsAccountsController.php index 4e62c55ce..e559d41d0 100644 --- a/controllers/admin/AdminAjaxPsAccountsController.php +++ b/controllers/admin/AdminAjaxPsAccountsController.php @@ -25,6 +25,7 @@ use PrestaShop\Module\PsAccounts\Account\StatusManager; use PrestaShop\Module\PsAccounts\AccountLogin\OAuth2Session; use PrestaShop\Module\PsAccounts\Adapter\Link as AccountsLink; +use PrestaShop\Module\PsAccounts\Installer\Installer; use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\Module\PsAccounts\Polyfill\Traits\Controller\AjaxRender; use PrestaShop\Module\PsAccounts\Provider\ShopProvider; @@ -295,17 +296,20 @@ protected function getNotificationsUrlMismatch() */ protected function getNotificationsUpgradeFailed() { + /** @var StatusManager $statusManager */ + $statusManager = $this->module->getService(StatusManager::class); + /** @var UpgradeService $upgradeService */ $upgradeService = $this->module->getService(UpgradeService::class); if ($upgradeService->getCoreRegisteredVersion() === \Ps_accounts::VERSION && - $upgradeService->getRegisteredVersion() === \Ps_accounts::VERSION) { + (!$statusManager->identityCreated() || $upgradeService->getRegisteredVersion() === \Ps_accounts::VERSION)) { return []; } /** @var AccountsLink $link */ $link = $this->module->getService(AccountsLink::class); - $moduleManagerLink = $link->getAdminLink('AdminModules'); + $resetLink = $link->getAdminLink('AdminAjaxPsAccounts', true, [], ['ajax' => 1, 'action' => 'resetModule']); return [[ 'html' => $this->alertCss . ' @@ -317,12 +321,35 @@ protected function getNotificationsUpgradeFailed()

' . $this->module->l('Please reset or reinstall the module') . '

-
', ]]; } + + /** + * @return void + */ + public function ajaxProcessResetModule() + { + $status = false; + try { + /** @var Installer $installer */ + $installer = $this->module->getService(Installer::class); + $status = $installer->resetModule('ps_accounts'); + } catch (\Exception $e) { + Logger::getInstance()->error($e->getMessage()); + } catch (\Throwable $e) { + Logger::getInstance()->error($e->getMessage()); + } + $this->ajaxRender( + (string) json_encode([ + 'status' => $status, + ]) + ); + } } diff --git a/src/Hook/DisplayBackOfficeHeader.php b/src/Hook/DisplayBackOfficeHeader.php index 1c30f50b8..e64c9af5c 100644 --- a/src/Hook/DisplayBackOfficeHeader.php +++ b/src/Hook/DisplayBackOfficeHeader.php @@ -48,8 +48,9 @@ public function execute(array $params = []) // } $this->module->getContext()->controller->addJs( - $this->module->getLocalPath() . 'views/js/notifications.js?' . - 'ctx=' . urlencode($psAccountsService->getAdminAjaxUrl() . '&action=getNotifications') + $this->module->getLocalPath() . 'views/js/notifications.js' . + '?ctx=' . urlencode($psAccountsService->getAdminAjaxUrl() . '&action=getNotifications') . + '&v=' . urlencode($this->module->version) ); } catch (\Exception $e) { } catch (\Throwable $e) { diff --git a/src/Installer/Installer.php b/src/Installer/Installer.php index 87acfcdff..ff22d5482 100644 --- a/src/Installer/Installer.php +++ b/src/Installer/Installer.php @@ -23,7 +23,7 @@ use Module; use PrestaShop\Module\PsAccounts\Adapter\Link; use PrestaShop\Module\PsAccounts\Context\ShopContext; -use PrestaShop\Module\PsAccounts\Service\SentryService; +use PrestaShop\Module\PsAccounts\Log\Logger; use PrestaShop\PrestaShop\Adapter\SymfonyContainer; use PrestaShop\PrestaShop\Core\Addon\Module\ModuleManagerBuilder; use Tools; @@ -84,12 +84,36 @@ public function installModule($module, $upgrade = true) $moduleIsInstalled = $moduleManager->install($module); if (false === $moduleIsInstalled) { - SentryService::capture(new \Exception('Module ' . $module . " can't be installed")); + Logger::getInstance()->error('Module ' . $module . " can't be installed"); } return $moduleIsInstalled; } + /** + * @param string $module + * + * @return bool + * + * @throws \Exception + */ + public function resetModule($module) + { + if (false === $this->shopContext->isShop17()) { + return true; + } + + $moduleManager = ModuleManagerBuilder::getInstance()->build(); + + $status = $moduleManager->reset($module); + + if (false === $status) { + Logger::getInstance()->error('Module ' . $module . " can't be reset"); + } + + return $status; + } + /** * @param string $module * @param string $psxName From 89e2d17e3da6e146150f31fe0b2e582ed63b9018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Thu, 11 Sep 2025 10:00:25 +0200 Subject: [PATCH 44/46] feat: update readme (#554) * fix: update readme --- README.md | 10 ++++------ ps_accounts.php | 12 ------------ src/Hook/DisplayBackOfficeHeader.php | 8 ++------ 3 files changed, 6 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index fb30b1bf0..ce64674cc 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ # Context The module **ps_accounts** is the interface between your module and PrestaShop's services. It manages: -- Shop association/dissociation process; +- Shop **Identification** and **Verification** process; - Providing tokens to communicate safely with PrestaShop services; - Synchronize basic informations about the shops (ex: shop URLs, name, ...). -This module is a basis for other modules using PrestaShop services. +This module is a base component for other modules using PrestaShop services. # Installation @@ -27,6 +27,7 @@ We aims to follow partially the Prestashop compatibility charts | ps_accounts version | Prestashop Version | PHP Version | |----------------------|----------------------|-------------------| +| ^8.0.0 | \>=1.6 && <= 9.x | PHP 5.6 - 8 | | ^7.0.9 | \>=1.6 && <= 9.x | PHP 5.6 - 8 | | 7.x | \>=1.6 && <9.x | PHP 5.6 - 8 | | ~~6.x (deprecated)~~ | ~~\>=8.0.0~~ | ~~PHP 7.2 - 8~~ | @@ -70,11 +71,10 @@ Media::addJsDef([ return $this->display(__FILE__, 'views/templates/admin/app.tpl'); ``` -Alternatively you can still use : [PrestaShop Accounts Installer](http://github.com/PrestaShopCorp/prestashop-accounts-installer) for more details on how to setup Installer. ### Load and init the component on your page -For detailed usage you can follow the component's documentation : [prestashop_accounts_vue_components](https://github.com/PrestaShopCorp/prestashop_accounts_vue_components) +For detailed usage you can follow the component's documentation : [vue-components](https://github.com/PrestaShopCorp/accounts/tree/main/packages/vue-components#readme) ## How to retrieve tokens with PsAccounts @@ -216,8 +216,6 @@ Here are listed custom hooks provided with this module: | hook | params | description | |-----------------------------------|------------------|----------------------------------------------| -| actionShopAccountLinkAfter | shopId, shopUuid | Triggered after link shop acknowledged | -| actionShopAccountUnlinkAfter | shopId, shopUuid | Triggered after unlink shop acknowledged | | actionShopAccessTokenRefreshAfter | token | Triggered after OAuth access token refreshed | # Building the module locally diff --git a/ps_accounts.php b/ps_accounts.php index a1632e04e..4189a98a6 100644 --- a/ps_accounts.php +++ b/ps_accounts.php @@ -52,18 +52,6 @@ class Ps_accounts extends Module * @var array */ private $customHooks = [ - [ - 'name' => 'actionShopAccountLinkAfter', - 'title' => 'Shop linked event', - 'description' => 'Shop linked with PrestaShop Account', - 'position' => 1, - ], - [ - 'name' => 'actionShopAccountUnlinkAfter', - 'title' => 'Shop unlinked event', - 'description' => 'Shop unlinked with PrestaShop Account', - 'position' => 1, - ], [ 'name' => 'actionShopAccessTokenRefreshAfter', 'title' => 'Shop access token refreshed event', diff --git a/src/Hook/DisplayBackOfficeHeader.php b/src/Hook/DisplayBackOfficeHeader.php index e64c9af5c..d8782ced5 100644 --- a/src/Hook/DisplayBackOfficeHeader.php +++ b/src/Hook/DisplayBackOfficeHeader.php @@ -33,9 +33,6 @@ class DisplayBackOfficeHeader extends Hook public function execute(array $params = []) { try { - /** @var PsAccountsService $psAccountsService */ - $psAccountsService = $this->module->getService(PsAccountsService::class); - if (preg_match('/controller=AdminModules/', $_SERVER['REQUEST_URI']) && preg_match('/configure=ps_accounts/', $_SERVER['REQUEST_URI']) || preg_match('@modules/manage/action/configure/ps_accounts@', $_SERVER['REQUEST_URI']) @@ -43,9 +40,8 @@ public function execute(array $params = []) return ''; } -// if (!$psAccountsService->isShopIdentityCreated()) { -// return; -// } + /** @var PsAccountsService $psAccountsService */ + $psAccountsService = $this->module->getService(PsAccountsService::class); $this->module->getContext()->controller->addJs( $this->module->getLocalPath() . 'views/js/notifications.js' . From d3559e51a9c8fdadeaf9941d4c91434cf4f47660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:21:25 +0200 Subject: [PATCH 45/46] refactor: cleanup obsolete abstract rest controller (#555) * refactor: remove obsolete controller class * refactor: abstract methods for scope and audience --- controllers/front/apiV1ShopToken.php | 45 --- controllers/front/apiV1ShopUrl.php | 70 ---- controllers/front/apiV2ShopHealthCheck.php | 4 +- controllers/front/apiV2ShopProof.php | 14 +- .../Controller/AbstractRestController.php | 348 ------------------ .../Controller/AbstractShopRestController.php | 48 --- .../Controller/AbstractV2RestController.php | 231 +++++++++++- .../AbstractV2ShopRestController.php | 2 +- .../Api/V1/ShopLinkAccount/DeleteTest.php | 95 ----- .../Api/V1/ShopLinkAccount/StoreTest.php | 116 ------ .../Api/V1/ShopOauth2Client/DeleteTest.php | 61 --- .../Api/V1/ShopOauth2Client/StoreTest.php | 106 ------ .../src/Feature/Api/V1/ShopToken/ShowTest.php | 108 ------ tests/src/Feature/Api/V1/ShopUrl/ShowTest.php | 106 ------ 14 files changed, 226 insertions(+), 1128 deletions(-) delete mode 100644 controllers/front/apiV1ShopToken.php delete mode 100644 controllers/front/apiV1ShopUrl.php delete mode 100644 src/Http/Controller/AbstractRestController.php delete mode 100644 src/Http/Controller/AbstractShopRestController.php delete mode 100644 tests/src/Feature/Api/V1/ShopLinkAccount/DeleteTest.php delete mode 100644 tests/src/Feature/Api/V1/ShopLinkAccount/StoreTest.php delete mode 100644 tests/src/Feature/Api/V1/ShopOauth2Client/DeleteTest.php delete mode 100644 tests/src/Feature/Api/V1/ShopOauth2Client/StoreTest.php delete mode 100644 tests/src/Feature/Api/V1/ShopToken/ShowTest.php delete mode 100644 tests/src/Feature/Api/V1/ShopUrl/ShowTest.php diff --git a/controllers/front/apiV1ShopToken.php b/controllers/front/apiV1ShopToken.php deleted file mode 100644 index c981654e5..000000000 --- a/controllers/front/apiV1ShopToken.php +++ /dev/null @@ -1,45 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -use PrestaShop\Module\PsAccounts\Account\Exception\RefreshTokenException; -use PrestaShop\Module\PsAccounts\Account\Session\Firebase\ShopSession; -use PrestaShop\Module\PsAccounts\Http\Controller\AbstractShopRestController; - -class ps_AccountsApiV1ShopTokenModuleFrontController extends AbstractShopRestController -{ - /** - * @param Shop $shop - * @param array $payload - * - * @return string[] - * - * @throws RefreshTokenException - */ - public function show(Shop $shop, array $payload) - { - /** @var ShopSession $shopSession */ - $shopSession = $this->module->getService(ShopSession::class); - - return [ - 'token' => (string) $shopSession->getValidToken(), - 'refresh_token' => (string) $shopSession->getToken()->getRefreshToken(), - ]; - } -} diff --git a/controllers/front/apiV1ShopUrl.php b/controllers/front/apiV1ShopUrl.php deleted file mode 100644 index 0d8d0cc1f..000000000 --- a/controllers/front/apiV1ShopUrl.php +++ /dev/null @@ -1,70 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -use PrestaShop\Module\PsAccounts\Http\Controller\AbstractShopRestController; -use PrestaShop\Module\PsAccounts\Provider\ShopProvider; -use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; - -class ps_AccountsApiV1ShopUrlModuleFrontController extends AbstractShopRestController -{ - /** - * @var ConfigurationRepository - */ - private $configuration; - - /** - * @var ShopProvider - */ - private $shopProvider; - - /** - * ps_AccountsApiV1ShopUrlModuleFrontController constructor. - * - * @throws Exception - */ - public function __construct() - { - parent::__construct(); - - $this->configuration = $this->module->getService(ConfigurationRepository::class); - $this->shopProvider = $this->module->getService(ShopProvider::class); - } - - /** - * @param Shop $shop - * @param array $payload - * - * @return array - * - * @throws Exception - */ - public function show(Shop $shop, array $payload) - { - $shopDto = $this->shopProvider->formatShopData((array) \Shop::getShop($shop->id), '', false); - - return [ - 'domain' => $shopDto->domain, - 'domain_ssl' => $shopDto->domainSsl, - 'physical_uri' => $shopDto->physicalUri, - 'virtual_uri' => $shopDto->virtualUri, - 'ssl_activated' => $this->configuration->sslEnabled(), - ]; - } -} diff --git a/controllers/front/apiV2ShopHealthCheck.php b/controllers/front/apiV2ShopHealthCheck.php index b82f9ea43..96d4896ad 100644 --- a/controllers/front/apiV2ShopHealthCheck.php +++ b/controllers/front/apiV2ShopHealthCheck.php @@ -77,7 +77,7 @@ class ps_AccountsApiV2ShopHealthCheckModuleFrontController extends AbstractV2Sho /** * @return array */ - protected function getScope() + public function getScope() { return [ 'shop.health', @@ -87,7 +87,7 @@ protected function getScope() /** * @return array */ - protected function getAudience() + public function getAudience() { return [ 'ps_accounts/' . $this->statusManager->getCloudShopId(), diff --git a/controllers/front/apiV2ShopProof.php b/controllers/front/apiV2ShopProof.php index bf28993ad..6ec46e2fa 100644 --- a/controllers/front/apiV2ShopProof.php +++ b/controllers/front/apiV2ShopProof.php @@ -24,6 +24,11 @@ class ps_AccountsApiV2ShopProofModuleFrontController extends AbstractV2ShopRestController { + /** + * @var bool + */ + protected $authenticated = true; + /** * @var ProofManager */ @@ -34,15 +39,10 @@ class ps_AccountsApiV2ShopProofModuleFrontController extends AbstractV2ShopRestC */ private $statusManager; - /** - * @var bool - */ - protected $authenticated = true; - /** * @return array */ - protected function getScope() + public function getScope() { return [ 'shop.proof.read', @@ -52,7 +52,7 @@ protected function getScope() /** * @return array */ - protected function getAudience() + public function getAudience() { return [ 'ps_accounts/' . $this->statusManager->getCloudShopId(), diff --git a/src/Http/Controller/AbstractRestController.php b/src/Http/Controller/AbstractRestController.php deleted file mode 100644 index 00f01c8ba..000000000 --- a/src/Http/Controller/AbstractRestController.php +++ /dev/null @@ -1,348 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Http\Controller; - -use Context; -use ModuleFrontController; -use PrestaShop\Module\PsAccounts\Http\Exception\HttpException; -use PrestaShop\Module\PsAccounts\Http\Exception\MethodNotAllowedException; -use PrestaShop\Module\PsAccounts\Http\Exception\UnauthorizedException; -use PrestaShop\Module\PsAccounts\Polyfill\Traits\Controller\AjaxRender; -use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; -use PrestaShop\Module\PsAccounts\Service\SentryService; -use ReflectionException; -use ReflectionParameter; - -abstract class AbstractRestController extends ModuleFrontController -{ - use AjaxRender; - use GetHeader; - - const TOKEN_HEADER = 'X-PrestaShop-Signature'; - - /** - * @var string - */ - public $resourceId = 'id'; - - /** - * @var \Ps_accounts - */ - public $module; - - /** - * @var bool - */ - protected $authenticated = true; - - public function __construct() - { - parent::__construct(); - - $this->ajax = true; - $this->content_only = true; - $this->controller_type = 'module'; - } - - /** - * @return void - */ - public function initContent() - { - } - - /** - * @return void - * - * @throws \PrestaShopException - */ - // public function init() - // public function displayAjax() - public function postProcess() - { - try { - $payload = $this->extractPayload(); - $method = $_SERVER['REQUEST_METHOD']; - // detect method from payload (hack with some shop server configuration) - if (isset($payload['method'])) { - $method = $payload['method']; - unset($payload['method']); - } - $this->dispatchVerb($method, $payload); - } catch (HttpException $e) { - $this->module->getLogger()->error($e); - - $this->dieWithResponseJson([ - 'error' => true, - 'message' => $e->getMessage(), - ], $e->getStatusCode()); - } catch (\Throwable $e) { - $this->handleError($e); - /* @phpstan-ignore-next-line */ - } catch (\Exception $e) { - $this->handleError($e); - } - } - - /** - * @param array $response - * @param int|null $httpResponseCode - * - * @return void - * - * @throws \PrestaShopException - */ - public function dieWithResponseJson(array $response, $httpResponseCode = null) - { - @ob_end_flush(); - @ob_end_clean(); - - if (is_integer($httpResponseCode)) { - http_response_code($httpResponseCode); - } - - header('Content-Type: text/json'); - - $this->ajaxRender((string) json_encode($response)); - } - - /** - * @param string $httpMethod - * @param array $payload - * - * @return void - * - * @throws \Exception - */ - protected function dispatchVerb($httpMethod, array $payload) - { - $id = array_key_exists($this->resourceId, $payload) - ? $payload[$this->resourceId] - : null; - - $statusCode = 200; - - switch ($httpMethod) { - case 'GET': - $method = null !== $id - ? RestMethod::SHOW - : RestMethod::INDEX; - break; - case 'POST': - list($method, $statusCode) = null !== $id - ? [RestMethod::UPDATE, $statusCode] - : [RestMethod::STORE, 201]; - break; - case 'PUT': - case 'PATCH': - $method = RestMethod::UPDATE; - break; - case 'DELETE': - $statusCode = 204; - $method = RestMethod::DELETE; - break; - default: - throw new \Exception('Invalid Method : ' . $httpMethod); - } - - $this->dieWithResponseJson($this->invokeMethod($method, $id, $payload), $statusCode); - } - - /** - * @param string $method - * @param mixed $id - * @param mixed $payload - * - * @return mixed - */ - protected function invokeMethod($method, $id, $payload) - { - try { - $method = new \ReflectionMethod($this, $method); - $params = $method->getParameters(); - - $args = []; - - if (null !== $id) { - $args[] = $this->buildResource($id); - } - - if (null !== $payload) { - $args[] = $this->buildArg($payload, $params[1]); - } - - return $method->invokeArgs($this, $args); - } catch (ReflectionException $e) { - throw new MethodNotAllowedException(); - } - } - - /** - * @param mixed $id - * - * @return mixed - */ - protected function buildResource($id) - { - return $id; - } - - /** - * @param array $payload - * @param ReflectionParameter $reflectionParam - * - * @return array|object - * - * @throws ReflectionException - */ - protected function buildArg(array $payload, ReflectionParameter $reflectionParam) - { -// if ($reflectionParam->getType()->isBuiltin()) { -// return $payload; -// } else { -// // Instantiate DTO like value bag -// return $reflectionParam->getClass()->newInstance($payload); -// } - if ($reflectionParam->getClass()) { - // Instantiate DTO like value bag - return $reflectionParam->getClass()->newInstance($payload); - } - - return $payload; - } - - /** - * @return array - */ - protected function extractPayload() - { - $defaultShopId = Context::getContext()->shop->id; - if ($this->authenticated) { - return $this->decodePayload($defaultShopId); - } - - return $this->decodeRawPayload($defaultShopId); - } - - /** - * @param int $defaultShopId - * - * @return array - */ - protected function decodeRawPayload($defaultShopId = null) - { - $payload = $_REQUEST; - if (!isset($payload['shop_id'])) { - // context fallback - $payload['shop_id'] = $defaultShopId; - } - $shop = new \Shop((int) $payload['shop_id']); - if ($shop->id) { - $this->setContextShop($shop); - } - - return $payload; - } - - /** - * @param int $defaultShopId - * - * @return array - * - * @throws UnauthorizedException - * - * @deprecated since v8.0.0 in favor of AbstractV2RestController - */ - protected function decodePayload($defaultShopId = null) - { - throw new UnauthorizedException(); - } - - /** - * Force shop context - * - * @param \Shop $shop - * - * @return void - * - * @throws \Exception - */ - protected function setContextShop(\Shop $shop) - { - /** @var ConfigurationRepository $conf */ - $conf = $this->module->getService(ConfigurationRepository::class); - $conf->setShopId($shop->id); - - /** @var Context $context */ - $context = $this->module->getService('ps_accounts.context'); - $context->shop = $shop; - } - - /** - * @return bool - */ - protected function displayMaintenancePage() - { - return true; - } - - /** - * Override displayRestrictedCountryPage to prevent page country is not allowed - * - * @see FrontController::displayRestrictedCountryPage() - * - * @return void - */ - protected function displayRestrictedCountryPage() - { - } - - /** - * Override geolocationManagement to prevent country GEOIP blocking - * - * @see FrontController::geolocationManagement() - * - * @param \Country $defaultCountry - * - * @return false - */ - protected function geolocationManagement($defaultCountry) - { - return false; - } - - /** - * @param \Throwable|\Exception $e - * - * @return void - * - * @throws \PrestaShopException - */ - protected function handleError($e) - { - SentryService::capture($e); - - $this->dieWithResponseJson([ - 'error' => true, - 'message' => 'Failed processing your request', - ], 500); - } -} diff --git a/src/Http/Controller/AbstractShopRestController.php b/src/Http/Controller/AbstractShopRestController.php deleted file mode 100644 index 80c9bdf11..000000000 --- a/src/Http/Controller/AbstractShopRestController.php +++ /dev/null @@ -1,48 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PsAccounts\Http\Controller; - -use PrestaShop\Module\PsAccounts\Http\Exception\NotFoundException; -use Shop; - -class AbstractShopRestController extends AbstractRestController -{ - /** - * @var string - */ - public $resourceId = 'shop_id'; - - /** - * @param mixed $id - * - * @return Shop - */ - protected function buildResource($id) - { - $shop = new Shop((int) $id); - - if (!$shop->id) { - throw new NotFoundException('Shop not found [' . $id . ']'); - } - - return $shop; - } -} diff --git a/src/Http/Controller/AbstractV2RestController.php b/src/Http/Controller/AbstractV2RestController.php index 5b582de89..7125d8d6c 100644 --- a/src/Http/Controller/AbstractV2RestController.php +++ b/src/Http/Controller/AbstractV2RestController.php @@ -21,8 +21,12 @@ namespace PrestaShop\Module\PsAccounts\Http\Controller; use Context; +use ModuleFrontController; use PrestaShop\Module\PsAccounts\Http\Exception\HttpException; +use PrestaShop\Module\PsAccounts\Http\Exception\MethodNotAllowedException; use PrestaShop\Module\PsAccounts\Http\Exception\UnauthorizedException; +use PrestaShop\Module\PsAccounts\Polyfill\Traits\Controller\AjaxRender; +use PrestaShop\Module\PsAccounts\Repository\ConfigurationRepository; use PrestaShop\Module\PsAccounts\Service\OAuth2\OAuth2Service; use PrestaShop\Module\PsAccounts\Service\OAuth2\Token\Validator\Exception\AudienceInvalidException; use PrestaShop\Module\PsAccounts\Service\OAuth2\Token\Validator\Exception\ScopeInvalidException; @@ -31,15 +35,35 @@ use PrestaShop\Module\PsAccounts\Service\OAuth2\Token\Validator\Exception\TokenInvalidException; use PrestaShop\Module\PsAccounts\Service\OAuth2\Token\Validator\Validator; use PrestaShop\Module\PsAccounts\Service\SentryService; +use ReflectionException; +use ReflectionParameter; -abstract class AbstractV2RestController extends AbstractRestController +abstract class AbstractV2RestController extends ModuleFrontController { + use AjaxRender; + use GetHeader; + /** * Header to retrieve bearer from * FIXME: "Authorization" standard header might be filtered by server configuration */ const HEADER_AUTHORIZATION = 'X-Prestashop-Authorization'; + /** + * @var string + */ + public $resourceId = 'id'; + + /** + * @var \Ps_accounts + */ + public $module; + + /** + * @var bool + */ + protected $authenticated = true; + /** * @var object */ @@ -54,6 +78,10 @@ public function __construct() { parent::__construct(); + $this->ajax = true; + $this->content_only = true; + $this->controller_type = 'module'; + $this->validator = new Validator( $this->module->getService(OAuth2Service::class) ); @@ -64,19 +92,20 @@ public function __construct() * * @return array */ - protected function getScope() - { - return []; - } + abstract public function getScope(); /** * Controller level audiences * * @return array */ - protected function getAudience() + abstract public function getAudience(); + + /** + * @return void + */ + public function initContent() { - return []; } /** @@ -107,6 +136,153 @@ public function postProcess() } } + /** + * @param array $response + * @param int|null $httpResponseCode + * + * @return void + * + * @throws \PrestaShopException + */ + public function dieWithResponseJson(array $response, $httpResponseCode = null) + { + @ob_end_flush(); + @ob_end_clean(); + + if (is_integer($httpResponseCode)) { + http_response_code($httpResponseCode); + } + + header('Content-Type: text/json'); + + $this->ajaxRender((string) json_encode($response)); + } + + /** + * @param string $httpMethod + * @param array $payload + * + * @return void + * + * @throws \Exception + */ + protected function dispatchVerb($httpMethod, array $payload) + { + $id = array_key_exists($this->resourceId, $payload) + ? $payload[$this->resourceId] + : null; + + $statusCode = 200; + + switch ($httpMethod) { + case 'GET': + $method = null !== $id + ? RestMethod::SHOW + : RestMethod::INDEX; + break; + case 'POST': + list($method, $statusCode) = null !== $id + ? [RestMethod::UPDATE, $statusCode] + : [RestMethod::STORE, 201]; + break; + case 'PUT': + case 'PATCH': + $method = RestMethod::UPDATE; + break; + case 'DELETE': + $statusCode = 204; + $method = RestMethod::DELETE; + break; + default: + throw new \Exception('Invalid Method : ' . $httpMethod); + } + + $this->dieWithResponseJson($this->invokeMethod($method, $id, $payload), $statusCode); + } + + /** + * @param string $method + * @param mixed $id + * @param mixed $payload + * + * @return mixed + */ + protected function invokeMethod($method, $id, $payload) + { + try { + $method = new \ReflectionMethod($this, $method); + $params = $method->getParameters(); + + $args = []; + + if (null !== $id) { + $args[] = $this->buildResource($id); + } + + if (null !== $payload) { + $args[] = $this->buildArg($payload, $params[1]); + } + + return $method->invokeArgs($this, $args); + } catch (ReflectionException $e) { + throw new MethodNotAllowedException(); + } + } + + /** + * @param mixed $id + * + * @return mixed + */ + protected function buildResource($id) + { + return $id; + } + + /** + * @param array $payload + * @param ReflectionParameter $reflectionParam + * + * @return array|object + * + * @throws ReflectionException + */ + protected function buildArg(array $payload, ReflectionParameter $reflectionParam) + { +// if ($reflectionParam->getType()->isBuiltin()) { +// return $payload; +// } else { +// // Instantiate DTO like value bag +// return $reflectionParam->getClass()->newInstance($payload); +// } + if ($reflectionParam->getClass()) { + // Instantiate DTO like value bag + return $reflectionParam->getClass()->newInstance($payload); + } + + return $payload; + } + + /** + * Force shop context + * + * @param \Shop $shop + * + * @return void + * + * @throws \Exception + */ + protected function setContextShop(\Shop $shop) + { + /** @var ConfigurationRepository $conf */ + $conf = $this->module->getService(ConfigurationRepository::class); + $conf->setShopId($shop->id); + + /** @var Context $context */ + $context = $this->module->getService('ps_accounts.context'); + $context->shop = $shop; + } + /** * @param int|null $defaultShopId * @@ -164,14 +340,6 @@ protected function extractMethod(array & $payload) return $method; } -// /** -// * @return bool -// */ -// protected function displayMaintenancePage() -// { -// return false; -// } - /** * @return true * @@ -276,4 +444,37 @@ protected function handleException($e, $message = null) ], 500); } } + + /** + * @return bool + */ + protected function displayMaintenancePage() + { + return true; + } + + /** + * Override displayRestrictedCountryPage to prevent page country is not allowed + * + * @see FrontController::displayRestrictedCountryPage() + * + * @return void + */ + protected function displayRestrictedCountryPage() + { + } + + /** + * Override geolocationManagement to prevent country GEOIP blocking + * + * @see FrontController::geolocationManagement() + * + * @param \Country $defaultCountry + * + * @return false + */ + protected function geolocationManagement($defaultCountry) + { + return false; + } } diff --git a/src/Http/Controller/AbstractV2ShopRestController.php b/src/Http/Controller/AbstractV2ShopRestController.php index 8e34eb68c..b6d73b97d 100644 --- a/src/Http/Controller/AbstractV2ShopRestController.php +++ b/src/Http/Controller/AbstractV2ShopRestController.php @@ -23,7 +23,7 @@ use PrestaShop\Module\PsAccounts\Http\Exception\NotFoundException; use Shop; -class AbstractV2ShopRestController extends AbstractV2RestController +abstract class AbstractV2ShopRestController extends AbstractV2RestController { /** * @var string diff --git a/tests/src/Feature/Api/V1/ShopLinkAccount/DeleteTest.php b/tests/src/Feature/Api/V1/ShopLinkAccount/DeleteTest.php deleted file mode 100644 index 91ce7d24c..000000000 --- a/tests/src/Feature/Api/V1/ShopLinkAccount/DeleteTest.php +++ /dev/null @@ -1,95 +0,0 @@ -markTestSkipped(); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldSucceed() - { - $this->statusManager->update(new \PrestaShop\Module\PsAccounts\Account\Dto\ShopIdentity([ - 'shopId' => $this->faker->numberBetween(), - 'uid' => $this->faker->uuid, - 'employeeId' => $this->faker->numberBetween() - ])); - - $this->shopSession->setToken((string) $this->makeJwtToken(new \DateTimeImmutable(), ['foo' => 'bar'])); - $this->ownerSession->setToken((string) $this->makeJwtToken(new \DateTimeImmutable(), ['foo' => 'bar'])); - $this->session->setToken((string) $this->makeJwtToken(new \DateTimeImmutable(), ['foo' => 'bar'])); - - $response = $this->client->delete('/module/ps_accounts/apiV1ShopLinkAccount', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload([ - 'method' => 'DELETE', - 'shop_id' => 1, - ]) - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseDeleted($response); - - // FIXME: empty response - //$this->assertArraySubset(['success' => true], $json); - - \Configuration::clearConfigurationCacheForTesting(); - \Configuration::loadConfiguration(); - - $this->assertFalse($this->statusManager->identityCreated()); - - $this->assertEmpty($this->statusManager->getCloudShopId()); - $this->assertEmpty($this->statusManager->getEmployeeId()); - $this->assertEmpty($this->statusManager->getPointOfContactUuid()); - $this->assertEmpty($this->statusManager->getPointOfContactEmail()); - - $this->assertInstanceOf(NullToken::class, $this->shopSession->getToken()->getJwt()); - $this->assertInstanceOf(NullToken::class, $this->ownerSession->getToken()->getJwt()); - $this->assertInstanceOf(NullToken::class, $this->session->getToken()->getJwt()); - - // compat - $this->assertEmpty($this->configuration->get(ConfigurationKeys::PS_ACCOUNTS_FIREBASE_EMAIL)); - $this->assertEmpty($this->configuration->get(ConfigurationKeys::PSX_UUID_V4)); - } -} diff --git a/tests/src/Feature/Api/V1/ShopLinkAccount/StoreTest.php b/tests/src/Feature/Api/V1/ShopLinkAccount/StoreTest.php deleted file mode 100644 index 97238b003..000000000 --- a/tests/src/Feature/Api/V1/ShopLinkAccount/StoreTest.php +++ /dev/null @@ -1,116 +0,0 @@ -markTestSkipped(); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldSucceed() - { - $payload = [ - 'shop_id' => 1, - 'uid' => $this->faker->uuid, - 'employee_id' => $this->faker->numberBetween(1), - 'owner_uid' => $this->faker->uuid, - 'owner_email' => $this->faker->safeEmail, - ]; - - $response = $this->client->post('/module/ps_accounts/apiV1ShopLinkAccount', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload($payload) - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseOk($response); - $this->assertBodySubsetOrMarkAsIncomplete(['success' => true], $json); - - \Configuration::clearConfigurationCacheForTesting(); - \Configuration::loadConfiguration(); - - $this->assertTrue($this->statusManager->identityCreated()); - $this->assertEquals($payload['uid'], $this->statusManager->getCloudShopId()); - $this->assertEquals($payload['employee_id'], $this->statusManager->getEmployeeId()); - $this->assertEquals($payload['owner_uid'], $this->statusManager->getPointOfContactUuid()); - $this->assertEquals($payload['owner_email'], $this->statusManager->getPointOfContactEmail()); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldSucceedWithoutEmployeeId() - { - $shopUuid = $this->faker->uuid; - $userUuid = $this->faker->uuid; - $email = $this->faker->safeEmail; - - $payload = [ - 'shop_id' => 1, - 'uid' => $shopUuid, - //'employee_id' => $employeeId, - ]; - - $response = $this->client->post('/module/ps_accounts/apiV1ShopLinkAccount', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload($payload) - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseOk($response); - $this->assertBodySubsetOrMarkAsIncomplete(['success' => true], $json); - - \Configuration::clearConfigurationCacheForTesting(); - \Configuration::loadConfiguration(); - - $this->assertTrue($this->statusManager->identityCreated()); - $this->assertEquals($shopUuid, $this->statusManager->getCloudShopId()); - $this->assertEquals(null, $this->statusManager->getEmployeeId()); - } - -} diff --git a/tests/src/Feature/Api/V1/ShopOauth2Client/DeleteTest.php b/tests/src/Feature/Api/V1/ShopOauth2Client/DeleteTest.php deleted file mode 100644 index 62a662fe4..000000000 --- a/tests/src/Feature/Api/V1/ShopOauth2Client/DeleteTest.php +++ /dev/null @@ -1,61 +0,0 @@ -markTestSkipped(); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldSucceed() - { - $this->oauth2Client->update($this->faker->slug, $this->faker->password); - - $response = $this->client->delete('/module/ps_accounts/apiV1ShopOauth2Client', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload([ - 'method' => 'DELETE', - 'shop_id' => 1, - ]) - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseDeleted($response); - - // FIXME: empty response - // $this->assertArraySubset(['success' => true], $json); - - \Configuration::clearConfigurationCacheForTesting(); - \Configuration::loadConfiguration(); - - $this->assertEmpty($this->oauth2Client->getClientId()); - $this->assertEmpty($this->oauth2Client->getClientSecret()); - $this->assertFalse($this->oauth2Client->exists()); - - - } -} diff --git a/tests/src/Feature/Api/V1/ShopOauth2Client/StoreTest.php b/tests/src/Feature/Api/V1/ShopOauth2Client/StoreTest.php deleted file mode 100644 index 6ecf06357..000000000 --- a/tests/src/Feature/Api/V1/ShopOauth2Client/StoreTest.php +++ /dev/null @@ -1,106 +0,0 @@ -markTestSkipped(); - } - - /** - * @test - */ - public function itShouldSucceed() - { - $payload = [ - 'shop_id' => 1, - 'client_id' => $this->faker->slug, - // FIXME: something's wrong there - 'client_secret' => preg_replace('/faker->password), - 'uid' => $this->faker->uuid, - ]; - - $response = $this->client->post('/module/ps_accounts/apiV1ShopOauth2Client', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload($payload) - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseOk($response); -// $this->assertBodySubsetOrMarkAsIncomplete(['success' => true], $json); - - \Configuration::clearConfigurationCacheForTesting(); - \Configuration::loadConfiguration(); - - $this->assertEquals($payload['client_id'], $this->oauth2Client->getClientId()); - $this->assertEquals($payload['client_secret'], $this->oauth2Client->getClientSecret()); - } - - /** - * @test - */ - public function itShouldFail() - { - $payload = [ - 'shop_id' => 1, - // 'client_id' => $this->faker->slug, - 'client_secret' => $this->faker->password, - ]; - - $response = $this->client->post('/module/ps_accounts/apiV1ShopOauth2Client', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload($payload) - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - if ($response->statusCode !== 400) { - // trigger incomplete test status with some PHP5.6 environments - $this->assertBodySubsetOrMarkAsIncomplete(['error' => true], $json); - } - - $this->assertResponseBadRequest($response); - - $this->assertArraySubset(['error' => true], $json); - } -} diff --git a/tests/src/Feature/Api/V1/ShopToken/ShowTest.php b/tests/src/Feature/Api/V1/ShopToken/ShowTest.php deleted file mode 100644 index 532fffb74..000000000 --- a/tests/src/Feature/Api/V1/ShopToken/ShowTest.php +++ /dev/null @@ -1,108 +0,0 @@ -markTestSkipped(); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldSucceed() - { - $expiry = new \DateTimeImmutable('+10 days'); - $shopToken = $this->makeJwtToken($expiry, ['sub' => $this->faker->uuid]); - - $this->shopSession->setToken((string) $shopToken); - - $response = $this->client->get('/module/ps_accounts/apiV1ShopToken', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload([ - 'shop_id' => 1, - ]) - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseOk($response); - - $this->assertArraySubset([ - 'token' => (string) $shopToken, - 'refresh_token' => null, - ], $json); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldReturnInvalidPayloadError() - { - $response = $this->client->get('/module/ps_accounts/apiV1ShopToken', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload([ - 'shop_id' => 1, - ]) . 'foo' - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseUnauthorized($response); - - $this->assertArraySubset([ - 'error' => true, - ], $json); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldReturnUnauthorizedError() - { - $response = $this->client->get('/module/ps_accounts/apiV1ShopToken', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload([ - 'shop_id' => 99, - ]), - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseUnauthorized($response); - - $this->assertArraySubset([ - 'error' => true, - ], $json); - } -} diff --git a/tests/src/Feature/Api/V1/ShopUrl/ShowTest.php b/tests/src/Feature/Api/V1/ShopUrl/ShowTest.php deleted file mode 100644 index 22f9ea704..000000000 --- a/tests/src/Feature/Api/V1/ShopUrl/ShowTest.php +++ /dev/null @@ -1,106 +0,0 @@ -markTestSkipped(); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldSucceed() - { - $shop = $this->shopContext->formatShopData((array) \Shop::getShop(1)); - - $response = $this->client->get('/module/ps_accounts/apiV1ShopUrl', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload([ - 'shop_id' => 1, - ]) - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseOk($response); - - $this->assertEquals($shop->domain, $json['domain']); - $this->assertEquals($shop->domainSsl, $json['domain_ssl']); - $this->assertEquals($shop->physicalUri, $json['physical_uri']); - $this->assertEquals($shop->virtualUri, $json['virtual_uri']); - $this->assertEquals($this->configurationRepository->sslEnabled(), $json['ssl_activated']); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldReturnInvalidPayloadError() - { - $response = $this->client->get('/module/ps_accounts/apiV1ShopUrl', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload([ - 'shop_id' => 1, - ]) . 'foobar' - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseUnauthorized($response); - - $this->assertArraySubset([ - 'error' => true, - ], $json); - } - - /** - * @test - * - * @throws \Exception - */ - public function itShouldReturnUnauthorizedError() - { - $response = $this->client->get('/module/ps_accounts/apiV1ShopUrl', [ - 'headers' => [ - AbstractRestController::TOKEN_HEADER => (string) $this->encodePayload([ - 'shop_id' => 99, - ]), - ], - ]); - - $json = $this->getResponseJson($response); - - $this->module->getLogger()->info(print_r($json, true)); - - $this->assertResponseUnauthorized($response); - - $this->assertArraySubset([ - 'error' => true, - ], $json); - } -} From c06b276d5b8230945c95989fdc97ee4f465c5b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Schoenenberger?= <54308193+hschoenenberger@users.noreply.github.com> Date: Mon, 15 Sep 2025 09:45:54 +0200 Subject: [PATCH 46/46] [ACCOUNT-3087] feat: upgrade fail banner ux (#556) * feat: add button states * feat: update wordings & disable button on trigger reset * feat: translations for the new wordings * feat: translations by Gemini * feat: translations specific class --- .../admin/AdminAjaxPsAccountsController.php | 27 +++++++++----- translations/de.php | 36 +++++++++--------- translations/en.php | 7 ++-- translations/es.php | 36 +++++++++--------- translations/fr.php | 36 +++++++++--------- translations/it.php | 36 +++++++++--------- translations/nl.php | 36 +++++++++--------- translations/pl.php | 36 +++++++++--------- translations/pt.php | 36 +++++++++--------- translations/ro.php | 36 +++++++++--------- translations/ru.php | 37 ++++++++++--------- 11 files changed, 185 insertions(+), 174 deletions(-) diff --git a/controllers/admin/AdminAjaxPsAccountsController.php b/controllers/admin/AdminAjaxPsAccountsController.php index e559d41d0..e60004bd5 100644 --- a/controllers/admin/AdminAjaxPsAccountsController.php +++ b/controllers/admin/AdminAjaxPsAccountsController.php @@ -148,6 +148,10 @@ class AdminAjaxPsAccountsController extends \ModuleAdminController } '; + /** + * @var string + */ + private $translationClass; /** * AdminAjaxPsAccountsController constructor. @@ -160,6 +164,7 @@ public function __construct() $this->ajax = true; $this->content_only = true; + $this->translationClass = self::class; } /** @@ -273,17 +278,21 @@ protected function getNotificationsUrlMismatch()
- ' . $this->module->l('Action required: confirm your store URL') . ' + ' . $this->module->l('Action required: confirm your store URL', $this->translationClass) . '
-

' . $this->module->l('We\'ve noticed that your store\'s URL no longer matches the one registered in your PrestaShop Account.') . '
' . $this->module->l('For your services to function properly, you must either confirm this change or create a new identity for your store.') . '

+

+ ' . $this->module->l('We\'ve noticed that your store\'s URL no longer matches the one registered in your PrestaShop Account.', $this->translationClass) . ' +
+ ' . $this->module->l('For your services to function properly, you must either confirm this change or create a new identity for your store.', $this->translationClass) . ' +

    -
  • - ' . $this->module->l('Current store URL') . ': ' . $localFrontendUrl . '
  • -
  • - ' . $this->module->l('URL registered in PrestaShop Account') . ': ' . $cloudFrontendUrl . '
  • +
  • - ' . $this->module->l('Current store URL', $this->translationClass) . ': ' . $localFrontendUrl . '
  • +
  • - ' . $this->module->l('URL registered in PrestaShop Account', $this->translationClass) . ': ' . $cloudFrontendUrl . '
@@ -316,14 +325,14 @@ protected function getNotificationsUpgradeFailed()
- ' . $this->module->l('PrestaShop Account module wasn\'t upgraded properly.') . ' + ' . $this->module->l('Action required: reset your PS Account module', $this->translationClass) . '
-

' . $this->module->l('Please reset or reinstall the module') . '

+

' . $this->module->l('A simple reset is needed to finish the update and ensure all your modules are working correctly.', $this->translationClass) . '

diff --git a/translations/de.php b/translations/de.php index c064ceee2..0b00ba158 100644 --- a/translations/de.php +++ b/translations/de.php @@ -20,27 +20,27 @@ global $_MODULE; $_MODULE = []; $_MODULE['<{ps_accounts}prestashop>ps_accounts_40047b296fb8a4952e0512c7184e1ca3'] = 'PrestaShop Account'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_248ed4a0f71472c36d19bb0f1bcb68a0'] = 'Verknüpfen Sie Ihren Shop mit Ihrem PrestaShop-Konto, um Ihre Abonnements in Ihrem Backoffice zu aktivieren und zu verwalten. Deinstallieren Sie dieses Modul nicht, wenn Sie ein aktuelles Abonnement haben.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_ceeadaf870d9eaa81cf1fbe32614af64'] = 'Wenn Sie diese Aktion durchführen, werden Ihre PrestaShop-Dienste und Community-Dienste nicht mehr ordnungsgemäß funktionieren, da sie zur Authentifizierung das PrestaShop-Accounts-Modul nutzen.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_01a84505e1f42ba1b80ffed4f9f21221'] = 'Dieser Shop ist mit Ihrem PrestaShop Account verknüpft. Heben Sie die Verknüpfung Ihres Shops auf, wenn Ihre Live-Einstellungen nicht beeinträchtigt werden sollen.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_d82864f800210969f401370615e30568'] = 'Einige Shops sind mit Ihrem PrestaShop Account verknüpft. Wenn Sie diese Shops löschen, hat dies Auswirkungen auf Ihre Live-Einstellungen.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_aa8c92c02e922a0fff679d9f66fce2d0'] = 'Verknüpfen Sie Ihren Shop mit Ihrem PrestaShop-Konto, um Ihre Abonnements in Ihrem Backoffice zu aktivieren und zu verwalten. Deinstallieren Sie dieses Modul nicht, wenn Sie ein aktuelles Abonnement haben.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_05eeaea4d4c380b44bbcca3b1341f811'] = 'Wenn Sie diese Aktion durchführen, werden Ihre PrestaShop-Dienste und Community-Dienste nicht mehr ordnungsgemäß funktionieren, da sie zur Authentifizierung das PrestaShop-Accounts-Modul nutzen.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a6589b17d51617df13a46421dc86e004'] = 'Handlung erforderlich: Bestätigen Sie die URL Ihres Shops'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_97c826995e093d19f3c940c389515195'] = 'Wir haben bemerkt, dass die URL Ihres Shops nicht mehr mit der in Ihrem PrestaShop-Konto registrierten URL übereinstimmt.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_33fba3aba5e5d575db9f041232e64d2b'] = 'Damit Ihre Dienste ordnungsgemäß funktionieren, müssen Sie entweder diese Änderung bestätigen oder eine neue Identität für Ihren Shop erstellen.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'Aktuelle URL des Shops'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'Im PrestaShop-Konto registrierte URL'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Einstellungen überprüfen'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_7285968d2690ae18df8ee5e1686f545d'] = 'Handlung erforderlich: Ihr PS Account-Modul zurücksetzen'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_e1b80ece0a497aca4c753d437c288ca1'] = 'Ein einfacher Reset ist erforderlich, um das Update abzuschließen und sicherzustellen, dass alle Ihre Module ordnungsgemäß funktionieren.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_66949b1c09d857d4ecd2497a0734072b'] = 'Modul wird zurückgesetzt...'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_817ea9c22e6b11ecd69691c1dd47ef1a'] = 'Modul zurücksetzen'; +$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Beim Login ist ein Fehler aufgetreten, bitte kontaktieren Sie den PrestaShop-Support'; +$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Herzlich willkommen,'; +$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'greifen Sie auf Ihr Backoffice zu, um Ihr Geschäft zu verwalten.'; +$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Gehen Sie zum Backoffice'; +$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Auf anderem Weg verbinden'; +$_MODULE['<{ps_accounts}prestashop>displaybackofficeemployeemenu_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Verwalten Sie Ihr PrestaShop-Konto'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_f4f70727dc34561dfde1a3c529b6205c'] = 'Einstellungen'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Hilfe'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_b7ca62f12bc3b8e0d6422c028473a265'] = 'Ihr PrestaShop Account'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_53c4c7fe12fffd658daacfa5b41eb495'] = 'Ein Konto für die Verwaltung all Ihrer PrestaShop-Shops'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_655aa4af96bbb20cf78e14b560affb38'] = 'Erstellen Sie Ihren PrestaShop Account oder melden Sie sich bei Ihrem bestehenden Konto an'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_a60d78ec73c0c133ce16b10419a3fdf6'] = 'Verknüpfen Sie Ihr Geschäft mit Ihrem Konto'; -$_MODULE['<{ps_accounts}prestashop>login_23feb5280da1e3a86dc0fcc048f1a58d'] = 'Eine modulare Schnittstelle'; -$_MODULE['<{ps_accounts}prestashop>login_e661ffe463ad9b9bcea4c49d744058e7'] = 'Verwalten Sie Ihren gesamten Geschäftsbetrieb an einem Ort: Produktkatalog, Bestellungen, Zahlungen, Lieferungen und vieles mehr.'; -$_MODULE['<{ps_accounts}prestashop>login_8ea4efcd82809db344ff0d7a76695448'] = 'Alles, was Sie für Ihr Unternehmen brauchen'; -$_MODULE['<{ps_accounts}prestashop>login_f27ec0e8d78513e2e60830c62a3a13b8'] = 'Marketing-, Payment- und Performance-Analyse: Die Essentials-Suite von PrestaShop enthält alle Funktionen, die Sie für einen erfolgreichen Shop benötigen.'; -$_MODULE['<{ps_accounts}prestashop>login_f2d32f40a4060af2595051d0ab0278cb'] = 'Eine vollständig anpassbare Lösung'; -$_MODULE['<{ps_accounts}prestashop>login_9b4e2d0b927094565d6ce673644868a4'] = 'PrestaShop begleitet Ihr Wachstum. Unsere Module und die Module unserer Partner, mit denen Sie Ihren Shop anpassen und weiterentwickeln können, finden Sie auf dem PrestaShop Addons Marketplace'; -$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Herzlich willkommen,'; -$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'greifen Sie auf Ihr Backoffice zu, um Ihr Geschäft zu verwalten.'; -$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Gehen Sie zum Backoffice'; -$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Auf anderem Weg verbinden'; -$_MODULE['<{ps_accounts}prestashop>login_729f27762821b8ec88f1a656be7dff72'] = 'Sie müssen zuerst Ihr Konto aktivieren, indem Sie auf den Link in der E-Mail klicken. Wenn Sie einen neuen Aktivierungslink erhalten möchten,[1]klicken Sie bitte hier[/1]'; -$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Beim Login ist ein Fehler aufgetreten, bitte kontaktieren Sie den PrestaShop-Support'; -$_MODULE['<{ps_accounts}prestashop>login_fd359ea79740b865e45c2bf6fe5d3ab1'] = 'Mit diesem Konto können Sie nicht auf das Backoffice zugreifen. Versuchen Sie es mit einem anderen Konto oder wenden Sie sich an Ihren Administrator.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Verwalten Sie Ihr PrestaShop-Konto'; diff --git a/translations/en.php b/translations/en.php index 29d23e600..2f434a0f4 100644 --- a/translations/en.php +++ b/translations/en.php @@ -28,9 +28,10 @@ $_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'Current store URL'; $_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'URL registered in PrestaShop Account'; $_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Review settings'; -$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_dc3fd488f03d423a04da27ce66274c1b'] = 'Warning!'; -$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a1183bc21e7a9a0dc3410b1e88497cce'] = 'PrestaShop Account module wasn\'t upgraded properly.'; -$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_790360932d7b6d0f61cbd2611c9303b1'] = 'Please reset the module'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_7285968d2690ae18df8ee5e1686f545d'] = 'Action required: reset your PS Account module'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_e1b80ece0a497aca4c753d437c288ca1'] = 'A simple reset is needed to finish the update and ensure all your modules are working correctly.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_66949b1c09d857d4ecd2497a0734072b'] = 'Resetting module...'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_817ea9c22e6b11ecd69691c1dd47ef1a'] = 'Reset module'; $_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'An error occured during login, please contact PrestaShop support'; $_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Welcome,'; $_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Access your back office to manage your store.'; diff --git a/translations/es.php b/translations/es.php index ac2c4f21c..5ca6bd332 100644 --- a/translations/es.php +++ b/translations/es.php @@ -20,27 +20,27 @@ global $_MODULE; $_MODULE = []; $_MODULE['<{ps_accounts}prestashop>ps_accounts_40047b296fb8a4952e0512c7184e1ca3'] = 'PrestaShop Account'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_248ed4a0f71472c36d19bb0f1bcb68a0'] = 'Vincule su tienda a su cuenta PrestaShop para activar y gestionar sus suscripciones en su back office. No desinstale este módulo si tiene una suscripción en vigor.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_ceeadaf870d9eaa81cf1fbe32614af64'] = 'Esta acción impedirá inmediatamente que tus servicios PrestaShop y los servicios de la Comunidad funcionen, ya que utilizan el módulo PrestaShop Account para la autenticación.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_01a84505e1f42ba1b80ffed4f9f21221'] = 'Esta tienda está vinculada a tu cuenta PrestaShop. Desvincula tu tienda si no quieres que afecte a tu configuración live.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_d82864f800210969f401370615e30568'] = 'Algunas tiendas están vinculadas a tu cuenta PrestaShop. La eliminación de estas tiendas afectará a tu configuración live.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_aa8c92c02e922a0fff679d9f66fce2d0'] = 'Vincule su tienda a su cuenta PrestaShop para activar y gestionar sus suscripciones en su back office. No desinstale este módulo si tiene una suscripción en vigor.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_05eeaea4d4c380b44bbcca3b1341f811'] = 'Esta acción impedirá inmediatamente que tus servicios PrestaShop y los servicios de la Comunidad funcionen, ya que utilizan el módulo PrestaShop Account para la autenticación.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a6589b17d51617df13a46421dc86e004'] = 'Acción requerida: confirma el URL de tu tienda'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_97c826995e093d19f3c940c389515195'] = 'Hemos notado que la URL de su tienda ya no coincide con la registrada en su cuenta PrestaShop.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_33fba3aba5e5d575db9f041232e64d2b'] = 'Para que tus servicios funcionen correctamente, debes confirmar este cambio o crear una nueva identidad para tu tienda.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'URL actual de la tienda'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'URL registrado en la cuenta PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Revisar la configuración'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_7285968d2690ae18df8ee5e1686f545d'] = 'Acción requerida: restablecer su módulo de cuenta de PS'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_e1b80ece0a497aca4c753d437c288ca1'] = 'Es necesario un simple restablecimiento para finalizar la actualización y asegurarse de que todos los módulos funcionan correctamente.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_66949b1c09d857d4ecd2497a0734072b'] = 'Restableciendo módulo...'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_817ea9c22e6b11ecd69691c1dd47ef1a'] = 'Restablecer módulo'; +$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Se ha producido un error durante el inicio de sesión, ponte en contacto con el soporte de PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Bienvenido,'; +$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Accede a tu back office para gestionar tu tienda.'; +$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Ir al back office'; +$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Conectar con otro método'; +$_MODULE['<{ps_accounts}prestashop>displaybackofficeemployeemenu_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Gestione tu cuenta PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_f4f70727dc34561dfde1a3c529b6205c'] = 'Configuración'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Centro de ayuda'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_b7ca62f12bc3b8e0d6422c028473a265'] = 'Tu cuenta PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_53c4c7fe12fffd658daacfa5b41eb495'] = 'Una cuenta para gestionar todas sus tiendas PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_655aa4af96bbb20cf78e14b560affb38'] = 'Crea tu cuenta PrestaShop o inicia sesión en tu cuenta existente'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_a60d78ec73c0c133ce16b10419a3fdf6'] = 'Vincule su tienda a su cuenta'; -$_MODULE['<{ps_accounts}prestashop>login_23feb5280da1e3a86dc0fcc048f1a58d'] = 'Un back-office fácil de usar'; -$_MODULE['<{ps_accounts}prestashop>login_e661ffe463ad9b9bcea4c49d744058e7'] = 'Gestiona todo tu negocio en un solo lugar: catálogo de productos, pedidos, pagos, envíos y mucho más.'; -$_MODULE['<{ps_accounts}prestashop>login_8ea4efcd82809db344ff0d7a76695448'] = 'Todo lo esencial para tu negocio'; -$_MODULE['<{ps_accounts}prestashop>login_f27ec0e8d78513e2e60830c62a3a13b8'] = 'Marketing, pagos y análisis de rendimiento: la suite PrestaShop Essentials incluye todas las funciones que necesitas para que tu tienda tenga éxito.'; -$_MODULE['<{ps_accounts}prestashop>login_f2d32f40a4060af2595051d0ab0278cb'] = 'Una solución 100% personalizable'; -$_MODULE['<{ps_accounts}prestashop>login_9b4e2d0b927094565d6ce673644868a4'] = 'PrestaShop acompaña tu crecimiento. Encuentra nuestros módulos y los de nuestros partners en PrestaShop Marketplace para personalizar y desarrollar tu tienda'; -$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Bienvenido,'; -$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Accede a tu back office para gestionar tu tienda.'; -$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Ir al back office'; -$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Conectar con otro método '; -$_MODULE['<{ps_accounts}prestashop>login_729f27762821b8ec88f1a656be7dff72'] = 'Primero tienes que activar tu cuenta haciendo clic en el enlace del correo electrónico. Si necesitas recibir un nuevo enlace de activación, [1]haz clic aquí[/1]'; -$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Se ha producido un error durante el inicio de sesión, ponte en contacto con el soporte de PrestaShop'; -$_MODULE['<{ps_accounts}prestashop>login_fd359ea79740b865e45c2bf6fe5d3ab1'] = 'No puedes acceder al back office con esta cuenta. Intenta otra cuenta o contacta con tu administrador.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Gestione tu cuenta PrestaShop'; diff --git a/translations/fr.php b/translations/fr.php index 08545bc50..ffc24aede 100644 --- a/translations/fr.php +++ b/translations/fr.php @@ -20,27 +20,27 @@ global $_MODULE; $_MODULE = []; $_MODULE['<{ps_accounts}prestashop>ps_accounts_40047b296fb8a4952e0512c7184e1ca3'] = 'PrestaShop Account'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_248ed4a0f71472c36d19bb0f1bcb68a0'] = 'Liez votre boutique à votre compte PrestaShop pour activer et gérer vos abonnements dans votre back-office. Ne désinstallez pas ce module si vous avez un abonnement en cours.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_ceeadaf870d9eaa81cf1fbe32614af64'] = 'Cette action empêchera immédiatement vos services PrestaShop et Community Services de fonctionner car ils utilisent le module PrestaShop Account pour l\'authentification.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_01a84505e1f42ba1b80ffed4f9f21221'] = 'Votre boutique est liée avec PrestaShop account. Veuillez délier votre boutique si vous ne voulez pas impacter les changements sur Prestashop account.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_d82864f800210969f401370615e30568'] = 'Certaines de vos boutiques sont liées avec PrestaShop account. Supprimer une de ces boutiques impactera les changements sur Prestashop account.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_aa8c92c02e922a0fff679d9f66fce2d0'] = 'Liez votre boutique à votre compte PrestaShop pour activer et gérer vos abonnements dans votre back-office. Ne désinstallez pas ce module si vous avez un abonnement en cours.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_05eeaea4d4c380b44bbcca3b1341f811'] = 'Cette action empêchera immédiatement vos services PrestaShop et Community Services de fonctionner car ils utilisent le module PrestaShop Account pour l\'authentification.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a6589b17d51617df13a46421dc86e004'] = 'Action requise : confirmez l\'URL de votre boutique'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_97c826995e093d19f3c940c389515195'] = 'Nous avons constaté que l\'URL de votre boutique ne correspond plus à celle enregistrée dans votre compte PrestaShop.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_33fba3aba5e5d575db9f041232e64d2b'] = 'Pour que vos services fonctionnent correctement, vous devez soit confirmer cette modification, soit créer une nouvelle identité pour votre boutique.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'URL de la boutique actuelle'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'URL enregistrée dans PrestaShop Account'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Examiner les paramètres'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_7285968d2690ae18df8ee5e1686f545d'] = 'Action requise : réinitialisez votre module PS Account'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_e1b80ece0a497aca4c753d437c288ca1'] = 'Une simple réinitialisation est nécessaire pour terminer la mise à jour et s\'assurer que tous vos modules fonctionnent correctement.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_66949b1c09d857d4ecd2497a0734072b'] = 'Réinitialisation du module...'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_817ea9c22e6b11ecd69691c1dd47ef1a'] = 'Réinitialiser le module'; +$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Une erreur s\'est produite lors de la connexion, veuillez contacter le support PrestaShop.'; +$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Bienvenue,'; +$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Accédez à votre back-office pour gérer votre boutique.'; +$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Accéder au back-office'; +$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Se connecter avec une autre méthode'; +$_MODULE['<{ps_accounts}prestashop>displaybackofficeemployeemenu_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Gérer votre compte PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_f4f70727dc34561dfde1a3c529b6205c'] = 'Paramètres'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Aide'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_b7ca62f12bc3b8e0d6422c028473a265'] = 'Votre compte PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_53c4c7fe12fffd658daacfa5b41eb495'] = 'Un seul compte pour gérer toutes vos boutiques PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_655aa4af96bbb20cf78e14b560affb38'] = 'Créer votre compte PrestaShop ou vous connecter à votre compte existant'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_a60d78ec73c0c133ce16b10419a3fdf6'] = 'Liez votre boutique à votre compte'; -$_MODULE['<{ps_accounts}prestashop>login_23feb5280da1e3a86dc0fcc048f1a58d'] = 'Un back-office simple à prendre en main'; -$_MODULE['<{ps_accounts}prestashop>login_e661ffe463ad9b9bcea4c49d744058e7'] = 'Gérez toute votre activité au même endroit : catalogue de produits, commandes, paiements, livraisons et bien plus encore.'; -$_MODULE['<{ps_accounts}prestashop>login_8ea4efcd82809db344ff0d7a76695448'] = 'Tous les indispensables pour votre business'; -$_MODULE['<{ps_accounts}prestashop>login_f27ec0e8d78513e2e60830c62a3a13b8'] = 'Marketing, paiement et analyse des performances : la suite PrestaShop Essentials comprend toutes les fonctionnalités dont vous avez besoin pour assurer le succès de votre boutique.'; -$_MODULE['<{ps_accounts}prestashop>login_f2d32f40a4060af2595051d0ab0278cb'] = 'Une solution 100% personnalisable'; -$_MODULE['<{ps_accounts}prestashop>login_9b4e2d0b927094565d6ce673644868a4'] = 'PrestaShop accompagne votre croissance. Retrouvez nos modules et ceux de nos partenaires sur la Marketplace PrestaShop pour personnaliser et développer votre boutique.'; -$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Bienvenue,'; -$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Accédez à votre back-office pour gérer votre boutique.'; -$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Accéder au back-office'; -$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Se connecter avec une autre méthode'; -$_MODULE['<{ps_accounts}prestashop>login_729f27762821b8ec88f1a656be7dff72'] = 'Vous devez d\'abord activer votre compte en cliquant sur le lien contenu dans l\'e-mail. Si vous souhaitez recevoir un nouveau lien d\'activation, [1]veuillez cliquer ici[/1].'; -$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Une erreur s\'est produite lors de la connexion, veuillez contacter le support PrestaShop.'; -$_MODULE['<{ps_accounts}prestashop>login_fd359ea79740b865e45c2bf6fe5d3ab1'] = 'Vous ne pouvez pas accéder au back-office avec ce compte. Essayez un autre compte ou contactez votre administrateur.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Gérer votre compte PrestaShop'; diff --git a/translations/it.php b/translations/it.php index fec4225c6..697446ec0 100644 --- a/translations/it.php +++ b/translations/it.php @@ -20,27 +20,27 @@ global $_MODULE; $_MODULE = []; $_MODULE['<{ps_accounts}prestashop>ps_accounts_40047b296fb8a4952e0512c7184e1ca3'] = 'Account PrestaShop'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_248ed4a0f71472c36d19bb0f1bcb68a0'] = 'Collegate il vostro negozio al vostro account PrestaShop per attivare e gestire gli abbonamenti nel vostro back office. Non disinstallate questo modulo se avete un abbonamento in corso.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_ceeadaf870d9eaa81cf1fbe32614af64'] = 'Questa azione impedirà immediatamente il funzionamento dei tuoi servizi PrestaShop e di quelli della Community che utilizzano il modulo PrestaShop Accounts per l\'autenticazione.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_01a84505e1f42ba1b80ffed4f9f21221'] = 'Questo negozio è collegato al tuo account PrestaShop. Disconnetti il tuo negozio se non vuoi avere un impatto sulle tue impostazioni live.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_d82864f800210969f401370615e30568'] = 'Alcuni negozi sono collegati al tuo account PrestaShop. La cancellazione di questi negozi avrà un impatto sulle tue impostazioni live.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_aa8c92c02e922a0fff679d9f66fce2d0'] = 'Collegate il vostro negozio al vostro account PrestaShop per attivare e gestire gli abbonamenti nel vostro back office. Non disinstallate questo modulo se avete un abbonamento in corso.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_05eeaea4d4c380b44bbcca3b1341f811'] = 'Questa azione impedirà immediatamente il funzionamento dei tuoi servizi PrestaShop e di quelli della Community che utilizzano il modulo PrestaShop Accounts per l\'autenticazione.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a6589b17d51617df13a46421dc86e004'] = 'Azione richiesta: conferma l\'URL del tuo negozio'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_97c826995e093d19f3c940c389515195'] = 'Abbiamo notato che l\'URL del tuo negozio non corrisponde più a quello registrato nel tuo account PrestaShop.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_33fba3aba5e5d575db9f041232e64d2b'] = 'Per il corretto funzionamento dei tuoi servizi, devi confermare questa modifica o creare una nuova identità per il tuo negozio.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'URL attuale del negozio'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'URL registrato nell\'account PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Rivedi le impostazioni'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_7285968d2690ae18df8ee5e1686f545d'] = 'Azione richiesta: reimposta il tuo modulo PS Account'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_e1b80ece0a497aca4c753d437c288ca1'] = 'È necessario un semplice ripristino per completare l\'aggiornamento e garantire che tutti i moduli funzionino correttamente.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_66949b1c09d857d4ecd2497a0734072b'] = 'Ripristino del modulo...'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_817ea9c22e6b11ecd69691c1dd47ef1a'] = 'Reimposta modulo'; +$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Si è verificato un errore durante il login, contatta l\'assistenza PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Ti diamo il benvenuto.'; +$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Accedi al tuo back office per gestire il tuo negozio.'; +$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Entra nel back office'; +$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Connettiti con un altro metodo'; +$_MODULE['<{ps_accounts}prestashop>displaybackofficeemployeemenu_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Gestire l\'account PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_f4f70727dc34561dfde1a3c529b6205c'] = 'Impostazioni'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Assistenza'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_b7ca62f12bc3b8e0d6422c028473a265'] = 'Il tuo account PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_53c4c7fe12fffd658daacfa5b41eb495'] = 'Un unico account per gestire tutti i vostri negozi PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_655aa4af96bbb20cf78e14b560affb38'] = 'Crea il tuo account PrestaShop o accedi al tuo account esistente'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_a60d78ec73c0c133ce16b10419a3fdf6'] = 'Collegate il vostro negozio al vostro account'; -$_MODULE['<{ps_accounts}prestashop>login_23feb5280da1e3a86dc0fcc048f1a58d'] = 'Un\'interfaccia modulare'; -$_MODULE['<{ps_accounts}prestashop>login_e661ffe463ad9b9bcea4c49d744058e7'] = 'Gestisci tutta la tua attività in un unico luogo: catalogo prodotti, ordini, pagamenti, consegna e molto altro.'; -$_MODULE['<{ps_accounts}prestashop>login_8ea4efcd82809db344ff0d7a76695448'] = 'Tutto l\'essenziale per la tua attività'; -$_MODULE['<{ps_accounts}prestashop>login_f27ec0e8d78513e2e60830c62a3a13b8'] = 'Marketing, pagamenti e analisi delle prestazioni: la suite PrestaShop Essentials include tutte le funzionalità necessarie per il successo del tuo negozio.'; -$_MODULE['<{ps_accounts}prestashop>login_f2d32f40a4060af2595051d0ab0278cb'] = 'Una soluzione personalizzabile al 100%'; -$_MODULE['<{ps_accounts}prestashop>login_9b4e2d0b927094565d6ce673644868a4'] = 'PrestaShop accompagna la tua crescita. Trova i nostri moduli e quelli dei nostri partner sul PrestaShop Addons Marketplace per personalizzare e sviluppare il tuo negozio'; -$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Ti diamo il benvenuto.'; -$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Accedi al tuo back office per gestire il tuo negozio.'; -$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Entra nel back office'; -$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Connettiti con un altro metodo'; -$_MODULE['<{ps_accounts}prestashop>login_729f27762821b8ec88f1a656be7dff72'] = 'È necessario attivare il proprio account facendo clic sul link contenuto nell\'e-mail. Se hai bisogno di ricevere un nuovo link di attivazione,[1]clicca qui[/1].'; -$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Si è verificato un errore durante il login, contatta l\'assistenza PrestaShop'; -$_MODULE['<{ps_accounts}prestashop>login_fd359ea79740b865e45c2bf6fe5d3ab1'] = 'Non è possibile accedere al back office con questo account. Prova con un altro account o contatta l\'amministratore.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Gestire l\'account PrestaShop'; diff --git a/translations/nl.php b/translations/nl.php index 9822c14b6..b143eb87a 100644 --- a/translations/nl.php +++ b/translations/nl.php @@ -20,27 +20,27 @@ global $_MODULE; $_MODULE = []; $_MODULE['<{ps_accounts}prestashop>ps_accounts_40047b296fb8a4952e0512c7184e1ca3'] = 'PrestaShop-account'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_248ed4a0f71472c36d19bb0f1bcb68a0'] = 'Koppel uw winkel aan uw PrestaShop-account om uw abonnementen te activeren en te beheren in uw backoffice. Verwijder deze module niet als u een lopend abonnement hebt.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_ceeadaf870d9eaa81cf1fbe32614af64'] = 'Deze actie zal onmiddellijk voorkomen dat uw PrestaShop diensten en communautaire diensten werken als ze PrestaShop Accounts module voor authenticatie gebruiken.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_01a84505e1f42ba1b80ffed4f9f21221'] = 'Deze winkel is gekoppeld aan uw PrestaShop account. Unlink uw winkel als u niet wilt dat uw live-instellingen beïnvloeden.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_d82864f800210969f401370615e30568'] = 'Sommige winkels zijn gekoppeld aan uw PrestaShop account. Verwijderen van deze winkels zal invloed hebben op uw live-instellingen.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_aa8c92c02e922a0fff679d9f66fce2d0'] = 'Koppel uw winkel aan uw PrestaShop-account om uw abonnementen te activeren en te beheren in uw backoffice. Verwijder deze module niet als u een lopend abonnement hebt.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_05eeaea4d4c380b44bbcca3b1341f811'] = 'Deze actie zal onmiddellijk voorkomen dat uw PrestaShop diensten en Community diensten werken als ze PrestaShop Accounts module voor authenticatie gebruiken.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a6589b17d51617df13a46421dc86e004'] = 'Actie vereist: bevestig de URL van uw winkel'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_97c826995e093d19f3c940c389515195'] = 'We hebben gemerkt dat de URL van uw winkel niet meer overeenkomt met de URL die is geregistreerd in uw PrestaShop-account.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_33fba3aba5e5d575db9f041232e64d2b'] = 'Om uw diensten goed te laten functioneren, moet u deze wijziging bevestigen of een nieuwe identiteit voor uw winkel aanmaken.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'Huidige URL van de winkel'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'URL geregistreerd in PrestaShop-account'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Instellingen bekijken'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_7285968d2690ae18df8ee5e1686f545d'] = 'Actie vereist: reset uw PS Account-module'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_e1b80ece0a497aca4c753d437c288ca1'] = 'Een eenvoudige reset is nodig om de update te voltooien en ervoor te zorgen dat al uw modules correct werken.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_66949b1c09d857d4ecd2497a0734072b'] = 'Module wordt gereset...'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_817ea9c22e6b11ecd69691c1dd47ef1a'] = 'Module resetten'; +$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Er is een fout opgetreden tijdens het inloggen, neem contact op met PrestaShop support.'; +$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Welkom,'; +$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Toegang tot uw backoffice om uw winkel te beheren.'; +$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Ga naar de back-office'; +$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Verbinden met een andere methode'; +$_MODULE['<{ps_accounts}prestashop>displaybackofficeemployeemenu_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Beheer uw PrestaShop account'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_f4f70727dc34561dfde1a3c529b6205c'] = 'Instellingen'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Help'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_b7ca62f12bc3b8e0d6422c028473a265'] = 'Uw PrestaShop account'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_53c4c7fe12fffd658daacfa5b41eb495'] = 'Eén account om al uw PrestaShop-winkels te beheren'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_655aa4af96bbb20cf78e14b560affb38'] = 'Maak uw PrestaShop account aan of log in op uw bestaande account'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_a60d78ec73c0c133ce16b10419a3fdf6'] = 'Koppel je winkel aan je account'; -$_MODULE['<{ps_accounts}prestashop>login_23feb5280da1e3a86dc0fcc048f1a58d'] = 'Een modulaire interface'; -$_MODULE['<{ps_accounts}prestashop>login_e661ffe463ad9b9bcea4c49d744058e7'] = 'Beheer uw hele bedrijf op één plaats: productcatalogus, bestellingen, betalingen, levering en nog veel meer.'; -$_MODULE['<{ps_accounts}prestashop>login_8ea4efcd82809db344ff0d7a76695448'] = 'Al het essentiële voor uw bedrijf'; -$_MODULE['<{ps_accounts}prestashop>login_f27ec0e8d78513e2e60830c62a3a13b8'] = 'Marketing, betaling en prestatieanalyse: de PrestaShop Essentials suite bevat alle functies die u nodig heeft om uw winkel succesvol te maken.'; -$_MODULE['<{ps_accounts}prestashop>login_f2d32f40a4060af2595051d0ab0278cb'] = 'Een 100% aanpasbare oplossing'; -$_MODULE['<{ps_accounts}prestashop>login_9b4e2d0b927094565d6ce673644868a4'] = 'PrestaShop begeleidt uw groei. Vind onze modules en die van onze partners op PrestaShop Addons Marketplace om uw winkel aan te passen en te ontwikkelen.'; -$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Welkom,'; -$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Toegang tot uw backoffice om uw winkel te beheren.'; -$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Ga naar de back-office'; -$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Verbinden met een andere methode'; -$_MODULE['<{ps_accounts}prestashop>login_729f27762821b8ec88f1a656be7dff72'] = 'U moet uw account eerst activeren door op de link in de e-mail te klikken. Als u een nieuwe activeringslink wilt ontvangen,[1]klik dan hier[/1]'; -$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Er is een fout opgetreden tijdens het inloggen, neem contact op met PrestaShop support.'; -$_MODULE['<{ps_accounts}prestashop>login_fd359ea79740b865e45c2bf6fe5d3ab1'] = 'U hebt geen toegang tot de backoffice met dit account. Probeer een ander account of neem contact op met uw beheerder.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Beheer uw PrestaShop account'; diff --git a/translations/pl.php b/translations/pl.php index 75e4b61d5..000934f0d 100644 --- a/translations/pl.php +++ b/translations/pl.php @@ -20,27 +20,27 @@ global $_MODULE; $_MODULE = []; $_MODULE['<{ps_accounts}prestashop>ps_accounts_40047b296fb8a4952e0512c7184e1ca3'] = 'Konto PrestaShop'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_248ed4a0f71472c36d19bb0f1bcb68a0'] = 'Połącz swój sklep z kontem PrestaShop, aby aktywować subskrypcje i zarządzać nimi na swoim zapleczu. Nie odinstalowuj tego modułu, jeśli posiadasz aktualną subskrypcję.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_ceeadaf870d9eaa81cf1fbe32614af64'] = 'Ta akcja uniemożliwi natychmiastowe działanie Twoich usług PrestaShop i usług Community, ponieważ używają one modułu PrestaShop Accounts do uwierzytelniania.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_01a84505e1f42ba1b80ffed4f9f21221'] = 'Ten sklep jest powiązany z Twoim kontem PrestaShop. Odłącz Twój sklep, jeśli nie chcesz wpływać na Twoje aktualne ustawienia.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_d82864f800210969f401370615e30568'] = 'Niektóre sklepy są powiązane z Twoim kontem PrestaShop. Usunięcie tych sklepów będzie miało wpływ na Twoje aktualne ustawienia.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_aa8c92c02e922a0fff679d9f66fce2d0'] = 'Połącz swój sklep z kontem PrestaShop, aby aktywować subskrypcje i zarządzać nimi na swoim zapleczu. Nie odinstalowuj tego modułu, jeśli posiadasz aktualną subskrypcję.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_05eeaea4d4c380b44bbcca3b1341f811'] = 'Ta akcja uniemożliwi natychmiastowe działanie Twoich usług PrestaShop i usług Community, ponieważ używają one modułu PrestaShop Accounts do uwierzytelniania.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a6589b17d51617df13a46421dc86e004'] = 'Wymagane działanie: potwierdź URL swojego sklepu'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_97c826995e093d19f3c940c389515195'] = 'Zauważyliśmy, że adres URL Twojego sklepu nie pasuje już do adresu zarejestrowanego na Twoim koncie PrestaShop.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_33fba3aba5e5d575db9f041232e64d2b'] = 'Aby Twoje usługi działały poprawnie, musisz potwierdzić tę zmianę lub utworzyć nową tożsamość dla swojego sklepu.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'Aktualny URL sklepu'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'URL zarejestrowany na koncie PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Przejrzyj ustawienia'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_7285968d2690ae18df8ee5e1686f545d'] = 'Wymagane działanie: zresetuj moduł PS Account'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_e1b80ece0a497aca4c753d437c288ca1'] = 'Wystarczy proste zresetowanie, aby dokończyć aktualizację i upewnić się, że wszystkie moduły działają poprawnie.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_66949b1c09d857d4ecd2497a0734072b'] = 'Resetowanie modułu...'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_817ea9c22e6b11ecd69691c1dd47ef1a'] = 'Zresetuj moduł'; +$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Wystąpił błąd podczas logowania. Skontaktuj się z zespołem wsparcia technicznego PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Witaj,'; +$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Uzyskaj dostęp do back office, aby zarządzać swoim sklepem.'; +$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Przejdź do back office'; +$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Połącz za pomocą innej metody'; +$_MODULE['<{ps_accounts}prestashop>displaybackofficeemployeemenu_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Zarządzaj swoim kontem PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_f4f70727dc34561dfde1a3c529b6205c'] = 'Ustawienia'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Pomoc'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_b7ca62f12bc3b8e0d6422c028473a265'] = 'Twoje konto PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_53c4c7fe12fffd658daacfa5b41eb495'] = 'Jedno konto do zarządzania wszystkimi sklepami PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_655aa4af96bbb20cf78e14b560affb38'] = 'Utwórz konto PrestaShop lub zaloguj się do istniejącego konta'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_a60d78ec73c0c133ce16b10419a3fdf6'] = 'Połącz swój sklep z kontem'; -$_MODULE['<{ps_accounts}prestashop>login_23feb5280da1e3a86dc0fcc048f1a58d'] = 'Łatwy w obsłudze back office'; -$_MODULE['<{ps_accounts}prestashop>login_e661ffe463ad9b9bcea4c49d744058e7'] = 'Zarządzaj całym biznesem w jednym miejscu: katalogiem produktów, zamówieniami, płatnościami, dostawą i innymi.'; -$_MODULE['<{ps_accounts}prestashop>login_8ea4efcd82809db344ff0d7a76695448'] = 'Wszystkie niezbędne elementy dla Twojej firmy'; -$_MODULE['<{ps_accounts}prestashop>login_f27ec0e8d78513e2e60830c62a3a13b8'] = 'Marketing, płatności i analiza wydajności: pakiet Essentials od PrestaShop zawiera wszystkie funkcje, których potrzebujesz, aby Twój sklep odniósł sukces.'; -$_MODULE['<{ps_accounts}prestashop>login_f2d32f40a4060af2595051d0ab0278cb'] = 'Rozwiązanie w 100% dostosowane do potrzeb klienta'; -$_MODULE['<{ps_accounts}prestashop>login_9b4e2d0b927094565d6ce673644868a4'] = 'PrestaShop wspiera Twój rozwój. Na PrestaShop Marketplace znajdziesz nasze autorskie moduły oraz te od naszych partnerów - to one pomogą Ci dostosować i rozbudować Twój sklep'; -$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Cześć,'; -$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Uzyskaj dostęp do back office, aby zarządzać swoim sklepem.'; -$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Przejdź do back office'; -$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Połącz za pomocą innej metody'; -$_MODULE['<{ps_accounts}prestashop>login_729f27762821b8ec88f1a656be7dff72'] = 'Najpierw musisz aktywować swoje konto, klikając na link w mailu. Jeśli chcesz otrzymać nowy link aktywacyjny, [1]kliknij tutaj[/1]'; -$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Wystąpił błąd podczas logowania. Skontaktuj się z zespołem wsparcia technicznego PrestaShop'; -$_MODULE['<{ps_accounts}prestashop>login_fd359ea79740b865e45c2bf6fe5d3ab1'] = 'Nie można uzyskać dostępu do back office z wykorzystaniem tego konta. Spróbuj użyć innego konta lub skontaktuj się z administratorem.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Zarządzaj swoim kontem PrestaShop'; diff --git a/translations/pt.php b/translations/pt.php index 21052440e..8f60ff7dd 100644 --- a/translations/pt.php +++ b/translations/pt.php @@ -20,27 +20,27 @@ global $_MODULE; $_MODULE = []; $_MODULE['<{ps_accounts}prestashop>ps_accounts_40047b296fb8a4952e0512c7184e1ca3'] = 'Conta PrestaShop'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_248ed4a0f71472c36d19bb0f1bcb68a0'] = 'Ligue a sua loja à sua conta PrestaShop para ativar e gerir as suas subscrições no seu back office. Não desinstale este módulo se tiver uma subscrição em curso.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_ceeadaf870d9eaa81cf1fbe32614af64'] = 'Esta ação impedirá imediatamente que os seus serviços PrestaShop e os serviços da Comunidade funcionem visto que eles usam o módulo PrestaShop Accounts para autenticação.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_01a84505e1f42ba1b80ffed4f9f21221'] = 'Esta loja está conectada à sua conta PrestaShop. Desconecte a sua loja se não quiser impactar as suas configurações ao vivo.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_d82864f800210969f401370615e30568'] = 'Algumas lojas estão conectadas à sua conta PrestaShop. A exclusão destas lojas impactará nas suas configurações ao vivo.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_aa8c92c02e922a0fff679d9f66fce2d0'] = 'Ligue a sua loja à sua conta PrestaShop para ativar e gerir as suas subscrições no seu back office. Não desinstale este módulo se tiver uma subscrição em curso.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_05eeaea4d4c380b44bbcca3b1341f811'] = 'Esta ação impedirá imediatamente que os seus serviços PrestaShop e os serviços da Comunidade funcionem visto que eles usam o módulo PrestaShop Accounts para autenticação.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a6589b17d51617df13a46421dc86e004'] = 'Ação necessária: confirme o URL da sua loja'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_97c826995e093d19f3c940c389515195'] = 'Notamos que o URL da sua loja não corresponde mais ao que foi registado na sua conta PrestaShop.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_33fba3aba5e5d575db9f041232e64d2b'] = 'Para que os seus serviços funcionem corretamente, você deve confirmar esta alteração ou criar uma nova identidade para a sua loja.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'URL atual da loja'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'URL registado na conta PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Rever configurações'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_7285968d2690ae18df8ee5e1686f545d'] = 'Ação necessária: redefina o seu módulo PS Account'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_e1b80ece0a497aca4c753d437c288ca1'] = 'É necessário um simples reset para concluir a atualização e garantir que todos os seus módulos estão a funcionar corretamente.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_66949b1c09d857d4ecd2497a0734072b'] = 'A redefinir o módulo...'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_817ea9c22e6b11ecd69691c1dd47ef1a'] = 'Redefinir módulo'; +$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Ocorreu um erro durante o login, entre em contacto com o suporte da PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Boas-vindas,'; +$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Acesse o seu back-office para gerenciar a sua loja.'; +$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Ir para o back-office'; +$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Conectar com outro método'; +$_MODULE['<{ps_accounts}prestashop>displaybackofficeemployeemenu_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Gerir a sua conta PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_f4f70727dc34561dfde1a3c529b6205c'] = 'Configurações'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Ajuda'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_b7ca62f12bc3b8e0d6422c028473a265'] = 'Sua conta PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_53c4c7fe12fffd658daacfa5b41eb495'] = 'Uma conta para gerir todas as suas lojas PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_655aa4af96bbb20cf78e14b560affb38'] = 'Crie a sua conta PrestaShop ou faça login na sua conta'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_a60d78ec73c0c133ce16b10419a3fdf6'] = 'Associe a sua loja à sua conta'; -$_MODULE['<{ps_accounts}prestashop>login_23feb5280da1e3a86dc0fcc048f1a58d'] = 'Um back-office fácil de usar'; -$_MODULE['<{ps_accounts}prestashop>login_e661ffe463ad9b9bcea4c49d744058e7'] = 'Gerencie todo o seu negócio em um só local: catálogo de produtos, encomendas, pagamentos, entregas e muito mais.'; -$_MODULE['<{ps_accounts}prestashop>login_8ea4efcd82809db344ff0d7a76695448'] = 'Tudo o que é essencial para o seu negócio'; -$_MODULE['<{ps_accounts}prestashop>login_f27ec0e8d78513e2e60830c62a3a13b8'] = 'Marketing, pagamento e análise de desempenho: a suite PrestaShop Essentials inclui todas as funcionalidades necessárias para que a sua loja seja bem sucedida.'; -$_MODULE['<{ps_accounts}prestashop>login_f2d32f40a4060af2595051d0ab0278cb'] = 'Uma solução 100% personalizável'; -$_MODULE['<{ps_accounts}prestashop>login_9b4e2d0b927094565d6ce673644868a4'] = 'A PrestaShop acompanha o seu crescimento. Encontre os nossos módulos e os dos nossos parceiros no PrestaShop Addons Marketplace para personalizar e desenvolver a sua loja'; -$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Boas-vindas,'; -$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Acesse o seu back-office para gerenciar a sua loja.'; -$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Ir para o back-office'; -$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Conectar com outro método'; -$_MODULE['<{ps_accounts}prestashop>login_729f27762821b8ec88f1a656be7dff72'] = 'Você precisa ativar a sua conta primeiro clicando no link no e-mail. Se precisar receber um novo link de ativação,[1]clique aqui[/1]'; -$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'Ocorreu um erro durante o login, entre em contato com o suporte da PrestaShop'; -$_MODULE['<{ps_accounts}prestashop>login_fd359ea79740b865e45c2bf6fe5d3ab1'] = 'Você não pode acessar o back-office com esta conta. Tente outra conta ou entre em contato com o administrador.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Gerir a sua conta PrestaShop'; diff --git a/translations/ro.php b/translations/ro.php index b7096ffb6..0dec45933 100644 --- a/translations/ro.php +++ b/translations/ro.php @@ -20,27 +20,27 @@ global $_MODULE; $_MODULE = []; $_MODULE['<{ps_accounts}prestashop>ps_accounts_40047b296fb8a4952e0512c7184e1ca3'] = 'PrestaShop Account'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_248ed4a0f71472c36d19bb0f1bcb68a0'] = 'Conectați magazinul dvs. la contul PrestaShop pentru a activa și gestiona abonamentele dvs. în back office. Nu dezinstalați acest modul dacă aveți un abonament curent.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_ceeadaf870d9eaa81cf1fbe32614af64'] = 'Această acțiune va împiedica imediat funcționarea serviciilor PrestaShop și a serviciilor comunitare, deoarece acestea utilizează modulul PrestaShop Account pentru autentificare.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_01a84505e1f42ba1b80ffed4f9f21221'] = 'Acest magazin este legat de contul tău PrestaShop. Deconectează-ți magazinul dacă nu dorești să îți afectezi setările live.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_d82864f800210969f401370615e30568'] = 'Unele magazine sunt legate de contul tău de PrestaShop. Ștergerea acestor magazine îți va afecta setările live.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_aa8c92c02e922a0fff679d9f66fce2d0'] = 'Conectați magazinul dvs. la contul PrestaShop pentru a activa și gestiona abonamentele dvs. în back office. Nu dezinstalați acest modul dacă aveți un abonament curent.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_05eeaea4d4c380b44bbcca3b1341f811'] = 'Această acțiune va împiedica imediat funcționarea serviciilor PrestaShop și a serviciilor comunitare, deoarece acestea utilizează modulul PrestaShop Account pentru autentificare.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a6589b17d51617df13a46421dc86e004'] = 'Acțiune necesară: confirmă adresa URL a magazinului tău'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_97c826995e093d19f3c940c389515195'] = 'Am observat că adresa URL a magazinului tău nu se mai potrivește cu cea înregistrată în contul tău PrestaShop.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_33fba3aba5e5d575db9f041232e64d2b'] = 'Pentru ca serviciile tale să funcționeze corect, trebuie fie să confirmi această modificare, fie să creezi o nouă identitate pentru magazinul tău.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'Adresa URL actuală a magazinului'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'Adresa URL înregistrată în contul PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Revizuiește setările'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_7285968d2690ae18df8ee5e1686f545d'] = 'Acțiune necesară: resetează modulul tău PS Account'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_e1b80ece0a497aca4c753d437c288ca1'] = 'O simplă resetare este necesară pentru a finaliza actualizarea și pentru a te asigura că toate modulele tale funcționează corect.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_66949b1c09d857d4ecd2497a0734072b'] = 'Resetare modul...'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_817ea9c22e6b11ecd69691c1dd47ef1a'] = 'Resetează modulul'; +$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'A apărut o eroare în timpul autentificării, te rugăm să contactezi suportul PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Bun venit,'; +$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Accesează back office pentru a-ți gestiona magazinul.'; +$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Înapoi la back office'; +$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Conectează-te cu o altă metodă'; +$_MODULE['<{ps_accounts}prestashop>displaybackofficeemployeemenu_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Gestionați-vă contul PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_f4f70727dc34561dfde1a3c529b6205c'] = 'Setări'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Ajutor'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_b7ca62f12bc3b8e0d6422c028473a265'] = 'Contul tău PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_53c4c7fe12fffd658daacfa5b41eb495'] = 'Un singur cont pentru a gestiona toate magazinele PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_655aa4af96bbb20cf78e14b560affb38'] = 'Creează-ți un cont PrestaShop sau autentifică-te în contul tău existent'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_a60d78ec73c0c133ce16b10419a3fdf6'] = 'Conectați magazinul dvs. la contul dvs.'; -$_MODULE['<{ps_accounts}prestashop>login_23feb5280da1e3a86dc0fcc048f1a58d'] = 'Un back office ușor de utilizat'; -$_MODULE['<{ps_accounts}prestashop>login_e661ffe463ad9b9bcea4c49d744058e7'] = 'Gestionează-ți întreaga afacere într-un singur loc: catalog de produse, comenzi, plăți, livrări și multe altele.'; -$_MODULE['<{ps_accounts}prestashop>login_8ea4efcd82809db344ff0d7a76695448'] = 'Toate elementele esențiale pentru afacerea ta'; -$_MODULE['<{ps_accounts}prestashop>login_f27ec0e8d78513e2e60830c62a3a13b8'] = 'Marketing, plăți și analiza performanțelor: suita PrestaShop Essentials include toate funcționalitățile de care ai nevoie pentru ca magazinul tău să aibă succes.'; -$_MODULE['<{ps_accounts}prestashop>login_f2d32f40a4060af2595051d0ab0278cb'] = 'O soluție 100% personalizabilă'; -$_MODULE['<{ps_accounts}prestashop>login_9b4e2d0b927094565d6ce673644868a4'] = 'PrestaShop îți însoțește creșterea. Găsești modulele noastre și ale partenerilor noștri pe PrestaShop Addons Marketplace pentru a-ți personaliza și dezvolta magazinul'; -$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Bun venit,'; -$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Accesează back office pentru a-ți gestiona magazinul.'; -$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Înapoi la back office'; -$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Conectează-te cu o altă metodă'; -$_MODULE['<{ps_accounts}prestashop>login_729f27762821b8ec88f1a656be7dff72'] = 'Trebuie să îți activezi contul mai întâi, făcând clic pe linkul din e-mail. Dacă vrei să primești un nou link de activare,[1]te rugăm să apeși aici[/1]'; -$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'A apărut o eroare în timpul autentificării, te rugăm să contactezi suportul PrestaShop'; -$_MODULE['<{ps_accounts}prestashop>login_fd359ea79740b865e45c2bf6fe5d3ab1'] = 'Nu poți accesa back office cu acest cont. Încearcă un alt cont sau contactează administratorul.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Gestionați-vă contul PrestaShop'; diff --git a/translations/ru.php b/translations/ru.php index 83a49f39a..a07951b53 100644 --- a/translations/ru.php +++ b/translations/ru.php @@ -20,27 +20,28 @@ global $_MODULE; $_MODULE = []; $_MODULE['<{ps_accounts}prestashop>ps_accounts_40047b296fb8a4952e0512c7184e1ca3'] = 'Учетная запись PrestaShop'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_248ed4a0f71472c36d19bb0f1bcb68a0'] = 'Свяжите ваш магазин с учетной записью PrestaShop, чтобы активировать подписки и управлять ими в бэк-офисе. Не удаляйте этот модуль, если у вас есть текущая подписка.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_ceeadaf870d9eaa81cf1fbe32614af64'] = 'Это действие немедленно предотвратит работу сервисов PrestaShop и Community, так как они используют модуль PrestaShop Accounts для аутентификации.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_01a84505e1f42ba1b80ffed4f9f21221'] = 'Этот магазин связан с вашей учетной записью PrestaShop. Отключите магазин, если вы не хотите влиять на настройки в реальном времени.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_d82864f800210969f401370615e30568'] = 'Некоторые магазины связаны с вашей учетной записью PrestaShop. Удаление этих магазинов повлияет на ваши живые настройки.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_aa8c92c02e922a0fff679d9f66fce2d0'] = 'Свяжите ваш магазин с учетной записью PrestaShop, чтобы активировать подписки и управлять ими в бэк-офисе. Не удаляйте этот модуль, если у вас есть текущая подписка.'; +$_MODULE['<{ps_accounts}prestashop>ps_accounts_05eeaea4d4c380b44bbcca3b1341f811'] = 'Это действие немедленно предотвратит работу сервисов PrestaShop и Community, так как они используют модуль PrestaShop Accounts для аутентификации.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_a6589b17d51617df13a46421dc86e004'] = 'Требуется действие: подтвердите URL вашего магазина'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_97c826995e093d19f3c940c389515195'] = 'Мы заметили, что URL вашего магазина больше не совпадает с тем, который зарегистрирован в вашей учетной записи PrestaShop.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_33fba3aba5e5d575db9f041232e64d2b'] = 'Чтобы ваши сервисы работали правильно, вы должны либо подтвердить это изменение, либо создать новую учетную запись для вашего магазина.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_bcabe16a516e2025713ad5af0adfd49a'] = 'Текущий URL магазина'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_5181e6b2ae79016706b1d18380a8059d'] = 'URL, зарегистрированный в учетной записи PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_0dd9a339c52287eaa3280fb956a07324'] = 'Просмотреть настройки'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_7285968d2690ae18df8ee5e1686f545d'] = 'Требуется действие: сбросьте модуль PS Account'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_e1b80ece0a497aca4c753d437c288ca1'] = 'Для завершения обновления и обеспечения правильной работы всех ваших модулей требуется простой сброс.'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_66949b1c09d857d4ecd2497a0734072b'] = 'Сброс модуля...'; +$_MODULE['<{ps_accounts}prestashop>adminajaxpsaccountscontroller_817ea9c22e6b11ecd69691c1dd47ef1a'] = 'Сбросить модуль'; +$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'При входе в систему произошла ошибка, обратитесь в службу поддержки PrestaShop'; +$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Добро пожаловать,'; +$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Получите доступ к своему бэк-офису для управления магазином.'; +$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Пройдите в задний офис'; +$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744868a4'] = '100% настраиваемое решение'; +$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Подключение с помощью другого метода'; +$_MODULE['<{ps_accounts}prestashop>displaybackofficeemployeemenu_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Управление учетной записью PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_f4f70727dc34561dfde1a3c529b6205c'] = 'Настройки'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_6a26f548831e6a8c26bfbbd9f6ec61e0'] = 'Помощь'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_b7ca62f12bc3b8e0d6422c028473a265'] = 'Ваша учетная запись PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_53c4c7fe12fffd658daacfa5b41eb495'] = 'Один аккаунт для управления всеми вашими магазинами PrestaShop'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_655aa4af96bbb20cf78e14b560affb38'] = 'Создайте учетную запись PrestaShop или войдите в существующую учетную запись'; $_MODULE['<{ps_accounts}prestashop>settingstranslations_a60d78ec73c0c133ce16b10419a3fdf6'] = 'Свяжите свой магазин с вашей учетной записью'; -$_MODULE['<{ps_accounts}prestashop>login_23feb5280da1e3a86dc0fcc048f1a58d'] = 'A modular interface'; -$_MODULE['<{ps_accounts}prestashop>login_e661ffe463ad9b9bcea4c49d744058e7'] = 'Manage your entire business in one place: product catalog, orders, payments, delivery and much more.'; -$_MODULE['<{ps_accounts}prestashop>login_8ea4efcd82809db344ff0d7a76695448'] = 'Все самое необходимое для вашего бизнеса'; -$_MODULE['<{ps_accounts}prestashop>login_f27ec0e8d78513e2e60830c62a3a13b8'] = 'Маркетинг, оплата и анализ эффективности: пакет PrestaShop Essentials включает все функции, необходимые для успешной работы вашего магазина.'; -$_MODULE['<{ps_accounts}prestashop>login_f2d32f40a4060af2595051d0ab0278cb'] = '100% настраиваемое решение'; -$_MODULE['<{ps_accounts}prestashop>login_9b4e2d0b927094565d6ce673644868a4'] = 'PrestaShop сопровождает ваш рост. Найдите наши модули и модули наших партнеров на PrestaShop Addons Marketplace для настройки и развития вашего магазина'; -$_MODULE['<{ps_accounts}prestashop>login_1ab1f34b16d32fa54b90ee0f91f39dfd'] = 'Добро пожаловать,'; -$_MODULE['<{ps_accounts}prestashop>login_474684646f833216fdaf79c606af208b'] = 'Получите доступ к своему бэк-офису для управления магазином.'; -$_MODULE['<{ps_accounts}prestashop>login_6ed6a381a1de805e2b807f88a95faa2b'] = 'Пройдите в задний офис'; -$_MODULE['<{ps_accounts}prestashop>login_8905a5daaa6744846452108776efa036'] = 'Подключение с помощью другого метода'; -$_MODULE['<{ps_accounts}prestashop>login_729f27762821b8ec88f1a656be7dff72'] = 'Сначала вам нужно активировать свой аккаунт, перейдя по ссылке в письме. Если вам нужно получить новую ссылку для активации,[1] пожалуйста, нажмите здесь[/1].'; -$_MODULE['<{ps_accounts}prestashop>login_476ff05d1175f9a35d268828caf300f2'] = 'При входе в систему произошла ошибка, обратитесь в службу поддержки PrestaShop'; -$_MODULE['<{ps_accounts}prestashop>login_fd359ea79740b865e45c2bf6fe5d3ab1'] = 'Вы не можете получить доступ к бэк-офису с помощью этой учетной записи. Попробуйте использовать другую учетную запись или обратитесь к администратору.'; -$_MODULE['<{ps_accounts}prestashop>ps_accounts_e53a9fbe0e31ac65b72e5e2c876074d8'] = 'Управление учетной записью PrestaShop';