/Modules/user/rememberme_model.php

https://github.com/eppak/emoncms · PHP · 211 lines · 125 code · 39 blank · 47 comment · 12 complexity · 60e06a925c41623feb22e7e31175fb6e MD5 · raw file

  1. <?php
  2. // Remember me implementation, thanks to Gabriel Birke's example here:
  3. // https://github.com/gbirke/rememberme
  4. class Rememberme {
  5. private $mysqli;
  6. /**
  7. * Cookie settings
  8. * @var string
  9. */
  10. private $cookieName = "PHP_EMONCMS";
  11. private $path = '/';
  12. private $domain = "";
  13. private $secure = false;
  14. private $httpOnly = true;
  15. /**
  16. * @var int Number of seconds in the future the cookie and storage will expire
  17. */
  18. private $expireTime = 604800; // 1 week
  19. /**
  20. * If the return from the storage was Rememberme_Storage_StorageInterface::TRIPLET_INVALID,
  21. * this is set to true
  22. *
  23. * @var bool
  24. */
  25. protected $lastLoginTokenWasInvalid = false;
  26. /**
  27. * If the login token was invalid, delete all login tokens of this user
  28. *
  29. * @var type
  30. */
  31. protected $cleanStoredTokensOnInvalidResult = true;
  32. /**
  33. * Additional salt to add more entropy when the tokens are stored as hashes.
  34. * @var type
  35. */
  36. private $salt = "";
  37. const TRIPLET_FOUND = 1,
  38. TRIPLET_NOT_FOUND = 0,
  39. TRIPLET_INVALID = -1;
  40. public function __construct($mysqli)
  41. {
  42. $this->mysqli = $mysqli;
  43. }
  44. /**
  45. * Check Credentials from cookie
  46. * @return bool|string False if login was not successful, credential string if it was successful
  47. */
  48. public function login() {
  49. $cookieValues = $this->getCookieValues();
  50. if(!$cookieValues) {
  51. return false;
  52. }
  53. $loginResult = false;
  54. switch($this->findTriplet($cookieValues[0], $cookieValues[1].$this->salt, $cookieValues[2].$this->salt)) {
  55. case self::TRIPLET_FOUND:
  56. $expire = time() + $this->expireTime;
  57. $newToken = $this->createToken();
  58. // remove old triplet before creating new one, otherwise since the salt is defaulted to "" it would create
  59. // a new triplet with the same persistentToken in DB which will cause the next findTriplet to fail (finding the incorrect one) and remove the cookie again.
  60. $this->cleanTriplet($cookieValues[0], $cookieValues[2]);
  61. // create new cookie and register values in db - refresh token
  62. $this->storeTriplet($cookieValues[0], $newToken.$this->salt, $cookieValues[2].$this->salt, $expire);
  63. setcookie($this->cookieName,implode("|",array($cookieValues[0],$newToken, $cookieValues[2])),$expire,$this->path,"",false,true);
  64. $loginResult = $cookieValues[0];
  65. break;
  66. case self::TRIPLET_INVALID:
  67. setcookie($this->cookieName,"",time() - $this->expireTime,$this->path,"",false,true);
  68. $this->lastLoginTokenWasInvalid = true;
  69. if($this->cleanStoredTokensOnInvalidResult) {
  70. $this->cleanAllTriplets($cookieValues[0]);
  71. }
  72. break;
  73. }
  74. return $loginResult;
  75. }
  76. public function cookieIsValid($userid) {
  77. $cookieValues = $this->getCookieValues();
  78. if(!$cookieValues) {
  79. return false;
  80. }
  81. $state = $this->findTriplet($cookieValues[0], $cookieValues[1].$this->salt, $cookieValues[2].$this->salt);
  82. return $state == self::TRIPLET_FOUND;
  83. }
  84. public function createCookie($userid)
  85. {
  86. $newToken = $this->createToken();
  87. $newPersistentToken = $this->createToken();
  88. $expire = time() + $this->expireTime;
  89. $this->storeTriplet($userid, $newToken, $newPersistentToken, $expire);
  90. setcookie($this->cookieName,implode("|",array($userid,$newToken.$this->salt,$newPersistentToken.$this->salt)),$expire,$this->path,"",false,true);
  91. }
  92. /**
  93. * Expire the rememberme cookie, unset $_COOKIE[$this->cookieName] value and
  94. * remove current login triplet from storage.
  95. *
  96. * @param boolean $clearFromStorage
  97. * @return boolean
  98. */
  99. public function clearCookie($clearFromStorage=true) {
  100. if(empty($_COOKIE[$this->cookieName]))
  101. return false;
  102. $cookieValues = explode("|", $_COOKIE[$this->cookieName], 3);
  103. setcookie($this->cookieName,"",time() - $this->expireTime,$this->path,"",false,true);
  104. unset($_COOKIE[$this->cookieName]);
  105. if(!$clearFromStorage) {
  106. return true;
  107. }
  108. if(count($cookieValues) < 3) {
  109. return false;
  110. }
  111. $this->cleanTriplet($cookieValues[0], $cookieValues[2].$this->salt);
  112. return true;
  113. }
  114. public function getCookieName() {
  115. return $this->cookieName;
  116. }
  117. public function loginTokenWasInvalid() {
  118. return $this->lastLoginTokenWasInvalid;
  119. }
  120. /**
  121. * Create a pseudo-random token.
  122. *
  123. * The token is pseudo-random. If you need better security, read from /dev/urandom
  124. */
  125. private function createToken() {
  126. return md5(uniqid(mt_rand(), true));
  127. }
  128. private function getCookieValues()
  129. {
  130. // Cookie was not sent with incoming request
  131. if(empty($_COOKIE[$this->cookieName])) {
  132. return array();
  133. }
  134. $cookieValues = explode("|", $_COOKIE[$this->cookieName], 3);
  135. if(count($cookieValues) < 3) {
  136. return array();
  137. }
  138. return $cookieValues;
  139. }
  140. // Storage
  141. private function findTriplet($userid, $token, $persistentToken) {
  142. // We don't store the sha1 as binary values because otherwise we could not use
  143. // proper XML test data
  144. $sql = "SELECT IF(SHA1('$token') = token, 1, -1) AS token_match " .
  145. "FROM rememberme WHERE userid = '$userid' " .
  146. "AND persistentToken = SHA1('$persistentToken') LIMIT 1 ";
  147. $result = $this->mysqli->query($sql);
  148. $row = $result->fetch_array();
  149. if(!$row['token_match']) {
  150. return self::TRIPLET_NOT_FOUND;
  151. }
  152. elseif ($row['token_match'] == 1) {
  153. return self::TRIPLET_FOUND;
  154. }
  155. else {
  156. return self::TRIPLET_INVALID;
  157. }
  158. }
  159. private function storeTriplet($userid, $token, $persistentToken, $expire=0)
  160. {
  161. $date = date("Y-m-d H:i:s", $expire);
  162. $this->mysqli->query("INSERT INTO rememberme (userid, token, persistentToken, expire) VALUES ('$userid', SHA1('$token'), SHA1('$persistentToken'), '$date')");
  163. }
  164. private function cleanTriplet($userid, $persistentToken)
  165. {
  166. $this->mysqli->query("DELETE FROM rememberme WHERE userid = '$userid' AND persistentToken = SHA1('$persistentToken')");
  167. }
  168. private function cleanAllTriplets($userid)
  169. {
  170. $this->mysqli->query("DELETE FROM rememberme WHERE userid = '$userid'");
  171. }
  172. }