diff --git a/framework/web/Controller.php b/framework/web/Controller.php index 126fbbc..8049299 100644 --- a/framework/web/Controller.php +++ b/framework/web/Controller.php @@ -41,19 +41,4 @@ class Controller extends \yii\base\Controller } return Yii::$app->getUrlManager()->createUrl($route, $params); } - - /** - * Redirects the browser to the specified URL or route (controller/action). - * @param mixed $url the URL to be redirected to. If the parameter is an array, - * the first element must be a route to a controller action and the rest - * are GET parameters in name-value pairs. - * @param boolean $terminate whether to terminate the current application after calling this method. Defaults to true. - * @param integer $statusCode the HTTP status code. Defaults to 302. See {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html} - * for details about HTTP status code. - */ - public function redirect($url, $terminate = true, $statusCode = 302) - { - $url = Html::url($url); - Yii::$app->getResponse()->redirect($url, $terminate, $statusCode); - } } \ No newline at end of file diff --git a/framework/web/Identity.php b/framework/web/Identity.php index 5f07d58..89d4282 100644 --- a/framework/web/Identity.php +++ b/framework/web/Identity.php @@ -15,6 +15,14 @@ namespace yii\web; interface Identity { /** + * Finds an identity by the given ID. + * @param string|integer $id the ID to be looked for + * @return Identity the identity object that matches the given ID. + * Null should be returned if such an identity cannot be found + * or the identity is not in an active state (disabled, deleted, etc.) + */ + public static function findIdentity($id); + /** * Returns an ID that can uniquely identify a user identity. * @return string|integer an ID that uniquely identifies a user identity. */ @@ -23,23 +31,19 @@ interface Identity * Returns a key that can be used to check the validity of a given identity ID. * The space of such keys should be big and random enough to defeat potential identity attacks. * The returned key can be a string, an integer, or any serializable data. + * + * This is required if [[User::enableAutoLogin]] is enabled. * @return string a key that is used to check the validity of a given identity ID. * @see validateAuthKey() */ public function getAuthKey(); /** * Validates the given auth key. + * + * This is required if [[User::enableAutoLogin]] is enabled. * @param string $authKey the given auth key * @return boolean whether the given auth key is valid. * @see getAuthKey() */ public function validateAuthKey($authKey); - /** - * Finds an identity by the given ID. - * @param string|integer $id the ID to be looked for - * @return Identity the identity object that matches the given ID. - * Null should be returned if such an identity cannot be found - * or the identity is not in an active state (disabled, deleted, etc.) - */ - public static function findIdentity($id); } \ No newline at end of file diff --git a/framework/web/Response.php b/framework/web/Response.php index 8133132..da2482f 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -9,6 +9,7 @@ namespace yii\web; use Yii; use yii\helpers\FileHelper; +use yii\helpers\Html; /** * @author Qiang Xue @@ -17,6 +18,14 @@ use yii\helpers\FileHelper; class Response extends \yii\base\Response { /** + * @var integer the HTTP status code that should be used when redirecting in AJAX mode. + * This is used by [[redirect()]]. A 2xx code should normally be used for this purpose + * so that the AJAX handler will treat the response as a success. + * @see redirect + */ + public $ajaxRedirectCode = 278; + + /** * Sends a file to user. * @param string $fileName file name * @param string $content content to be set. @@ -147,24 +156,45 @@ class Response extends \yii\base\Response /** * Redirects the browser to the specified URL. - * @param string $url URL to be redirected to. Note that when URL is not - * absolute (not starting with "/") it will be relative to current request URL. + * This method will send out a "Location" header to achieve the redirection. + * In AJAX mode, this normally will not work as expected unless there are some + * client-side JavaScript code handling the redirection. To help achieve this goal, + * this method will use [[ajaxRedirectCode]] as the HTTP status code when performing + * redirection in AJAX mode. The following JavaScript code may be used on the client + * side to handle the redirection response: + * + * ~~~ + * $(document).ajaxSuccess(function(event, xhr, settings) { + * if (xhr.status == 278) { + * window.location = xhr.getResponseHeader('Location'); + * } + * }); + * ~~~ + * + * @param array|string $url the URL to be redirected to. [[\yii\helpers\Html::url()]] + * will be used to normalize the URL. If the resulting URL is still a relative URL + * (one without host info), the current request host info will be used. * @param boolean $terminate whether to terminate the current application - * @param integer $statusCode the HTTP status code. Defaults to 302. See {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html} + * @param integer $statusCode the HTTP status code. Defaults to 302. + * See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]] * for details about HTTP status code. + * Note that if the request is an AJAX request, [[ajaxRedirectCode]] will be used instead. */ public function redirect($url, $terminate = true, $statusCode = 302) { + $url = Html::url($url); if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) { $url = Yii::$app->getRequest()->getHostInfo() . $url; } + if (Yii::$app->getRequest()->getIsAjaxRequest()) { + $statusCode = $this->ajaxRedirectCode; + } header('Location: ' . $url, true, $statusCode); if ($terminate) { Yii::$app->end(); } } - /** * Returns the cookie collection. * Through the returned cookie collection, you add or remove cookies as follows, diff --git a/framework/web/User.php b/framework/web/User.php index aa9e421..ebfcd03 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -11,7 +11,6 @@ use Yii; use yii\base\Component; use yii\base\HttpException; use yii\base\InvalidConfigException; -use yii\helpers\Html; /** * @author Qiang Xue @@ -67,17 +66,27 @@ class User extends Component public $authTimeout; /** * @var boolean whether to automatically renew the identity cookie each time a page is requested. - * Defaults to false. This property is effective only when {@link enableAutoLogin} is true. + * This property is effective only when [[enableAutoLogin]] is true. * When this is false, the identity cookie will expire after the specified duration since the user * is initially logged in. When this is true, the identity cookie will expire after the specified duration * since the user visits the site the last time. * @see enableAutoLogin */ public $autoRenewCookie = true; + /** + * @var string the session variable name used to store the value of [[id]]. + */ + public $idVar = '__id'; + /** + * @var string the session variable name used to store the value of expiration timestamp of the authenticated state. + * This is used when [[authTimeout]] is set. + */ + public $authTimeoutVar = '__expire'; + /** + * @var string the session variable name used to store the value of [[returnUrl]]. + */ + public $returnUrlVar = '__returnUrl'; - public $idSessionVar = '__id'; - public $authTimeoutSessionVar = '__expire'; - public $returnUrlSessionVar = '__returnUrl'; /** * Initializes the application component. @@ -108,7 +117,10 @@ class User extends Component } } - public function loadIdentity() + /** + * Loads the [[identity]] object according to [[id]]. + */ + protected function loadIdentity() { $id = $this->getId(); if ($id === null) { @@ -116,25 +128,22 @@ class User extends Component } else { /** @var $class Identity */ $class = $this->identityClass; - $this->identity = $class::findIdentity($this->getId()); + $this->identity = $class::findIdentity($id); } } /** * Logs in a user. * - * The user identity information will be saved in storage that is - * persistent during the user session. By default, the storage is simply - * the session storage. If the duration parameter is greater than 0, - * a cookie will be sent to prepare for cookie-based login in future. - * - * Note, you have to set {@link enableAutoLogin} to true - * if you want to allow user to be authenticated based on the cookie information. + * This method stores the necessary session information to keep track + * of the user identity information. If `$duration` is greater than 0 + * and [[enableAutoLogin]] is true, it will also send out an identity + * cookie to support cookie-based login. * * @param Identity $identity the user identity (which should already be authenticated) - * @param integer $duration number of seconds that the user can remain in logged-in status. Defaults to 0, meaning login till the user closes the browser. - * If greater than 0, cookie-based login will be used. In this case, {@link enableAutoLogin} - * must be set true, otherwise an exception will be thrown. + * @param integer $duration number of seconds that the user can remain in logged-in status. + * Defaults to 0, meaning login till the user closes the browser or the session is manually destroyed. + * If greater than 0 and [[enableAutoLogin]] is true, cookie-based login will be supported. * @return boolean whether the user is logged in */ public function login($identity, $duration = 0) @@ -150,11 +159,10 @@ class User extends Component } /** - * Populates the current user object with the information obtained from cookie. - * This method is used when automatic login ({@link enableAutoLogin}) is enabled. - * The user identity information is recovered from cookie. - * Sufficient security measures are used to prevent cookie data from being tampered. - * @see sendIdentityCookie + * Logs in a user by cookie. + * + * This method attempts to log in a user using the ID and authKey information + * provided by the given cookie. */ protected function loginByCookie() { @@ -185,9 +193,8 @@ class User extends Component /** * Logs out the current user. * This will remove authentication-related session data. - * If the parameter is true, the whole session will be destroyed as well. - * @param boolean $destroySession whether to destroy the whole session. Defaults to true. If false, - * then {@link clearStates} will be called, which removes only the data stored via {@link setState}. + * If `$destroySession` is true, all session data will be removed. + * @param boolean $destroySession whether to destroy the whole session. Defaults to true. */ public function logout($destroySession = true) { @@ -215,73 +222,63 @@ class User extends Component /** * Returns a value that uniquely represents the user. - * @return mixed the unique identifier for the user. If null, it means the user is a guest. + * @return string|integer the unique identifier for the user. If null, it means the user is a guest. */ public function getId() { - return Yii::$app->getSession()->get($this->idSessionVar); + return Yii::$app->getSession()->get($this->idVar); } /** - * @param mixed $value the unique identifier for the user. If null, it means the user is a guest. + * @param string|integer $value the unique identifier for the user. If null, it means the user is a guest. */ public function setId($value) { - Yii::$app->getSession()->set($this->idSessionVar, $value); + Yii::$app->getSession()->set($this->idVar, $value); } /** * Returns the URL that the user should be redirected to after successful login. * This property is usually used by the login action. If the login is successful, * the action should read this property and use it to redirect the user browser. - * @param string $defaultUrl the default return URL in case it was not set previously. If this is null, - * the application entry URL will be considered as the default return URL. + * @param string|array $defaultUrl the default return URL in case it was not set previously. + * If this is null, it means [[Application::homeUrl]] will be redirected to. + * Please refer to [[\yii\helpers\Html::url()]] on acceptable URL formats. * @return string the URL that the user should be redirected to after login. * @see loginRequired */ public function getReturnUrl($defaultUrl = null) { - $url = Yii::$app->getSession()->get($this->returnUrlSessionVar, $defaultUrl); - if ($url === null) { - return Yii::$app->getHomeUrl(); - } else { - return Html::url($url); - } + $url = Yii::$app->getSession()->get($this->returnUrlVar, $defaultUrl); + return $url === null ? Yii::$app->getHomeUrl() : $url; } /** - * @param string $value the URL that the user should be redirected to after login. + * @param string|array $url the URL that the user should be redirected to after login. + * Please refer to [[\yii\helpers\Html::url()]] on acceptable URL formats. */ - public function setReturnUrl($value) + public function setReturnUrl($url) { - Yii::$app->getSession()->set($this->returnUrlSessionVar, $value); + Yii::$app->getSession()->set($this->returnUrlVar, $url); } /** * Redirects the user browser to the login page. * Before the redirection, the current URL (if it's not an AJAX url) will be - * kept in {@link returnUrl} so that the user browser may be redirected back - * to the current page after successful login. Make sure you set {@link loginUrl} + * kept as [[returnUrl]] so that the user browser may be redirected back + * to the current page after successful login. Make sure you set [[loginUrl]] * so that the user browser can be redirected to the specified login URL after * calling this method. * After calling this method, the current request processing will be terminated. */ public function loginRequired() { - if (($url = $this->loginUrl) !== null) { - $url = Html::url($url); - $request = Yii::$app->getRequest(); - if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) { - $url = $request->getHostInfo() . $url; - } - if ($request->getIsAjaxRequest()) { - echo json_encode(array( - 'redirect' => $url, - )); - Yii::$app->end(); - } else { - Yii::$app->getResponse()->redirect($url); - } + $request = Yii::$app->getRequest(); + if (!$request->getIsAjaxRequest()) { + $this->setReturnUrl($request->getUrl()); + } + if ($this->loginUrl !== null) { + Yii::$app->getResponse()->redirect($this->loginUrl); } else { throw new HttpException(403, Yii::t('yii|Login Required')); } @@ -289,21 +286,18 @@ class User extends Component /** * This method is called before logging in a user. - * You may override this method to provide additional security check. - * For example, when the login is cookie-based, you may want to verify - * that the user ID together with a random token in the states can be found - * in the database. This will prevent hackers from faking arbitrary - * identity cookies even if they crack down the server private key. - * @param mixed $id the user ID. This is the same as returned by {@link getId()}. - * @param array $states a set of name-value pairs that are provided by the user identity. - * @param boolean $fromCookie whether the login is based on cookie - * @return boolean whether the user should be logged in + * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event. + * If you override this method, make sure you call the parent implementation + * so that the event is triggered. + * @param Identity $identity the user identity information + * @param boolean $cookieBased whether the login is cookie-based + * @return boolean whether the user should continue to be logged in */ - protected function beforeLogin($identity, $fromCookie) + protected function beforeLogin($identity, $cookieBased) { $event = new UserEvent(array( 'identity' => $identity, - 'fromCookie' => $fromCookie, + 'cookieBased' => $cookieBased, )); $this->trigger(self::EVENT_BEFORE_LOGIN, $event); return $event->isValid; @@ -311,24 +305,27 @@ class User extends Component /** * This method is called after the user is successfully logged in. - * You may override this method to do some postprocessing (e.g. log the user - * login IP and time; load the user permission information). - * @param boolean $fromCookie whether the login is based on cookie. + * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event. + * If you override this method, make sure you call the parent implementation + * so that the event is triggered. + * @param Identity $identity the user identity information + * @param boolean $cookieBased whether the login is cookie-based */ - protected function afterLogin($identity, $fromCookie) + protected function afterLogin($identity, $cookieBased) { $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent(array( 'identity' => $identity, - 'fromCookie' => $fromCookie, + 'cookieBased' => $cookieBased, ))); } /** - * This method is invoked when calling {@link logout} to log out a user. - * If this method return false, the logout action will be cancelled. - * You may override this method to provide additional check before - * logging out a user. - * @return boolean whether to log out the user + * This method is invoked when calling [[logout()]] to log out a user. + * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event. + * If you override this method, make sure you call the parent implementation + * so that the event is triggered. + * @param Identity $identity the user identity information + * @return boolean whether the user should continue to be logged out */ protected function beforeLogout($identity) { @@ -340,8 +337,11 @@ class User extends Component } /** - * This method is invoked right after a user is logged out. - * You may override this method to do some extra cleanup work for the user. + * This method is invoked right after a user is logged out via [[logout()]]. + * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event. + * If you override this method, make sure you call the parent implementation + * so that the event is triggered. + * @param Identity $identity the user identity information */ protected function afterLogout($identity) { @@ -350,7 +350,6 @@ class User extends Component ))); } - /** * Renews the identity cookie. * This method will set the expiration time of the identity cookie to be the current time @@ -372,12 +371,12 @@ class User extends Component } /** - * Saves necessary user data into a cookie. - * This method is used when automatic login ({@link enableAutoLogin}) is enabled. - * This method saves user ID, username, other identity states and a validation key to cookie. - * These information are used to do authentication next time when user visits the application. + * Sends an identity cookie. + * This method is used when [[enableAutoLogin]] is true. + * It saves [[id]], [[Identity::getAuthKey()|auth key]], and the duration of cookie-based login + * information in the cookie. * @param Identity $identity - * @param integer $duration number of seconds that the user can remain in logged-in status. Defaults to 0, meaning login till the user closes the browser. + * @param integer $duration number of seconds that the user can remain in logged-in status. * @see loginByCookie */ protected function sendIdentityCookie($identity, $duration) @@ -394,42 +393,40 @@ class User extends Component /** * Changes the current user with the specified identity information. - * This method is called by {@link login} and {@link restoreFromCookie} - * when the current user needs to be populated with the corresponding - * identity information. Derived classes may override this method - * by retrieving additional user-related information. Make sure the - * parent implementation is called first. - * @param Identity $identity a unique identifier for the user + * This method is called by [[login()]] and [[loginByCookie()]] + * when the current user needs to be associated with the corresponding + * identity information. + * @param Identity $identity the identity information to be associated with the current user. */ protected function switchIdentity($identity) { Yii::$app->getSession()->regenerateID(true); $this->identity = $identity; + $session = Yii::$app->getSession(); + $session->remove($this->idVar); + $session->remove($this->authTimeoutVar); if ($identity instanceof Identity) { $this->setId($identity->getId()); if ($this->authTimeout !== null) { - Yii::$app->getSession()->set($this->authTimeoutSessionVar, time() + $this->authTimeout); + Yii::$app->getSession()->set($this->authTimeoutVar, time() + $this->authTimeout); } - } else { - $session = Yii::$app->getSession(); - $session->remove($this->idSessionVar); - $session->remove($this->authTimeoutSessionVar); } } /** - * Updates the authentication status according to {@link authTimeout}. - * If the user has been inactive for {@link authTimeout} seconds, - * he will be automatically logged out. + * Updates the authentication status according to [[authTimeout]]. + * This method is called during [[init()]]. + * It will update the user's authentication status if it has not outdated yet. + * Otherwise, it will logout the user. */ protected function renewAuthStatus() { - if ($this->authTimeout !== null && !$this->getIsGuest()) { - $expire = Yii::$app->getSession()->get($this->authTimeoutSessionVar); + if ($this->authTimeout !== null && $this->identity !== null) { + $expire = Yii::$app->getSession()->get($this->authTimeoutVar); if ($expire !== null && $expire < time()) { $this->logout(false); } else { - Yii::$app->getSession()->set($this->authTimeoutSessionVar, time() + $this->authTimeout); + Yii::$app->getSession()->set($this->authTimeoutVar, time() + $this->authTimeout); } } } diff --git a/framework/web/UserEvent.php b/framework/web/UserEvent.php index 3a8723a..6955ae5 100644 --- a/framework/web/UserEvent.php +++ b/framework/web/UserEvent.php @@ -24,7 +24,7 @@ class UserEvent extends Event * @var boolean whether the login is cookie-based. This property is only meaningful * for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_AFTER_LOGIN]] events. */ - public $fromCookie; + public $cookieBased; /** * @var boolean whether the login or logout should proceed. * Event handlers may modify this property to determine whether the login or logout should proceed.