diff --git a/docs/book/installation/installation_with_docker.rst b/docs/book/installation/installation_with_docker.rst index f425325bc99..a8b1ad7b51f 100644 --- a/docs/book/installation/installation_with_docker.rst +++ b/docs/book/installation/installation_with_docker.rst @@ -14,7 +14,7 @@ testing, and implementation. Docker significantly reduces the delay between writ .. note:: - Make sure you have `Docker `_ installed on your local machine. + Make sure you have `Docker `_ and `make `_ installed on your local machine. Project Setup ------------- @@ -29,15 +29,12 @@ with Sylius-Standard content. Development ----------- -`Sylius Standard `_ comes with the `multi-stage build `_. -You can execute it via the ``docker compose up -d`` command in your favorite terminal. Please note that the speed of building images -and initializing containers depends on your local machine and internet connection - it may take some time. Then enter ``localhost`` in your browser or execute ``open localhost`` in your terminal. +`Sylius Standard `_ comes with the `docker compose `_ configuration. +You can start the development environment via the ``make init`` command in your favorite terminal. Please note that the speed of building images +and initializing containers depends on your local machine and internet connection - it may take some time. +Then enter ``localhost`` in your browser or execute ``open http://localhost/`` in your terminal. .. code-block:: bash - docker compose up -d - open localhost - -.. tip:: - - :doc:`Learn how to deploy Sylius-Standard production ready Docker Compose configuration ` + make init + open http://localhost/ diff --git a/docs/cookbook/deployment/docker.rst b/docs/cookbook/deployment/docker.rst deleted file mode 100644 index c2919f1c6b5..00000000000 --- a/docs/cookbook/deployment/docker.rst +++ /dev/null @@ -1,33 +0,0 @@ -How to deploy Sylius with Docker? -================================= - -The simplest way to deploy your Sylius store with Docker is to use the template provided in the Sylius-Standard ``docker-compose.prod.yml`` configuration file. - -.. tip:: - - When using a Virtual Private Server (VPS) we recommend having at least 2GB of RAM memory. - -1. Install Docker on your VPS ------------------------------ - -.. code-block:: bash - - curl -fsSL https://get.docker.com -o get-docker.sh - sudo sh get-docker.sh - -2. Execute Docker Compose Configuration ---------------------------------------- - -.. code-block:: bash - - export MYSQL_PASSWORD=SLyPJLaye7 - docker compose -f docker-compose.prod.yml up -d - -.. tip:: - - Deploying the database on the same machine as the application is not the best practice. **Use Managed Database solution instead.** - -Learn more ----------- - -* `Check out Docker learning recommendations! `_ diff --git a/docs/cookbook/deployment/map.rst.inc b/docs/cookbook/deployment/map.rst.inc index ce3f33827ea..18e78166130 100644 --- a/docs/cookbook/deployment/map.rst.inc +++ b/docs/cookbook/deployment/map.rst.inc @@ -1,3 +1,2 @@ * :doc:`/cookbook/deployment/platform-sh` * :doc:`/cookbook/deployment/cron-jobs` -* :doc:`/cookbook/deployment/docker` diff --git a/docs/cookbook/index.rst b/docs/cookbook/index.rst index f10b0558cf8..1849714f09d 100644 --- a/docs/cookbook/index.rst +++ b/docs/cookbook/index.rst @@ -143,7 +143,6 @@ Deployment deployment/platform-sh deployment/cron-jobs - deployment/docker .. include:: /cookbook/deployment/map.rst.inc diff --git a/docs/getting-started-with-sylius/deployment.rst b/docs/getting-started-with-sylius/deployment.rst index 477d0c5dee5..a1cb2e6adb5 100644 --- a/docs/getting-started-with-sylius/deployment.rst +++ b/docs/getting-started-with-sylius/deployment.rst @@ -6,14 +6,11 @@ application deployment into the server. We believe, that it should be as easy an Check out our deployment cookbooks: - .. tip:: - 👉 :doc:`How to deploy Sylius to Platform.sh? ` - - 🐳 :doc:`How to deploy Sylius with Docker ` Learn more about the deployment platforms ----------------------------------------- * `Platform.sh `_ -* `Docker `_ diff --git a/features/admin/product/managing_products/preventing_xss_attack_while_adding_new_product.feature b/features/admin/product/managing_products/preventing_xss_attack_while_adding_new_product.feature new file mode 100644 index 00000000000..8ab868842a6 --- /dev/null +++ b/features/admin/product/managing_products/preventing_xss_attack_while_adding_new_product.feature @@ -0,0 +1,21 @@ +@managing_products +Feature: Preventing a potential XSS attack while adding a new product + In order to keep my information safe + As an Administrator + I want to be protected against the potential XSS attacks + + Background: + Given the store operates on a single channel in "United States" + And the store has "" taxonomy + And the store has "No XSS" taxonomy + And I am logged in as an administrator + + @ui @javascript @no-api + Scenario: Preventing a potential XSS attack while adding new product + When I want to create a new simple product + Then I should be able to name it "No XSS" in "English (United States)" + + @ui @javascript @no-api + Scenario: Preventing a potential XSS attack while choosing main taxon for a new product + When I want to create a new simple product + Then I should be able to choose main taxon "No XSS" diff --git a/features/admin/product/managing_products/preventing_xss_attack_while_editing_product.feature b/features/admin/product/managing_products/preventing_xss_attack_while_editing_product.feature new file mode 100644 index 00000000000..2b058d1544e --- /dev/null +++ b/features/admin/product/managing_products/preventing_xss_attack_while_editing_product.feature @@ -0,0 +1,16 @@ +@managing_products +Feature: Preventing a potential XSS attack while selecting similar product + In order to keep my information safe + As an Administrator + I want to be protected against the potential XSS attacks + + Background: + Given the store operates on a single channel in "United States" + And the store has a product association type "Accessories" + And the store has "" and "LG headphones" products + And I am logged in as an administrator + + @ui @javascript @no-api + Scenario: Preventing a potential XSS attack while editing product + When I want to create a new simple product + Then I should be able to associate as "Accessories" the "LG headphones" product diff --git a/features/admin/taxonomy/managing_taxons/preventing_xss_attack_while_adding_new_taxon.feature b/features/admin/taxonomy/managing_taxons/preventing_xss_attack_while_adding_new_taxon.feature new file mode 100644 index 00000000000..72f507aad82 --- /dev/null +++ b/features/admin/taxonomy/managing_taxons/preventing_xss_attack_while_adding_new_taxon.feature @@ -0,0 +1,16 @@ +@managing_taxons +Feature: Preventing a potential XSS attack while adding a new taxon + In order to keep my information safe + As an Administrator + I want to be protected against the potential XSS attacks + + Background: + Given the store operates on a single channel in "United States" + And the store has "Category" taxonomy + And the store has "" taxonomy + And I am logged in as an administrator + + @ui @javascript @no-api + Scenario: Preventing a potential XSS attack while adding new taxon + When I want to create a new taxon + Then I should be able to change its parent taxon to "Category" diff --git a/features/shop/account/customer_account/address_book/preventing_xss_attack_during_updating_address.feature b/features/shop/account/customer_account/address_book/preventing_xss_attack_during_updating_address.feature new file mode 100644 index 00000000000..db2cc2adc7f --- /dev/null +++ b/features/shop/account/customer_account/address_book/preventing_xss_attack_during_updating_address.feature @@ -0,0 +1,16 @@ +@address_book +Feature: Preventing a potential XSS attack during updating the address + In order to keep my information safe + As a Customer + I want to be protected against the potential XSS attacks + + Background: + Given the store operates on a single channel in "United States" + And I am a logged in customer + And I have an address "Lucifer Morningstar", "Seaside Fwy", "90802", "Los Angeles", "United States", "Arkansas" in my address book + And this address has province '">' + + @ui @javascript @no-api + Scenario: Preventing a potential XSS attack during updating the address + When I want to edit the address of "Lucifer Morningstar" + Then I should be able to update it without unexpected alert diff --git a/features/shop/checkout/addressing_order/preventing_xss_attack_during_checkout.feature b/features/shop/checkout/addressing_order/preventing_xss_attack_during_checkout.feature new file mode 100644 index 00000000000..f8c11316fd4 --- /dev/null +++ b/features/shop/checkout/addressing_order/preventing_xss_attack_during_checkout.feature @@ -0,0 +1,21 @@ +@checkout +Feature: Preventing a potential XSS attack during updating the address in the checkout + In order to keep my information safe + As a Visitor + I want to be protected against the potential XSS attacks + + Background: + Given the store operates on a single channel in "United States" + And the store has a product "PHP T-Shirt" priced at "$19.99" + And the store ships everywhere for Free + And I have product "PHP T-Shirt" in the cart + And I am at the checkout addressing step + + @ui @javascript @no-api + Scenario: Preventing a potential XSS attack during updating the address in the checkout + When I specify the email as "john.doe@example.com" + And I specify the billing address as "Ankh Morpork", "Frost Alley", "90210", "United States" for "Jon Doe" + And I specify the province name manually as '">' for billing address + And I complete the addressing step + And I decide to change my address + Then I should be able to update the address without unexpected alert diff --git a/src/Sylius/Behat/Context/Setup/AddressContext.php b/src/Sylius/Behat/Context/Setup/AddressContext.php index f810565fff7..82c762964d7 100644 --- a/src/Sylius/Behat/Context/Setup/AddressContext.php +++ b/src/Sylius/Behat/Context/Setup/AddressContext.php @@ -66,6 +66,19 @@ public function iHaveAnAddressInAddressBook(ShopUserInterface $user, AddressInte $customer = $user->getCustomer(); $this->addAddressToCustomer($customer, $address); + + $this->sharedStorage->set('address', $address); + } + + /** + * @Given this address has province :province + */ + public function thisAddressHasProvince(string $provinceName): void + { + $address = $this->sharedStorage->get('address'); + $address->setProvinceName($provinceName); + + $this->customerManager->flush(); } /** diff --git a/src/Sylius/Behat/Context/Ui/Admin/ManagingProductsContext.php b/src/Sylius/Behat/Context/Ui/Admin/ManagingProductsContext.php index 3cdac986314..7a0b6121369 100644 --- a/src/Sylius/Behat/Context/Ui/Admin/ManagingProductsContext.php +++ b/src/Sylius/Behat/Context/Ui/Admin/ManagingProductsContext.php @@ -92,6 +92,7 @@ public function iSpecifyItsCodeAs(?string $code = null): void * @When I do not name it * @When I name it :name in :language * @When I rename it to :name in :language + * @When I should be able to name it :name in :language */ public function iRenameItToIn(?string $name = null, ?string $language = null): void { @@ -777,6 +778,7 @@ public function iShouldNotBeAbleToEditItsOptions(): void /** * @When /^I choose main (taxon "[^"]+")$/ + * @Then /^I should be able to choose main (taxon "[^"]+")$/ */ public function iChooseMainTaxon(TaxonInterface $taxon) { @@ -881,6 +883,7 @@ public function iSelectVariantForTheFirstImage(ProductVariantInterface $productV /** * @When I associate as :productAssociationType the :productName product * @When I associate as :productAssociationType the :firstProductName and :secondProductName products + * @Then I should be able to associate as :productAssociationType the :productName product */ public function iAssociateProductsAsProductAssociation( ProductAssociationTypeInterface $productAssociationType, diff --git a/src/Sylius/Behat/Context/Ui/Admin/ManagingTaxonsContext.php b/src/Sylius/Behat/Context/Ui/Admin/ManagingTaxonsContext.php index 8ba45ca87b5..b5f53a2fa87 100644 --- a/src/Sylius/Behat/Context/Ui/Admin/ManagingTaxonsContext.php +++ b/src/Sylius/Behat/Context/Ui/Admin/ManagingTaxonsContext.php @@ -143,6 +143,7 @@ public function iDescribeItAs($description, $language) /** * @Given /^I set its (parent taxon to "[^"]+")$/ * @Given /^I change its (parent taxon to "[^"]+")$/ + * @Then /^I should be able to change its (parent taxon to "[^"]+")$/ */ public function iChangeItsParentTaxonTo(TaxonInterface $taxon) { diff --git a/src/Sylius/Behat/Context/Ui/Shop/AddressBookContext.php b/src/Sylius/Behat/Context/Ui/Shop/AddressBookContext.php index dd223762044..2b538eead96 100644 --- a/src/Sylius/Behat/Context/Ui/Shop/AddressBookContext.php +++ b/src/Sylius/Behat/Context/Ui/Shop/AddressBookContext.php @@ -41,8 +41,9 @@ public function __construct( /** * @Given I am editing the address of :fullName + * @When I want to edit the address of :fullName */ - public function iEditAddressOf($fullName) + public function iEditAddressOf(string $fullName): void { $this->sharedStorage->set('full_name', $fullName); @@ -350,6 +351,14 @@ public function addressShouldBeMarkedAsMyDefaultAddress(AddressInterface $addres Assert::same($actualFullName, $expectedFullName); } + /** + * @Then I should be able to update it without unexpected alert + */ + public function iShouldBeAbleToUpdateItWithoutUnexpectedAlert(): void + { + $this->addressBookUpdatePage->waitForFormToStopLoading(); + } + /** * @param string $fullName * diff --git a/src/Sylius/Behat/Context/Ui/Shop/Checkout/CheckoutAddressingContext.php b/src/Sylius/Behat/Context/Ui/Shop/Checkout/CheckoutAddressingContext.php index c2ed635eb6c..10577201501 100644 --- a/src/Sylius/Behat/Context/Ui/Shop/Checkout/CheckoutAddressingContext.php +++ b/src/Sylius/Behat/Context/Ui/Shop/Checkout/CheckoutAddressingContext.php @@ -484,6 +484,14 @@ public function shouldHaveCountriesToChooseFrom(string ...$countries): void Assert::same($availableBillingCountries, $countries); } + /** + * @Then I should be able to update the address without unexpected alert + */ + public function iShouldBeAbleToUpdateTheAddressWithoutUnexpectedAlert(): void + { + $this->addressPage->waitForFormToStopLoading(); + } + /** * @return AddressInterface */ diff --git a/src/Sylius/Behat/Page/Shop/Account/AddressBook/UpdatePage.php b/src/Sylius/Behat/Page/Shop/Account/AddressBook/UpdatePage.php index 5cda3d3606f..5308a0f4278 100644 --- a/src/Sylius/Behat/Page/Shop/Account/AddressBook/UpdatePage.php +++ b/src/Sylius/Behat/Page/Shop/Account/AddressBook/UpdatePage.php @@ -71,6 +71,11 @@ public function selectCountry(string $name): void JQueryHelper::waitForFormToStopLoading($this->getDocument()); } + public function waitForFormToStopLoading(): void + { + JQueryHelper::waitForFormToStopLoading($this->getDocument()); + } + public function saveChanges(): void { JQueryHelper::waitForFormToStopLoading($this->getDocument()); diff --git a/src/Sylius/Behat/Page/Shop/Account/AddressBook/UpdatePageInterface.php b/src/Sylius/Behat/Page/Shop/Account/AddressBook/UpdatePageInterface.php index 336354eca79..5988a1da23f 100644 --- a/src/Sylius/Behat/Page/Shop/Account/AddressBook/UpdatePageInterface.php +++ b/src/Sylius/Behat/Page/Shop/Account/AddressBook/UpdatePageInterface.php @@ -29,5 +29,7 @@ public function selectProvince(string $name): void; public function selectCountry(string $name): void; + public function waitForFormToStopLoading(): void; + public function saveChanges(): void; } diff --git a/src/Sylius/Behat/Page/Shop/Checkout/AddressPage.php b/src/Sylius/Behat/Page/Shop/Checkout/AddressPage.php index 89fea14f9f5..26aea7708ed 100644 --- a/src/Sylius/Behat/Page/Shop/Checkout/AddressPage.php +++ b/src/Sylius/Behat/Page/Shop/Checkout/AddressPage.php @@ -281,6 +281,11 @@ public function getAvailableBillingCountries(): array return $this->getOptionsFromSelect($this->getElement('billing_country')); } + public function waitForFormToStopLoading(): void + { + JQueryHelper::waitForFormToStopLoading($this->getDocument()); + } + protected function getDefinedElements(): array { return array_merge(parent::getDefinedElements(), [ diff --git a/src/Sylius/Behat/Page/Shop/Checkout/AddressPageInterface.php b/src/Sylius/Behat/Page/Shop/Checkout/AddressPageInterface.php index de0fa8ad7a6..2a787df6f16 100644 --- a/src/Sylius/Behat/Page/Shop/Checkout/AddressPageInterface.php +++ b/src/Sylius/Behat/Page/Shop/Checkout/AddressPageInterface.php @@ -81,4 +81,6 @@ public function getAvailableBillingCountries(): array; public function isDifferentShippingAddressChecked(): bool; public function isShippingAddressVisible(): bool; + + public function waitForFormToStopLoading(): void; } diff --git a/src/Sylius/Bundle/AdminBundle/Resources/private/js/sylius-lazy-choice-tree.js b/src/Sylius/Bundle/AdminBundle/Resources/private/js/sylius-lazy-choice-tree.js index b1f4dd0d27d..18bb2d3a27e 100644 --- a/src/Sylius/Bundle/AdminBundle/Resources/private/js/sylius-lazy-choice-tree.js +++ b/src/Sylius/Bundle/AdminBundle/Resources/private/js/sylius-lazy-choice-tree.js @@ -10,6 +10,7 @@ import 'semantic-ui-css/components/api'; import 'semantic-ui-css/components/checkbox'; import $ from 'jquery'; +import { sanitizeInput } from "sylius/ui/sylius-sanitizer"; const createRootContainer = function createRootContainer() { return $('
'); @@ -81,7 +82,7 @@ $.fn.extend({ onSuccess(response) { response.forEach((leafNode) => { leafContainerElement.append(( - createLeafFunc(leafNode.name, leafNode.code, leafNode.hasChildren, multiple, leafNode.level) + createLeafFunc(sanitizeInput(leafNode.name), sanitizeInput(leafNode.code), leafNode.hasChildren, multiple, leafNode.level) )); }); content.append(leafContainerElement); @@ -169,7 +170,7 @@ $.fn.extend({ const rootContainer = createRootContainer(); response.forEach((rootNode) => { rootContainer.append(( - createLeaf(rootNode.name, rootNode.code, rootNode.hasChildren, multiple, rootNode.level) + createLeaf(sanitizeInput(rootNode.name), sanitizeInput(rootNode.code), rootNode.hasChildren, multiple, rootNode.level) )); }); tree.append(rootContainer); diff --git a/src/Sylius/Bundle/ShopBundle/Resources/private/js/sylius-province-field.js b/src/Sylius/Bundle/ShopBundle/Resources/private/js/sylius-province-field.js index 17b3d350cf0..60983648ab1 100644 --- a/src/Sylius/Bundle/ShopBundle/Resources/private/js/sylius-province-field.js +++ b/src/Sylius/Bundle/ShopBundle/Resources/private/js/sylius-province-field.js @@ -8,9 +8,10 @@ */ import $ from 'jquery'; +import { sanitizeInput } from 'sylius/ui/sylius-sanitizer'; const getProvinceInputValue = function getProvinceInputValue(valueSelector) { - return valueSelector == undefined ? '' : `value="${valueSelector}"`; + return valueSelector == undefined ? '' : `value="${sanitizeInput(valueSelector)}"`; }; $.fn.extend({ diff --git a/src/Sylius/Bundle/UiBundle/Resources/private/js/sylius-auto-complete.js b/src/Sylius/Bundle/UiBundle/Resources/private/js/sylius-auto-complete.js index 737c47a55a2..e52a6f42dac 100644 --- a/src/Sylius/Bundle/UiBundle/Resources/private/js/sylius-auto-complete.js +++ b/src/Sylius/Bundle/UiBundle/Resources/private/js/sylius-auto-complete.js @@ -9,6 +9,7 @@ import 'semantic-ui-css/components/dropdown'; import $ from 'jquery'; +import { sanitizeInput } from "./sylius-sanitizer"; $.fn.extend({ autoComplete() { @@ -37,8 +38,8 @@ $.fn.extend({ }, onResponse(response) { let results = response.map(item => ({ - name: item[choiceName], - value: item[choiceValue], + name: sanitizeInput(item[choiceName]), + value: sanitizeInput(item[choiceValue]), })); if (!element.hasClass('multiple')) { @@ -72,7 +73,7 @@ $.fn.extend({ onSuccess(response) { response.forEach((item) => { menuElement.append(( - $(`
${item[choiceName]}
`) + $(`
${sanitizeInput(item[choiceName])}
`) )); }); diff --git a/src/Sylius/Bundle/UiBundle/Resources/private/js/sylius-product-auto-complete.js b/src/Sylius/Bundle/UiBundle/Resources/private/js/sylius-product-auto-complete.js index acbe655ba32..64e34d9bfb3 100644 --- a/src/Sylius/Bundle/UiBundle/Resources/private/js/sylius-product-auto-complete.js +++ b/src/Sylius/Bundle/UiBundle/Resources/private/js/sylius-product-auto-complete.js @@ -9,6 +9,7 @@ import 'semantic-ui-css/components/dropdown'; import $ from 'jquery'; +import { sanitizeInput } from "./sylius-sanitizer"; $.fn.extend({ productAutoComplete() { @@ -38,8 +39,8 @@ $.fn.extend({ return { success: true, results: response._embedded.items.map(item => ({ - name: item.name, - value: item.code, + name: sanitizeInput(item.name), + value: sanitizeInput(item.code), })), }; }, diff --git a/src/Sylius/Bundle/UiBundle/Resources/private/js/sylius-sanitizer.js b/src/Sylius/Bundle/UiBundle/Resources/private/js/sylius-sanitizer.js new file mode 100644 index 00000000000..f3dd2cd4cfb --- /dev/null +++ b/src/Sylius/Bundle/UiBundle/Resources/private/js/sylius-sanitizer.js @@ -0,0 +1,5 @@ +export function sanitizeInput(input) { + const div = document.createElement('div'); + div.textContent = input; + return div.innerHTML; // Converts text content to plain HTML, stripping any scripts +}