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\Base\Exceptions\UnauthorizedException; |
9
|
|
|
use eZ\Publish\Core\MVC\Symfony\Security\Authentication\AuthenticatorInterface; |
10
|
|
|
use eZ\Publish\Core\REST\Common\Exceptions\NotFoundException; |
11
|
|
|
use eZ\Publish\Core\REST\Common\Message; |
12
|
|
|
use eZ\Publish\Core\REST\Server\Controller; |
13
|
|
|
use eZ\Publish\Core\REST\Server\Values; |
14
|
|
|
use eZ\Publish\Core\REST\Server\Exceptions; |
15
|
|
|
use Symfony\Component\HttpFoundation\Request; |
16
|
|
|
use Symfony\Component\Security\Csrf\CsrfToken; |
17
|
|
|
use Symfony\Component\Security\Csrf\CsrfTokenManager; |
18
|
|
|
use Symfony\Component\Security\Core\Exception\AccessDeniedException; |
19
|
|
|
use Symfony\Component\Security\Core\Exception\AuthenticationException; |
20
|
|
|
use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; |
21
|
|
|
|
22
|
|
|
class SessionController extends Controller |
23
|
|
|
{ |
24
|
|
|
/** |
25
|
|
|
* @var \eZ\Publish\Core\MVC\Symfony\Security\Authentication\AuthenticatorInterface |
26
|
|
|
*/ |
27
|
|
|
private $authenticator; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var \Symfony\Component\Security\Csrf\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
|
|
|
$this->authenticator = $authenticator; |
51
|
|
|
$this->csrfTokenIntention = $tokenIntention; |
52
|
|
|
$this->csrfTokenManager = $csrfTokenManager; |
53
|
|
|
$this->csrfTokenStorage = $csrfTokenStorage; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Creates a new session based on the credentials provided as POST parameters. |
58
|
|
|
* |
59
|
|
|
* @throws \eZ\Publish\Core\Base\Exceptions\UnauthorizedException If the login or password are incorrect or invalid CSRF |
60
|
|
|
* |
61
|
|
|
* @return Values\UserSession|Values\Conflict |
62
|
|
|
*/ |
63
|
|
|
public function createSessionAction(Request $request) |
64
|
|
|
{ |
65
|
|
|
/** @var $sessionInput \eZ\Publish\Core\REST\Server\Values\SessionInput */ |
66
|
|
|
$sessionInput = $this->inputDispatcher->parse( |
67
|
|
|
new Message( |
68
|
|
|
array('Content-Type' => $request->headers->get('Content-Type')), |
69
|
|
|
$request->getContent() |
|
|
|
|
70
|
|
|
) |
71
|
|
|
); |
72
|
|
|
$request->attributes->set('username', $sessionInput->login); |
73
|
|
|
$request->attributes->set('password', $sessionInput->password); |
74
|
|
|
|
75
|
|
|
try { |
76
|
|
|
$session = $request->getSession(); |
77
|
|
|
if ($session->isStarted() && $this->hasStoredCsrfToken()) { |
78
|
|
|
$this->checkCsrfToken($request); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
$token = $this->authenticator->authenticate($request); |
82
|
|
|
$csrfToken = $this->getCsrfToken(); |
83
|
|
|
|
84
|
|
|
return new Values\UserSession( |
85
|
|
|
$token->getUser()->getAPIUser(), |
86
|
|
|
$session->getName(), |
87
|
|
|
$session->getId(), |
88
|
|
|
$csrfToken, |
89
|
|
|
!$token->hasAttribute('isFromSession') |
90
|
|
|
); |
91
|
|
|
} catch (Exceptions\UserConflictException $e) { |
92
|
|
|
// Already logged in with another user, this will be converted to HTTP status 409 |
93
|
|
|
return new Values\Conflict(); |
94
|
|
|
} catch (AuthenticationException $e) { |
95
|
|
|
throw new UnauthorizedException('Invalid login or password', $request->getPathInfo()); |
96
|
|
|
} catch (AccessDeniedException $e) { |
97
|
|
|
throw new UnauthorizedException($e->getMessage(), $request->getPathInfo()); |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Refresh given session. |
103
|
|
|
* |
104
|
|
|
* @param string $sessionId |
105
|
|
|
* |
106
|
|
|
* @throws \eZ\Publish\Core\REST\Common\Exceptions\NotFoundException |
107
|
|
|
* |
108
|
|
|
* @return \eZ\Publish\Core\REST\Server\Values\UserSession |
109
|
|
|
*/ |
110
|
|
|
public function refreshSessionAction($sessionId, Request $request) |
111
|
|
|
{ |
112
|
|
|
$session = $request->getSession(); |
113
|
|
|
|
114
|
|
View Code Duplication |
if ($session === null || !$session->isStarted() || $session->getId() != $sessionId || !$this->hasStoredCsrfToken()) { |
|
|
|
|
115
|
|
|
$response = $this->authenticator->logout($request); |
116
|
|
|
$response->setStatusCode(404); |
117
|
|
|
|
118
|
|
|
return $response; |
|
|
|
|
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
$this->checkCsrfToken($request); |
122
|
|
|
|
123
|
|
|
return new Values\UserSession( |
124
|
|
|
$this->repository->getCurrentUser(), |
|
|
|
|
125
|
|
|
$session->getName(), |
126
|
|
|
$session->getId(), |
127
|
|
|
$request->headers->get('X-CSRF-Token'), |
128
|
|
|
false |
129
|
|
|
); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Deletes given session. |
134
|
|
|
* |
135
|
|
|
* @param string $sessionId |
136
|
|
|
* |
137
|
|
|
* @return Values\DeletedUserSession |
138
|
|
|
* |
139
|
|
|
* @throws NotFoundException |
140
|
|
|
*/ |
141
|
|
|
public function deleteSessionAction($sessionId, Request $request) |
142
|
|
|
{ |
143
|
|
|
/** @var $session \Symfony\Component\HttpFoundation\Session\Session */ |
144
|
|
|
$session = $request->getSession(); |
145
|
|
View Code Duplication |
if (!$session->isStarted() || $session->getId() != $sessionId || !$this->hasStoredCsrfToken()) { |
|
|
|
|
146
|
|
|
$response = $this->authenticator->logout($request); |
147
|
|
|
$response->setStatusCode(404); |
148
|
|
|
|
149
|
|
|
return $response; |
|
|
|
|
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
$this->checkCsrfToken($request); |
153
|
|
|
|
154
|
|
|
return new Values\DeletedUserSession($this->authenticator->logout($request)); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* Tests if a CSRF token is stored. |
159
|
|
|
* |
160
|
|
|
* @return bool |
161
|
|
|
*/ |
162
|
|
|
private function hasStoredCsrfToken() |
163
|
|
|
{ |
164
|
|
|
if (!isset($this->csrfTokenStorage)) { |
165
|
|
|
return true; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
return $this->csrfTokenStorage->hasToken($this->csrfTokenIntention); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* Checks the presence / validity of the CSRF token. |
173
|
|
|
* |
174
|
|
|
* @param Request $request |
175
|
|
|
* |
176
|
|
|
* @throws UnauthorizedException if the token is missing or invalid. |
177
|
|
|
*/ |
178
|
|
|
private function checkCsrfToken(Request $request) |
179
|
|
|
{ |
180
|
|
|
if ($this->csrfTokenManager === null) { |
181
|
|
|
return; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
$exception = new UnauthorizedException( |
185
|
|
|
'Missing or invalid CSRF token', |
186
|
|
|
$request->getMethod() . ' ' . $request->getPathInfo() |
187
|
|
|
); |
188
|
|
|
|
189
|
|
|
if (!$request->headers->has('X-CSRF-Token')) { |
190
|
|
|
throw $exception; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
$csrfToken = new CsrfToken( |
194
|
|
|
$this->csrfTokenIntention, |
195
|
|
|
$request->headers->get('X-CSRF-Token') |
196
|
|
|
); |
197
|
|
|
|
198
|
|
|
if (!$this->csrfTokenManager->isTokenValid($csrfToken)) { |
199
|
|
|
throw $exception; |
200
|
|
|
} |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Returns the csrf token for REST. The token is generated if it doesn't exist. |
205
|
|
|
* |
206
|
|
|
* @return string The csrf token, or an empty string if csrf check is disabled. |
207
|
|
|
*/ |
208
|
|
|
private function getCsrfToken() |
209
|
|
|
{ |
210
|
|
|
if ($this->csrfTokenManager === null) { |
211
|
|
|
return ''; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
return $this->csrfTokenManager->getToken($this->csrfTokenIntention)->getValue(); |
215
|
|
|
} |
216
|
|
|
} |
217
|
|
|
|
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.