diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/RequestPasswordResetEmail.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/RequestPasswordResetEmail.php new file mode 100644 index 0000000000000..d0985d6acaf19 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/RequestPasswordResetEmail.php @@ -0,0 +1,116 @@ +authentication = $authentication; + $this->customerRepository = $customerRepository; + $this->customerAccountManagement = $customerAccountManagement; + $this->emailValidator = $emailValidator; + } + + /** + * Send password email request + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return bool|Value|mixed + * + * @throws GraphQlInputException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (empty($args['email'])) { + throw new GraphQlInputException(__('You must specify an email address.')); + } + + if (!$this->emailValidator->isValid($args['email'])) { + throw new GraphQlInputException(__('The email address has an invalid format.')); + } + + try { + $customer = $this->customerRepository->get($args['email']); + } catch (LocalizedException $e) { + throw new GraphQlInputException(__('Cannot reset the customer\'s password'), $e); + } + + if (true === $this->authentication->isLocked($customer->getId())) { + throw new GraphQlInputException(__('The account is locked')); + } + + try { + return $this->customerAccountManagement->initiatePasswordReset( + $args['email'], + AccountManagement::EMAIL_RESET + ); + } catch (LocalizedException $e) { + throw new GraphQlInputException(__('Cannot reset the customer\'s password'), $e); + } + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/ResetPassword.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/ResetPassword.php new file mode 100644 index 0000000000000..fa2ae669cc89d --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/ResetPassword.php @@ -0,0 +1,124 @@ +authentication = $authentication; + $this->customerRepository = $customerRepository; + $this->customerAccountManagement = $customerAccountManagement; + $this->emailValidator = $emailValidator; + } + + /** + * Reset old password and set new + * + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * + * @return bool|Value|mixed + * + * @throws GraphQlInputException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (empty($args['email'])) { + throw new GraphQlInputException(__('You must specify an email address.')); + } + + if (!$this->emailValidator->isValid($args['email'])) { + throw new GraphQlInputException(__('The email address has an invalid format.')); + } + + if (empty($args['resetPasswordToken'])) { + throw new GraphQlInputException(__('resetPasswordToken must be specified')); + } + + if (empty($args['newPassword'])) { + throw new GraphQlInputException(__('newPassword must be specified')); + } + + try { + $customer = $this->customerRepository->get($args['email']); + } catch (LocalizedException $e) { + throw new GraphQlInputException(__('Cannot set the customer\'s password'), $e); + } + + if (true === $this->authentication->isLocked($customer->getId())) { + throw new GraphQlInputException(__('The account is locked')); + } + + try { + return $this->customerAccountManagement->resetPassword( + $args['email'], + $args['resetPasswordToken'], + $args['newPassword'] + ); + } catch (LocalizedException $e) { + throw new GraphQlInputException(__('Cannot set the customer\'s password'), $e); + } + } +} diff --git a/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml b/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml index cb4ab0b7b27f4..1ba0e457430e0 100644 --- a/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml @@ -20,4 +20,13 @@ + + + + customer/password/required_character_classes_number + customer/password/minimum_password_length + customer/password/autocomplete_on_storefront + + + diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls index 9aa1fdaa841e4..3bf36d11e71fe 100644 --- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls @@ -1,6 +1,12 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. +type StoreConfig { + required_character_classes_number : String @doc(description: "The number of different character classes required in a password (lowercase, uppercase, digits, special characters).") + minimum_password_length : String @doc(description: "The minimum number of characters required for a valid password.") + autocomplete_on_storefront : Boolean @doc(description: "Enable autocomplete on login and forgot password forms") +} + type Query { customer: Customer @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\Customer") @doc(description: "The customer query returns information about a customer account") @cache(cacheable: false) isEmailAvailable ( @@ -17,6 +23,8 @@ type Mutation { createCustomerAddress(input: CustomerAddressInput!): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomerAddress") @doc(description: "Create customer address") updateCustomerAddress(id: Int!, input: CustomerAddressInput): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerAddress") @doc(description: "Update customer address") deleteCustomerAddress(id: Int!): Boolean @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\DeleteCustomerAddress") @doc(description: "Delete customer address") + requestPasswordResetEmail(email: String!): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RequestPasswordResetEmail") @doc(description: "Request an email with a reset password token for the registered customer identified by the specified email.") + resetPassword(email: String!, resetPasswordToken: String!, newPassword: String!): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ResetPassword") @doc(description: "Reset a customer's password using the reset password token that the customer received in an email after requesting it using requestPasswordResetEmail.") } input CustomerAddressInput { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RequestPasswordResetEmailTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RequestPasswordResetEmailTest.php new file mode 100644 index 0000000000000..9f30124fbb54a --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RequestPasswordResetEmailTest.php @@ -0,0 +1,112 @@ +lockCustomer = Bootstrap::getObjectManager()->get(LockCustomer::class); + } + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testCustomerAccountWithEmailAvailable() + { + $query = + <<graphQlMutation($query); + + self::assertArrayHasKey('requestPasswordResetEmail', $response); + self::assertTrue($response['requestPasswordResetEmail']); + } + + /** + * Check if customer account is not available + * + * @expectedException \Exception + * @expectedExceptionMessage Cannot reset the customer's password + */ + public function testCustomerAccountWithEmailNotAvailable() + { + $query = + <<graphQlMutation($query); + } + + /** + * Check if email value empty + * + * @expectedException \Exception + * @expectedExceptionMessage You must specify an email address. + */ + public function testEmailAvailableEmptyValue() + { + $query = <<graphQlMutation($query); + } + + /** + * Check if email is invalid + * + * @expectedException \Exception + * @expectedExceptionMessage The email address has an invalid format. + */ + public function testEmailAvailableInvalidValue() + { + $query = <<graphQlMutation($query); + } + + /** + * Check if email was sent for lock customer + * + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage The account is locked + */ + public function testRequestPasswordResetEmailForLockCustomer() + { + $this->lockCustomer->execute(1); + $query = + <<graphQlMutation($query); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ResetPasswordTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ResetPasswordTest.php new file mode 100644 index 0000000000000..ff9831abb590d --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ResetPasswordTest.php @@ -0,0 +1,265 @@ +objectManager = Bootstrap::getObjectManager(); + $this->accountManagement = $this->objectManager->get(AccountManagementInterface::class); + $this->customerRegistry = $this->objectManager->get(CustomerRegistry::class); + $this->lockCustomer = Bootstrap::getObjectManager()->get(LockCustomer::class); + parent::setUp(); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @return void + * @throws NoSuchEntityException + * @throws Exception + * + * @throws LocalizedException + */ + public function testResetCustomerAccountPasswordSuccessfully(): void + { + $query = <<getCustomerEmail()}" + resetPasswordToken: "{$this->getResetPasswordToken()}" + newPassword: "{$this->getNewPassword()}" + ) +} +QUERY; + $response = $this->graphQlMutation($query); + self::assertArrayHasKey('resetPassword', $response); + self::assertTrue($response['resetPassword']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage You must specify an email address. + * + * @throws NoSuchEntityException + * @throws Exception + * @throws LocalizedException + */ + public function testEmailAvailableEmptyValue() + { + $query = <<getResetPasswordToken()}" + newPassword: "{$this->getNewPassword()}" + ) +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage The email address has an invalid format. + * + * @throws NoSuchEntityException + * @throws Exception + * @throws LocalizedException + */ + public function testEmailInvalidValue() + { + $query = <<getResetPasswordToken()}" + newPassword: "{$this->getNewPassword()}" + ) +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage resetPasswordToken must be specified + * + * @throws NoSuchEntityException + * @throws Exception + * @throws LocalizedException + */ + public function testResetPasswordTokenEmptyValue() + { + $query = <<getCustomerEmail()}" + resetPasswordToken: "" + newPassword: "{$this->getNewPassword()}" + ) +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage Cannot set the customer's password + * + * @throws NoSuchEntityException + * @throws Exception + * @throws LocalizedException + */ + public function testResetPasswordTokenMismatched() + { + $query = <<getCustomerEmail()}" + resetPasswordToken: "1234567890XYZ" + newPassword: "{$this->getNewPassword()}" + ) +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage newPassword must be specified + * + * @throws NoSuchEntityException + * @throws Exception + * @throws LocalizedException + */ + public function testNewPasswordEmptyValue() + { + $query = <<getCustomerEmail()}" + resetPasswordToken: "{$this->getResetPasswordToken()}" + newPassword: "" + ) +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * Check password reset for lock customer + * + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * + * @expectedException \Exception + * @expectedExceptionMessage The account is locked + * + * @throws LocalizedException + * @throws NoSuchEntityException + */ + public function testPasswordResetForLockCustomer() + { + $this->lockCustomer->execute(1); + $query = <<getCustomerEmail()}" + resetPasswordToken: "{$this->getResetPasswordToken()}" + newPassword: "{$this->getNewPassword()}" + ) +} +QUERY; + $this->graphQlMutation($query); + } + + /** + * Get reset password token + * + * @return string + * + * @throws LocalizedException + * @throws NoSuchEntityException + */ + private function getResetPasswordToken() + { + $this->accountManagement->initiatePasswordReset( + $this->getCustomerEmail(), + AccountManagement::EMAIL_RESET, + 1 + ); + + $customerSecure = $this->customerRegistry->retrieveSecureData(1); + return $customerSecure->getRpToken(); + } + + /** + * Get customer email + * + * @return string + */ + private function getCustomerEmail() + { + return self::CUSTOMER_EMAIL; + } + + /** + * Get new password for customer account + * + * @return string + */ + private function getNewPassword() + { + return self::CUSTOMER_NEW_PASSWORD; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/StoreConfigTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/StoreConfigTest.php new file mode 100644 index 0000000000000..473105c2c46be --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/StoreConfigTest.php @@ -0,0 +1,38 @@ +graphQlQuery($query); + self::assertArrayHasKey('autocomplete_on_storefront', $response['storeConfig']); + self::assertTrue($response['storeConfig']['autocomplete_on_storefront']); + } +}