|
1 <?php |
|
2 |
|
3 namespace Symfony\Component\Security\Http\RememberMe; |
|
4 |
|
5 use Symfony\Component\Security\Core\Exception\AuthenticationException; |
|
6 use Symfony\Component\Security\Core\User\UserInterface; |
|
7 use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; |
|
8 use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; |
|
9 use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; |
|
10 use Symfony\Component\Security\Core\Exception\UnsupportedUserException; |
|
11 use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; |
|
12 use Symfony\Component\Security\Core\Exception\CookieTheftException; |
|
13 use Symfony\Component\Security\Core\User\UserProviderInterface; |
|
14 use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; |
|
15 use Symfony\Component\HttpFoundation\Response; |
|
16 use Symfony\Component\HttpFoundation\Request; |
|
17 use Symfony\Component\HttpFoundation\Cookie; |
|
18 use Symfony\Component\HttpKernel\Log\LoggerInterface; |
|
19 |
|
20 /* |
|
21 * This file is part of the Symfony package. |
|
22 * |
|
23 * (c) Fabien Potencier <fabien@symfony.com> |
|
24 * |
|
25 * For the full copyright and license information, please view the LICENSE |
|
26 * file that was distributed with this source code. |
|
27 */ |
|
28 |
|
29 /** |
|
30 * Base class implementing the RememberMeServicesInterface |
|
31 * |
|
32 * @author Johannes M. Schmitt <schmittjoh@gmail.com> |
|
33 */ |
|
34 abstract class AbstractRememberMeServices implements RememberMeServicesInterface, LogoutHandlerInterface |
|
35 { |
|
36 const COOKIE_DELIMITER = ':'; |
|
37 |
|
38 protected $logger; |
|
39 protected $options; |
|
40 private $providerKey; |
|
41 private $key; |
|
42 private $userProviders; |
|
43 |
|
44 /** |
|
45 * Constructor |
|
46 * |
|
47 * @param array $userProviders |
|
48 * @param string $key |
|
49 * @param string $providerKey |
|
50 * @param array $options |
|
51 * @param LoggerInterface $logger |
|
52 */ |
|
53 public function __construct(array $userProviders, $key, $providerKey, array $options = array(), LoggerInterface $logger = null) |
|
54 { |
|
55 if (empty($key)) { |
|
56 throw new \InvalidArgumentException('$key must not be empty.'); |
|
57 } |
|
58 if (empty($providerKey)) { |
|
59 throw new \InvalidArgumentException('$providerKey must not be empty.'); |
|
60 } |
|
61 if (0 === count($userProviders)) { |
|
62 throw new \InvalidArgumentException('You must provide at least one user provider.'); |
|
63 } |
|
64 |
|
65 $this->userProviders = $userProviders; |
|
66 $this->key = $key; |
|
67 $this->providerKey = $providerKey; |
|
68 $this->options = $options; |
|
69 $this->logger = $logger; |
|
70 } |
|
71 |
|
72 /** |
|
73 * Returns the parameter that is used for checking whether remember-me |
|
74 * services have been requested. |
|
75 * |
|
76 * @return string |
|
77 */ |
|
78 public function getRememberMeParameter() |
|
79 { |
|
80 return $this->options['remember_me_parameter']; |
|
81 } |
|
82 |
|
83 public function getKey() |
|
84 { |
|
85 return $this->key; |
|
86 } |
|
87 |
|
88 /** |
|
89 * Implementation of RememberMeServicesInterface. Detects whether a remember-me |
|
90 * cookie was set, decodes it, and hands it to subclasses for further processing. |
|
91 * |
|
92 * @param Request $request |
|
93 * @return TokenInterface |
|
94 */ |
|
95 public final function autoLogin(Request $request) |
|
96 { |
|
97 if (null === $cookie = $request->cookies->get($this->options['name'])) { |
|
98 return; |
|
99 } |
|
100 |
|
101 if (null !== $this->logger) { |
|
102 $this->logger->debug('Remember-me cookie detected.'); |
|
103 } |
|
104 |
|
105 $cookieParts = $this->decodeCookie($cookie); |
|
106 |
|
107 try { |
|
108 $user = $this->processAutoLoginCookie($cookieParts, $request); |
|
109 |
|
110 if (!$user instanceof UserInterface) { |
|
111 throw new \RuntimeException('processAutoLoginCookie() must return a UserInterface implementation.'); |
|
112 } |
|
113 |
|
114 if (null !== $this->logger) { |
|
115 $this->logger->info('Remember-me cookie accepted.'); |
|
116 } |
|
117 |
|
118 return new RememberMeToken($user, $this->providerKey, $this->key); |
|
119 } catch (CookieTheftException $theft) { |
|
120 $this->cancelCookie($request); |
|
121 |
|
122 throw $theft; |
|
123 } catch (UsernameNotFoundException $notFound) { |
|
124 if (null !== $this->logger) { |
|
125 $this->logger->info('User for remember-me cookie not found.'); |
|
126 } |
|
127 } catch (UnsupportedUserException $unSupported) { |
|
128 if (null !== $this->logger) { |
|
129 $this->logger->warn('User class for remember-me cookie not supported.'); |
|
130 } |
|
131 } catch (AuthenticationException $invalid) { |
|
132 if (null !== $this->logger) { |
|
133 $this->logger->debug('Remember-Me authentication failed: '.$invalid->getMessage()); |
|
134 } |
|
135 } |
|
136 |
|
137 $this->cancelCookie($request); |
|
138 |
|
139 return null; |
|
140 } |
|
141 |
|
142 /** |
|
143 * Implementation for LogoutHandlerInterface. Deletes the cookie. |
|
144 * |
|
145 * @param Request $request |
|
146 * @param Response $response |
|
147 * @param TokenInterface $token |
|
148 * @return void |
|
149 */ |
|
150 public function logout(Request $request, Response $response, TokenInterface $token) |
|
151 { |
|
152 $this->cancelCookie($request); |
|
153 } |
|
154 |
|
155 /** |
|
156 * Implementation for RememberMeServicesInterface. Deletes the cookie when |
|
157 * an attempted authentication fails. |
|
158 * |
|
159 * @param Request $request |
|
160 * @return void |
|
161 */ |
|
162 public final function loginFail(Request $request) |
|
163 { |
|
164 $this->cancelCookie($request); |
|
165 $this->onLoginFail($request); |
|
166 } |
|
167 |
|
168 /** |
|
169 * Implementation for RememberMeServicesInterface. This is called when an |
|
170 * authentication is successful. |
|
171 * |
|
172 * @param Request $request |
|
173 * @param Response $response |
|
174 * @param TokenInterface $token The token that resulted in a successful authentication |
|
175 * @return void |
|
176 */ |
|
177 public final function loginSuccess(Request $request, Response $response, TokenInterface $token) |
|
178 { |
|
179 if (!$token->getUser() instanceof UserInterface) { |
|
180 if (null !== $this->logger) { |
|
181 $this->logger->debug('Remember-me ignores token since it does not contain an UserInterface implementation.'); |
|
182 } |
|
183 |
|
184 return; |
|
185 } |
|
186 |
|
187 if (!$this->isRememberMeRequested($request)) { |
|
188 if (null !== $this->logger) { |
|
189 $this->logger->debug('Remember-me was not requested.'); |
|
190 } |
|
191 |
|
192 return; |
|
193 } |
|
194 |
|
195 if (null !== $this->logger) { |
|
196 $this->logger->debug('Remember-me was requested; setting cookie.'); |
|
197 } |
|
198 |
|
199 $this->onLoginSuccess($request, $response, $token); |
|
200 } |
|
201 |
|
202 /** |
|
203 * Subclasses should validate the cookie and do any additional processing |
|
204 * that is required. This is called from autoLogin(). |
|
205 * |
|
206 * @param array $cookieParts |
|
207 * @param Request $request |
|
208 * @return TokenInterface |
|
209 */ |
|
210 abstract protected function processAutoLoginCookie(array $cookieParts, Request $request); |
|
211 |
|
212 protected function onLoginFail(Request $request) |
|
213 { |
|
214 } |
|
215 |
|
216 /** |
|
217 * This is called after a user has been logged in successfully, and has |
|
218 * requested remember-me capabilities. The implementation usually sets a |
|
219 * cookie and possibly stores a persistent record of it. |
|
220 * |
|
221 * @param Request $request |
|
222 * @param Response $response |
|
223 * @param TokenInterface $token |
|
224 * @return void |
|
225 */ |
|
226 abstract protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token); |
|
227 |
|
228 protected final function getUserProvider($class) |
|
229 { |
|
230 foreach ($this->userProviders as $provider) { |
|
231 if ($provider->supportsClass($class)) { |
|
232 return $provider; |
|
233 } |
|
234 } |
|
235 |
|
236 throw new UnsupportedUserException(sprintf('There is no user provider that supports class "%s".', $class)); |
|
237 } |
|
238 |
|
239 /** |
|
240 * Decodes the raw cookie value |
|
241 * |
|
242 * @param string $rawCookie |
|
243 * @return array |
|
244 */ |
|
245 protected function decodeCookie($rawCookie) |
|
246 { |
|
247 return explode(self::COOKIE_DELIMITER, base64_decode($rawCookie)); |
|
248 } |
|
249 |
|
250 /** |
|
251 * Encodes the cookie parts |
|
252 * |
|
253 * @param array $cookieParts |
|
254 * @return string |
|
255 */ |
|
256 protected function encodeCookie(array $cookieParts) |
|
257 { |
|
258 return base64_encode(implode(self::COOKIE_DELIMITER, $cookieParts)); |
|
259 } |
|
260 |
|
261 /** |
|
262 * Deletes the remember-me cookie |
|
263 * |
|
264 * @param Request $request |
|
265 * @return void |
|
266 */ |
|
267 protected function cancelCookie(Request $request) |
|
268 { |
|
269 if (null !== $this->logger) { |
|
270 $this->logger->debug(sprintf('Clearing remember-me cookie "%s"', $this->options['name'])); |
|
271 } |
|
272 |
|
273 $request->attributes->set(self::COOKIE_ATTR_NAME, new Cookie($this->options['name'], null, 1, $this->options['path'], $this->options['domain'])); |
|
274 } |
|
275 |
|
276 /** |
|
277 * Checks whether remember-me capabilities where requested |
|
278 * |
|
279 * @param Request $request |
|
280 * @return Boolean |
|
281 */ |
|
282 protected function isRememberMeRequested(Request $request) |
|
283 { |
|
284 if (true === $this->options['always_remember_me']) { |
|
285 return true; |
|
286 } |
|
287 |
|
288 $parameter = $request->request->get($this->options['remember_me_parameter'], null, true); |
|
289 |
|
290 if ($parameter === null && null !== $this->logger) { |
|
291 $this->logger->debug(sprintf('Did not send remember-me cookie (remember-me parameter "%s" was not sent).', $this->options['remember_me_parameter'])); |
|
292 } |
|
293 |
|
294 return $parameter === 'true' || $parameter === 'on' || $parameter === '1' || $parameter === 'yes'; |
|
295 } |
|
296 } |