Elgg  Version 1.10
PersistentLoginService.php
Go to the documentation of this file.
1 <?php
2 namespace Elgg;
27 
38  public function __construct(
39  Database $db,
41  \ElggCrypto $crypto,
42  array $cookie_config,
43  $cookie_token,
44  $time = null) {
45  $this->db = $db;
46  $this->session = $session;
47  $this->crypto = $crypto;
48  $this->cookie_config = $cookie_config;
49  $this->cookie_token = $cookie_token;
50 
51  $prefix = $this->db->getTablePrefix();
52  $this->table = "{$prefix}users_remember_me_cookies";
53  $this->time = is_numeric($time) ? (int)$time : time();
54  }
55 
63  public function makeLoginPersistent(\ElggUser $user) {
64  $token = $this->generateToken();
65  $hash = $this->hashToken($token);
66 
67  $this->storeHash($user, $hash);
68  $this->setCookie($token);
69  $this->setSession($token);
70  }
71 
77  public function removePersistentLogin() {
78  if ($this->cookie_token) {
79  $client_hash = $this->hashToken($this->cookie_token);
80  $this->removeHash($client_hash);
81  }
82 
83  $this->setCookie("");
84  $this->setSession("");
85  }
86 
95  public function handlePasswordChange(\ElggUser $subject, \ElggUser $modifier = null) {
96  $this->removeAllHashes($subject);
97  if (!$modifier || ($modifier->guid !== $subject->guid) || !$this->cookie_token) {
98  return;
99  }
100 
101  $this->makeLoginPersistent($modifier);
102  }
103 
110  public function bootSession() {
111  if (!$this->cookie_token) {
112  return null;
113  }
114 
115  // is this token good?
116  $cookie_hash = $this->hashToken($this->cookie_token);
117  $user = $this->getUserFromHash($cookie_hash);
118  if ($user) {
119  $this->setSession($this->cookie_token);
120  // note: if the token is legacy, we don't both replacing it here because
121  // it will be replaced during the next request boot
122  return $user;
123  } else {
124  if ($this->isLegacyToken($this->cookie_token)) {
125  // may be attempt to brute force legacy low-entropy tokens
126  call_user_func($this->_callable_sleep, 1);
127  }
128  $this->setCookie('');
129  }
130  }
131 
139  public function replaceLegacyToken(\ElggUser $logged_in_user) {
140  if (!$this->cookie_token || !$this->isLegacyToken($this->cookie_token)) {
141  return;
142  }
143 
144  // replace user's old weaker-entropy code with new one
145  $this->removeHash($this->hashToken($this->cookie_token));
146  $this->makeLoginPersistent($logged_in_user);
147  }
148 
156  public function getUserFromHash($hash) {
157  if (!$hash) {
158  return null;
159  }
160 
161  $hash = $this->db->sanitizeString($hash);
162  $query = "SELECT guid FROM {$this->table} WHERE code = '$hash'";
163  try {
164  $user_row = $this->db->getDataRow($query);
165  } catch (\DatabaseException $e) {
166  return $this->handleDbException($e);
167  }
168  if (!$user_row) {
169  return null;
170  }
171 
172  $user = call_user_func($this->_callable_get_user, $user_row->guid);
173  return $user ? $user : null;
174  }
175 
184  protected function storeHash(\ElggUser $user, $hash) {
185  $time = time();
186  $hash = $this->db->sanitizeString($hash);
187 
188  $query = "
189  INSERT INTO {$this->table} (code, guid, timestamp)
190  VALUES ('$hash', {$user->guid}, $time)
191  ";
192  try {
193  $this->db->insertData($query);
194  } catch (\DatabaseException $e) {
195  $this->handleDbException($e);
196  }
197  }
198 
205  protected function removeHash($hash) {
206  $hash = $this->db->sanitizeString($hash);
207 
208  $query = "DELETE FROM {$this->table} WHERE code = '$hash'";
209  try {
210  $this->db->deleteData($query);
211  } catch (\DatabaseException $e) {
212  $this->handleDbException($e);
213  }
214  }
215 
227  if (false !== strpos($exception->getMessage(), "users_remember_me_cookies' doesn't exist")) {
228  // schema has not been updated so we swallow this exception
229  return $default;
230  } else {
231  throw $exception;
232  }
233  }
234 
242  protected function removeAllHashes(\ElggUser $user) {
243  $query = "DELETE FROM {$this->table} WHERE guid = '{$user->guid}'";
244  try {
245  $this->db->deleteData($query);
246  } catch (\DatabaseException $e) {
247  $this->handleDbException($e);
248  }
249  }
250 
258  protected function hashToken($token) {
259  // note: with user passwords, you'd want legit password hashing, but since these are randomly
260  // generated and long tokens, rainbow tables aren't any help.
261  return md5($token);
262  }
263 
271  protected function setCookie($token) {
272  $cookie = new \ElggCookie($this->cookie_config['name']);
273  foreach (array('expire', 'path', 'domain', 'secure', 'httponly') as $key) {
274  $cookie->$key = $this->cookie_config[$key];
275  }
276  $cookie->value = $token;
277  if (!$token) {
278  $cookie->expire = $this->time - (86400 * 30);
279  }
280  call_user_func($this->_callable_elgg_set_cookie, $cookie);
281  }
282 
290  protected function setSession($token) {
291  if ($token) {
292  $this->session->set('code', $token);
293  } else {
294  $this->session->remove('code');
295  }
296  }
297 
306  protected function generateToken() {
307  return 'z' . $this->crypto->getRandomString(31);
308  }
309 
317  protected function isLegacyToken($token) {
318  return (isset($token[0]) && $token[0] !== 'z');
319  }
320 
324  protected $db;
325 
329  protected $table;
330 
334  protected $cookie_config;
335 
339  protected $cookie_token;
340 
344  protected $session;
345 
349  protected $crypto;
350 
354  protected $time;
355 
360  public $_callable_get_user = 'get_user';
361 
366  public $_callable_elgg_set_cookie = 'elgg_set_cookie';
367 
372  public $_callable_sleep = 'sleep';
373 }
374 
handleDbException(\DatabaseException $exception, $default=null)
Swallow a schema not upgraded exception, otherwise rethrow it.
$subject
Definition: exceptions.php:25
removeHash($hash)
Remove a hash from the DB.
makeLoginPersistent(\ElggUser $user)
Make the user&#39;s login persistent.
removeAllHashes(\ElggUser $user)
Remove all the hashes associated with a user.
$e
Definition: metadata.php:12
bootSession()
Boot the persistent login session, possibly returning the user who should be silently logged in...
__construct(Database $db,\ElggSession $session,\ElggCrypto $crypto, array $cookie_config, $cookie_token, $time=null)
Constructor.
$default
Definition: checkbox.php:36
storeHash(\ElggUser $user, $hash)
Store a hash in the DB.
setCookie($token)
Store the token in the client cookie (or remove the cookie)
getUserFromHash($hash)
Find a user with the given hash.
$exception
Save menu items.
isLegacyToken($token)
Is the given token a legacy MD5 hash?
$key
Definition: summary.php:34
$user
Definition: ban.php:13
$token
generateToken()
Generate a random token (base 64 URL)
hashToken($token)
Create a hash from the token.
removePersistentLogin()
Remove the persisted login token from client and server.
setSession($token)
Store the token in the session (or remove it from the session)
handlePasswordChange(\ElggUser $subject,\ElggUser $modifier=null)
Handle a password change.
$session
Definition: login.php:9
replaceLegacyToken(\ElggUser $logged_in_user)
Replace the user&#39;s token if it&#39;s a legacy hexadecimal token.
table
Definition: admin.php:59