diff --git a/core/modules/rest/src/Plugin/ResourceBase.php b/core/modules/rest/src/Plugin/ResourceBase.php index 4e77eb9..33cb3aa 100644 --- a/core/modules/rest/src/Plugin/ResourceBase.php +++ b/core/modules/rest/src/Plugin/ResourceBase.php @@ -64,7 +64,7 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $container->getParameter('serializer.formats'), - $container->get('logger.channel.rest') + $container->get('logger.factory')->get('rest') ); } diff --git a/core/modules/rest/src/Tests/ResourceTest.php b/core/modules/rest/src/Tests/ResourceTest.php index 0919205..df99cc6 100644 --- a/core/modules/rest/src/Tests/ResourceTest.php +++ b/core/modules/rest/src/Tests/ResourceTest.php @@ -112,10 +112,8 @@ public function testUriPaths() { $manager = \Drupal::service('plugin.manager.rest'); foreach ($manager->getDefinitions() as $resource => $definition) { - if (isset($definition['uri_paths'])) { - foreach ($definition['uri_paths'] as $key => $uri_path) { - $this->assertFalse(strpos($uri_path, '//'), 'The resource URI path does not have duplicate slashes.'); - } + foreach ($definition['uri_paths'] as $key => $uri_path) { + $this->assertFalse(strpos($uri_path, '//'), 'The resource URI path does not have duplicate slashes.'); } } } diff --git a/core/modules/user/src/Controller/UserLoginController.php b/core/modules/user/src/Controller/UserLoginController.php index f25b07e..52b5673 100644 --- a/core/modules/user/src/Controller/UserLoginController.php +++ b/core/modules/user/src/Controller/UserLoginController.php @@ -10,10 +10,12 @@ use Drupal\user\UserInterface; use Drupal\user\UserStorageInterface; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Encoder\XmlEncoder; +use Symfony\Component\Serializer\Serializer; /** * Provides controllers for login, login status and logout. @@ -63,6 +65,20 @@ class UserLoginController extends ControllerBase implements ContainerInjectionIn protected $userAuth; /** + * The Serializer. + * + * @var \Symfony\Component\Serializer\Serializer + */ + protected $serializer; + + /** + * The available serialization formats. + * + * @var array + */ + protected $serializerFormats = array(); + + /** * Constructs a new UserLoginResource object. * * @param \Drupal\Core\Flood\FloodInterface $flood @@ -73,23 +89,40 @@ class UserLoginController extends ControllerBase implements ContainerInjectionIn * The CSRF token generator. * @param \Drupal\user\UserAuthInterface $user_auth * The user authentication. + * @param array $serializer_formats + * The available serialization formats. */ - public function __construct(FloodInterface $flood, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthInterface $user_auth) { + public function __construct(FloodInterface $flood, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthInterface $user_auth, Serializer $serializer, array $serializer_formats) { $this->flood = $flood; $this->userStorage = $user_storage; $this->csrfToken = $csrf_token; $this->userAuth = $user_auth; + $this->serializerFormats = $serializer_formats; + $this->serializer = $serializer; + } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { + if ($container->hasParameter('serializer.formats') && $container->has('serializer')) { + $serializer = $container->get('serializer'); + $formats = $container->getParameter('serializer.formats'); + } + else { + $formats = ['json', 'xml']; + $encoders = [new JsonEncoder(), new XmlEncoder()]; + $serializer = new Serializer([], $encoders); + } + return new static( $container->get('flood'), $container->get('entity_type.manager')->getStorage('user'), $container->get('csrf_token'), - $container->get('user.auth') + $container->get('user.auth'), + $serializer, + $formats ); } @@ -103,7 +136,10 @@ public static function create(ContainerInterface $container) { * Returns a response which contains the ID and CSRF token. */ public function login(Request $request) { - $credentials = json_decode($request->getContent(), TRUE); + $format = $this->getRequestFormat($request); + + $content = $request->getContent(); + $credentials = $this->serializer->decode($content, $format); if (!isset($credentials['name']) && !isset($credentials['pass'])) { throw new BadRequestHttpException('Missing credentials.'); } @@ -141,8 +177,8 @@ public function login(Request $request) { } $response_data['csrf_token'] = $this->csrfToken->get('rest'); - $response = new JsonResponse($response_data); - return $response; + $encoded_response_data = $this->serializer->encode($response_data, $format); + return new Response($encoded_response_data); } $this->flood->register('rest.login_cookie', $this->configFactory->get('user.flood')->get('user_window')); @@ -221,4 +257,21 @@ public function loginStatus() { return $response; } + /** + * Gets the format of the current request. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The current request. + * + * @return string + * The format of the request. + */ + protected function getRequestFormat(Request $request) { + $format = $request->getRequestFormat(); + if (!in_array($format, $this->serializerFormats)) { + throw new BadRequestHttpException("Unrecognized format: $format."); + } + return $format; + } + } diff --git a/core/modules/user/tests/src/Functional/UserLoginHttpTest.php b/core/modules/user/tests/src/Functional/UserLoginHttpTest.php index a7335be..2fb1322 100644 --- a/core/modules/user/tests/src/Functional/UserLoginHttpTest.php +++ b/core/modules/user/tests/src/Functional/UserLoginHttpTest.php @@ -6,6 +6,8 @@ use Drupal\Tests\BrowserTestBase; use Drupal\user\Controller\UserLoginController; use GuzzleHttp\Cookie\CookieJar; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Serializer; /** * Tests login via direct HTTP. @@ -20,12 +22,21 @@ class UserLoginHttpTest extends BrowserTestBase { protected $cookies; /** + * The Serializer. + * + * @var \Symfony\Component\Serializer\Serializer + */ + protected $serializer; + + /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); $this->cookies = new CookieJar(); + + $this->serializer = new Serializer([], [new JsonEncoder()]); } /** @@ -35,13 +46,14 @@ protected function setUp() { * The username. * @param $pass * The user password. + * @ * * @return \Psr\Http\Message\ResponseInterface * The HTTP response. */ - protected function loginRequest($name, $pass) { - $user_login_url = Url::fromRoute('user.login.json') - ->setRouteParameter('_format', 'json') + protected function loginRequest($name, $pass, $format) { + $user_login_url = Url::fromRoute('user.login.http') + ->setRouteParameter('_format', $format) ->setAbsolute(); $request_body = []; @@ -51,10 +63,11 @@ protected function loginRequest($name, $pass) { if (isset($pass)) { $request_body['pass'] = $pass; } + $result = \Drupal::httpClient()->post($user_login_url->toString(), [ - 'body' => json_encode($request_body), + 'body' => $this->encode($request_body, $format), 'headers' => [ - 'Accept' => 'application/json', + 'Accept' => "application/$format", ], 'http_errors' => FALSE, 'cookies' => $this->cookies, @@ -70,97 +83,119 @@ public function testLogin() { $name = $account->getUsername(); $pass = $account->passRaw; - $client = \Drupal::httpClient(); - - $user_login_status_url = Url::fromRoute('user.login_status.json'); - $user_login_status_url->setRouteParameter('_format', 'json'); - $user_login_status_url->setAbsolute(); - - $result = $client->post($user_login_status_url->toString()); - $this->assertEquals(200, $result->getStatusCode()); - $this->assertEquals(UserLoginController::LOGGED_OUT, (string) $result->getBody()); - - // Flooded. - \Drupal::configFactory()->getEditable('user.flood') - ->set('user_limit', 3) - ->save(); - - $result = $this->loginRequest($name, 'wrong-pass'); - $this->assertEquals(400, $result->getStatusCode()); - $this->assertEquals('{"message":"Sorry, unrecognized username or password."}', (string) $result->getBody()); - - $result = $this->loginRequest($name, 'wrong-pass'); - $this->assertEquals(400, $result->getStatusCode()); - $this->assertEquals('{"message":"Sorry, unrecognized username or password."}', (string) $result->getBody()); - - $result = $this->loginRequest($name, 'wrong-pass'); - $this->assertEquals(400, $result->getStatusCode()); - $this->assertEquals('{"message":"Sorry, unrecognized username or password."}', (string) $result->getBody()); - - $result = $this->loginRequest($name, 'wrong-pass'); - $this->assertEquals(400, $result->getStatusCode()); - $this->assertEquals('{"message":"Blocked."}', (string) $result->getBody()); - - // After testing the flood control we can increase the limit. - \Drupal::configFactory()->getEditable('user.flood') - ->set('user_limit', 100) - ->save(); - - $result = $this->loginRequest(NULL, NULL); - $this->assertEquals(400, $result->getStatusCode()); - $this->assertEquals('{"message":"Missing credentials."}', (string) $result->getBody()); - - $result = $this->loginRequest(NULL, $pass); - $this->assertEquals(400, $result->getStatusCode()); - $this->assertEquals('{"message":"Missing credentials.name."}', (string) $result->getBody()); - $result = $this->loginRequest($name, NULL); - $this->assertEquals(400, $result->getStatusCode()); - $this->assertEquals('{"message":"Missing credentials.pass."}', (string) $result->getBody()); - - // Blocked. - $account - ->block() - ->save(); - - $result = $this->loginRequest($name, $pass); - $this->assertEquals(400, $result->getStatusCode()); - $this->assertEquals('{"message":"The user has not been activated or is blocked."}', (string) $result->getBody()); - - $account - ->activate() - ->save(); - - $result = $this->loginRequest($name, 'garbage'); - $this->assertEquals(400, $result->getStatusCode()); - $this->assertEquals('{"message":"Sorry, unrecognized username or password."}', (string) $result->getBody()); - - $result = $this->loginRequest('garbage', $pass); - $this->assertEquals(400, $result->getStatusCode()); - $this->assertEquals('{"message":"Sorry, unrecognized username or password."}', (string) $result->getBody()); + $client = \Drupal::httpClient(); - $result = $this->loginRequest($name, $pass); - $this->assertEquals(200, $result->getStatusCode()); - $result_data = json_decode((string) $result->getBody(), TRUE); - $this->assertEquals($name, $result_data['current_user']['name']); + $formats = ['json']; + foreach ($formats as $format) { + + $user_login_status_url = Url::fromRoute('user.login_status.http'); + $user_login_status_url->setRouteParameter('_format', $format); + $user_login_status_url->setAbsolute(); + + $result = $client->post($user_login_status_url->toString()); + $this->assertEquals(200, $result->getStatusCode()); + $this->assertEquals(UserLoginController::LOGGED_OUT, (string) $result->getBody()); + + // Flooded. + \Drupal::configFactory()->getEditable('user.flood') + ->set('user_limit', 3) + ->save(); + + $result = $this->loginRequest($name, 'wrong-pass', $format); + $this->assertEquals(400, $result->getStatusCode()); + $this->assertEquals('{"message":"Sorry, unrecognized username or password."}', (string) $result->getBody()); + + $result = $this->loginRequest($name, 'wrong-pass', $format); + $this->assertEquals(400, $result->getStatusCode()); + $this->assertEquals('{"message":"Sorry, unrecognized username or password."}', (string) $result->getBody()); + + $result = $this->loginRequest($name, 'wrong-pass', $format); + $this->assertEquals(400, $result->getStatusCode()); + $this->assertEquals('{"message":"Sorry, unrecognized username or password."}', (string) $result->getBody()); + + $result = $this->loginRequest($name, 'wrong-pass', $format); + $this->assertEquals(400, $result->getStatusCode()); + $this->assertEquals('{"message":"Blocked."}', (string) $result->getBody()); + + // After testing the flood control we can increase the limit. + \Drupal::configFactory()->getEditable('user.flood') + ->set('user_limit', 100) + ->save(); + + $result = $this->loginRequest(NULL, NULL, $format); + $this->assertEquals(400, $result->getStatusCode()); + $this->assertEquals('{"message":"Missing credentials."}', (string) $result->getBody()); + + $result = $this->loginRequest(NULL, $pass, $format); + $this->assertEquals(400, $result->getStatusCode()); + $this->assertEquals('{"message":"Missing credentials.name."}', (string) $result->getBody()); + + $result = $this->loginRequest($name, NULL, $format); + $this->assertEquals(400, $result->getStatusCode()); + $this->assertEquals('{"message":"Missing credentials.pass."}', (string) $result->getBody()); + + // Blocked. + $account + ->block() + ->save(); + + $result = $this->loginRequest($name, $pass, $format); + $this->assertEquals(400, $result->getStatusCode()); + $this->assertEquals('{"message":"The user has not been activated or is blocked."}', (string) $result->getBody()); + + $account + ->activate() + ->save(); + + $result = $this->loginRequest($name, 'garbage', $format); + $this->assertEquals(400, $result->getStatusCode()); + $this->assertEquals('{"message":"Sorry, unrecognized username or password."}', (string) $result->getBody()); + + $result = $this->loginRequest('garbage', $pass, $format); + $this->assertEquals(400, $result->getStatusCode()); + $this->assertEquals('{"message":"Sorry, unrecognized username or password."}', (string) $result->getBody()); + + $result = $this->loginRequest($name, $pass, $format); + $this->assertEquals(200, $result->getStatusCode()); + $result_data = $this->decode($result->getBody(), $format); + $this->assertEquals($name, $result_data['current_user']['name']); + + $result = $client->post($user_login_status_url->toString(), ['cookies' => $this->cookies]); + $this->assertEquals(200, $result->getStatusCode()); + $this->assertEquals(UserLoginController::LOGGED_IN, (string) $result->getBody()); + + $user_logout_url = Url::fromRoute('user.logout.http')->setRouteParameter('_format', $format)->setAbsolute(); + $result = $client->post($user_logout_url->toString(), [ + 'headers' => [ + 'Accept' => "application/$format", + ], + 'http_errors' => FALSE, + 'cookies' => $this->cookies, + ]); + $this->assertEquals(204, $result->getStatusCode()); + + $result = $client->post($user_login_status_url->toString(), ['cookies' => $this->cookies]); + $this->assertEquals(200, $result->getStatusCode()); + $this->assertEquals(UserLoginController::LOGGED_OUT, (string) $result->getBody()); + } - $result = $client->post($user_login_status_url->toString(), ['cookies' => $this->cookies]); - $this->assertEquals(200, $result->getStatusCode()); - $this->assertEquals(UserLoginController::LOGGED_IN, (string) $result->getBody()); + } - $user_logout_url = Url::fromRoute('user.logout.json')->setRouteParameter('_format', 'json')->setAbsolute(); - $result = $client->post($user_logout_url->toString(), [ - 'headers' => [ - 'Accept' => 'application/json', - ], - 'http_errors' => FALSE, - 'cookies' => $this->cookies, - ]); - $this->assertEquals(204, $result->getStatusCode()); + /** + * Encodes data for a request into a given format. + * + * @param $data + * The data to be encoded + * @param $format + * The format to be encoded into. + */ + protected function encode($data, $format) { + return $this->serializer->encode($data, $format); + } - $result = $client->post($user_login_status_url->toString(), ['cookies' => $this->cookies]); - $this->assertEquals(200, $result->getStatusCode()); - $this->assertEquals(UserLoginController::LOGGED_OUT, (string) $result->getBody()); + protected function decode($data, $format) { + return $this->serializer->decode($data, $format); } } diff --git a/core/modules/user/user.routing.yml b/core/modules/user/user.routing.yml index bebe35e..27f98ae 100644 --- a/core/modules/user/user.routing.yml +++ b/core/modules/user/user.routing.yml @@ -129,16 +129,15 @@ user.login: options: _maintenance_access: TRUE -user.login.json: - path: '/user/login' +user.login.http: + path: '/user/login/http' defaults: _controller: \Drupal\user\Controller\UserLoginController::login methods: [POST] requirements: _user_is_logged_in: 'FALSE' - _format: 'json' -user.login_status.json: +user.login_status.http: path: '/user/login/status' defaults: _controller: \Drupal\user\Controller\UserLoginController::loginStatus @@ -146,13 +145,12 @@ user.login_status.json: requirements: _access: 'TRUE' -user.logout.json: - path: '/user/logout' +user.logout.http: + path: '/user/logout/http' defaults: _controller: \Drupal\user\Controller\UserLoginController::logout methods: [POST] requirements: - _format: 'json' _user_is_logged_in: 'TRUE' user.cancel_confirm: