diff --git a/eZ/Bundle/EzPublishRestBundle/Features/Context/RestClient/BuzzDriver.php b/eZ/Bundle/EzPublishRestBundle/Features/Context/RestClient/BuzzDriver.php index 2e54cc3ba48..b838f3bf67d 100644 --- a/eZ/Bundle/EzPublishRestBundle/Features/Context/RestClient/BuzzDriver.php +++ b/eZ/Bundle/EzPublishRestBundle/Features/Context/RestClient/BuzzDriver.php @@ -115,6 +115,10 @@ public function setResource($resource) */ public function setMethod($method) { + if (in_array(strtolower($method), ['publish', 'patch', 'move', 'swap'])) { + $this->getRequest()->addHeader("X-HTTP-Method-Override: $method"); + $method = 'POST'; + } $this->getRequest()->setMethod($method); } diff --git a/eZ/Bundle/EzPublishRestBundle/Resources/config/routing.yml b/eZ/Bundle/EzPublishRestBundle/Resources/config/routing.yml index 1b297a17c1e..bcb0f38e719 100644 --- a/eZ/Bundle/EzPublishRestBundle/Resources/config/routing.yml +++ b/eZ/Bundle/EzPublishRestBundle/Resources/config/routing.yml @@ -1087,19 +1087,19 @@ ezpublish_rest_unassignRoleFromUserGroup: ezpublish_rest_createSession: path: /user/sessions defaults: - _controller: ezpublish_rest.controller.user:createSession + _controller: ezpublish_rest.controller.session:createSessionAction methods: [POST] ezpublish_rest_deleteSession: path: /user/sessions/{sessionId} defaults: - _controller: ezpublish_rest.controller.user:deleteSession + _controller: ezpublish_rest.controller.session:deleteSessionAction methods: [DELETE] ezpublish_rest_refreshSession: path: /user/sessions/{sessionId}/refresh defaults: - _controller: ezpublish_rest.controller.user:refreshSession + _controller: ezpublish_rest.controller.session:refreshSessionAction methods: [POST] diff --git a/eZ/Bundle/EzPublishRestBundle/Resources/config/services.yml b/eZ/Bundle/EzPublishRestBundle/Resources/config/services.yml index e929d37d083..46beb130d53 100644 --- a/eZ/Bundle/EzPublishRestBundle/Resources/config/services.yml +++ b/eZ/Bundle/EzPublishRestBundle/Resources/config/services.yml @@ -204,8 +204,6 @@ services: - "@ezpublish.api.service.location" - "@ezpublish.api.service.section" - "@ezpublish.api.repository" - calls: - - [setTokenStorage, ['@?security.csrf.token_storage']] ezpublish_rest.controller.url_wildcard: class: "%ezpublish_rest.controller.url_wildcard.class%" @@ -226,6 +224,15 @@ services: arguments: - "@ezpublish.api.service.search" + ezpublish_rest.controller.session: + class: eZ\Publish\Core\REST\Server\Controller\SessionController + parent: ezpublish_rest.controller.base + arguments: + - "@ezpublish_rest.session_authenticator" + - "%ezpublish_rest.csrf_token_intention%" + - "@?security.csrf.token_manager" + - "@?security.csrf.token_storage" + ezpublish_rest.request_listener: class: "%ezpublish_rest.request_listener.class%" tags: diff --git a/eZ/Publish/Core/REST/Server/Controller/SessionController.php b/eZ/Publish/Core/REST/Server/Controller/SessionController.php new file mode 100644 index 00000000000..a3fff2a9860 --- /dev/null +++ b/eZ/Publish/Core/REST/Server/Controller/SessionController.php @@ -0,0 +1,216 @@ +authenticator = $authenticator; + $this->csrfTokenIntention = $tokenIntention; + $this->csrfTokenManager = $csrfTokenManager; + $this->csrfTokenStorage = $csrfTokenStorage; + } + + /** + * Creates a new session based on the credentials provided as POST parameters. + * + * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException If the login or password are incorrect or invalid CSRF + * + * @return Values\UserSession|Values\Conflict + */ + public function createSessionAction(Request $request) + { + /** @var $sessionInput \eZ\Publish\Core\REST\Server\Values\SessionInput */ + $sessionInput = $this->inputDispatcher->parse( + new Message( + array('Content-Type' => $request->headers->get('Content-Type')), + $request->getContent() + ) + ); + $request->attributes->set('username', $sessionInput->login); + $request->attributes->set('password', $sessionInput->password); + + try { + $session = $request->getSession(); + if ($session->isStarted() && $this->hasStoredCsrfToken()) { + $this->checkCsrfToken($request); + } + + $token = $this->authenticator->authenticate($request); + $csrfToken = $this->getCsrfToken(); + + return new Values\UserSession( + $token->getUser()->getAPIUser(), + $session->getName(), + $session->getId(), + $csrfToken, + !$token->hasAttribute('isFromSession') + ); + } catch (Exceptions\UserConflictException $e) { + // Already logged in with another user, this will be converted to HTTP status 409 + return new Values\Conflict(); + } catch (AuthenticationException $e) { + throw new UnauthorizedException('Invalid login or password', $request->getPathInfo()); + } catch (AccessDeniedException $e) { + throw new UnauthorizedException($e->getMessage(), $request->getPathInfo()); + } + } + + /** + * Refresh given session. + * + * @param string $sessionId + * + * @throws \eZ\Publish\Core\REST\Common\Exceptions\NotFoundException + * + * @return \eZ\Publish\Core\REST\Server\Values\UserSession + */ + public function refreshSessionAction($sessionId, Request $request) + { + $session = $request->getSession(); + + if ($session === null || !$session->isStarted() || $session->getId() != $sessionId || !$this->hasStoredCsrfToken()) { + $response = $this->authenticator->logout($request); + $response->setStatusCode(404); + + return $response; + } + + $this->checkCsrfToken($request); + + return new Values\UserSession( + $this->repository->getCurrentUser(), + $session->getName(), + $session->getId(), + $request->headers->get('X-CSRF-Token'), + false + ); + } + + /** + * Deletes given session. + * + * @param string $sessionId + * + * @return Values\DeletedUserSession + * + * @throws NotFoundException + */ + public function deleteSessionAction($sessionId, Request $request) + { + /** @var $session \Symfony\Component\HttpFoundation\Session\Session */ + $session = $request->getSession(); + if (!$session->isStarted() || $session->getId() != $sessionId || !$this->hasStoredCsrfToken()) { + $response = $this->authenticator->logout($request); + $response->setStatusCode(404); + + return $response; + } + + $this->checkCsrfToken($request); + + return new Values\DeletedUserSession($this->authenticator->logout($request)); + } + + /** + * Tests if a CSRF token is stored. + * + * @return bool + */ + private function hasStoredCsrfToken() + { + if (!isset($this->csrfTokenStorage)) { + return true; + } + + return $this->csrfTokenStorage->hasToken($this->csrfTokenIntention); + } + + /** + * Checks the presence / validity of the CSRF token. + * + * @param Request $request + * + * @throws UnauthorizedException if the token is missing or invalid. + */ + private function checkCsrfToken(Request $request) + { + if ($this->csrfTokenManager === null) { + return; + } + + $exception = new UnauthorizedException( + 'Missing or invalid CSRF token', + $request->getMethod() . ' ' . $request->getPathInfo() + ); + + if (!$request->headers->has('X-CSRF-Token')) { + throw $exception; + } + + $csrfToken = new CsrfToken( + $this->csrfTokenIntention, + $request->headers->get('X-CSRF-Token') + ); + + if (!$this->csrfTokenManager->isTokenValid($csrfToken)) { + throw $exception; + } + } + + /** + * Returns the csrf token for REST. The token is generated if it doesn't exist. + * + * @return string The csrf token, or an empty string if csrf check is disabled. + */ + private function getCsrfToken() + { + if ($this->csrfTokenManager === null) { + return ''; + } + + return $this->csrfTokenManager->getToken($this->csrfTokenIntention)->getValue(); + } +} diff --git a/eZ/Publish/Core/REST/Server/Controller/User.php b/eZ/Publish/Core/REST/Server/Controller/User.php index cc972b0a919..0796150e69a 100644 --- a/eZ/Publish/Core/REST/Server/Controller/User.php +++ b/eZ/Publish/Core/REST/Server/Controller/User.php @@ -28,11 +28,7 @@ use eZ\Publish\Core\REST\Server\Exceptions\ForbiddenException; use eZ\Publish\Core\REST\Common\Exceptions\NotFoundException; use eZ\Publish\Core\Base\Exceptions\UnauthorizedException; -use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Security\Core\Exception\AccessDeniedException; -use Symfony\Component\Security\Core\Exception\AuthenticationException; -use Symfony\Component\Security\Csrf\CsrfToken; use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; /** @@ -91,9 +87,16 @@ class User extends RestController /** * @var \Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface + * @deprecated This property is deprecated since 6.5, and will be removed in 7.0. */ private $csrfTokenStorage; + /** + * @var \eZ\Publish\Core\REST\Server\Controller\SessionController + * @deprecated This property is added for backward compatibility. It is deprecated, and will be removed in 7.0. + */ + private $sessionController; + /** * Construct controller. * @@ -974,44 +977,17 @@ public function assignUserToUserGroup($userId, Request $request) * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException If the login or password are incorrect or invalid CSRF * * @return Values\UserSession|Values\Conflict + * + * @deprecated Deprecated since 6.5. Use SessionController::refreshSessionAction(). */ public function createSession(Request $request) { - /** @var $sessionInput \eZ\Publish\Core\REST\Server\Values\SessionInput */ - $sessionInput = $this->inputDispatcher->parse( - new Message( - array('Content-Type' => $request->headers->get('Content-Type')), - $request->getContent() - ) + @trigger_error( + E_USER_DEPRECATED, + 'The session actions from the User controller are deprecated since 6.5. Use the SessionController instead.' ); - $request->attributes->set('username', $sessionInput->login); - $request->attributes->set('password', $sessionInput->password); - try { - $session = $request->getSession(); - if ($session->isStarted() && $this->hasStoredCsrfToken()) { - $this->checkCsrfToken($request); - } - - $authenticator = $this->container->get('ezpublish_rest.session_authenticator'); - $token = $authenticator->authenticate($request); - $csrfToken = $this->getCsrfToken(); - - return new Values\UserSession( - $token->getUser()->getAPIUser(), - $session->getName(), - $session->getId(), - $csrfToken, - !$token->hasAttribute('isFromSession') - ); - } catch (Exceptions\UserConflictException $e) { - // Already logged in with another user, this will be converted to HTTP status 409 - return new Values\Conflict(); - } catch (AuthenticationException $e) { - throw new UnauthorizedException('Invalid login or password', $request->getPathInfo()); - } catch (AccessDeniedException $e) { - throw new UnauthorizedException($e->getMessage(), $request->getPathInfo()); - } + return $this->sessionController->createSessionAction($request); } /** @@ -1022,27 +998,17 @@ public function createSession(Request $request) * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException If the CSRF token is missing or invalid. * * @return \eZ\Publish\Core\REST\Server\Values\UserSession + * + * @deprecated Deprecated since 6.5. Use SessionController::refreshSessionAction(). */ public function refreshSession($sessionId, Request $request) { - $session = $request->getSession(); - - if ($session === null || !$session->isStarted() || $session->getId() != $sessionId || !$this->hasStoredCsrfToken()) { - $response = $this->container->get('ezpublish_rest.session_authenticator')->logout($request); - $response->setStatusCode(404); - - return $response; - } - - $this->checkCsrfToken($request); - - return new Values\UserSession( - $this->repository->getCurrentUser(), - $session->getName(), - $session->getId(), - $request->headers->get('X-CSRF-Token'), - false + @trigger_error( + E_USER_DEPRECATED, + 'The session actions from the User controller are deprecated since 6.5. Use the SessionController instead.' ); + + return $this->sessionController->refreshSessionAction($sessionId, $request); } /** @@ -1053,23 +1019,18 @@ public function refreshSession($sessionId, Request $request) * @return Values\DeletedUserSession|\Symfony\Component\HttpFoundation\Response * * @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException If the CSRF token is missing or invalid. + * @throws RestNotFoundException + * + * @deprecated Deprecated since 6.5. Use SessionController::refreshSessionAction(). */ public function deleteSession($sessionId, Request $request) { - $session = $request->getSession(); - - if (!$session->isStarted() || $session->getId() != $sessionId || !$this->hasStoredCsrfToken()) { - $response = $this->container->get('ezpublish_rest.session_authenticator')->logout($request); - $response->setStatusCode(404); - - return $response; - } - - $this->checkCsrfToken($request); - - return new Values\DeletedUserSession( - $this->container->get('ezpublish_rest.session_authenticator')->logout($request) + @trigger_error( + E_USER_DEPRECATED, + 'The session actions from the User controller are deprecated since 6.5. Use the SessionController instead.' ); + + return $this->sessionController->deleteSessionAction($sessionId, $request); } /** @@ -1086,82 +1047,18 @@ private function extractLocationIdFromPath($path) return array_pop($pathParts); } - /** - * Tests if a CSRF token is stored. - * - * @return bool - */ - private function hasStoredCsrfToken() - { - if (!isset($this->csrfTokenStorage)) { - return true; - } - - return $this->csrfTokenStorage->hasToken( - $this->container->getParameter('ezpublish_rest.csrf_token_intention') - ); - } - public function setTokenStorage(TokenStorageInterface $csrfTokenStorage) { - $this->csrfTokenStorage = $csrfTokenStorage; - } - - /** - * Checks the presence / validity of the CSRF token. - * - * @param Request $request - * - * @throws UnauthorizedException if the token is missing or invalid. - */ - private function checkCsrfToken(Request $request) - { - $csrfTokenManager = $this->container->get( - 'security.csrf.token_manager', - ContainerInterface::NULL_ON_INVALID_REFERENCE + @trigger_error( + E_USER_DEPRECATED, + 'setTokenStorage() is deprecated since 6.5 and will be removed in 7.0.' ); - if ($csrfTokenManager === null) { - return; - } - - $exception = new UnauthorizedException( - 'Missing or invalid CSRF token', - $request->getMethod() . ' ' . $request->getPathInfo() - ); - - if (!$request->headers->has('X-CSRF-Token')) { - throw $exception; - } - - $csrfToken = new CsrfToken( - $this->container->getParameter('ezpublish_rest.csrf_token_intention'), - $request->headers->get('X-CSRF-Token') - ); - - if (!$csrfTokenManager->isTokenValid($csrfToken)) { - throw $exception; - } + $this->csrfTokenStorage = $csrfTokenStorage; } - /** - * Returns the csrf token for REST. The token is generated if it doesn't exist. - * - * @return string The csrf token, or an empty string if csrf check is disabled. - */ - private function getCsrfToken() + public function setSessionController(SessionController $sessionController) { - $csrfTokenManager = $this->container->get( - 'security.csrf.token_manager', - ContainerInterface::NULL_ON_INVALID_REFERENCE - ); - - if ($csrfTokenManager === null) { - return ''; - } - - return $csrfTokenManager->getToken( - $this->container->getParameter('ezpublish_rest.csrf_token_intention') - )->getValue(); + $this->sessionController = $sessionController; } }