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;
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.
*
* @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') {
* 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);
}
/**
* @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 = '') {
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');
+ }
+
}