Skip to content
This repository was archived by the owner on Dec 19, 2019. It is now read-only.

Allow changing logged in customers password with Graphql mutation #150

Merged
merged 2 commits into from
Sep 20, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\CustomerGraphQl\Model\Resolver\Customer\Account;

use Magento\Authorization\Model\UserContextInterface;
use Magento\Customer\Api\AccountManagementInterface;
use Magento\Customer\Model\Customer;
use Magento\CustomerGraphQl\Model\Resolver\Customer\CustomerDataProvider;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
use Magento\Framework\GraphQl\Query\Resolver\Value;
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;

/**
* {@inheritdoc}
*/
class ChangePassword implements ResolverInterface
{
/**
* @var UserContextInterface
*/
private $userContext;

/**
* @var AccountManagementInterface
*/
private $accountManagement;

/**
* @var CustomerDataProvider
*/
private $customerResolver;

/**
* @var ValueFactory
*/
private $valueFactory;

/**
* @param UserContextInterface $userContext
* @param AccountManagementInterface $accountManagement
* @param CustomerDataProvider $customerResolver
* @param ValueFactory $valueFactory
*/
public function __construct(
UserContextInterface $userContext,
AccountManagementInterface $accountManagement,
CustomerDataProvider $customerResolver,
ValueFactory $valueFactory
) {
$this->userContext = $userContext;
$this->accountManagement = $accountManagement;
$this->customerResolver = $customerResolver;
$this->valueFactory = $valueFactory;
}

/**
* @inheritdoc
*/
public function resolve(
Field $field,
$context,
ResolveInfo $info,
array $value = null,
array $args = null
): Value {
$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);
$result = function () use ($data) {
return !empty($data) ? $data : [];
};

return $this->valueFactory->create($result);
}
}
4 changes: 4 additions & 0 deletions app/code/Magento/CustomerGraphQl/etc/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ type Query {
customer: Customer @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\Customer") @doc(description: "The customer query returns information about a customer account")
}

type Mutation {
changeCustomerPassword(currentPassword: String!, newPassword: String!): Customer @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\Customer\\Account\\ChangePassword") @doc(description:"Changes password for logged in 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)")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
declare(strict_types=1);

namespace Magento\GraphQl\Customer;

use Magento\Customer\Api\AccountManagementInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Integration\Api\CustomerTokenServiceInterface;
use Magento\TestFramework\ObjectManager;
use Magento\TestFramework\TestCase\GraphQlAbstract;

class CustomerChangePasswordTest extends GraphQlAbstract
{
/**
* @var ObjectManager
*/
private $objectManager;

/**
* @var AccountManagementInterface
*/
private $accountManagement;

/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
*/
public function testCustomerChangeValidPassword()
{
$customerEmail = 'customer@example.com';
$oldCustomerPassword = 'password';
$newCustomerPassword = 'anotherPassword1';

$query = $this->getChangePassQuery($oldCustomerPassword, $newCustomerPassword);
$headerMap = $this->getCustomerAuthHeaders($customerEmail, $oldCustomerPassword);

$response = $this->graphQlQuery($query, [], '', $headerMap);
$this->assertEquals($customerEmail, $response['changeCustomerPassword']['email']);

try {
// registry contains the old password hash so needs to be reset
$this->objectManager->get(\Magento\Customer\Model\CustomerRegistry::class)
->removeByEmail($customerEmail);
$this->accountManagement->authenticate($customerEmail, $newCustomerPassword);
} catch (LocalizedException $e) {
$this->fail('Password was not changed: ' . $e->getMessage());
}
}

public function testGuestUserCannotChangePassword()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to make sure that the password can be changed only if correct current password was specified.

{
$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);
}

/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
*/
public function testChangeWeakPassword()
{
$customerEmail = 'customer@example.com';
$oldCustomerPassword = 'password';
$newCustomerPassword = 'weakpass';

$query = $this->getChangePassQuery($oldCustomerPassword, $newCustomerPassword);
$headerMap = $this->getCustomerAuthHeaders($customerEmail, $oldCustomerPassword);

$this->expectException(\Exception::class);
$this->expectExceptionMessageRegExp('/Minimum of different classes of characters in password is.*/');

$this->graphQlQuery($query, [], '', $headerMap);
}

/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
*/
public function testCannotChangeWithIncorrectPassword()
{
$customerEmail = 'customer@example.com';
$oldCustomerPassword = 'password';
$newCustomerPassword = 'anotherPassword1';
$incorrectPassword = 'password-incorrect';

$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);
}

private function getChangePassQuery($currentPassword, $newPassword)
{
$query = <<<QUERY
mutation {
changeCustomerPassword(
currentPassword: "$currentPassword",
newPassword: "$newPassword"
) {
id
email
firstname
lastname
}
}
QUERY;

return $query;
}

private function getCustomerAuthHeaders($customerEmail, $oldCustomerPassword)
{
/** @var CustomerTokenServiceInterface $customerTokenService */
$customerTokenService = $this->objectManager->create(CustomerTokenServiceInterface::class);
$customerToken = $customerTokenService->createCustomerAccessToken($customerEmail, $oldCustomerPassword);
return ['Authorization' => 'Bearer ' . $customerToken];
}

protected function setUp()
{
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
$this->accountManagement = $this->objectManager->get(AccountManagementInterface::class);
}
}