AbstractRememberMeServices.php 9.6 KB

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