User.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. <?php
  2. namespace Widget;
  3. use Typecho\Common;
  4. use Typecho\Cookie;
  5. use Typecho\Db\Exception as DbException;
  6. use Typecho\Widget;
  7. use Utils\PasswordHash;
  8. use Widget\Base\Users;
  9. if (!defined('__TYPECHO_ROOT_DIR__')) {
  10. exit;
  11. }
  12. /**
  13. * 当前登录用户
  14. *
  15. * @category typecho
  16. * @package Widget
  17. * @copyright Copyright (c) 2008 Typecho team (http://www.typecho.org)
  18. * @license GNU General Public License 2.0
  19. */
  20. class User extends Users
  21. {
  22. /**
  23. * 用户组
  24. *
  25. * @var array
  26. */
  27. public $groups = [
  28. 'administrator' => 0,
  29. 'editor' => 1,
  30. 'contributor' => 2,
  31. 'subscriber' => 3,
  32. 'visitor' => 4
  33. ];
  34. /**
  35. * 用户
  36. *
  37. * @var array
  38. */
  39. private $currentUser;
  40. /**
  41. * 是否已经登录
  42. *
  43. * @var boolean|null
  44. */
  45. private $hasLogin = null;
  46. /**
  47. * @param int $components
  48. */
  49. protected function initComponents(int &$components)
  50. {
  51. $components = self::INIT_OPTIONS;
  52. }
  53. /**
  54. * 执行函数
  55. *
  56. * @throws DbException
  57. */
  58. public function execute()
  59. {
  60. if ($this->hasLogin()) {
  61. $this->push($this->currentUser);
  62. // update last activated time
  63. $this->db->query($this->db
  64. ->update('table.users')
  65. ->rows(['activated' => $this->options->time])
  66. ->where('uid = ?', $this->currentUser['uid']));
  67. // merge personal options
  68. $options = $this->personalOptions->toArray();
  69. foreach ($options as $key => $val) {
  70. $this->options->{$key} = $val;
  71. }
  72. }
  73. }
  74. /**
  75. * 判断用户是否已经登录
  76. *
  77. * @return boolean
  78. * @throws DbException
  79. */
  80. public function hasLogin(): ?bool
  81. {
  82. if (null !== $this->hasLogin) {
  83. return $this->hasLogin;
  84. } else {
  85. $cookieUid = Cookie::get('__typecho_uid');
  86. if (null !== $cookieUid) {
  87. /** 验证登陆 */
  88. $user = $this->db->fetchRow($this->db->select()->from('table.users')
  89. ->where('uid = ?', intval($cookieUid))
  90. ->limit(1));
  91. $cookieAuthCode = Cookie::get('__typecho_authCode');
  92. if ($user && Common::hashValidate($user['authCode'], $cookieAuthCode)) {
  93. $this->currentUser = $user;
  94. return ($this->hasLogin = true);
  95. }
  96. $this->logout();
  97. }
  98. return ($this->hasLogin = false);
  99. }
  100. }
  101. /**
  102. * 用户登出函数
  103. *
  104. * @access public
  105. * @return void
  106. */
  107. public function logout()
  108. {
  109. self::pluginHandle()->trigger($logoutPluggable)->logout();
  110. if ($logoutPluggable) {
  111. return;
  112. }
  113. Cookie::delete('__typecho_uid');
  114. Cookie::delete('__typecho_authCode');
  115. }
  116. /**
  117. * 以用户名和密码登录
  118. *
  119. * @access public
  120. * @param string $name 用户名
  121. * @param string $password 密码
  122. * @param boolean $temporarily 是否为临时登录
  123. * @param integer $expire 过期时间
  124. * @return boolean
  125. * @throws DbException
  126. */
  127. public function login(string $name, string $password, bool $temporarily = false, int $expire = 0): bool
  128. {
  129. //插件接口
  130. $result = self::pluginHandle()->trigger($loginPluggable)->login($name, $password, $temporarily, $expire);
  131. if ($loginPluggable) {
  132. return $result;
  133. }
  134. /** 开始验证用户 **/
  135. $user = $this->db->fetchRow($this->db->select()
  136. ->from('table.users')
  137. ->where((strpos($name, '@') ? 'mail' : 'name') . ' = ?', $name)
  138. ->limit(1));
  139. if (empty($user)) {
  140. return false;
  141. }
  142. $hashValidate = self::pluginHandle()->trigger($hashPluggable)->hashValidate($password, $user['password']);
  143. if (!$hashPluggable) {
  144. if ('$P$' == substr($user['password'], 0, 3)) {
  145. $hasher = new PasswordHash(8, true);
  146. $hashValidate = $hasher->checkPassword($password, $user['password']);
  147. } else {
  148. $hashValidate = Common::hashValidate($password, $user['password']);
  149. }
  150. }
  151. if ($user && $hashValidate) {
  152. if (!$temporarily) {
  153. $this->commitLogin($user, $expire);
  154. }
  155. /** 压入数据 */
  156. $this->push($user);
  157. $this->currentUser = $user;
  158. $this->hasLogin = true;
  159. self::pluginHandle()->loginSucceed($this, $name, $password, $temporarily, $expire);
  160. return true;
  161. }
  162. self::pluginHandle()->loginFail($this, $name, $password, $temporarily, $expire);
  163. return false;
  164. }
  165. /**
  166. * @param $user
  167. * @param int $expire
  168. * @throws DbException
  169. */
  170. public function commitLogin(&$user, int $expire = 0)
  171. {
  172. $authCode = function_exists('openssl_random_pseudo_bytes') ?
  173. bin2hex(openssl_random_pseudo_bytes(16)) : sha1(Common::randString(20));
  174. $user['authCode'] = $authCode;
  175. Cookie::set('__typecho_uid', $user['uid'], $expire);
  176. Cookie::set('__typecho_authCode', Common::hash($authCode), $expire);
  177. //更新最后登录时间以及验证码
  178. $this->db->query($this->db
  179. ->update('table.users')
  180. ->expression('logged', 'activated')
  181. ->rows(['authCode' => $authCode])
  182. ->where('uid = ?', $user['uid']));
  183. }
  184. /**
  185. * 只需要提供uid或者完整user数组即可登录的方法, 多用于插件等特殊场合
  186. *
  187. * @param int | array $uid 用户id或者用户数据数组
  188. * @param boolean $temporarily 是否为临时登录,默认为临时登录以兼容以前的方法
  189. * @param integer $expire 过期时间
  190. * @return boolean
  191. * @throws DbException
  192. */
  193. public function simpleLogin($uid, bool $temporarily = true, int $expire = 0): bool
  194. {
  195. if (is_array($uid)) {
  196. $user = $uid;
  197. } else {
  198. $user = $this->db->fetchRow($this->db->select()
  199. ->from('table.users')
  200. ->where('uid = ?', $uid)
  201. ->limit(1));
  202. }
  203. if (empty($user)) {
  204. self::pluginHandle()->simpleLoginFail($this);
  205. return false;
  206. }
  207. if (!$temporarily) {
  208. $this->commitLogin($user, $expire);
  209. }
  210. $this->push($user);
  211. $this->currentUser = $user;
  212. $this->hasLogin = true;
  213. self::pluginHandle()->simpleLoginSucceed($this, $user);
  214. return true;
  215. }
  216. /**
  217. * 判断用户权限
  218. *
  219. * @access public
  220. * @param string $group 用户组
  221. * @param boolean $return 是否为返回模式
  222. * @return boolean
  223. * @throws DbException|Widget\Exception
  224. */
  225. public function pass(string $group, bool $return = false): bool
  226. {
  227. if ($this->hasLogin()) {
  228. if (array_key_exists($group, $this->groups) && $this->groups[$this->group] <= $this->groups[$group]) {
  229. return true;
  230. }
  231. } else {
  232. if ($return) {
  233. return false;
  234. } else {
  235. //防止循环重定向
  236. $this->response->redirect(defined('__TYPECHO_ADMIN__') ? $this->options->loginUrl .
  237. (0 === strpos($this->request->getReferer() ?? '', $this->options->loginUrl) ? '' :
  238. '?referer=' . urlencode($this->request->makeUriByRequest())) : $this->options->siteUrl, false);
  239. }
  240. }
  241. if ($return) {
  242. return false;
  243. } else {
  244. throw new Widget\Exception(_t('禁止访问'), 403);
  245. }
  246. }
  247. }