|
1 <?php |
|
2 |
|
3 namespace Symfony\Component\Security\Http\RememberMe; |
|
4 |
|
5 use Symfony\Component\HttpFoundation\Cookie; |
|
6 use Symfony\Component\HttpFoundation\Request; |
|
7 use Symfony\Component\HttpFoundation\Response; |
|
8 use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; |
|
9 use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; |
|
10 use Symfony\Component\Security\Core\Exception\AuthenticationException; |
|
11 use Symfony\Component\Security\Core\User\UserInterface; |
|
12 |
|
13 /* |
|
14 * This file is part of the Symfony package. |
|
15 * |
|
16 * (c) Fabien Potencier <fabien@symfony.com> |
|
17 * |
|
18 * For the full copyright and license information, please view the LICENSE |
|
19 * file that was distributed with this source code. |
|
20 */ |
|
21 |
|
22 /** |
|
23 * Concrete implementation of the RememberMeServicesInterface providing |
|
24 * remember-me capabilities without requiring a TokenProvider. |
|
25 * |
|
26 * @author Johannes M. Schmitt <schmittjoh@gmail.com> |
|
27 */ |
|
28 class TokenBasedRememberMeServices extends AbstractRememberMeServices |
|
29 { |
|
30 /** |
|
31 * {@inheritDoc} |
|
32 */ |
|
33 protected function processAutoLoginCookie(array $cookieParts, Request $request) |
|
34 { |
|
35 if (count($cookieParts) !== 4) { |
|
36 throw new AuthenticationException('The cookie is invalid.'); |
|
37 } |
|
38 |
|
39 list($class, $username, $expires, $hash) = $cookieParts; |
|
40 if (false === $username = base64_decode($username, true)) { |
|
41 throw new AuthenticationException('$username contains a character from outside the base64 alphabet.'); |
|
42 } |
|
43 try { |
|
44 $user = $this->getUserProvider($class)->loadUserByUsername($username); |
|
45 } catch (\Exception $ex) { |
|
46 if (!$ex instanceof AuthenticationException) { |
|
47 $ex = new AuthenticationException($ex->getMessage(), null, $ex->getCode(), $ex); |
|
48 } |
|
49 |
|
50 throw $ex; |
|
51 } |
|
52 |
|
53 if (!$user instanceof UserInterface) { |
|
54 throw new \RuntimeException(sprintf('The UserProviderInterface implementation must return an instance of UserInterface, but returned "%s".', get_class($user))); |
|
55 } |
|
56 |
|
57 if (true !== $this->compareHashes($hash, $this->generateCookieHash($class, $username, $expires, $user->getPassword()))) { |
|
58 throw new AuthenticationException('The cookie\'s hash is invalid.'); |
|
59 } |
|
60 |
|
61 if ($expires < time()) { |
|
62 throw new AuthenticationException('The cookie has expired.'); |
|
63 } |
|
64 |
|
65 return $user; |
|
66 } |
|
67 |
|
68 /** |
|
69 * Compares two hashes using a constant-time algorithm to avoid (remote) |
|
70 * timing attacks. |
|
71 * |
|
72 * This is the same implementation as used in the BasePasswordEncoder. |
|
73 * |
|
74 * @param string $hash1 The first hash |
|
75 * @param string $hash2 The second hash |
|
76 * |
|
77 * @return Boolean true if the two hashes are the same, false otherwise |
|
78 */ |
|
79 private function compareHashes($hash1, $hash2) |
|
80 { |
|
81 if (strlen($hash1) !== $c = strlen($hash2)) { |
|
82 return false; |
|
83 } |
|
84 |
|
85 $result = 0; |
|
86 for ($i = 0; $i < $c; $i++) { |
|
87 $result |= ord($hash1[$i]) ^ ord($hash2[$i]); |
|
88 } |
|
89 |
|
90 return 0 === $result; |
|
91 } |
|
92 |
|
93 /** |
|
94 * {@inheritDoc} |
|
95 */ |
|
96 protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token) |
|
97 { |
|
98 $user = $token->getUser(); |
|
99 $expires = time() + $this->options['lifetime']; |
|
100 $value = $this->generateCookieValue(get_class($user), $user->getUsername(), $expires, $user->getPassword()); |
|
101 |
|
102 $response->headers->setCookie( |
|
103 new Cookie( |
|
104 $this->options['name'], |
|
105 $value, |
|
106 $expires, |
|
107 $this->options['path'], |
|
108 $this->options['domain'], |
|
109 $this->options['secure'], |
|
110 $this->options['httponly'] |
|
111 ) |
|
112 ); |
|
113 } |
|
114 |
|
115 /** |
|
116 * Generates the cookie value. |
|
117 * |
|
118 * @param string $class |
|
119 * @param string $username The username |
|
120 * @param integer $expires The unixtime when the cookie expires |
|
121 * @param string $password The encoded password |
|
122 * |
|
123 * @throws \RuntimeException if username contains invalid chars |
|
124 * |
|
125 * @return string |
|
126 */ |
|
127 protected function generateCookieValue($class, $username, $expires, $password) |
|
128 { |
|
129 return $this->encodeCookie(array( |
|
130 $class, |
|
131 base64_encode($username), |
|
132 $expires, |
|
133 $this->generateCookieHash($class, $username, $expires, $password) |
|
134 )); |
|
135 } |
|
136 |
|
137 /** |
|
138 * Generates a hash for the cookie to ensure it is not being tempered with |
|
139 * |
|
140 * @param string $class |
|
141 * @param string $username The username |
|
142 * @param integer $expires The unixtime when the cookie expires |
|
143 * @param string $password The encoded password |
|
144 * @throws \RuntimeException when the private key is empty |
|
145 * @return string |
|
146 */ |
|
147 protected function generateCookieHash($class, $username, $expires, $password) |
|
148 { |
|
149 return hash('sha256', $class.$username.$expires.$password.$this->getKey()); |
|
150 } |
|
151 } |