Commit 1184e1e1 by Qiang Xue

Finished AccessControl and HttpCache.

parent 0ed14a74
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use Yii;
use yii\base\Action;
use yii\base\ActionFilter;
use yii\base\HttpException;
/**
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class AccessControl extends ActionFilter
{
/**
* @var callback a callback that will be called if the access should be denied
* to the current user. If not set, [[denyAccess()]] will be called.
*
* The signature of the callback should be as follows:
*
* ~~~
* function ($rule, $action)
* ~~~
*
* where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
*/
public $denyCallback;
/**
* @var string the default class of the access rules. This is used when
* a rule is configured without specifying a class in [[rules]].
*/
public $defaultRuleClass = 'yii\web\AccessRule';
/**
* @var array a list of access rule objects or configurations for creating the rule objects.
*/
public $rules = array();
/**
* Initializes the [[rules]] array by instantiating rule objects from configurations.
*/
public function init()
{
parent::init();
foreach ($this->rules as $i => $rule) {
if (is_array($rule)) {
if (!isset($rule['class'])) {
$rule['class'] = $this->defaultRuleClass;
}
$this->rules[$i] = Yii::createObject($rule);
}
}
}
/**
* This method is invoked right before an action is to be executed (after all possible filters.)
* You may override this method to do last-minute preparation for the action.
* @param Action $action the action to be executed.
* @return boolean whether the action should continue to be executed.
*/
public function beforeAction($action)
{
$user = Yii::$app->getUser();
$request = Yii::$app->getRequest();
/** @var $rule AccessRule */
foreach ($this->rules as $rule) {
if ($allow = $rule->allows($action, $user, $request)) {
break;
} elseif ($allow === false) {
if (isset($rule->denyCallback)) {
call_user_func($rule->denyCallback, $rule);
} elseif (isset($this->denyCallback)) {
call_user_func($this->denyCallback, $rule);
} else {
$this->denyAccess($user);
}
return false;
}
}
return true;
}
/**
* Denies the access of the user.
* The default implementation will redirect the user to the login page if he is a guest;
* if the user is already logged, a 403 HTTP exception will be thrown.
* @param User $user the current user
* @throws HttpException if the user is already logged in.
*/
protected function denyAccess($user)
{
if ($user->getIsGuest()) {
$user->loginRequired();
} else {
throw new HttpException(403, Yii::t('yii|You are not allowed to perform this action.'));
}
}
}
\ No newline at end of file
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use yii\base\Component;
use yii\base\Action;
use yii\base\Controller;
use yii\web\User;
use yii\web\Request;
/**
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class AccessRule extends Component
{
/**
* @var boolean whether this is an 'allow' rule or 'deny' rule.
*/
public $allow;
/**
* @var array list of action IDs that this rule applies to. The comparison is case-sensitive.
* If not set or empty, it means this rule applies to all actions.
*/
public $actions;
/**
* @var array list of controller IDs that this rule applies to. The comparison is case-sensitive.
* If not set or empty, it means this rule applies to all controllers.
*/
public $controllers;
/**
* @var array list of user names that this rule applies to. The comparison is case-insensitive.
* If not set or empty, it means this rule applies to all users. Two special tokens are recognized:
*
* - `?`: matches a guest user (not authenticated yet)
* - `@`: matches an authenticated user
*
* @see \yii\web\Application::user
*/
public $users;
/**
* @var array list of roles that this rule applies to. For each role, the current user's
* {@link CWebUser::checkAccess} method will be invoked. If one of the invocations
* returns true, the rule will be applied.
* Note, you should mainly use roles in an "allow" rule because by definition,
* a role represents a permission collection.
* If not set or empty, it means this rule applies to all roles.
*/
public $roles;
/**
* @var array list of user IP addresses that this rule applies to. An IP address
* can contain the wildcard `*` at the end so that it matches IP addresses with the same prefix.
* For example, '192.168.*' matches all IP addresses in the segment '192.168.'.
* If not set or empty, it means this rule applies to all IP addresses.
* @see Request::userIP
*/
public $ips;
/**
* @var array list of request methods (e.g. `GET`, `POST`) that this rule applies to.
* The request methods must be specified in uppercase.
* If not set or empty, it means this rule applies to all request methods.
* @see Request::requestMethod
*/
public $verbs;
/**
* @var callback a callback that will be called to determine if the rule should be applied.
* The signature of the callback should be as follows:
*
* ~~~
* function ($rule, $action)
* ~~~
*
* where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
* The callback should return a boolean value indicating whether this rule should be applied.
*/
public $matchCallback;
/**
* @var callback a callback that will be called if this rule determines the access to
* the current action should be denied. If not set, the behavior will be determined by
* [[AccessControl]].
*
* The signature of the callback should be as follows:
*
* ~~~
* function ($rule, $action)
* ~~~
*
* where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
*/
public $denyCallback;
/**
* Checks whether the Web user is allowed to perform the specified action.
* @param Action $action the action to be performed
* @param User $user the user object
* @param Request $request
* @return boolean|null true if the user is allowed, false if the user is denied, null if the rule does not apply to the user
*/
public function allows($action, $user, $request)
{
if ($this->matchAction($action)
&& $this->matchUser($user)
&& $this->matchRole($user)
&& $this->matchIP($request->getUserIP())
&& $this->matchVerb($request->getRequestMethod())
&& $this->matchController($action->controller)
&& $this->matchCustom($action)
) {
return $this->allow ? true : false;
} else {
return null;
}
}
/**
* @param Action $action the action
* @return boolean whether the rule applies to the action
*/
protected function matchAction($action)
{
return empty($this->actions) || in_array($action->id, $this->actions, true);
}
/**
* @param Controller $controller the controller
* @return boolean whether the rule applies to the controller
*/
protected function matchController($controller)
{
return empty($this->controllers) || in_array($controller->id, $this->controllers, true);
}
/**
* @param User $user the user
* @return boolean whether the rule applies to the user
*/
protected function matchUser($user)
{
if (empty($this->users)) {
return true;
}
foreach ($this->users as $u) {
if ($u === '?' && $user->getIsGuest()) {
return true;
} elseif ($u === '@' && !$user->getIsGuest()) {
return true;
} elseif (!strcasecmp($u, $user->getName())) {
return true;
}
}
return false;
}
/**
* @param User $user the user object
* @return boolean whether the rule applies to the role
*/
protected function matchRole($user)
{
if (empty($this->roles)) {
return true;
}
foreach ($this->roles as $role) {
if ($user->checkAccess($role)) {
return true;
}
}
return false;
}
/**
* @param string $ip the IP address
* @return boolean whether the rule applies to the IP address
*/
protected function matchIP($ip)
{
if (empty($this->ips)) {
return true;
}
foreach ($this->ips as $rule) {
if ($rule === '*' || $rule === $ip || (($pos = strpos($rule, '*')) !== false && !strncmp($ip, $rule, $pos))) {
return true;
}
}
return false;
}
/**
* @param string $verb the request method
* @return boolean whether the rule applies to the request
*/
protected function matchVerb($verb)
{
return empty($this->verbs) || in_array($verb, $this->verbs, true);
}
/**
* @param Action $action the action to be performed
* @return boolean whether the rule should be applied
*/
protected function matchCustom($action)
{
return empty($this->matchCallback) || call_user_func($this->matchCallback, $this, $action);
}
}
\ No newline at end of file
...@@ -69,6 +69,15 @@ class Application extends \yii\base\Application ...@@ -69,6 +69,15 @@ class Application extends \yii\base\Application
} }
/** /**
* Returns the user component.
* @return User the user component
*/
public function getUser()
{
return $this->getComponent('user');
}
/**
* Creates a URL using the given route and parameters. * Creates a URL using the given route and parameters.
* *
* This method first normalizes the given route by converting a relative route into an absolute one. * This method first normalizes the given route by converting a relative route into an absolute one.
...@@ -130,6 +139,9 @@ class Application extends \yii\base\Application ...@@ -130,6 +139,9 @@ class Application extends \yii\base\Application
'session' => array( 'session' => array(
'class' => 'yii\web\Session', 'class' => 'yii\web\Session',
), ),
'user' => array(
'class' => 'yii\web\User',
),
)); ));
} }
} }
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
use Yii;
use yii\base\ActionFilter;
use yii\base\Action;
/**
* @author Da:Sourcerer <webmaster@dasourcerer.net>
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class HttpCache extends ActionFilter
{
/**
* @var callback a PHP callback that returns the UNIX timestamp of the last modification time.
* The callback's signature should be:
*
* ~~~
* function ($action, $params)
* ~~~
*
* where `$action` is the [[Action]] object that this filter is currently handling;
* `$params` takes the value of [[params]].
*/
public $lastModified;
/**
* @var callback a PHP callback that generates the Etag seed string.
* The callback's signature should be:
*
* ~~~
* function ($action, $params)
* ~~~
*
* where `$action` is the [[Action]] object that this filter is currently handling;
* `$params` takes the value of [[params]]. The callback should return a string.
*/
public $etagSeed;
/**
* @var mixed additional parameters that should be passed to the [[lastModified]] and [[etagSeed]] callbacks.
*/
public $params;
/**
* Http cache control headers. Set this to an empty string in order to keep this
* header from being sent entirely.
* @var string
*/
public $cacheControl = 'max-age=3600, public';
/**
* This method is invoked right before an action is to be executed (after all possible filters.)
* You may override this method to do last-minute preparation for the action.
* @param Action $action the action to be executed.
* @return boolean whether the action should continue to be executed.
*/
public function beforeAction($action)
{
$requestMethod = Yii::$app->request->getRequestMethod();
if ($requestMethod !== 'GET' && $requestMethod !== 'HEAD') {
return true;
}
$lastModified = $etag = null;
if ($this->lastModified !== null) {
$lastModified = call_user_func($this->lastModified, $action, $this->params);
}
if ($this->etagSeed !== null) {
$seed = call_user_func($this->etagSeed, $action, $this->params);
$etag = $this->generateEtag($seed);
}
if ($etag !== null) {
header("ETag: $etag");
}
$this->sendCacheControlHeader();
if ($this->hasChanged($lastModified, $etag)) {
if ($lastModified !== null) {
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
}
return true;
} else {
header('HTTP/1.1 304 Not Modified');
return false;
}
}
protected function hasChanged($lastModified, $etag)
{
$changed = !isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < $lastModified;
if (!$changed && $etag !== null) {
$changed = !isset($_SERVER['HTTP_IF_NONE_MATCH']) || $_SERVER['HTTP_IF_NONE_MATCH'] !== $etag;
}
return $changed;
}
/**
* Sends the cache control header to the client
* @see cacheControl
*/
protected function sendCacheControlHeader()
{
if (Yii::$app->session->isActive) {
session_cache_limiter('public');
header('Pragma:', true);
}
header('Cache-Control: ' . $this->cacheControl, true);
}
/**
* Generates an Etag from a given seed string.
* @param string $seed Seed for the ETag
* @return string the generated Etag
*/
protected function generateEtag($seed)
{
return '"' . base64_encode(sha1($seed, true)) . '"';
}
}
\ No newline at end of file
...@@ -530,7 +530,7 @@ class Request extends \yii\base\Request ...@@ -530,7 +530,7 @@ class Request extends \yii\base\Request
* Returns the user IP address. * Returns the user IP address.
* @return string user IP address * @return string user IP address
*/ */
public function getUserHostAddress() public function getUserIP()
{ {
return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1'; return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment