Pull merge.
[yaffs-website] / web / core / modules / user / tests / src / Functional / UserLoginHttpTest.php
index 29b97277c0b1ea8374633b5ca58c5b1a2fda0530..079affaf54aed8f16c3a335ac58f92547276d959 100644 (file)
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\user\Functional;
 
 use Drupal\Core\Flood\DatabaseBackend;
+use Drupal\Core\Test\AssertMailTrait;
 use Drupal\Core\Url;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\user\Controller\UserAuthenticationController;
@@ -14,12 +15,16 @@ use Drupal\hal\Encoder\JsonEncoder as HALJsonEncoder;
 use Symfony\Component\Serializer\Serializer;
 
 /**
- * Tests login via direct HTTP.
+ * Tests login and password reset via direct HTTP.
  *
  * @group user
  */
 class UserLoginHttpTest extends BrowserTestBase {
 
+  use AssertMailTrait {
+    getMails as drupalGetMails;
+  }
+
   /**
    * Modules to install.
    *
@@ -61,7 +66,7 @@ class UserLoginHttpTest extends BrowserTestBase {
    * @param string $format
    *   The format to use to make the request.
    *
-   * @return \Psr\Http\Message\ResponseInterface The HTTP response.
+   * @return \Psr\Http\Message\ResponseInterface
    *   The HTTP response.
    */
   protected function loginRequest($name, $pass, $format = 'json') {
@@ -92,99 +97,153 @@ class UserLoginHttpTest extends BrowserTestBase {
    * Tests user session life cycle.
    */
   public function testLogin() {
+    // Without the serialization module only JSON is supported.
+    $this->doTestLogin('json');
+
+    // Enable serialization so we have access to additional formats.
+    $this->container->get('module_installer')->install(['serialization']);
+    $this->doTestLogin('json');
+    $this->doTestLogin('xml');
+    $this->doTestLogin('hal_json');
+  }
+
+  /**
+   * Do login testing for a given serialization format.
+   *
+   * @param string $format
+   *   Serialization format.
+   */
+  protected function doTestLogin($format) {
     $client = \Drupal::httpClient();
-    foreach ([FALSE, TRUE] as $serialization_enabled_option) {
-      if ($serialization_enabled_option) {
-        /** @var \Drupal\Core\Extension\ModuleInstaller $module_installer */
-        $module_installer = $this->container->get('module_installer');
-        $module_installer->install(['serialization']);
-        $formats = ['json', 'xml', 'hal_json'];
-      }
-      else {
-        // Without the serialization module only JSON is supported.
-        $formats = ['json'];
-      }
-      foreach ($formats as $format) {
-        // Create new user for each iteration to reset flood.
-        // Grant the user administer users permissions to they can see the
-        // 'roles' field.
-        $account = $this->drupalCreateUser(['administer users']);
-        $name = $account->getUsername();
-        $pass = $account->passRaw;
+    // Create new user for each iteration to reset flood.
+    // Grant the user administer users permissions to they can see the
+    // 'roles' field.
+    $account = $this->drupalCreateUser(['administer users']);
+    $name = $account->getUsername();
+    $pass = $account->passRaw;
 
-        $login_status_url = $this->getLoginStatusUrlString($format);
-        $response = $client->get($login_status_url);
-        $this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_OUT);
+    $login_status_url = $this->getLoginStatusUrlString($format);
+    $response = $client->get($login_status_url);
+    $this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_OUT);
 
-        // Flooded.
-        $this->config('user.flood')
-          ->set('user_limit', 3)
-          ->save();
+    // Flooded.
+    $this->config('user.flood')
+      ->set('user_limit', 3)
+      ->save();
 
-        $response = $this->loginRequest($name, 'wrong-pass', $format);
-        $this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
+    $response = $this->loginRequest($name, 'wrong-pass', $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
 
-        $response = $this->loginRequest($name, 'wrong-pass', $format);
-        $this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
+    $response = $this->loginRequest($name, 'wrong-pass', $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
 
-        $response = $this->loginRequest($name, 'wrong-pass', $format);
-        $this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
+    $response = $this->loginRequest($name, 'wrong-pass', $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
 
-        $response = $this->loginRequest($name, 'wrong-pass', $format);
-        $this->assertHttpResponseWithMessage($response, 403, 'Too many failed login attempts from your IP address. This IP address is temporarily blocked.', $format);
+    $response = $this->loginRequest($name, 'wrong-pass', $format);
+    $this->assertHttpResponseWithMessage($response, 403, 'Too many failed login attempts from your IP address. This IP address is temporarily blocked.', $format);
 
-        // After testing the flood control we can increase the limit.
-        $this->config('user.flood')
-          ->set('user_limit', 100)
-          ->save();
+    // After testing the flood control we can increase the limit.
+    $this->config('user.flood')
+      ->set('user_limit', 100)
+      ->save();
 
-        $response = $this->loginRequest(NULL, NULL, $format);
-        $this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.', $format);
+    $response = $this->loginRequest(NULL, NULL, $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.', $format);
 
-        $response = $this->loginRequest(NULL, $pass, $format);
-        $this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.name.', $format);
+    $response = $this->loginRequest(NULL, $pass, $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.name.', $format);
 
-        $response = $this->loginRequest($name, NULL, $format);
-        $this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.pass.', $format);
+    $response = $this->loginRequest($name, NULL, $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.pass.', $format);
 
-        // Blocked.
-        $account
-          ->block()
-          ->save();
+    // Blocked.
+    $account
+      ->block()
+      ->save();
 
-        $response = $this->loginRequest($name, $pass, $format);
-        $this->assertHttpResponseWithMessage($response, 400, 'The user has not been activated or is blocked.', $format);
+    $response = $this->loginRequest($name, $pass, $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'The user has not been activated or is blocked.', $format);
 
-        $account
-          ->activate()
-          ->save();
+    $account
+      ->activate()
+      ->save();
 
-        $response = $this->loginRequest($name, 'garbage', $format);
-        $this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
+    $response = $this->loginRequest($name, 'garbage', $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
 
-        $response = $this->loginRequest('garbage', $pass, $format);
-        $this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
+    $response = $this->loginRequest('garbage', $pass, $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'Sorry, unrecognized username or password.', $format);
 
-        $response = $this->loginRequest($name, $pass, $format);
-        $this->assertEquals(200, $response->getStatusCode());
-        $result_data = $this->serializer->decode($response->getBody(), $format);
-        $this->assertEquals($name, $result_data['current_user']['name']);
-        $this->assertEquals($account->id(), $result_data['current_user']['uid']);
-        $this->assertEquals($account->getRoles(), $result_data['current_user']['roles']);
-        $logout_token = $result_data['logout_token'];
+    $response = $this->loginRequest($name, $pass, $format);
+    $this->assertEquals(200, $response->getStatusCode());
+    $result_data = $this->serializer->decode($response->getBody(), $format);
+    $this->assertEquals($name, $result_data['current_user']['name']);
+    $this->assertEquals($account->id(), $result_data['current_user']['uid']);
+    $this->assertEquals($account->getRoles(), $result_data['current_user']['roles']);
+    $logout_token = $result_data['logout_token'];
 
-        $response = $client->get($login_status_url, ['cookies' => $this->cookies]);
-        $this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_IN);
+    // Logging in while already logged in results in a 403 with helpful message.
+    $response = $this->loginRequest($name, $pass, $format);
+    $this->assertSame(403, $response->getStatusCode());
+    $this->assertSame(['message' => 'This route can only be accessed by anonymous users.'], $this->serializer->decode($response->getBody(), $format));
 
-        $response = $this->logoutRequest($format, $logout_token);
-        $this->assertEquals(204, $response->getStatusCode());
+    $response = $client->get($login_status_url, ['cookies' => $this->cookies]);
+    $this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_IN);
 
-        $response = $client->get($login_status_url, ['cookies' => $this->cookies]);
-        $this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_OUT);
+    $response = $this->logoutRequest($format, $logout_token);
+    $this->assertEquals(204, $response->getStatusCode());
 
-        $this->resetFlood();
-      }
-    }
+    $response = $client->get($login_status_url, ['cookies' => $this->cookies]);
+    $this->assertHttpResponse($response, 200, UserAuthenticationController::LOGGED_OUT);
+
+    $this->resetFlood();
+  }
+
+  /**
+   * Executes a password HTTP request.
+   *
+   * @param array $request_body
+   *   The request body.
+   * @param string $format
+   *   The format to use to make the request.
+   *
+   * @return \Psr\Http\Message\ResponseInterface
+   *   The HTTP response.
+   */
+  protected function passwordRequest(array $request_body, $format = 'json') {
+    $password_reset_url = Url::fromRoute('user.pass.http')
+      ->setRouteParameter('_format', $format)
+      ->setAbsolute();
+
+    $result = \Drupal::httpClient()->post($password_reset_url->toString(), [
+      'body' => $this->serializer->encode($request_body, $format),
+      'headers' => [
+        'Accept' => "application/$format",
+      ],
+      'http_errors' => FALSE,
+      'cookies' => $this->cookies,
+    ]);
+
+    return $result;
+  }
+
+  /**
+   * Tests user password reset.
+   */
+  public function testPasswordReset() {
+    // Create a user account.
+    $account = $this->drupalCreateUser();
+
+    // Without the serialization module only JSON is supported.
+    $this->doTestPasswordReset('json', $account);
+
+    // Enable serialization so we have access to additional formats.
+    $this->container->get('module_installer')->install(['serialization']);
+
+    $this->doTestPasswordReset('json', $account);
+    $this->doTestPasswordReset('xml', $account);
+    $this->doTestPasswordReset('hal_json', $account);
   }
 
   /**
@@ -343,7 +402,7 @@ class UserLoginHttpTest extends BrowserTestBase {
    * @param string $logout_token
    *   The csrf token for user logout.
    *
-   * @return \Psr\Http\Message\ResponseInterface The HTTP response.
+   * @return \Psr\Http\Message\ResponseInterface
    *   The HTTP response.
    */
   protected function logoutRequest($format = 'json', $logout_token = '') {
@@ -426,4 +485,60 @@ class UserLoginHttpTest extends BrowserTestBase {
     return $user_login_status_url->toString();
   }
 
+  /**
+   * Do password reset testing for given format and account.
+   *
+   * @param string $format
+   *   Serialization format.
+   * @param \Drupal\user\UserInterface $account
+   *   Test account.
+   */
+  protected function doTestPasswordReset($format, $account) {
+    $response = $this->passwordRequest([], $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'Missing credentials.name or credentials.mail', $format);
+
+    $response = $this->passwordRequest(['name' => 'dramallama'], $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'Unrecognized username or email address.', $format);
+
+    $response = $this->passwordRequest(['mail' => 'llama@drupal.org'], $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'Unrecognized username or email address.', $format);
+
+    $account
+      ->block()
+      ->save();
+
+    $response = $this->passwordRequest(['name' => $account->getAccountName()], $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'The user has not been activated or is blocked.', $format);
+
+    $response = $this->passwordRequest(['mail' => $account->getEmail()], $format);
+    $this->assertHttpResponseWithMessage($response, 400, 'The user has not been activated or is blocked.', $format);
+
+    $account
+      ->activate()
+      ->save();
+
+    $response = $this->passwordRequest(['name' => $account->getAccountName()], $format);
+    $this->assertEquals(200, $response->getStatusCode());
+    $this->loginFromResetEmail();
+    $this->drupalLogout();
+
+    $response = $this->passwordRequest(['mail' => $account->getEmail()], $format);
+    $this->assertEquals(200, $response->getStatusCode());
+    $this->loginFromResetEmail();
+    $this->drupalLogout();
+  }
+
+  /**
+   * Login from reset password email.
+   */
+  protected function loginFromResetEmail() {
+    $_emails = $this->drupalGetMails();
+    $email = end($_emails);
+    $urls = [];
+    preg_match('#.+user/reset/.+#', $email['body'], $urls);
+    $resetURL = $urls[0];
+    $this->drupalGet($resetURL);
+    $this->drupalPostForm(NULL, NULL, 'Log in');
+  }
+
 }