diff --git a/features/account/customer_account/address_book/preventing_xss_attack_during_updating_address.feature b/features/account/customer_account/address_book/preventing_xss_attack_during_updating_address.feature new file mode 100644 index 00000000000..db2cc2adc7f --- /dev/null +++ b/features/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/checkout/addressing_order/preventing_xss_attack_during_checkout.feature b/features/checkout/addressing_order/preventing_xss_attack_during_checkout.feature new file mode 100644 index 00000000000..f8c11316fd4 --- /dev/null +++ b/features/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/features/product/managing_products/preventing_xss_attack_while_adding_new_product.feature b/features/product/managing_products/preventing_xss_attack_while_adding_new_product.feature new file mode 100644 index 00000000000..8ab868842a6 --- /dev/null +++ b/features/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/product/managing_products/preventing_xss_attack_while_editing_product.feature b/features/product/managing_products/preventing_xss_attack_while_editing_product.feature new file mode 100644 index 00000000000..2b058d1544e --- /dev/null +++ b/features/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/taxonomy/managing_taxons/preventing_xss_attack_while_adding_new_taxon.feature b/features/taxonomy/managing_taxons/preventing_xss_attack_while_adding_new_taxon.feature new file mode 100644 index 00000000000..72f507aad82 --- /dev/null +++ b/features/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/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 0c17a02ced6..5a32b027072 100644 --- a/src/Sylius/Behat/Context/Ui/Admin/ManagingProductsContext.php +++ b/src/Sylius/Behat/Context/Ui/Admin/ManagingProductsContext.php @@ -88,6 +88,7 @@ public function iSpecifyItsCodeAs($code = null) * @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 { @@ -747,6 +748,7 @@ public function theOptionFieldShouldBeDisabled() /** * @When /^I choose main (taxon "[^"]+")$/ + * @Then /^I should be able to choose main (taxon "[^"]+")$/ */ public function iChooseMainTaxon(TaxonInterface $taxon) { @@ -820,6 +822,7 @@ public function iAttachImageWithType($path, $type = null) /** * @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 3a193a15730..3a31e7fda2e 100644 --- a/src/Sylius/Behat/Context/Ui/Admin/ManagingTaxonsContext.php +++ b/src/Sylius/Behat/Context/Ui/Admin/ManagingTaxonsContext.php @@ -136,6 +136,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 05763b94ac0..ee2d9d9f574 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 +}