Skip to content

Commit

Permalink
Merge pull request #1782 from ezsystems/ezp26329-rest_session_control…
Browse files Browse the repository at this point in the history
…ler_refactoring

Refactored session rest actions to their own controller (EZP-26329)
  • Loading branch information
bdunogier authored Sep 27, 2016
2 parents 9dc396f + f040223 commit 0b27446
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 142 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
6 changes: 3 additions & 3 deletions eZ/Bundle/EzPublishRestBundle/Resources/config/routing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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]


Expand Down
11 changes: 9 additions & 2 deletions eZ/Bundle/EzPublishRestBundle/Resources/config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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%"
Expand All @@ -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:
Expand Down
216 changes: 216 additions & 0 deletions eZ/Publish/Core/REST/Server/Controller/SessionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
<?php
/**
* @copyright Copyright (C) eZ Systems AS. All rights reserved.
* @license For full copyright and license information view LICENSE file distributed with this source code.
*/
namespace eZ\Publish\Core\REST\Server\Controller;

use eZ\Publish\Core\Base\Exceptions\UnauthorizedException;
use eZ\Publish\Core\MVC\Symfony\Security\Authentication\AuthenticatorInterface;
use eZ\Publish\Core\REST\Common\Exceptions\NotFoundException;
use eZ\Publish\Core\REST\Common\Message;
use eZ\Publish\Core\REST\Server\Controller;
use eZ\Publish\Core\REST\Server\Values;
use eZ\Publish\Core\REST\Server\Exceptions;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;

class SessionController extends Controller
{
/**
* @var \eZ\Publish\Core\MVC\Symfony\Security\Authentication\AuthenticatorInterface
*/
private $authenticator;

/**
* @var \Symfony\Component\Security\Csrf\CsrfTokenManager
*/
private $csrfTokenManager;

/**
* @var \Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface
*/
private $csrfTokenStorage;

/**
* @var string
*/
private $csrfTokenIntention;

public function __construct(
AuthenticatorInterface $authenticator,
$tokenIntention,
CsrfTokenManager $csrfTokenManager = null,
TokenStorageInterface $csrfTokenStorage = null
) {
$this->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();
}
}
Loading

0 comments on commit 0b27446

Please # to comment.