user_api-1.0.0-beta1/modules/user_api_passwordless/src/Plugin/rest/resource/UnsetPasswordResource.php
modules/user_api_passwordless/src/Plugin/rest/resource/UnsetPasswordResource.php
<?php
declare(strict_types=1);
namespace Drupal\user_api_passwordless\Plugin\rest\resource;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Password\PasswordInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\user\UserInterface;
use Drupal\user_api\ErrorCode;
use Drupal\user_api_passwordless\Event\UnsetPasswordEvent;
use Drupal\verification\RequestVerifierInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Wunderwerk\HttpApiUtils\HttpApiValidationTrait;
use Wunderwerk\JsonApiError\JsonApiErrorResponse;
/**
* Provides a resource to unset a user's password.
*
* @RestResource(
* id = "user_api_passwordless_unset_password",
* label = @Translation("Unset user password"),
* uri_paths = {
* "create" = "/user-api/unset-password"
* }
* )
*/
class UnsetPasswordResource extends ResourceBase {
use HttpApiValidationTrait;
/**
* Request payload schema.
*/
protected array $schema = [
'type' => 'object',
'properties' => [
'currentPassword' => [
'type' => 'string',
],
],
];
/**
* The user entity.
*/
protected UserInterface $user;
/**
* Constructs a new UnsetPasswordResource object.
*/
public function __construct(
array $configuration,
$plugin_id,
$plugin_definition,
array $serializer_formats,
LoggerInterface $logger,
protected AccountProxyInterface $currentUser,
protected EntityTypeManagerInterface $entityTypeManager,
protected PasswordInterface $passwordChecker,
protected RequestVerifierInterface $verifier,
protected EventDispatcherInterface $eventDispatcher,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->getParameter('serializer.formats'),
$container->get('logger.factory')->get('rest'),
$container->get('current_user'),
$container->get('entity_type.manager'),
$container->get('password'),
$container->get(RequestVerifierInterface::class),
$container->get('event_dispatcher'),
);
}
/**
* Responds to POST requests.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* The response indicating success or failure.
*/
public function post(Request $request) {
$user = $this->getCurrentUser();
if (!$user || !$user->isAuthenticated()) {
return JsonApiErrorResponse::fromError(
status: 403,
code: ErrorCode::Unauthenticated->getCode(),
title: 'Unauthenticated',
detail: 'You are not authenticated.',
);
}
$this->user = $user;
// Validate payload.
$payload = $request->getContent();
$data = Json::decode($payload);
$result = $this->validateArray($data, $this->schema);
if (!$result->isValid()) {
return $result->getResponse();
}
$verifyResult = $this->verifier->verifyOperation($request, 'unset-password', $user);
if (!$verifyResult->ok && !array_key_exists('currentPassword', $data)) {
return JsonApiErrorResponse::fromError(
status: 400,
code: ErrorCode::PasswordUpdateFailed->getCode(),
title: 'Could not unset password',
detail: 'The request is neither verified to unset the password directly, nor was the currentPassword supplied in the request payload.',
);
}
// Dispatch event.
$event = new UnsetPasswordEvent($user, $data, $verifyResult->ok);
$this->eventDispatcher->dispatch($event, $event::getName());
if ($event->isAborted()) {
return $event->getResponse();
}
// Handle update if verified.
if ($verifyResult->ok) {
return $this->unsetPassword();
}
// Update password with current password.
return $this->unsetPasswordWithCurrentPassword($data['currentPassword']);
}
/**
* Handle password update with current password.
*
* @param string $currentPassword
* The current password.
*/
protected function unsetPasswordWithCurrentPassword(string $currentPassword) {
// Check current password, if user updates an existing one.
if ($currentPassHash = $this->user->getPassword()) {
// Check against currently set password.
if (!$this->passwordChecker->check($currentPassword, $currentPassHash)) {
return JsonApiErrorResponse::fromError(
status: 400,
code: ErrorCode::CurrentPasswordInvalid->getCode(),
title: 'Incorrect current password',
);
}
}
return $this->unsetPassword();
}
/**
* Unsets the password on the user entity.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* The response indicating success.
*/
protected function unsetPassword() {
$this->user->setPassword(NULL);
$this->user->save();
return new JsonResponse([
'status' => 'success',
]);
}
/**
* Loads the user entity for the current user.
*
* @return \Drupal\user\UserInterface|null
* The user entity.
*/
protected function getCurrentUser(): ?UserInterface {
return $this->entityTypeManager->getStorage('user')->load(
$this->currentUser->id()
);
}
}
