diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/ChangeSubscriptionStatus.php b/app/code/Magento/CustomerGraphQl/Model/Customer/ChangeSubscriptionStatus.php new file mode 100644 index 0000000000000..1acb418e7bba6 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/ChangeSubscriptionStatus.php @@ -0,0 +1,48 @@ +subscriberFactory = $subscriberFactory; + } + + /** + * Change subscription status. Subscribe OR unsubscribe if required + * + * @param int $customerId + * @param bool $subscriptionStatus + * @return void + */ + public function execute(int $customerId, bool $subscriptionStatus): void + { + $subscriber = $this->subscriberFactory->create()->loadByCustomerId($customerId); + + if ($subscriptionStatus === true && !$subscriber->isSubscribed()) { + $this->subscriberFactory->create()->subscribeCustomerById($customerId); + } elseif ($subscriptionStatus === false && $subscriber->isSubscribed()) { + $this->subscriberFactory->create()->unsubscribeCustomerById($customerId); + } + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerAccount.php b/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerAccount.php new file mode 100644 index 0000000000000..030fc47d19e81 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerAccount.php @@ -0,0 +1,103 @@ +authentication = $authentication; + $this->customerRepository = $customerRepository; + $this->accountManagement = $accountManagement; + } + + /** + * Check customer account + * + * @param int|null $customerId + * @param int|null $customerType + * @return void + * @throws GraphQlAuthorizationException + * @throws GraphQlNoSuchEntityException + * @throws GraphQlAuthenticationException + */ + public function execute(?int $customerId, ?int $customerType): void + { + if (true === $this->isCustomerGuest($customerId, $customerType)) { + throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); + } + + try { + $this->customerRepository->getById($customerId); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException( + __('Customer with id "%customer_id" does not exist.', ['customer_id' => $customerId]), + $e + ); + } + + if (true === $this->authentication->isLocked($customerId)) { + throw new GraphQlAuthenticationException(__('The account is locked.')); + } + + $confirmationStatus = $this->accountManagement->getConfirmationStatus($customerId); + if ($confirmationStatus === AccountManagementInterface::ACCOUNT_CONFIRMATION_REQUIRED) { + throw new GraphQlAuthenticationException(__("This account isn't confirmed. Verify and try again.")); + } + } + + /** + * Checking if current customer is guest + * + * @param int|null $customerId + * @param int|null $customerType + * @return bool + */ + private function isCustomerGuest(?int $customerId, ?int $customerType): bool + { + if (null === $customerId || null === $customerType) { + return true; + } + return 0 === (int)$customerId || (int)$customerType === UserContextInterface::USER_TYPE_GUEST; + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerPassword.php b/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerPassword.php new file mode 100644 index 0000000000000..f3c03e5fc18aa --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerPassword.php @@ -0,0 +1,50 @@ +authentication = $authentication; + } + + /** + * Check customer password + * + * @param string $password + * @param int $customerId + * @throws GraphQlAuthenticationException + */ + public function execute(string $password, int $customerId) + { + try { + $this->authentication->authenticate($customerId, $password); + } catch (InvalidEmailOrPasswordException $e) { + throw new GraphQlAuthenticationException( + __('The password doesn\'t match this account. Verify the password and try again.') + ); + } + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/CustomerDataProvider.php b/app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php similarity index 68% rename from app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/CustomerDataProvider.php rename to app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php index 1a942a2ab149a..646d426050b70 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/CustomerDataProvider.php +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php @@ -5,11 +5,12 @@ */ declare(strict_types=1); -namespace Magento\CustomerGraphQl\Model\Resolver\Customer; +namespace Magento\CustomerGraphQl\Model\Customer; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\Webapi\ServiceOutputProcessor; use Magento\Customer\Api\Data\CustomerInterface; @@ -32,21 +33,21 @@ class CustomerDataProvider /** * @var SerializerInterface */ - private $jsonSerializer; + private $serializer; /** * @param CustomerRepositoryInterface $customerRepository * @param ServiceOutputProcessor $serviceOutputProcessor - * @param SerializerInterface $jsonSerializer + * @param SerializerInterface $serializer */ public function __construct( CustomerRepositoryInterface $customerRepository, ServiceOutputProcessor $serviceOutputProcessor, - SerializerInterface $jsonSerializer + SerializerInterface $serializer ) { $this->customerRepository = $customerRepository; $this->serviceOutputProcessor = $serviceOutputProcessor; - $this->jsonSerializer = $jsonSerializer; + $this->serializer = $serializer; } /** @@ -56,42 +57,44 @@ public function __construct( * @return array * @throws NoSuchEntityException|LocalizedException */ - public function getCustomerById(int $customerId) : array + public function getCustomerById(int $customerId): array { try { - $customerObject = $this->customerRepository->getById($customerId); + $customer = $this->customerRepository->getById($customerId); } catch (NoSuchEntityException $e) { - // No error should be thrown, null result should be returned - return []; + throw new GraphQlNoSuchEntityException( + __('Customer id "%customer_id" does not exist.', ['customer_id' => $customerId]), + $e + ); } - return $this->processCustomer($customerObject); + return $this->processCustomer($customer); } /** * Transform single customer data from object to in array format * - * @param CustomerInterface $customerObject + * @param CustomerInterface $customer * @return array */ - private function processCustomer(CustomerInterface $customerObject) : array + private function processCustomer(CustomerInterface $customer): array { - $customer = $this->serviceOutputProcessor->process( - $customerObject, + $customerData = $this->serviceOutputProcessor->process( + $customer, CustomerRepositoryInterface::class, 'get' ); - if (isset($customer['extension_attributes'])) { - $customer = array_merge($customer, $customer['extension_attributes']); + if (isset($customerData['extension_attributes'])) { + $customerData = array_merge($customerData, $customerData['extension_attributes']); } $customAttributes = []; - if (isset($customer['custom_attributes'])) { - foreach ($customer['custom_attributes'] as $attribute) { + if (isset($customerData['custom_attributes'])) { + foreach ($customerData['custom_attributes'] as $attribute) { $isArray = false; if (is_array($attribute['value'])) { $isArray = true; foreach ($attribute['value'] as $attributeValue) { if (is_array($attributeValue)) { - $customAttributes[$attribute['attribute_code']] = $this->jsonSerializer->serialize( + $customAttributes[$attribute['attribute_code']] = $this->serializer->serialize( $attribute['value'] ); continue; @@ -106,8 +109,8 @@ private function processCustomer(CustomerInterface $customerObject) : array $customAttributes[$attribute['attribute_code']] = $attribute['value']; } } - $customer = array_merge($customer, $customAttributes); + $customerData = array_merge($customerData, $customAttributes); - return $customer; + return $customerData; } } diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php b/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php new file mode 100644 index 0000000000000..d235d51b5e52d --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateAccountInformation.php @@ -0,0 +1,94 @@ +customerRepository = $customerRepository; + $this->storeManager = $storeManager; + $this->checkCustomerPassword = $checkCustomerPassword; + } + + /** + * Update account information + * + * @param int $customerId + * @param array $data + * @return void + * @throws GraphQlNoSuchEntityException + * @throws GraphQlInputException + * @throws GraphQlAlreadyExistsException + */ + public function execute(int $customerId, array $data): void + { + $customer = $this->customerRepository->getById($customerId); + + if (isset($data['firstname'])) { + $customer->setFirstname($data['firstname']); + } + + if (isset($data['lastname'])) { + $customer->setLastname($data['lastname']); + } + + if (isset($data['email']) && $customer->getEmail() !== $data['email']) { + if (!isset($data['password']) || empty($data['password'])) { + throw new GraphQlInputException(__('For changing "email" you should provide current "password".')); + } + + $this->checkCustomerPassword->execute($data['password'], $customerId); + $customer->setEmail($data['email']); + } + + $customer->setStoreId($this->storeManager->getStore()->getId()); + + try { + $this->customerRepository->save($customer); + } catch (AlreadyExistsException $e) { + throw new GraphQlAlreadyExistsException( + __('A customer with the same email address already exists in an associated website.'), + $e + ); + } + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php new file mode 100644 index 0000000000000..98b0975b37169 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php @@ -0,0 +1,92 @@ +checkCustomerAccount = $checkCustomerAccount; + $this->checkCustomerPassword = $checkCustomerPassword; + $this->accountManagement = $accountManagement; + $this->customerDataProvider = $customerDataProvider; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($args['currentPassword'])) { + throw new GraphQlInputException(__('"currentPassword" value should be specified')); + } + + if (!isset($args['newPassword'])) { + throw new GraphQlInputException(__('"newPassword" value should be specified')); + } + + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); + + $currentUserId = (int)$currentUserId; + $this->checkCustomerPassword->execute($args['currentPassword'], $currentUserId); + + $this->accountManagement->changePasswordById($currentUserId, $args['currentPassword'], $args['newPassword']); + + $data = $this->customerDataProvider->getCustomerById($currentUserId); + return $data; + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php index 98941af3b73d2..c3c78a1004da6 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php @@ -7,14 +7,10 @@ namespace Magento\CustomerGraphQl\Model\Resolver; -use Magento\Authorization\Model\UserContextInterface; +use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\CustomerGraphQl\Model\Resolver\Customer\CustomerDataProvider; -use Magento\Framework\Exception\NoSuchEntityException; +use Magento\CustomerGraphQl\Model\Customer\CustomerDataProvider; use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; -use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; -use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; use Magento\Framework\GraphQl\Query\ResolverInterface; /** @@ -22,18 +18,26 @@ */ class Customer implements ResolverInterface { + /** + * @var CheckCustomerAccount + */ + private $checkCustomerAccount; + /** * @var CustomerDataProvider */ - private $customerResolver; + private $customerDataProvider; /** - * @param CustomerDataProvider $customerResolver + * @param CheckCustomerAccount $checkCustomerAccount + * @param CustomerDataProvider $customerDataProvider */ public function __construct( - CustomerDataProvider $customerResolver + CheckCustomerAccount $checkCustomerAccount, + CustomerDataProvider $customerDataProvider ) { - $this->customerResolver = $customerResolver; + $this->checkCustomerAccount = $checkCustomerAccount; + $this->customerDataProvider = $customerDataProvider; } /** @@ -46,21 +50,13 @@ public function resolve( array $value = null, array $args = null ) { - /** @var ContextInterface $context */ - if ((!$context->getUserId()) || $context->getUserType() == UserContextInterface::USER_TYPE_GUEST) { - throw new GraphQlAuthorizationException( - __( - 'Current customer does not have access to the resource "%1"', - [\Magento\Customer\Model\Customer::ENTITY] - ) - ); - } + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); - try { - $data = $this->customerResolver->getCustomerById($context->getUserId()); - return !empty($data) ? $data : []; - } catch (NoSuchEntityException $exception) { - throw new GraphQlNoSuchEntityException(__('Customer id %1 does not exist.', [$context->getUserId()])); - } + $currentUserId = (int)$currentUserId; + $data = $this->customerDataProvider->getCustomerById($currentUserId); + return $data; } } diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/ChangePassword.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/ChangePassword.php deleted file mode 100644 index 84425df62a622..0000000000000 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/ChangePassword.php +++ /dev/null @@ -1,88 +0,0 @@ -userContext = $userContext; - $this->accountManagement = $accountManagement; - $this->customerResolver = $customerResolver; - } - - /** - * @inheritdoc - */ - public function resolve( - Field $field, - $context, - ResolveInfo $info, - array $value = null, - array $args = null - ) { - if (!isset($args['currentPassword'])) { - throw new GraphQlInputException(__('"currentPassword" value should be specified')); - } - - if (!isset($args['newPassword'])) { - throw new GraphQlInputException(__('"newPassword" value should be specified')); - } - - $customerId = (int)$this->userContext->getUserId(); - if ($customerId === 0) { - throw new GraphQlAuthorizationException( - __( - 'Current customer does not have access to the resource "%1"', - [Customer::ENTITY] - ) - ); - } - - $this->accountManagement->changePasswordById($customerId, $args['currentPassword'], $args['newPassword']); - $data = $this->customerResolver->getCustomerById($customerId); - - return $data; - } -} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/GenerateCustomerToken.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php similarity index 70% rename from app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/GenerateCustomerToken.php rename to app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php index b756a96411a44..9719d048f606b 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/GenerateCustomerToken.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/GenerateCustomerToken.php @@ -5,11 +5,12 @@ */ declare(strict_types=1); -namespace Magento\CustomerGraphQl\Model\Resolver\Customer\Account; +namespace Magento\CustomerGraphQl\Model\Resolver; use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; +use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Integration\Api\CustomerTokenServiceInterface; @@ -43,19 +44,19 @@ public function resolve( array $value = null, array $args = null ) { + if (!isset($args['email'])) { + throw new GraphQlInputException(__('"email" value should be specified')); + } + + if (!isset($args['password'])) { + throw new GraphQlInputException(__('"password" value should be specified')); + } + try { - if (!isset($args['email'])) { - throw new GraphQlInputException(__('"email" value should be specified')); - } - if (!isset($args['password'])) { - throw new GraphQlInputException(__('"password" value should be specified')); - } $token = $this->customerTokenService->createCustomerAccessToken($args['email'], $args['password']); return ['token' => $token]; } catch (AuthenticationException $e) { - throw new GraphQlAuthorizationException( - __($e->getMessage()) - ); + throw new GraphQlAuthenticationException(__($e->getMessage()), $e); } } } diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/IsSubscribed.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/IsSubscribed.php new file mode 100644 index 0000000000000..c0bd864b3ee09 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/IsSubscribed.php @@ -0,0 +1,61 @@ +checkCustomerAccount = $checkCustomerAccount; + $this->subscriberFactory = $subscriberFactory; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); + + $status = $this->subscriberFactory->create()->loadByCustomerId((int)$currentUserId)->isSubscribed(); + return (bool)$status; + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/RevokeCustomerToken.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php similarity index 59% rename from app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/RevokeCustomerToken.php rename to app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php index 7eb219a01217e..d3b16c05a6492 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer/Account/RevokeCustomerToken.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php @@ -5,11 +5,10 @@ */ declare(strict_types=1); -namespace Magento\CustomerGraphQl\Model\Resolver\Customer\Account; +namespace Magento\CustomerGraphQl\Model\Resolver; -use Magento\Authorization\Model\UserContextInterface; +use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount; use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Integration\Api\CustomerTokenServiceInterface; @@ -20,9 +19,9 @@ class RevokeCustomerToken implements ResolverInterface { /** - * @var UserContextInterface + * @var CheckCustomerAccount */ - private $userContext; + private $checkCustomerAccount; /** * @var CustomerTokenServiceInterface @@ -30,14 +29,14 @@ class RevokeCustomerToken implements ResolverInterface private $customerTokenService; /** - * @param UserContextInterface $userContext + * @param CheckCustomerAccount $checkCustomerAccount * @param CustomerTokenServiceInterface $customerTokenService */ public function __construct( - UserContextInterface $userContext, + CheckCustomerAccount $checkCustomerAccount, CustomerTokenServiceInterface $customerTokenService ) { - $this->userContext = $userContext; + $this->checkCustomerAccount = $checkCustomerAccount; $this->customerTokenService = $customerTokenService; } @@ -51,17 +50,11 @@ public function resolve( array $value = null, array $args = null ) { - $customerId = (int)$this->userContext->getUserId(); + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); - if ($customerId === 0) { - throw new GraphQlAuthorizationException( - __( - 'Current customer does not have access to the resource "%1"', - [\Magento\Customer\Model\Customer::ENTITY] - ) - ); - } + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); - return $this->customerTokenService->revokeCustomerAccessToken($customerId); + return $this->customerTokenService->revokeCustomerAccessToken((int)$currentUserId); } } diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php new file mode 100644 index 0000000000000..5dc857f3f178c --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php @@ -0,0 +1,91 @@ +checkCustomerAccount = $checkCustomerAccount; + $this->updateAccountInformation = $updateAccountInformation; + $this->changeSubscriptionStatus = $changeSubscriptionStatus; + $this->customerDataProvider = $customerDataProvider; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($args['input']) || !is_array($args['input']) || empty($args['input'])) { + throw new GraphQlInputException(__('"input" value should be specified')); + } + + $currentUserId = $context->getUserId(); + $currentUserType = $context->getUserType(); + + $this->checkCustomerAccount->execute($currentUserId, $currentUserType); + + $currentUserId = (int)$currentUserId; + $this->updateAccountInformation->execute($currentUserId, $args['input']); + + if (isset($args['input']['is_subscribed'])) { + $this->changeSubscriptionStatus->execute($currentUserId, (bool)$args['input']['is_subscribed']); + } + + $data = $this->customerDataProvider->getCustomerById($currentUserId); + return ['customer' => $data]; + } +} diff --git a/app/code/Magento/CustomerGraphQl/composer.json b/app/code/Magento/CustomerGraphQl/composer.json index c26c83c95be38..488013c1ee586 100644 --- a/app/code/Magento/CustomerGraphQl/composer.json +++ b/app/code/Magento/CustomerGraphQl/composer.json @@ -6,7 +6,9 @@ "php": "~7.1.3||~7.2.0", "magento/module-customer": "*", "magento/module-authorization": "*", + "magento/module-newsletter": "*", "magento/module-integration": "*", + "magento/module-store": "*", "magento/framework": "*" }, "suggest": { diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls index f3e3365f1c35e..b8411f00c5cb1 100644 --- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls @@ -6,15 +6,28 @@ type Query { } type Mutation { - generateCustomerToken(email: String!, password: String!): CustomerToken @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\Customer\\Account\\GenerateCustomerToken") @doc(description:"Retrieve Customer token") - changeCustomerPassword(currentPassword: String!, newPassword: String!): Customer @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\Customer\\Account\\ChangePassword") @doc(description:"Changes password for logged in customer") - revokeCustomerToken: Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\Customer\\Account\\RevokeCustomerToken") @doc(description:"Revoke Customer token") + generateCustomerToken(email: String!, password: String!): CustomerToken @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\GenerateCustomerToken") @doc(description:"Retrieve Customer token") + changeCustomerPassword(currentPassword: String!, newPassword: String!): Customer @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ChangePassword") @doc(description:"Changes password for logged in customer") + updateCustomer (input: UpdateCustomerInput): UpdateCustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Update customer personal information") + revokeCustomerToken: Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RevokeCustomerToken") @doc(description:"Revoke Customer token") } type CustomerToken { token: String @doc(description: "The customer token") } +input UpdateCustomerInput { + firstname: String + lastname: String + email: String + password: String + is_subscribed: Boolean +} + +type UpdateCustomerOutput { + customer: Customer! +} + type Customer @doc(description: "Customer defines the customer name and address and other details") { created_at: String @doc(description: "Timestamp indicating when the account was created") group_id: Int @doc(description: "The group assigned to the user. Default values are 0 (Not logged in), 1 (General), 2 (Wholesale), and 3 (Retailer)") @@ -29,9 +42,9 @@ type Customer @doc(description: "Customer defines the customer name and address dob: String @doc(description: "The customer's date of birth") taxvat: String @doc(description: "The customer's Tax/VAT number (for corporate customers)") id: Int @doc(description: "The ID assigned to the customer") - is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter") + is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter") @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\IsSubscribed") addresses: [CustomerAddress] @doc(description: "An array containing the customer's shipping and billing addresses") -} +} type CustomerAddress @doc(description: "CustomerAddress contains detailed information about a customer's billing and shipping addresses"){ id: Int @doc(description: "The ID assigned to the address object") @@ -60,4 +73,3 @@ type CustomerAddressRegion @doc(description: "CustomerAddressRegion defines the region: String @doc(description: "The state or province name") region_id: Int @doc(description: "Uniquely identifies the region") } - diff --git a/app/code/Magento/QuoteGraphQl/Model/Authorization/IsCartMutationAllowedForCurrentUser.php b/app/code/Magento/QuoteGraphQl/Model/Authorization/IsCartMutationAllowedForCurrentUser.php deleted file mode 100644 index 2dec8c278800b..0000000000000 --- a/app/code/Magento/QuoteGraphQl/Model/Authorization/IsCartMutationAllowedForCurrentUser.php +++ /dev/null @@ -1,67 +0,0 @@ -userContext = $userContext; - $this->cartRepository = $cartRepository; - } - - /** - * Check that the shopping cart operations are allowed for current user - * - * @param int $quoteId - * @return bool - * @throws GraphQlNoSuchEntityException - */ - public function execute(int $quoteId): bool - { - try { - $quote = $this->cartRepository->get($quoteId); - } catch (NoSuchEntityException $exception) { - throw new GraphQlNoSuchEntityException(__($exception->getMessage())); - } - - $customerId = $quote->getCustomerId(); - - /* Guest cart, allow operations */ - if (!$customerId) { - return true; - } - - /* If the quote belongs to the current customer allow operations */ - return $customerId == $this->userContext->getUserId(); - } -} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php index daec9411307f8..96259f2264943 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php @@ -7,71 +7,47 @@ namespace Magento\QuoteGraphQl\Model\Cart; -use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; -use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\Message\AbstractMessage; use Magento\Quote\Api\CartRepositoryInterface; -use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; use Magento\Quote\Model\Quote; -use Magento\QuoteGraphQl\Model\Authorization\IsCartMutationAllowedForCurrentUser; /** * Add products to cart */ class AddProductsToCart { - /** - * @var MaskedQuoteIdToQuoteIdInterface - */ - private $maskedQuoteIdToQuoteId; - /** * @var CartRepositoryInterface */ private $cartRepository; - /** - * @var IsCartMutationAllowedForCurrentUser - */ - private $isCartMutationAllowedForCurrentUser; - /** * @var AddSimpleProductToCart */ private $addProductToCart; /** - * @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId * @param CartRepositoryInterface $cartRepository - * @param IsCartMutationAllowedForCurrentUser $isCartMutationAllowedForCurrentUser * @param AddSimpleProductToCart $addProductToCart */ public function __construct( - MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId, CartRepositoryInterface $cartRepository, - IsCartMutationAllowedForCurrentUser $isCartMutationAllowedForCurrentUser, AddSimpleProductToCart $addProductToCart ) { - $this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId; $this->cartRepository = $cartRepository; - $this->isCartMutationAllowedForCurrentUser = $isCartMutationAllowedForCurrentUser; $this->addProductToCart = $addProductToCart; } /** * Add products to cart * - * @param string $cartHash + * @param Quote $cart * @param array $cartItems - * @return Quote * @throws GraphQlInputException */ - public function execute(string $cartHash, array $cartItems): Quote + public function execute(Quote $cart, array $cartItems): void { - $cart = $this->getCart($cartHash); - foreach ($cartItems as $cartItemData) { $this->addProductToCart->execute($cart, $cartItemData); } @@ -83,39 +59,6 @@ public function execute(string $cartHash, array $cartItems): Quote } $this->cartRepository->save($cart); - return $cart; - } - - /** - * Get cart - * - * @param string $cartHash - * @return Quote - * @throws GraphQlNoSuchEntityException - * @throws GraphQlAuthorizationException - */ - private function getCart(string $cartHash): Quote - { - try { - $cartId = $this->maskedQuoteIdToQuoteId->execute($cartHash); - $cart = $this->cartRepository->get($cartId); - } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException( - __('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash]) - ); - } - - if (false === $this->isCartMutationAllowedForCurrentUser->execute($cartId)) { - throw new GraphQlAuthorizationException( - __( - 'The current user cannot perform operations on cart "%masked_cart_id"', - ['masked_cart_id' => $cartHash] - ) - ); - } - - /** @var Quote $cart */ - return $cart; } /** diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php new file mode 100644 index 0000000000000..9c50d4b85578b --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php @@ -0,0 +1,89 @@ +maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId; + $this->cartRepository = $cartRepository; + } + + /** + * Get cart for user + * + * @param string $cartHash + * @param int|null $userId + * @return Quote + * @throws GraphQlAuthenticationException + * @throws GraphQlNoSuchEntityException + */ + public function execute(string $cartHash, ?int $userId): Quote + { + try { + $cartId = $this->maskedQuoteIdToQuoteId->execute($cartHash); + } catch (NoSuchEntityException $exception) { + throw new GraphQlNoSuchEntityException( + __('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash]) + ); + } + + try { + /** @var Quote $cart */ + $cart = $this->cartRepository->get($cartId); + } catch (NoSuchEntityException $e) { + throw new GraphQlNoSuchEntityException( + __('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash]) + ); + } + + $customerId = (int)$cart->getCustomerId(); + + /* Guest cart, allow operations */ + if (!$customerId) { + return $cart; + } + + if ($customerId !== $userId) { + throw new GraphQlAuthenticationException( + __( + 'The current user cannot perform operations on cart "%masked_cart_id"', + ['masked_cart_id' => $cartHash] + ) + ); + } + return $cart; + } +} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php index 31a0b3d02e44a..f4335b262c854 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php @@ -14,6 +14,7 @@ use Magento\Framework\Stdlib\ArrayManager; use Magento\QuoteGraphQl\Model\Cart\AddProductsToCart; use Magento\QuoteGraphQl\Model\Cart\ExtractDataFromCart; +use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; /** * Add simple products to cart GraphQl resolver @@ -26,6 +27,11 @@ class AddSimpleProductsToCart implements ResolverInterface */ private $arrayManager; + /** + * @var GetCartForUser + */ + private $getCartForUser; + /** * @var AddProductsToCart */ @@ -38,15 +44,18 @@ class AddSimpleProductsToCart implements ResolverInterface /** * @param ArrayManager $arrayManager + * @param GetCartForUser $getCartForUser * @param AddProductsToCart $addProductsToCart * @param ExtractDataFromCart $extractDataFromCart */ public function __construct( ArrayManager $arrayManager, + GetCartForUser $getCartForUser, AddProductsToCart $addProductsToCart, ExtractDataFromCart $extractDataFromCart ) { $this->arrayManager = $arrayManager; + $this->getCartForUser = $getCartForUser; $this->addProductsToCart = $addProductsToCart; $this->extractDataFromCart = $extractDataFromCart; } @@ -67,7 +76,10 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value throw new GraphQlInputException(__('Missing key "cartItems" in cart data')); } - $cart = $this->addProductsToCart->execute((string)$cartHash, $cartItems); + $currentUserId = $context->getUserId(); + $cart = $this->getCartForUser->execute((string)$cartHash, $currentUserId); + + $this->addProductsToCart->execute($cart, $cartItems); $cartData = $this->extractDataFromCart->execute($cart); return [ diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php index ac5cb38326bc0..ec59416d49371 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php @@ -8,16 +8,15 @@ namespace Magento\QuoteGraphQl\Model\Resolver; use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Quote\Api\CouponManagementInterface; -use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; -use Magento\QuoteGraphQl\Model\Authorization\IsCartMutationAllowedForCurrentUser; +use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; /** * @inheritdoc @@ -25,33 +24,25 @@ class ApplyCouponToCart implements ResolverInterface { /** - * @var CouponManagementInterface + * @var GetCartForUser */ - private $couponManagement; + private $getCartForUser; /** - * @var MaskedQuoteIdToQuoteIdInterface - */ - private $maskedQuoteIdToQuoteId; - - /** - * @var IsCartMutationAllowedForCurrentUser + * @var CouponManagementInterface */ - private $isCartMutationAllowedForCurrentUser; + private $couponManagement; /** + * @param GetCartForUser $getCartForUser * @param CouponManagementInterface $couponManagement - * @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToId - * @param IsCartMutationAllowedForCurrentUser $isCartMutationAllowedForCurrentUser */ public function __construct( - CouponManagementInterface $couponManagement, - MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToId, - IsCartMutationAllowedForCurrentUser $isCartMutationAllowedForCurrentUser + GetCartForUser $getCartForUser, + CouponManagementInterface $couponManagement ) { + $this->getCartForUser = $getCartForUser; $this->couponManagement = $couponManagement; - $this->maskedQuoteIdToQuoteId = $maskedQuoteIdToId; - $this->isCartMutationAllowedForCurrentUser = $isCartMutationAllowedForCurrentUser; } /** @@ -69,22 +60,9 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } $couponCode = $args['input']['coupon_code']; - try { - $cartId = $this->maskedQuoteIdToQuoteId->execute($maskedCartId); - } catch (NoSuchEntityException $exception) { - throw new GraphQlNoSuchEntityException( - __('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $maskedCartId]) - ); - } - - if (false === $this->isCartMutationAllowedForCurrentUser->execute($cartId)) { - throw new GraphQlAuthorizationException( - __( - 'The current user cannot perform operations on cart "%masked_cart_id"', - ['masked_cart_id' => $maskedCartId] - ) - ); - } + $currentUserId = $context->getUserId(); + $cart = $this->getCartForUser->execute($maskedCartId, $currentUserId); + $cartId = $cart->getId(); /* Check current cart does not have coupon code applied */ $appliedCouponCode = $this->couponManagement->get($cartId); @@ -99,7 +77,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } catch (NoSuchEntityException $exception) { throw new GraphQlNoSuchEntityException(__($exception->getMessage())); } catch (CouldNotSaveException $exception) { - throw new GraphQlInputException(__($exception->getMessage())); + throw new LocalizedException(__($exception->getMessage())); } $data['cart']['applied_coupon'] = [ diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CreateEmptyCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CreateEmptyCart.php index 1ccc2fda58e56..06123abe615e6 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CreateEmptyCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CreateEmptyCart.php @@ -7,7 +7,6 @@ namespace Magento\QuoteGraphQl\Model\Resolver; -use Magento\Authorization\Model\UserContextInterface; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; @@ -25,6 +24,7 @@ class CreateEmptyCart implements ResolverInterface * @var CartManagementInterface */ private $cartManagement; + /** * @var GuestCartManagementInterface */ @@ -35,11 +35,6 @@ class CreateEmptyCart implements ResolverInterface */ private $quoteIdToMaskedId; - /** - * @var UserContextInterface - */ - private $userContext; - /** * @var QuoteIdMaskFactory */ @@ -48,20 +43,17 @@ class CreateEmptyCart implements ResolverInterface /** * @param CartManagementInterface $cartManagement * @param GuestCartManagementInterface $guestCartManagement - * @param UserContextInterface $userContext * @param QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedId * @param QuoteIdMaskFactory $quoteIdMaskFactory */ public function __construct( CartManagementInterface $cartManagement, GuestCartManagementInterface $guestCartManagement, - UserContextInterface $userContext, QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedId, QuoteIdMaskFactory $quoteIdMaskFactory ) { $this->cartManagement = $cartManagement; $this->guestCartManagement = $guestCartManagement; - $this->userContext = $userContext; $this->quoteIdToMaskedId = $quoteIdToMaskedId; $this->quoteIdMaskFactory = $quoteIdMaskFactory; } @@ -71,7 +63,7 @@ public function __construct( */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { - $customerId = $this->userContext->getUserId(); + $customerId = $context->getUserId(); if (0 !== $customerId && null !== $customerId) { $quoteId = $this->cartManagement->createEmptyCartForCustomer($customerId); diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php index 40175cc589954..c21d869ddac7d 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php @@ -8,16 +8,15 @@ namespace Magento\QuoteGraphQl\Model\Resolver; use Magento\Framework\Exception\CouldNotDeleteException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Quote\Api\CouponManagementInterface; -use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface; -use Magento\QuoteGraphQl\Model\Authorization\IsCartMutationAllowedForCurrentUser; +use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; /** * @inheritdoc @@ -25,9 +24,9 @@ class RemoveCouponFromCart implements ResolverInterface { /** - * @var MaskedQuoteIdToQuoteIdInterface + * @var GetCartForUser */ - private $maskedQuoteIdToId; + private $getCartForUser; /** * @var CouponManagementInterface @@ -35,23 +34,15 @@ class RemoveCouponFromCart implements ResolverInterface private $couponManagement; /** - * @var IsCartMutationAllowedForCurrentUser - */ - private $isCartMutationAllowedForCurrentUser; - - /** + * @param GetCartForUser $getCartForUser * @param CouponManagementInterface $couponManagement - * @param IsCartMutationAllowedForCurrentUser $isCartMutationAllowedForCurrentUser - * @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToId */ public function __construct( - CouponManagementInterface $couponManagement, - IsCartMutationAllowedForCurrentUser $isCartMutationAllowedForCurrentUser, - MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToId + GetCartForUser $getCartForUser, + CouponManagementInterface $couponManagement ) { + $this->getCartForUser = $getCartForUser; $this->couponManagement = $couponManagement; - $this->isCartMutationAllowedForCurrentUser = $isCartMutationAllowedForCurrentUser; - $this->maskedQuoteIdToId = $maskedQuoteIdToId; } /** @@ -64,29 +55,16 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } $maskedCartId = $args['input']['cart_id']; - try { - $cartId = $this->maskedQuoteIdToId->execute($maskedCartId); - } catch (NoSuchEntityException $exception) { - throw new GraphQlNoSuchEntityException( - __('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $maskedCartId]) - ); - } - - if (false === $this->isCartMutationAllowedForCurrentUser->execute($cartId)) { - throw new GraphQlAuthorizationException( - __( - 'The current user cannot perform operations on cart "%masked_cart_id"', - ['masked_cart_id' => $maskedCartId] - ) - ); - } + $currentUserId = $context->getUserId(); + $cart = $this->getCartForUser->execute($maskedCartId, $currentUserId); + $cartId = $cart->getId(); try { $this->couponManagement->remove($cartId); } catch (NoSuchEntityException $exception) { throw new GraphQlNoSuchEntityException(__($exception->getMessage())); } catch (CouldNotDeleteException $exception) { - throw new GraphQlInputException(__($exception->getMessage())); + throw new LocalizedException(__($exception->getMessage())); } $data['cart']['applied_coupon'] = [ diff --git a/app/code/Magento/QuoteGraphQl/composer.json b/app/code/Magento/QuoteGraphQl/composer.json index bed9157c4de59..c9900dd5f3150 100644 --- a/app/code/Magento/QuoteGraphQl/composer.json +++ b/app/code/Magento/QuoteGraphQl/composer.json @@ -5,7 +5,6 @@ "require": { "php": "~7.1.3||~7.2.0", "magento/framework": "*", - "magento/module-authorization": "*", "magento/module-quote": "*", "magento/module-catalog": "*", "magento/module-store": "*" diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AccountInformationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AccountInformationTest.php new file mode 100644 index 0000000000000..2caee84ba6f00 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AccountInformationTest.php @@ -0,0 +1,343 @@ +customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + $this->accountManagement = Bootstrap::getObjectManager()->get(AccountManagementInterface::class); + $this->customerRegistry = Bootstrap::getObjectManager()->get(CustomerRegistry::class); + $this->customerAuthUpdate = Bootstrap::getObjectManager()->get(CustomerAuthUpdate::class); + $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testGetAccountInformation() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + + $this->assertEquals('John', $response['customer']['firstname']); + $this->assertEquals('Smith', $response['customer']['lastname']); + $this->assertEquals($currentEmail, $response['customer']['email']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testGetAccountInformationIfUserIsNotAuthorized() + { + $query = <<graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage The account is locked. + */ + public function testGetAccountInformationIfCustomerIsLocked() + { + $this->lockCustomer(1); + + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testUpdateAccountInformation() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $newFirstname = 'Richard'; + $newLastname = 'Rowe'; + $newEmail = 'customer_updated@example.com'; + + $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + + $this->assertEquals($newFirstname, $response['updateCustomer']['customer']['firstname']); + $this->assertEquals($newLastname, $response['updateCustomer']['customer']['lastname']); + $this->assertEquals($newEmail, $response['updateCustomer']['customer']['email']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage "input" value should be specified + */ + public function testUpdateAccountInformationIfInputDataIsEmpty() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testUpdateAccountInformationIfUserIsNotAuthorized() + { + $newFirstname = 'Richard'; + + $query = <<graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage The account is locked. + */ + public function testUpdateAccountInformationIfCustomerIsLocked() + { + $this->lockCustomer(1); + + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + $newFirstname = 'Richard'; + + $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage For changing "email" you should provide current "password". + */ + public function testUpdateEmailIfPasswordIsMissed() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + $newEmail = 'customer_updated@example.com'; + + $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage The password doesn't match this account. Verify the password and try again. + */ + public function testUpdateEmailIfPasswordIsInvalid() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + $invalidPassword = 'invalid_password'; + $newEmail = 'customer_updated@example.com'; + + $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/two_customers.php + * @expectedException \Exception + * @expectedExceptionMessage A customer with the same email address already exists in an associated website. + */ + public function testUpdateEmailIfEmailAlreadyExists() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + $existedEmail = 'customer_two@example.com'; + + $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + } + + /** + * @param string $email + * @param string $password + * @return array + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + /** + * @param int $customerId + * @return void + */ + private function lockCustomer(int $customerId): void + { + $customerSecure = $this->customerRegistry->retrieveSecureData($customerId); + $customerSecure->setLockExpires('2030-12-31 00:00:00'); + $this->customerAuthUpdate->saveAuth($customerId); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerAuthenticationTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AddressesTest.php similarity index 84% rename from dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerAuthenticationTest.php rename to dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AddressesTest.php index 88ce7e91d94bc..9b7e3f28327da 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerAuthenticationTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/AddressesTest.php @@ -14,16 +14,13 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Integration\Api\CustomerTokenServiceInterface; -class CustomerAuthenticationTest extends GraphQlAbstract +class AddressesTest extends GraphQlAbstract { /** - * Verify customers with valid credentials with a customer bearer token - * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testRegisteredCustomerWithValidCredentials() + public function testGetCustomerWithAddresses() { $query = <<assertCustomerAddressesFields($customer, $response); } - /** - * Verify customer with valid credentials but without the bearer token - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testCustomerWithValidCredentialsWithoutToken() - { - $query - = <<expectException(\Exception::class); - $this->expectExceptionMessage('GraphQL response contains errors: Current customer' . ' ' . - 'does not have access to the resource "customer"'); - $this->graphQlQuery($query); - } - /** * Verify the all the whitelisted fields for a Customer Object * diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerChangePasswordTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerChangePasswordTest.php index ede719bb569ba..f245181815217 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerChangePasswordTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CustomerChangePasswordTest.php @@ -41,7 +41,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php */ - public function testCustomerChangeValidPassword() + public function testChangePassword() { $customerEmail = 'customer@example.com'; $oldCustomerPassword = 'password'; @@ -62,14 +62,13 @@ public function testCustomerChangeValidPassword() } } - public function testGuestUserCannotChangePassword() + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testChangePasswordIfUserIsNotAuthorizedTest() { $query = $this->getChangePassQuery('currentpassword', 'newpassword'); - $this->expectException(\Exception::class); - $this->expectExceptionMessage( - 'GraphQL response contains errors: Current customer' . ' ' . - 'does not have access to the resource "customer"' - ); $this->graphQlQuery($query); } @@ -94,10 +93,11 @@ public function testChangeWeakPassword() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @expectedException \Exception + * @expectedExceptionMessage The password doesn't match this account. Verify the password and try again. */ - public function testCannotChangeWithIncorrectPassword() + public function testChangePasswordIfPasswordIsInvalid() { - $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/190'); $customerEmail = 'customer@example.com'; $oldCustomerPassword = 'password'; $newCustomerPassword = 'anotherPassword1'; @@ -105,13 +105,7 @@ public function testCannotChangeWithIncorrectPassword() $query = $this->getChangePassQuery($incorrectPassword, $newCustomerPassword); - // acquire authentication with correct password $headerMap = $this->getCustomerAuthHeaders($customerEmail, $oldCustomerPassword); - - $this->expectException(\Exception::class); - $this->expectExceptionMessageRegExp('/The password doesn\'t match this account. Verify the password.*/'); - - // but try to change with incorrect 'old' password $this->graphQlQuery($query, [], '', $headerMap); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php index dac48bf01e7db..415a81f8cf45a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/RevokeCustomerTokenTest.php @@ -7,6 +7,7 @@ namespace Magento\GraphQl\Customer; +use Magento\Integration\Api\CustomerTokenServiceInterface; use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -16,7 +17,6 @@ class RevokeCustomerTokenTest extends GraphQlAbstract { /** - * Verify customers with valid credentials * @magentoApiDataFixture Magento/Customer/_files/customer.php */ public function testRevokeCustomerTokenValidCredentials() @@ -30,8 +30,7 @@ public function testRevokeCustomerTokenValidCredentials() $userName = 'customer@example.com'; $password = 'password'; /** @var CustomerTokenServiceInterface $customerTokenService */ - $customerTokenService = ObjectManager::getInstance() - ->get(\Magento\Integration\Api\CustomerTokenServiceInterface::class); + $customerTokenService = ObjectManager::getInstance()->get(CustomerTokenServiceInterface::class); $customerToken = $customerTokenService->createCustomerAccessToken($userName, $password); $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; @@ -40,7 +39,8 @@ public function testRevokeCustomerTokenValidCredentials() } /** - * Verify guest customers + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. */ public function testRevokeCustomerTokenForGuestCustomer() { @@ -49,11 +49,6 @@ public function testRevokeCustomerTokenForGuestCustomer() revokeCustomerToken } QUERY; - $this->expectException(\Exception::class); - $this->expectExceptionMessage( - 'GraphQL response contains errors: Current customer' . ' ' . - 'does not have access to the resource "customer"' - ); $this->graphQlQuery($query, [], ''); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php new file mode 100644 index 0000000000000..191ea1ae6b877 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/SubscriptionStatusTest.php @@ -0,0 +1,134 @@ +customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + $this->subscriberFactory = Bootstrap::getObjectManager()->get(SubscriberFactory::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testGetSubscriptionStatusTest() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->assertFalse($response['customer']['is_subscribed']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testGetSubscriptionStatusIfUserIsNotAuthorizedTest() + { + $query = <<graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testChangeSubscriptionStatusTest() + { + $currentEmail = 'customer@example.com'; + $currentPassword = 'password'; + + $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword)); + $this->assertTrue($response['updateCustomer']['customer']['is_subscribed']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. + */ + public function testChangeSubscriptionStatuIfUserIsNotAuthorizedTest() + { + $query = <<graphQlQuery($query); + } + + /** + * @param string $email + * @param string $password + * @return array + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } + + protected function tearDown() + { + parent::tearDown(); + + $this->subscriberFactory->create()->loadByCustomerId(1)->delete(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/two_customers_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/two_customers_rollback.php new file mode 100644 index 0000000000000..5beb9e654de63 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/two_customers_rollback.php @@ -0,0 +1,32 @@ +get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); +try { + $customerRepository->deleteById(2); +} catch (NoSuchEntityException $e) { + /** + * Tests which are wrapped with MySQL transaction clear all data by transaction rollback. + */ +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAlreadyExistsException.php b/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAlreadyExistsException.php new file mode 100644 index 0000000000000..d25707b2ca347 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAlreadyExistsException.php @@ -0,0 +1,56 @@ +isSafe = $isSafe; + parent::__construct($phrase, $cause, $code); + } + + /** + * @inheritdoc + */ + public function isClientSafe(): bool + { + return $this->isSafe; + } + + /** + * @inheritdoc + */ + public function getCategory(): string + { + return self::EXCEPTION_CATEGORY; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAuthenticationException.php b/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAuthenticationException.php new file mode 100644 index 0000000000000..4df74bbf332cd --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlAuthenticationException.php @@ -0,0 +1,56 @@ +isSafe = $isSafe; + parent::__construct($phrase, $cause, $code); + } + + /** + * @inheritdoc + */ + public function isClientSafe(): bool + { + return $this->isSafe; + } + + /** + * @inheritdoc + */ + public function getCategory(): string + { + return self::EXCEPTION_CATEGORY; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ResolverInterface.php b/lib/internal/Magento/Framework/GraphQl/Query/ResolverInterface.php index 295113a98e465..f0450dce7f5f0 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/ResolverInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/ResolverInterface.php @@ -20,7 +20,7 @@ interface ResolverInterface * Fetches the data from persistence models and format it according to the GraphQL schema. * * @param \Magento\Framework\GraphQl\Config\Element\Field $field - * @param $context + * @param \Magento\Framework\GraphQl\Query\Resolver\ContextInterface $context * @param ResolveInfo $info * @param array|null $value * @param array|null $args