1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @copyright Copyright (C) eZ Systems AS. All rights reserved. |
4
|
|
|
* @license For full copyright and license information view LICENSE file distributed with this source code. |
5
|
|
|
*/ |
6
|
|
|
namespace eZ\Publish\Core\REST\Server\Controller; |
7
|
|
|
|
8
|
|
|
use eZ\Publish\Core\MVC\Symfony\Security\Authentication\AuthenticatorInterface; |
9
|
|
|
use eZ\Publish\Core\REST\Common\Message; |
10
|
|
|
use eZ\Publish\Core\REST\Server\Controller; |
11
|
|
|
use Symfony\Component\HttpFoundation\Request; |
12
|
|
|
use Symfony\Component\Security\Core\Exception\SessionUnavailableException; |
13
|
|
|
use Symfony\Component\Security\Csrf\CsrfToken; |
14
|
|
|
use Symfony\Component\Security\Csrf\CsrfTokenManager; |
15
|
|
|
use eZ\Publish\Core\REST\Server\Values; |
16
|
|
|
use eZ\Publish\Core\REST\Server\Exceptions; |
17
|
|
|
use eZ\Publish\Core\Base\Exceptions\UnauthorizedException; |
18
|
|
|
use eZ\Publish\Core\REST\Common\Exceptions\NotFoundException; |
19
|
|
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException; |
20
|
|
|
use Symfony\Component\Security\Core\Exception\AuthenticationException; |
21
|
|
|
use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; |
22
|
|
|
|
23
|
|
|
class SessionController extends Controller |
24
|
|
|
{ |
25
|
|
|
/** |
26
|
|
|
* @var AuthenticatorInterface |
27
|
|
|
*/ |
28
|
|
|
private $authenticator; |
29
|
|
|
/** |
30
|
|
|
* @var CsrfTokenManager |
31
|
|
|
*/ |
32
|
|
|
private $csrfTokenManager; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var \Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface |
36
|
|
|
*/ |
37
|
|
|
private $csrfTokenStorage; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var string |
41
|
|
|
*/ |
42
|
|
|
private $csrfTokenIntention; |
43
|
|
|
|
44
|
|
|
public function __construct( |
45
|
|
|
AuthenticatorInterface $authenticator, |
46
|
|
|
$tokenIntention, |
47
|
|
|
CsrfTokenManager $csrfTokenManager = null, |
48
|
|
|
TokenStorageInterface $csrfTokenStorage = null |
49
|
|
|
) { |
50
|
|
|
|
51
|
|
|
$this->authenticator = $authenticator; |
52
|
|
|
$this->csrfTokenIntention = $tokenIntention; |
53
|
|
|
$this->csrfTokenManager = $csrfTokenManager; |
54
|
|
|
$this->csrfTokenStorage = $csrfTokenStorage; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Creates a new session based on the credentials provided as POST parameters. |
59
|
|
|
* |
60
|
|
|
* @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException If the login or password are incorrect or invalid CSRF |
61
|
|
|
* |
62
|
|
|
* @return Values\UserSession|Values\Conflict |
63
|
|
|
*/ |
64
|
|
|
public function createSessionAction(Request $request) |
65
|
|
|
{ |
66
|
|
|
/** @var $sessionInput \eZ\Publish\Core\REST\Server\Values\SessionInput */ |
67
|
|
|
$sessionInput = $this->inputDispatcher->parse( |
68
|
|
|
new Message( |
69
|
|
|
array('Content-Type' => $request->headers->get('Content-Type')), |
70
|
|
|
$request->getContent() |
|
|
|
|
71
|
|
|
) |
72
|
|
|
); |
73
|
|
|
$request->attributes->set('username', $sessionInput->login); |
74
|
|
|
$request->attributes->set('password', $sessionInput->password); |
75
|
|
|
|
76
|
|
|
try { |
77
|
|
|
$session = $request->getSession(); |
78
|
|
|
if ($session->isStarted() && $this->hasStoredCsrfToken()) { |
79
|
|
|
$this->checkCsrfToken($request); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
$token = $this->authenticator->authenticate($request); |
83
|
|
|
$csrfToken = $this->getCsrfToken(); |
84
|
|
|
|
85
|
|
|
return new Values\UserSession( |
86
|
|
|
$token->getUser()->getAPIUser(), |
87
|
|
|
$session->getName(), |
88
|
|
|
$session->getId(), |
89
|
|
|
$csrfToken, |
90
|
|
|
!$token->hasAttribute('isFromSession') |
91
|
|
|
); |
92
|
|
|
} catch (Exceptions\UserConflictException $e) { |
93
|
|
|
// Already logged in with another user, this will be converted to HTTP status 409 |
94
|
|
|
return new Values\Conflict(); |
95
|
|
|
} catch (AuthenticationException $e) { |
96
|
|
|
throw new UnauthorizedException('Invalid login or password', $request->getPathInfo()); |
97
|
|
|
} catch (AccessDeniedException $e) { |
98
|
|
|
throw new UnauthorizedException($e->getMessage(), $request->getPathInfo()); |
99
|
|
|
} |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Refresh given session. |
104
|
|
|
* |
105
|
|
|
* @param string $sessionId |
106
|
|
|
* |
107
|
|
|
* @throws \eZ\Publish\Core\REST\Common\Exceptions\NotFoundException |
108
|
|
|
* |
109
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\UserSession |
110
|
|
|
*/ |
111
|
|
|
public function refreshSessionAction($sessionId, Request $request) |
112
|
|
|
{ |
113
|
|
|
$session = $request->getSession(); |
114
|
|
|
|
115
|
|
|
if ($session === null || !$session->isStarted() || $session->getId() != $sessionId || !$this->hasStoredCsrfToken()) { |
116
|
|
|
$response = $this->authenticator->logout($request); |
117
|
|
|
$response->setStatusCode(404); |
118
|
|
|
|
119
|
|
|
return $response; |
|
|
|
|
120
|
|
|
} |
121
|
|
|
|
122
|
|
|
$this->checkCsrfToken($request); |
123
|
|
|
|
124
|
|
|
return new Values\UserSession( |
125
|
|
|
$this->repository->getCurrentUser(), |
|
|
|
|
126
|
|
|
$session->getName(), |
127
|
|
|
$session->getId(), |
128
|
|
|
$request->headers->get('X-CSRF-Token'), |
129
|
|
|
false |
130
|
|
|
); |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* Deletes given session. |
135
|
|
|
* |
136
|
|
|
* @param string $sessionId |
137
|
|
|
* |
138
|
|
|
* @return Values\DeletedUserSession |
139
|
|
|
* |
140
|
|
|
* @throws NotFoundException |
141
|
|
|
*/ |
142
|
|
|
public function deleteSessionAction($sessionId, Request $request) |
143
|
|
|
{ |
144
|
|
|
/** @var $session \Symfony\Component\HttpFoundation\Session\Session */ |
145
|
|
|
$session = $request->getSession(); |
146
|
|
|
if (!$session->isStarted() || $session->getId() != $sessionId) { |
147
|
|
|
throw new NotFoundException("Session not found: '{$sessionId}'."); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
return new Values\DeletedUserSession( |
151
|
|
|
$this->authenticator->logout($request) |
152
|
|
|
); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Tests if a CSRF token is stored. |
157
|
|
|
* |
158
|
|
|
* @return bool |
159
|
|
|
*/ |
160
|
|
|
private function hasStoredCsrfToken() |
161
|
|
|
{ |
162
|
|
|
if (!isset($this->csrfTokenStorage)) { |
163
|
|
|
return true; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
return $this->csrfTokenStorage->hasToken( |
167
|
|
|
$this->container->getParameter('ezpublish_rest.csrf_token_intention') |
168
|
|
|
); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
public function setTokenStorage(TokenStorageInterface $csrfTokenStorage) |
172
|
|
|
{ |
173
|
|
|
$this->csrfTokenStorage = $csrfTokenStorage; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Checks the presence / validity of the CSRF token. |
178
|
|
|
* |
179
|
|
|
* @param Request $request |
180
|
|
|
* |
181
|
|
|
* @throws UnauthorizedException if the token is missing or invalid. |
182
|
|
|
*/ |
183
|
|
|
private function checkCsrfToken(Request $request) |
184
|
|
|
{ |
185
|
|
|
if ($this->csrfTokenManager === null) { |
186
|
|
|
return; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
$exception = new UnauthorizedException( |
190
|
|
|
'Missing or invalid CSRF token', |
191
|
|
|
$request->getMethod() . ' ' . $request->getPathInfo() |
192
|
|
|
); |
193
|
|
|
|
194
|
|
|
if (!$request->headers->has('X-CSRF-Token')) { |
195
|
|
|
throw $exception; |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
$csrfToken = new CsrfToken( |
199
|
|
|
$this->csrfTokenIntention, |
200
|
|
|
$request->headers->get('X-CSRF-Token') |
201
|
|
|
); |
202
|
|
|
|
203
|
|
|
if (!$this->csrfTokenManager->isTokenValid($csrfToken)) { |
204
|
|
|
throw $exception; |
205
|
|
|
} |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* Returns the csrf token for REST. The token is generated if it doesn't exist. |
210
|
|
|
* |
211
|
|
|
* @return string The csrf token, or an empty string if csrf check is disabled. |
212
|
|
|
*/ |
213
|
|
|
private function getCsrfToken() |
214
|
|
|
{ |
215
|
|
|
if ($this->csrfTokenManager === null) { |
216
|
|
|
return ''; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
return $this->csrfTokenManager->getToken($this->csrfTokenIntention)->getValue(); |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.