From ffb06852a697db2bdc5467d4f0a1b2eb9d404b4f Mon Sep 17 00:00:00 2001 From: Radoslav Terezka Date: Wed, 24 Jun 2020 15:28:47 +0200 Subject: [PATCH] feat(data producer): Add password reset mutation data producer with violations (#1013) --- src/GraphQL/Response/Response.php | 45 ++++++ src/GraphQL/Response/ResponseInterface.php | 40 +++++ .../DataProducer/User/PasswordReset.php | 145 ++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 src/GraphQL/Response/Response.php create mode 100644 src/GraphQL/Response/ResponseInterface.php create mode 100644 src/Plugin/GraphQL/DataProducer/User/PasswordReset.php diff --git a/src/GraphQL/Response/Response.php b/src/GraphQL/Response/Response.php new file mode 100644 index 000000000..f8c38a1ec --- /dev/null +++ b/src/GraphQL/Response/Response.php @@ -0,0 +1,45 @@ +violations[] = $properties; + } + + /** + * {@inheritdoc} + */ + public function addViolations(array $messages, array $properties = []): void { + foreach ($messages as $message) { + $this->addViolation($message, $properties); + } + } + + /** + * {@inheritdoc} + */ + public function getViolations(): array { + return $this->violations; + } + +} diff --git a/src/GraphQL/Response/ResponseInterface.php b/src/GraphQL/Response/ResponseInterface.php new file mode 100644 index 000000000..21afef779 --- /dev/null +++ b/src/GraphQL/Response/ResponseInterface.php @@ -0,0 +1,40 @@ +get('request_stack'); + /** @var \Drupal\Core\Logger\LoggerChannelInterface $logger */ + $logger = $container->get('logger.channel.graphql'); + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $request_stack->getCurrentRequest(), + $logger + ); + } + + /** + * UserRegister constructor. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + * @param \Symfony\Component\HttpFoundation\Request $current_request + * The current request. + * @param \Drupal\Core\Logger\LoggerChannelInterface $logger + * The logger service. + */ + public function __construct( + array $configuration, + string $plugin_id, + array $plugin_definition, + Request $current_request, + LoggerChannelInterface $logger + ) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + $this->currentRequest = $current_request; + $this->logger = $logger; + } + + /** + * Creates an user. + * + * @param string $email + * The email address to reset the password for. + * + * @return \Drupal\graphql\GraphQL\Response\ResponseInterface + * Response for password reset mutation with violations in case of failure. + */ + public function resolve(string $email): ResponseInterface { + $content = [ + 'mail' => $email, + ]; + + // Drupal does not have a user authentication service so we need to use the + // authentication controller instead. + $controller = UserAuthenticationController::create(\Drupal::getContainer()); + // Build up an authentication request for controller out of current request + // but replace the request body with proper content. This way most of the + // data are reused including the client's IP which is needed for flood + // control. The request body is the only thing (besides client's IP) which + // is pulled from the request within controller. + $auth_request = new Request( + $this->currentRequest->query->all(), + $this->currentRequest->request->all(), + $this->currentRequest->attributes->all(), + $this->currentRequest->cookies->all(), + $this->currentRequest->files->all(), + $this->currentRequest->server->all(), + json_encode($content) + ); + $auth_request->setRequestFormat('json'); + + $response = new Response(); + try { + $controller_response = $controller->resetPassword($auth_request); + } + catch (\Exception $e) { + // Show general error message so potential attacker cannot abuse endpoint + // to eg check if some email exist or not. Log to watchdog for potential + // further investigation. + $this->logger->warning($e->getMessage()); + $response->addViolation($this->t('Unable to reset password, please try again later.')); + return $response; + } + // Show general error message also in case of unexpected response. Log to + // watchdog for potential further investigation. + if ($controller_response->getStatusCode() !== 200) { + $this->logger->warning("Unexpected response code @code during password reset.", ['@code' => $response->getStatusCode()]); + $response->addViolation($this->t('Unable to reset password, please try again later.')); + } + + return $response; + } + +}