diff --git a/core/modules/rest/src/Plugin/rest/resource/UserLoginResource.php b/core/modules/rest/src/Plugin/rest/resource/UserLoginResource.php index 6171490..b801bcb 100644 --- a/core/modules/rest/src/Plugin/rest/resource/UserLoginResource.php +++ b/core/modules/rest/src/Plugin/rest/resource/UserLoginResource.php @@ -4,12 +4,11 @@ use Drupal\Core\Access\CsrfTokenGenerator; use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Config\ImmutableConfig; use Drupal\Core\Flood\FloodInterface; use Drupal\rest\ResourceResponse; use Drupal\rest\Plugin\ResourceBase; -use Drupal\user\Entity\User; use Drupal\user\UserAuthInterface; +use Drupal\user\UserInterface; use Drupal\user\UserStorageInterface; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -33,7 +32,7 @@ class UserLoginResource extends ResourceBase { protected $configFactory; /** - * The flood control mechanism. + * The flood controller. * * @var \Drupal\Core\Flood\FloodInterface */ @@ -47,14 +46,14 @@ class UserLoginResource extends ResourceBase { protected $userStorage; /** - * The Csrf Token Generator. + * The CSRF token generator. * * @var \Drupal\Core\Access\CsrfTokenGenerator */ protected $csrfToken; /** - * The User Authentication service. + * The user authentication. * * @var \Drupal\user\UserAuthInterface */ @@ -76,16 +75,16 @@ class UserLoginResource extends ResourceBase { * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. * @param \Drupal\Core\Flood\FloodInterface $flood - * The flood control mechanism. + * The flood controller. * @param \Drupal\user\UserStorageInterface $user_storage * The user storage. * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token - * The Csrf Token Generator. + * The CSRF token generator. * @param \Drupal\user\UserAuthInterface $user_auth - * The User Authentication service. + * The user authentication. */ public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, ConfigFactoryInterface $config_factory, FloodInterface $flood, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthInterface $user_auth) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger, $flood); + parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger); $this->configFactory = $config_factory; $this->flood = $flood; $this->userStorage = $user_storage; @@ -114,6 +113,9 @@ public static function create(ContainerInterface $container, array $configuratio /** * Responds to the user login POST requests and logs in a user. * + * @param array $credentials + * The login credentials. + * * @return \Drupal\rest\ResourceResponse * The HTTP response object. */ @@ -132,7 +134,7 @@ public function post($credentials) { } // Flood control. - if (!$this->isFloodBlocked($this->configFactory->get('user.flood'), 'rest.login_cookie')) { + if (!$this->isFloodBlocked()) { throw new BadRequestHttpException('Blocked.'); } @@ -145,7 +147,7 @@ public function post($credentials) { if ($uid = $this->userAuth->authenticate($credentials['name'], $credentials['pass'])) { /** @var \Drupal\user\Entity\User $user */ $user = $this->userStorage->load($uid); - user_login_finalize($user); + $this->userLoginFinalize($user); // Add some basics about the user's account. $response_data = [ @@ -157,7 +159,7 @@ public function post($credentials) { 'csrf_token' => $this->csrfToken->get('rest'), ]; - $response = new ResourceResponse($response_data, 200, []); + $response = new ResourceResponse($response_data, 200, []); return $response->addCacheableDependency($user); } @@ -169,28 +171,37 @@ public function post($credentials) { * Verifies if the user is blocked. * * @param string $name + * The username. + * * @return bool + * Returns TRUE if the user is blocked, otherwise FALSE. */ protected function userIsBlocked($name) { return user_is_blocked($name); } /** - * Checks for flooding. + * Finalizes the user login. * - * @param \Drupal\Core\Config\ImmutableConfig $config - * The flood control config object. - * @param string $name - * The name of the event. + * @param \Drupal\user\UserInterface $user + * The user. + */ + protected function userLoginFinalize(UserInterface $user) { + user_login_finalize($user); + } + + /** + * Checks for flooding. * * @return bool * TRUE if the user is allowed to proceed, FALSE otherwise. */ - protected function isFloodBlocked(ImmutableConfig $config, $name) { + protected function isFloodBlocked() { + $config = $this->configFactory->get('user.flood'); $limit = $config->get('user_limit'); $interval = $config->get('user_window'); - return $this->flood->isAllowed($name, $limit, $interval); + return $this->flood->isAllowed('rest.login_cookie', $limit, $interval); } } diff --git a/core/modules/rest/src/Plugin/rest/resource/UserLoginStatus.php b/core/modules/rest/src/Plugin/rest/resource/UserLoginStatus.php index 5fe3019..4ea6e0b 100644 --- a/core/modules/rest/src/Plugin/rest/resource/UserLoginStatus.php +++ b/core/modules/rest/src/Plugin/rest/resource/UserLoginStatus.php @@ -2,7 +2,6 @@ namespace Drupal\rest\Plugin\rest\resource; -use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Session\AccountInterface; use Drupal\rest\Plugin\ResourceBase; @@ -52,6 +51,7 @@ class UserLoginStatus extends ResourceBase { */ public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, AccountInterface $current_user) { parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger); + $this->currentUser = $current_user; } diff --git a/core/modules/rest/src/Plugin/rest/resource/UserLogout.php b/core/modules/rest/src/Plugin/rest/resource/UserLogout.php index 2a751dc..cece0cf 100644 --- a/core/modules/rest/src/Plugin/rest/resource/UserLogout.php +++ b/core/modules/rest/src/Plugin/rest/resource/UserLogout.php @@ -1,17 +1,18 @@ loadUserByNameOrEmail($name)) { // No success, the user does not exist. - throw new BadRequestHttpException($this->t("Sorry, %name is not recognized as a user name or an e-mail address.", ['%name' => $name])); + throw new BadRequestHttpException(new FormattableMarkup('Sorry, %name is not recognized as a user name or an e-mail address.', ['%name' => $name])); } - $mail = _user_mail_notify('password_reset', $account, $langcode); + $mail = $this->userMailNotify($account, $langcode); if (empty($mail)) { throw new BadRequestHttpException('The email with the instructions was not sent as expected.'); } - $this->logger->notice('Password reset instructions mailed to %name at %email.', ['%name' => $account->getUsername(), '%email' => $account->getEmail()]); + $this->logger->notice('Password reset instructions mailed to %name at %email.', ['%name' => $account->getDisplayName(), '%email' => $account->getEmail()]); return new ResourceResponse('Further instructions have been sent to your email address.', 200, []); } /** + * Conditionally create and send a notification email. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The user object of the account being notified. Must contain at + * least the fields 'uid', 'name', and 'mail'. + * @param string $langcode + * (optional) Language code to use for the notification, overriding account + * language. + * + * @return array + * An array containing various information about the message. + * See \Drupal\Core\Mail\MailManagerInterface::mail() for details. + */ + protected function userMailNotify(AccountInterface $account, $langcode = NULL) { + return _user_mail_notify('password_reset', $account, $langcode); + } + + /** * Loads a user by name OR mail address. * * @param string $name diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php index ef70046..bdf30e1 100644 --- a/core/modules/rest/src/RequestHandler.php +++ b/core/modules/rest/src/RequestHandler.php @@ -58,7 +58,8 @@ public function handle(RouteMatchInterface $route_match, Request $request) { if (array_key_exists('serialization_class', $definition)) { $unserialized = $serializer->deserialize($received, $definition['serialization_class'], $format, array('request_method' => $method)); } - // If the plugin does not specify a serialization class just decode the received data. + // If the plugin does not specify a serialization class just decode + // the received data. // Example: received JSON is decoded into a PHP array. else { $unserialized = $serializer->decode($received, $format, array('request_method' => $method)); diff --git a/core/modules/rest/src/Tests/NodeTest.php b/core/modules/rest/src/Tests/NodeTest.php index 9bc7210..357109c 100644 --- a/core/modules/rest/src/Tests/NodeTest.php +++ b/core/modules/rest/src/Tests/NodeTest.php @@ -175,7 +175,7 @@ public function testInvalidBundle() { // Make sure the response is "Bad Request". $this->assertResponse(400); - $this->assertResponseBody(NULL, '{"error":"\"bad_bundle_name\" is not a valid bundle type for denormalization."}'); + $this->assertResponseBody('{"error":"\"bad_bundle_name\" is not a valid bundle type for denormalization."}'); } /** @@ -192,6 +192,6 @@ public function testMissingBundle() { // Make sure the response is "Bad Request". $this->assertResponse(400); - $this->assertResponseBody(NULL, '{"error":"A string must be provided as a bundle value."}'); + $this->assertResponseBody('{"error":"A string must be provided as a bundle value."}'); } } diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php index ce7b66c..c9338ae 100644 --- a/core/modules/rest/src/Tests/RESTTestBase.php +++ b/core/modules/rest/src/Tests/RESTTestBase.php @@ -410,10 +410,6 @@ protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) { * Check to see if the HTTP request response body is identical to the expected * value. * - * - * @param $code - * (optional) Response code. For example 200 is a successful page request. For a list - * of all codes see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html. * @param $expected * The first value to check. * @param $message @@ -430,11 +426,7 @@ protected function removeNodeFieldsForNonAdminUsers(NodeInterface $node) { * @return bool * TRUE if the assertion succeeded, FALSE otherwise. */ - protected function assertResponseBody($code = NULL, $expected, $message = '', $group = 'REST Response') { - if ($code) { - $this->assertResponse($code); - } + protected function assertResponseBody($expected, $message = '', $group = 'REST Response') { return $this->assertIdentical($expected, $this->responseBody, $message ? $message : strtr('Response body @expected (expected) is equal to @response (actual).', array('@expected' => var_export($expected, TRUE), '@response' => var_export($this->responseBody, TRUE))), $group); } - } diff --git a/core/modules/rest/src/Tests/UserLoginTest.php b/core/modules/rest/src/Tests/UserLoginTest.php index 28b0ab7..3aa3362 100644 --- a/core/modules/rest/src/Tests/UserLoginTest.php +++ b/core/modules/rest/src/Tests/UserLoginTest.php @@ -88,7 +88,8 @@ public function testLogin() { $url = Url::fromRoute('rest.user_login_status.GET.json'); $url->setRouteParameter('_format', 'json'); $this->httpRequest($url, 'GET', NULL, 'application/json'); - $this->assertResponseBody('200', '"' . UserLoginStatus::LOGGED_IN . '"'); + $this->assertResponse(200); + $this->assertResponseBody('"' . UserLoginStatus::LOGGED_IN . '"'); $payload = ['name' => $name, 'pass' => $pass]; $this->httpRequest('user_logout', 'POST', json_encode($payload), 'application/json'); @@ -97,7 +98,8 @@ public function testLogin() { $url = Url::fromRoute('rest.user_login_status.GET.json'); $url->setRouteParameter('_format', 'json'); $this->httpRequest($url, 'GET', NULL, 'application/json'); - $this->assertResponseBody('200', '"' . UserLoginStatus::LOGGED_OUT . '"'); + $this->assertResponse(200); + $this->assertResponseBody('"' . UserLoginStatus::LOGGED_OUT . '"'); } } diff --git a/core/modules/rest/tests/src/Unit/UserLoginResourceTest.php b/core/modules/rest/tests/src/Unit/UserLoginResourceTest.php index 01846a2..a3a274a 100644 --- a/core/modules/rest/tests/src/Unit/UserLoginResourceTest.php +++ b/core/modules/rest/tests/src/Unit/UserLoginResourceTest.php @@ -7,11 +7,16 @@ use Drupal\Core\Flood\FloodInterface; use Drupal\rest\Plugin\rest\resource\UserLoginResource; use Drupal\Tests\UnitTestCase; +use Drupal\user\UserAuthInterface; +use Drupal\user\UserStorageInterface; /** * Tests the Login Resource. - * @TODO Test flood control after https://www.drupal.org/node/2431357 has landed. + * + * @coversDefaultClass \Drupal\rest\Plugin\rest\resource\UserLoginResource * @group rest + * + * @TODO Test flood after https://www.drupal.org/node/2431357 has landed. */ class UserLoginResourceTest extends UnitTestCase { @@ -20,7 +25,7 @@ class UserLoginResourceTest extends UnitTestCase { * * @var \Drupal\rest\Plugin\rest\resource\UserLoginResource */ - protected $testClass; + protected $userLoginResource; /** * The flood control mechanism. @@ -51,7 +56,9 @@ class UserLoginResourceTest extends UnitTestCase { protected $userStorage; /** - * @var + * The CSRF token generator. + * + * @var \Drupal\Core\Access\CsrfTokenGenerator */ protected $csrfToken; @@ -61,7 +68,7 @@ class UserLoginResourceTest extends UnitTestCase { protected function setUp() { parent::setUp(); - $user_auth_service = $this->getMock('Drupal\user\UserAuthInterface'); + $user_auth_service = $this->getMock(UserAuthInterface::class); $user_auth_service->expects($this->any()) ->method('authenticate') ->will($this->returnValue(FALSE)); @@ -72,7 +79,7 @@ protected function setUp() { $this->flood = $this->getMock(FloodInterface::class); - $this->userStorage = $this->getMockBuilder('\Drupal\user\UserStorage') + $this->userStorage = $this->getMockBuilder(UserStorageInterface::class) ->disableOriginalConstructor() ->getMock(); @@ -84,7 +91,7 @@ protected function setUp() { $this->csrfToken = $this->prophesize(CsrfTokenGenerator::class); - $this->testClass = new TestUserLoginResource([], 'plugin_id', '', [], $this->logger, $this->config, $this->flood, $this->userStorage, $this->csrfToken->reveal(), $user_auth_service); + $this->userLoginResource = new TestUserLoginResource([], 'plugin_id', '', [], $this->logger, $this->config, $this->flood, $this->userStorage, $this->csrfToken->reveal(), $user_auth_service); } /** @@ -92,7 +99,7 @@ protected function setUp() { * @expectedExceptionMessage Missing credentials. */ public function testMissingCredentials() { - $this->testClass->post([]); + $this->userLoginResource->post([]); } /** @@ -100,7 +107,7 @@ public function testMissingCredentials() { * @expectedExceptionMessage Missing credentials.pass. */ public function testLoginMissingCredentialPass() { - $this->testClass->post(['name' => 'Druplicon']); + $this->userLoginResource->post(['name' => 'Druplicon']); } /** @@ -112,7 +119,7 @@ public function testLoginBlockedUserByFloodControl() { ->method('isAllowed') ->willReturn(FALSE); - $this->testClass->post(['name' => 'Druplicon', 'pass' => 'SuperSecret']); + $this->userLoginResource->post(['name' => 'Druplicon', 'pass' => 'SuperSecret']); } /** @@ -124,9 +131,9 @@ public function testLoginBlockedUser() { ->method('isAllowed') ->willReturn(TRUE); - $this->testClass->setUserIsBlocked(TRUE); + $this->userLoginResource->setUserIsBlocked(TRUE); - $this->testClass->post(['name' => 'Druplicon', 'pass' => 'SuperSecret']); + $this->userLoginResource->post(['name' => 'Druplicon', 'pass' => 'SuperSecret']); } /** @@ -138,9 +145,9 @@ public function testLoginUnrecognizedUsernameOrPassword() { ->method('isAllowed') ->willReturn(TRUE); - $this->testClass->setUserIsBlocked(FALSE); + $this->userLoginResource->setUserIsBlocked(FALSE); - $this->testClass->post(['name' => 'Druplicon', 'pass' => 'SuperSecret']); + $this->userLoginResource->post(['name' => 'Druplicon', 'pass' => 'SuperSecret']); } /** @@ -149,7 +156,7 @@ public function testLoginUnrecognizedUsernameOrPassword() { */ public function testResetPasswordMissingNameOrEmail() { $method = $this->getProtectedMethod('requestNewPassword'); - $method->invokeArgs($this->testClass, [['name' => '']]); + $method->invokeArgs($this->userLoginResource, [['name' => '']]); } /** @@ -162,7 +169,7 @@ public function testResetPasswordNotRecognizedNameOrEmail() { ->will($this->returnValue([])); $method = $this->getProtectedMethod('requestNewPassword'); - $method->invokeArgs($this->testClass, [['name' => 'Druplicon']]); + $method->invokeArgs($this->userLoginResource, [['name' => 'Druplicon']]); } } diff --git a/core/modules/rest/tests/src/Unit/UserPasswordResetTest.php b/core/modules/rest/tests/src/Unit/UserPasswordResetTest.php index de24be9..c51fb22 100644 --- a/core/modules/rest/tests/src/Unit/UserPasswordResetTest.php +++ b/core/modules/rest/tests/src/Unit/UserPasswordResetTest.php @@ -2,6 +2,11 @@ namespace Drupal\Tests\rest\Unit; +use Drupal\Core\Access\CsrfTokenGenerator; +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Flood\FloodInterface; +use Drupal\rest\Plugin\rest\resource\UserPasswordReset; + /** * @coversDefaultClass \Drupal\rest\Plugin\rest\resource\UserPasswordReset * @group rest @@ -13,7 +18,7 @@ class UserPasswordResetTest extends \PHPUnit_Framework_TestCase { * * @var \Drupal\rest\Plugin\rest\resource\UserLoginResource */ - protected $testClass; + protected $userPasswordReset; /** * The flood control mechanism. @@ -44,7 +49,9 @@ class UserPasswordResetTest extends \PHPUnit_Framework_TestCase { protected $userStorage; /** - * @var + * The CSRF token generator. + * + * @var \Drupal\Core\Access\CsrfTokenGenerator */ protected $csrfToken; @@ -77,7 +84,7 @@ protected function setUp() { $this->csrfToken = $this->prophesize(CsrfTokenGenerator::class); - $this->testClass = new TestUserLoginResource([], 'plugin_id', '', [], $this->logger, $this->config, $this->flood, $this->userStorage, $this->csrfToken->reveal()); + $this->userPasswordReset = new UserPasswordReset([], 'plugin_id', '', [], $this->logger, $this->config, $this->flood, $this->userStorage, $this->csrfToken->reveal()); } }