Elgg  Version 1.9
PersistentLoginService.php
Go to the documentation of this file.
1 <?php
26 
37  public function __construct(
41  array $cookie_config,
43  $time = null) {
44  $this->db = $db;
45  $this->session = $session;
46  $this->crypto = $crypto;
47  $this->cookie_config = $cookie_config;
48  $this->cookie_token = $cookie_token;
49 
50  $prefix = $this->db->getTablePrefix();
51  $this->table = "{$prefix}users_remember_me_cookies";
52  $this->time = is_numeric($time) ? (int)$time : time();
53  }
54 
61  public function makeLoginPersistent(ElggUser $user) {
62  $token = $this->generateToken();
63  $hash = $this->hashToken($token);
64 
65  $this->storeHash($user, $hash);
66  $this->setCookie($token);
67  $this->setSession($token);
68  }
69 
74  public function removePersistentLogin() {
75  if ($this->cookie_token) {
76  $client_hash = $this->hashToken($this->cookie_token);
77  $this->removeHash($client_hash);
78  }
79 
80  $this->setCookie("");
81  $this->setSession("");
82  }
83 
91  public function handlePasswordChange(ElggUser $subject, ElggUser $modifier = null) {
92  $this->removeAllHashes($subject);
93  if (!$modifier || ($modifier->guid !== $subject->guid) || !$this->cookie_token) {
94  return;
95  }
96 
97  $this->makeLoginPersistent($modifier);
98  }
99 
106  public function bootSession() {
107  if (!$this->cookie_token) {
108  return null;
109  }
110 
111  // is this token good?
112  $cookie_hash = $this->hashToken($this->cookie_token);
113  $user = $this->getUserFromHash($cookie_hash);
114  if ($user) {
115  $this->setSession($this->cookie_token);
116  // note: if the token is legacy, we don't both replacing it here because
117  // it will be replaced during the next request boot
118  return $user;
119  } else {
120  if ($this->isLegacyToken($this->cookie_token)) {
121  // may be attempt to brute force legacy low-entropy tokens
122  call_user_func($this->_callable_sleep, 1);
123  }
124  $this->setCookie('');
125  }
126  }
127 
134  public function replaceLegacyToken(ElggUser $logged_in_user) {
135  if (!$this->cookie_token || !$this->isLegacyToken($this->cookie_token)) {
136  return;
137  }
138 
139  // replace user's old weaker-entropy code with new one
140  $this->removeHash($this->hashToken($this->cookie_token));
141  $this->makeLoginPersistent($logged_in_user);
142  }
143 
150  public function getUserFromHash($hash) {
151  if (!$hash) {
152  return null;
153  }
154 
155  $hash = $this->db->sanitizeString($hash);
156  $query = "SELECT guid FROM {$this->table} WHERE code = '$hash'";
157  try {
158  $user_row = $this->db->getDataRow($query);
159  } catch (DatabaseException $e) {
160  return $this->handleDbException($e);
161  }
162  if (!$user_row) {
163  return null;
164  }
165 
166  $user = call_user_func($this->_callable_get_user, $user_row->guid);
167  return $user ? $user : null;
168  }
169 
177  protected function storeHash(ElggUser $user, $hash) {
178  $time = time();
179  $hash = $this->db->sanitizeString($hash);
180 
181  $query = "
182  INSERT INTO {$this->table} (code, guid, timestamp)
183  VALUES ('$hash', {$user->guid}, $time)
184  ";
185  try {
186  $this->db->insertData($query);
187  } catch (DatabaseException $e) {
188  $this->handleDbException($e);
189  }
190  }
191 
198  protected function removeHash($hash) {
199  $hash = $this->db->sanitizeString($hash);
200 
201  $query = "DELETE FROM {$this->table} WHERE code = '$hash'";
202  try {
203  $this->db->deleteData($query);
204  } catch (DatabaseException $e) {
205  $this->handleDbException($e);
206  }
207  }
208 
219  if (false !== strpos($exception->getMessage(), "users_remember_me_cookies' doesn't exist")) {
220  // schema has not been updated so we swallow this exception
221  return $default;
222  } else {
223  throw $exception;
224  }
225  }
226 
233  protected function removeAllHashes(ElggUser $user) {
234  $query = "DELETE FROM {$this->table} WHERE guid = '{$user->guid}'";
235  try {
236  $this->db->deleteData($query);
237  } catch (DatabaseException $e) {
238  $this->handleDbException($e);
239  }
240  }
241 
248  protected function hashToken($token) {
249  // note: with user passwords, you'd want legit password hashing, but since these are randomly
250  // generated and long tokens, rainbow tables aren't any help.
251  return md5($token);
252  }
253 
260  protected function setCookie($token) {
261  $cookie = new ElggCookie($this->cookie_config['name']);
262  foreach (array('expire', 'path', 'domain', 'secure', 'httponly') as $key) {
263  $cookie->$key = $this->cookie_config[$key];
264  }
265  $cookie->value = $token;
266  if (!$token) {
267  $cookie->expire = $this->time - (86400 * 30);
268  }
269  call_user_func($this->_callable_elgg_set_cookie, $cookie);
270  }
271 
278  protected function setSession($token) {
279  if ($token) {
280  $this->session->set('code', $token);
281  } else {
282  $this->session->remove('code');
283  }
284  }
285 
294  protected function generateToken() {
295  return 'z' . $this->crypto->getRandomString(31);
296  }
297 
304  protected function isLegacyToken($token) {
305  return (isset($token[0]) && $token[0] !== 'z');
306  }
307 
311  protected $db;
312 
316  protected $table;
317 
321  protected $cookie_config;
322 
326  protected $cookie_token;
327 
331  protected $session;
332 
336  protected $crypto;
337 
341  protected $time;
342 
347  public $_callable_get_user = 'get_user';
348 
353  public $_callable_elgg_set_cookie = 'elgg_set_cookie';
354 
359  public $_callable_sleep = 'sleep';
360 }
setCookie($token)
Store the token in the client cookie (or remove the cookie)
removePersistentLogin()
Remove the persisted login token from client and server.
$subject
Definition: exceptions.php:25
__construct(Elgg_Database $db, ElggSession $session, ElggCrypto $crypto, array $cookie_config, $cookie_token, $time=null)
Constructor.
replaceLegacyToken(ElggUser $logged_in_user)
Replace the user&#39;s token if it&#39;s a legacy hexadecimal token.
makeLoginPersistent(ElggUser $user)
Make the user&#39;s login persistent.
$e
Definition: metadata.php:12
handlePasswordChange(ElggUser $subject, ElggUser $modifier=null)
Handle a password change.
handleDbException(DatabaseException $exception, $default=null)
Swallow a schema not upgraded exception, otherwise rethrow it.
$default
Definition: checkbox.php:36
getUserFromHash($hash)
Find a user with the given hash.
$exception
$key
Definition: summary.php:34
isLegacyToken($token)
Is the given token a legacy MD5 hash?
$user
Definition: ban.php:13
setSession($token)
Store the token in the session (or remove it from the session)
storeHash(ElggUser $user, $hash)
Store a hash in the DB.
bootSession()
Boot the persistent login session, possibly returning the user who should be silently logged in...
$token
hashToken($token)
Create a hash from the token.
generateToken()
Generate a random token (base 64 URL)
removeHash($hash)
Remove a hash from the DB.
table
Definition: admin.php:59
removeAllHashes(ElggUser $user)
Remove all the hashes associated with a user.