User.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\web;
  8. use Yii;
  9. use yii\base\Component;
  10. use yii\base\InvalidConfigException;
  11. /**
  12. * User is the class for the "user" application component that manages the user authentication status.
  13. *
  14. * In particular, [[User::isGuest]] returns a value indicating whether the current user is a guest or not.
  15. * Through methods [[login()]] and [[logout()]], you can change the user authentication status.
  16. *
  17. * User works with a class implementing the [[IdentityInterface]]. This class implements
  18. * the actual user authentication logic and is often backed by a user database table.
  19. *
  20. * User is configured as an application component in [[yii\web\Application]] by default.
  21. * You can access that instance via `Yii::$app->user`.
  22. *
  23. * You can modify its configuration by adding an array to your application config under `components`
  24. * as it is shown in the following example:
  25. *
  26. * ~~~
  27. * 'user' => [
  28. * 'identityClass' => 'app\models\User', // User must implement the IdentityInterface
  29. * 'enableAutoLogin' => true,
  30. * // 'loginUrl' => ['user/login'],
  31. * // ...
  32. * ]
  33. * ~~~
  34. *
  35. * @property string|integer $id The unique identifier for the user. If null, it means the user is a guest.
  36. * This property is read-only.
  37. * @property IdentityInterface $identity The identity object associated with the currently logged user. Null
  38. * is returned if the user is not logged in (not authenticated).
  39. * @property boolean $isGuest Whether the current user is a guest. This property is read-only.
  40. * @property string $returnUrl The URL that the user should be redirected to after login. Note that the type
  41. * of this property differs in getter and setter. See [[getReturnUrl()]] and [[setReturnUrl()]] for details.
  42. *
  43. * @author Qiang Xue <[email protected]>
  44. * @since 2.0
  45. */
  46. class User extends Component
  47. {
  48. const EVENT_BEFORE_LOGIN = 'beforeLogin';
  49. const EVENT_AFTER_LOGIN = 'afterLogin';
  50. const EVENT_BEFORE_LOGOUT = 'beforeLogout';
  51. const EVENT_AFTER_LOGOUT = 'afterLogout';
  52. /**
  53. * @var string the class name of the [[identity]] object.
  54. */
  55. public $identityClass;
  56. /**
  57. * @var boolean whether to enable cookie-based login. Defaults to false.
  58. */
  59. public $enableAutoLogin = false;
  60. /**
  61. * @var string|array the URL for login when [[loginRequired()]] is called.
  62. * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
  63. * The first element of the array should be the route to the login action, and the rest of
  64. * the name-value pairs are GET parameters used to construct the login URL. For example,
  65. *
  66. * ~~~
  67. * ['site/login', 'ref' => 1]
  68. * ~~~
  69. *
  70. * If this property is null, a 403 HTTP exception will be raised when [[loginRequired()]] is called.
  71. */
  72. public $loginUrl = ['site/login'];
  73. /**
  74. * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true.
  75. * @see Cookie
  76. */
  77. public $identityCookie = ['name' => '_identity', 'httpOnly' => true];
  78. /**
  79. * @var integer the number of seconds in which the user will be logged out automatically if he
  80. * remains inactive. If this property is not set, the user will be logged out after
  81. * the current session expires (c.f. [[Session::timeout]]).
  82. */
  83. public $authTimeout;
  84. /**
  85. * @var boolean whether to automatically renew the identity cookie each time a page is requested.
  86. * This property is effective only when [[enableAutoLogin]] is true.
  87. * When this is false, the identity cookie will expire after the specified duration since the user
  88. * is initially logged in. When this is true, the identity cookie will expire after the specified duration
  89. * since the user visits the site the last time.
  90. * @see enableAutoLogin
  91. */
  92. public $autoRenewCookie = true;
  93. /**
  94. * @var string the session variable name used to store the value of [[id]].
  95. */
  96. public $idVar = '__id';
  97. /**
  98. * @var string the session variable name used to store the value of expiration timestamp of the authenticated state.
  99. * This is used when [[authTimeout]] is set.
  100. */
  101. public $authTimeoutVar = '__expire';
  102. /**
  103. * @var string the session variable name used to store the value of [[returnUrl]].
  104. */
  105. public $returnUrlVar = '__returnUrl';
  106. private $_access = [];
  107. /**
  108. * Initializes the application component.
  109. */
  110. public function init()
  111. {
  112. parent::init();
  113. if ($this->identityClass === null) {
  114. throw new InvalidConfigException('User::identityClass must be set.');
  115. }
  116. if ($this->enableAutoLogin && !isset($this->identityCookie['name'])) {
  117. throw new InvalidConfigException('User::identityCookie must contain the "name" element.');
  118. }
  119. Yii::$app->getSession()->open();
  120. $this->renewAuthStatus();
  121. if ($this->enableAutoLogin) {
  122. if ($this->getIsGuest()) {
  123. $this->loginByCookie();
  124. } elseif ($this->autoRenewCookie) {
  125. $this->renewIdentityCookie();
  126. }
  127. }
  128. }
  129. private $_identity = false;
  130. /**
  131. * Returns the identity object associated with the currently logged user.
  132. * @return IdentityInterface the identity object associated with the currently logged user.
  133. * Null is returned if the user is not logged in (not authenticated).
  134. * @see login()
  135. * @see logout()
  136. */
  137. public function getIdentity()
  138. {
  139. if ($this->_identity === false) {
  140. $id = $this->getId();
  141. if ($id === null) {
  142. $this->_identity = null;
  143. } else {
  144. /** @var IdentityInterface $class */
  145. $class = $this->identityClass;
  146. $this->_identity = $class::findIdentity($id);
  147. }
  148. }
  149. return $this->_identity;
  150. }
  151. /**
  152. * Sets the identity object.
  153. * This method should be mainly be used by the User component or its child class
  154. * to maintain the identity object.
  155. *
  156. * You should normally update the user identity via methods [[login()]], [[logout()]]
  157. * or [[switchIdentity()]].
  158. *
  159. * @param IdentityInterface $identity the identity object associated with the currently logged user.
  160. */
  161. public function setIdentity($identity)
  162. {
  163. $this->_identity = $identity;
  164. }
  165. /**
  166. * Logs in a user.
  167. *
  168. * This method stores the necessary session information to keep track
  169. * of the user identity information. If `$duration` is greater than 0
  170. * and [[enableAutoLogin]] is true, it will also send out an identity
  171. * cookie to support cookie-based login.
  172. *
  173. * @param IdentityInterface $identity the user identity (which should already be authenticated)
  174. * @param integer $duration number of seconds that the user can remain in logged-in status.
  175. * Defaults to 0, meaning login till the user closes the browser or the session is manually destroyed.
  176. * If greater than 0 and [[enableAutoLogin]] is true, cookie-based login will be supported.
  177. * @return boolean whether the user is logged in
  178. */
  179. public function login($identity, $duration = 0)
  180. {
  181. if ($this->beforeLogin($identity, false)) {
  182. $this->switchIdentity($identity, $duration);
  183. $id = $identity->getId();
  184. $ip = Yii::$app->getRequest()->getUserIP();
  185. Yii::info("User '$id' logged in from $ip.", __METHOD__);
  186. $this->afterLogin($identity, false);
  187. }
  188. return !$this->getIsGuest();
  189. }
  190. /**
  191. * Logs in a user by cookie.
  192. *
  193. * This method attempts to log in a user using the ID and authKey information
  194. * provided by the given cookie.
  195. */
  196. protected function loginByCookie()
  197. {
  198. $name = $this->identityCookie['name'];
  199. $value = Yii::$app->getRequest()->getCookies()->getValue($name);
  200. if ($value !== null) {
  201. $data = json_decode($value, true);
  202. if (count($data) === 3 && isset($data[0], $data[1], $data[2])) {
  203. list ($id, $authKey, $duration) = $data;
  204. /** @var IdentityInterface $class */
  205. $class = $this->identityClass;
  206. $identity = $class::findIdentity($id);
  207. if ($identity !== null && $identity->validateAuthKey($authKey)) {
  208. if ($this->beforeLogin($identity, true)) {
  209. $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0);
  210. $ip = Yii::$app->getRequest()->getUserIP();
  211. Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__);
  212. $this->afterLogin($identity, true);
  213. }
  214. } elseif ($identity !== null) {
  215. Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__);
  216. }
  217. }
  218. }
  219. }
  220. /**
  221. * Logs out the current user.
  222. * This will remove authentication-related session data.
  223. * If `$destroySession` is true, all session data will be removed.
  224. * @param boolean $destroySession whether to destroy the whole session. Defaults to true.
  225. */
  226. public function logout($destroySession = true)
  227. {
  228. $identity = $this->getIdentity();
  229. if ($identity !== null && $this->beforeLogout($identity)) {
  230. $this->switchIdentity(null);
  231. $id = $identity->getId();
  232. $ip = Yii::$app->getRequest()->getUserIP();
  233. Yii::info("User '$id' logged out from $ip.", __METHOD__);
  234. if ($destroySession) {
  235. Yii::$app->getSession()->destroy();
  236. }
  237. $this->afterLogout($identity);
  238. }
  239. }
  240. /**
  241. * Returns a value indicating whether the user is a guest (not authenticated).
  242. * @return boolean whether the current user is a guest.
  243. */
  244. public function getIsGuest()
  245. {
  246. return $this->getIdentity() === null;
  247. }
  248. /**
  249. * Returns a value that uniquely represents the user.
  250. * @return string|integer the unique identifier for the user. If null, it means the user is a guest.
  251. */
  252. public function getId()
  253. {
  254. return Yii::$app->getSession()->get($this->idVar);
  255. }
  256. /**
  257. * Returns the URL that the user should be redirected to after successful login.
  258. * This property is usually used by the login action. If the login is successful,
  259. * the action should read this property and use it to redirect the user browser.
  260. * @param string|array $defaultUrl the default return URL in case it was not set previously.
  261. * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to.
  262. * Please refer to [[setReturnUrl()]] on accepted format of the URL.
  263. * @return string the URL that the user should be redirected to after login.
  264. * @see loginRequired()
  265. */
  266. public function getReturnUrl($defaultUrl = null)
  267. {
  268. $url = Yii::$app->getSession()->get($this->returnUrlVar, $defaultUrl);
  269. if (is_array($url)) {
  270. if (isset($url[0])) {
  271. $route = array_shift($url);
  272. return Yii::$app->getUrlManager()->createUrl($route, $url);
  273. } else {
  274. $url = null;
  275. }
  276. }
  277. return $url === null ? Yii::$app->getHomeUrl() : $url;
  278. }
  279. /**
  280. * @param string|array $url the URL that the user should be redirected to after login.
  281. * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
  282. * The first element of the array should be the route, and the rest of
  283. * the name-value pairs are GET parameters used to construct the URL. For example,
  284. *
  285. * ~~~
  286. * ['admin/index', 'ref' => 1]
  287. * ~~~
  288. */
  289. public function setReturnUrl($url)
  290. {
  291. Yii::$app->getSession()->set($this->returnUrlVar, $url);
  292. }
  293. /**
  294. * Redirects the user browser to the login page.
  295. * Before the redirection, the current URL (if it's not an AJAX url) will be
  296. * kept as [[returnUrl]] so that the user browser may be redirected back
  297. * to the current page after successful login. Make sure you set [[loginUrl]]
  298. * so that the user browser can be redirected to the specified login URL after
  299. * calling this method.
  300. *
  301. * Note that when [[loginUrl]] is set, calling this method will NOT terminate the application execution.
  302. *
  303. * @return Response the redirection response if [[loginUrl]] is set
  304. * @throws AccessDeniedHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set
  305. */
  306. public function loginRequired()
  307. {
  308. $request = Yii::$app->getRequest();
  309. if (!$request->getIsAjax()) {
  310. $this->setReturnUrl($request->getUrl());
  311. }
  312. if ($this->loginUrl !== null) {
  313. return Yii::$app->getResponse()->redirect($this->loginUrl);
  314. } else {
  315. throw new AccessDeniedHttpException(Yii::t('yii', 'Login Required'));
  316. }
  317. }
  318. /**
  319. * This method is called before logging in a user.
  320. * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event.
  321. * If you override this method, make sure you call the parent implementation
  322. * so that the event is triggered.
  323. * @param IdentityInterface $identity the user identity information
  324. * @param boolean $cookieBased whether the login is cookie-based
  325. * @return boolean whether the user should continue to be logged in
  326. */
  327. protected function beforeLogin($identity, $cookieBased)
  328. {
  329. $event = new UserEvent([
  330. 'identity' => $identity,
  331. 'cookieBased' => $cookieBased,
  332. ]);
  333. $this->trigger(self::EVENT_BEFORE_LOGIN, $event);
  334. return $event->isValid;
  335. }
  336. /**
  337. * This method is called after the user is successfully logged in.
  338. * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event.
  339. * If you override this method, make sure you call the parent implementation
  340. * so that the event is triggered.
  341. * @param IdentityInterface $identity the user identity information
  342. * @param boolean $cookieBased whether the login is cookie-based
  343. */
  344. protected function afterLogin($identity, $cookieBased)
  345. {
  346. $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent([
  347. 'identity' => $identity,
  348. 'cookieBased' => $cookieBased,
  349. ]));
  350. }
  351. /**
  352. * This method is invoked when calling [[logout()]] to log out a user.
  353. * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event.
  354. * If you override this method, make sure you call the parent implementation
  355. * so that the event is triggered.
  356. * @param IdentityInterface $identity the user identity information
  357. * @return boolean whether the user should continue to be logged out
  358. */
  359. protected function beforeLogout($identity)
  360. {
  361. $event = new UserEvent([
  362. 'identity' => $identity,
  363. ]);
  364. $this->trigger(self::EVENT_BEFORE_LOGOUT, $event);
  365. return $event->isValid;
  366. }
  367. /**
  368. * This method is invoked right after a user is logged out via [[logout()]].
  369. * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event.
  370. * If you override this method, make sure you call the parent implementation
  371. * so that the event is triggered.
  372. * @param IdentityInterface $identity the user identity information
  373. */
  374. protected function afterLogout($identity)
  375. {
  376. $this->trigger(self::EVENT_AFTER_LOGOUT, new UserEvent([
  377. 'identity' => $identity,
  378. ]));
  379. }
  380. /**
  381. * Renews the identity cookie.
  382. * This method will set the expiration time of the identity cookie to be the current time
  383. * plus the originally specified cookie duration.
  384. */
  385. protected function renewIdentityCookie()
  386. {
  387. $name = $this->identityCookie['name'];
  388. $value = Yii::$app->getRequest()->getCookies()->getValue($name);
  389. if ($value !== null) {
  390. $data = json_decode($value, true);
  391. if (is_array($data) && isset($data[2])) {
  392. $cookie = new Cookie($this->identityCookie);
  393. $cookie->value = $value;
  394. $cookie->expire = time() + (int)$data[2];
  395. Yii::$app->getResponse()->getCookies()->add($cookie);
  396. }
  397. }
  398. }
  399. /**
  400. * Sends an identity cookie.
  401. * This method is used when [[enableAutoLogin]] is true.
  402. * It saves [[id]], [[IdentityInterface::getAuthKey()|auth key]], and the duration of cookie-based login
  403. * information in the cookie.
  404. * @param IdentityInterface $identity
  405. * @param integer $duration number of seconds that the user can remain in logged-in status.
  406. * @see loginByCookie()
  407. */
  408. protected function sendIdentityCookie($identity, $duration)
  409. {
  410. $cookie = new Cookie($this->identityCookie);
  411. $cookie->value = json_encode([
  412. $identity->getId(),
  413. $identity->getAuthKey(),
  414. $duration,
  415. ]);
  416. $cookie->expire = time() + $duration;
  417. Yii::$app->getResponse()->getCookies()->add($cookie);
  418. }
  419. /**
  420. * Switches to a new identity for the current user.
  421. *
  422. * This method will save necessary session information to keep track of the user authentication status.
  423. * If `$duration` is provided, it will also send out appropriate identity cookie
  424. * to support cookie-based login.
  425. *
  426. * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]]
  427. * when the current user needs to be associated with the corresponding identity information.
  428. *
  429. * @param IdentityInterface $identity the identity information to be associated with the current user.
  430. * If null, it means switching to be a guest.
  431. * @param integer $duration number of seconds that the user can remain in logged-in status.
  432. * This parameter is used only when `$identity` is not null.
  433. */
  434. public function switchIdentity($identity, $duration = 0)
  435. {
  436. $session = Yii::$app->getSession();
  437. if (!YII_ENV_TEST) {
  438. $session->regenerateID(true);
  439. }
  440. $this->setIdentity($identity);
  441. $session->remove($this->idVar);
  442. $session->remove($this->authTimeoutVar);
  443. if ($identity instanceof IdentityInterface) {
  444. $session->set($this->idVar, $identity->getId());
  445. if ($this->authTimeout !== null) {
  446. $session->set($this->authTimeoutVar, time() + $this->authTimeout);
  447. }
  448. if ($duration > 0 && $this->enableAutoLogin) {
  449. $this->sendIdentityCookie($identity, $duration);
  450. }
  451. } elseif ($this->enableAutoLogin) {
  452. Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie));
  453. }
  454. }
  455. /**
  456. * Updates the authentication status according to [[authTimeout]].
  457. * This method is called during [[init()]].
  458. * It will update the user's authentication status if it has not outdated yet.
  459. * Otherwise, it will logout the user.
  460. */
  461. protected function renewAuthStatus()
  462. {
  463. if ($this->authTimeout !== null && !$this->getIsGuest()) {
  464. $expire = Yii::$app->getSession()->get($this->authTimeoutVar);
  465. if ($expire !== null && $expire < time()) {
  466. $this->logout(false);
  467. } else {
  468. Yii::$app->getSession()->set($this->authTimeoutVar, time() + $this->authTimeout);
  469. }
  470. }
  471. }
  472. /**
  473. * Performs access check for this user.
  474. * @param string $operation the name of the operation that need access check.
  475. * @param array $params name-value pairs that would be passed to business rules associated
  476. * with the tasks and roles assigned to the user. A param with name 'userId' is added to
  477. * this array, which holds the value of [[id]] when [[DbAuthManager]] or
  478. * [[PhpAuthManager]] is used.
  479. * @param boolean $allowCaching whether to allow caching the result of access check.
  480. * When this parameter is true (default), if the access check of an operation was performed
  481. * before, its result will be directly returned when calling this method to check the same
  482. * operation. If this parameter is false, this method will always call
  483. * [[AuthManager::checkAccess()]] to obtain the up-to-date access result. Note that this
  484. * caching is effective only within the same request and only works when `$params = []`.
  485. * @return boolean whether the operations can be performed by this user.
  486. */
  487. public function checkAccess($operation, $params = [], $allowCaching = true)
  488. {
  489. $auth = Yii::$app->getAuthManager();
  490. if ($auth === null) {
  491. return false;
  492. }
  493. if ($allowCaching && empty($params) && isset($this->_access[$operation])) {
  494. return $this->_access[$operation];
  495. }
  496. $access = $auth->checkAccess($this->getId(), $operation, $params);
  497. if ($allowCaching && empty($params)) {
  498. $this->_access[$operation] = $access;
  499. }
  500. return $access;
  501. }
  502. }