Controller.php 7.52 KB
Newer Older
Qiang Xue committed
1 2 3 4 5 6 7 8 9 10
<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */

namespace yii\rest;

use Yii;
Qiang Xue committed
11
use yii\base\InvalidConfigException;
Qiang Xue committed
12 13
use yii\web\Response;
use yii\web\UnauthorizedHttpException;
Qiang Xue committed
14
use yii\web\UnsupportedMediaTypeHttpException;
15
use yii\web\TooManyRequestsHttpException;
Qiang Xue committed
16
use yii\web\VerbFilter;
Qiang Xue committed
17
use yii\web\ForbiddenHttpException;
Qiang Xue committed
18 19 20 21 22 23 24

/**
 * Controller is the base class for RESTful API controller classes.
 *
 * Controller implements the following steps in a RESTful API request handling cycle:
 *
 * 1. Resolving response format and API version number (see [[supportedFormats]], [[supportedVersions]] and [[version]]);
Qiang Xue committed
25
 * 2. Validating request method (see [[verbs()]]).
Qiang Xue committed
26 27 28 29 30 31 32 33 34
 * 3. Authenticating user (see [[authenticate()]]);
 * 4. Formatting response data (see [[serializeData()]]).
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
class Controller extends \yii\web\Controller
{
	/**
Qiang Xue committed
35
	 * @var string the name of the header parameter representing the API version number.
Qiang Xue committed
36
	 */
Qiang Xue committed
37
	public $versionHeaderParam = 'version';
Qiang Xue committed
38 39 40 41 42 43 44 45
	/**
	 * @var string|array the configuration for creating the serializer that formats the response data.
	 */
	public $serializer = 'yii\rest\Serializer';
	/**
	 * @inheritdoc
	 */
	public $enableCsrfValidation = false;
46
	/**
Qiang Xue committed
47 48
	 * @var array the supported authentication methods. This property should take a list of supported
	 * authentication methods, each represented by an authentication class or configuration.
Qiang Xue committed
49
	 * If this is not set or empty, it means authentication is disabled.
50
	 */
Qiang Xue committed
51
	public $authMethods;
Qiang Xue committed
52 53
	/**
	 * @var string|array the rate limiter class or configuration. If this is not set or empty,
Qiang Xue committed
54 55
	 * the rate limiting will be disabled. Note that if the user is not authenticated, the rate limiting
	 * will also NOT be performed.
Qiang Xue committed
56
	 * @see checkRateLimit()
Qiang Xue committed
57
	 * @see authMethods
Qiang Xue committed
58 59
	 */
	public $rateLimiter = 'yii\rest\RateLimiter';
Qiang Xue committed
60
	/**
Qiang Xue committed
61
	 * @var string the chosen API version number, or null if [[supportedVersions]] is empty.
Qiang Xue committed
62 63 64 65 66
	 * @see supportedVersions
	 */
	public $version;
	/**
	 * @var array list of supported API version numbers. If the current request does not specify a version
Qiang Xue committed
67 68
	 * number, the first element will be used as the [[version|chosen version number]]. For this reason, you should
	 * put the latest version number at the first. If this property is empty, [[version]] will not be set.
Qiang Xue committed
69
	 */
Qiang Xue committed
70
	public $supportedVersions = [];
Qiang Xue committed
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
	/**
	 * @var array list of supported response formats. The array keys are the requested content MIME types,
	 * and the array values are the corresponding response formats. The first element will be used
	 * as the response format if the current request does not specify a content type.
	 */
	public $supportedFormats = [
		'application/json' => Response::FORMAT_JSON,
		'application/xml' => Response::FORMAT_XML,
	];

	/**
	 * @inheritdoc
	 */
	public function behaviors()
	{
		return [
			'verbFilter' => [
				'class' => VerbFilter::className(),
				'actions' => $this->verbs(),
			],
		];
	}

	/**
	 * @inheritdoc
	 */
	public function init()
	{
		parent::init();
		$this->resolveFormatAndVersion();
	}

	/**
	 * @inheritdoc
	 */
	public function beforeAction($action)
	{
		if (parent::beforeAction($action)) {
			$this->authenticate();
110
			$this->checkRateLimit($action);
Qiang Xue committed
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
			return true;
		} else {
			return false;
		}
	}

	/**
	 * @inheritdoc
	 */
	public function afterAction($action, $result)
	{
		$result = parent::afterAction($action, $result);
		return $this->serializeData($result);
	}

	/**
	 * Resolves the response format and the API version number.
Qiang Xue committed
128
	 * @throws UnsupportedMediaTypeHttpException
Qiang Xue committed
129 130 131
	 */
	protected function resolveFormatAndVersion()
	{
Qiang Xue committed
132
		$this->version = empty($this->supportedVersions) ? null : reset($this->supportedVersions);
Qiang Xue committed
133 134 135 136 137 138 139 140 141
		Yii::$app->getResponse()->format = reset($this->supportedFormats);
		$types = Yii::$app->getRequest()->getAcceptableContentTypes();
		if (empty($types)) {
			$types['*/*'] = [];
		}

		foreach ($types as $type => $params) {
			if (isset($this->supportedFormats[$type])) {
				Yii::$app->getResponse()->format = $this->supportedFormats[$type];
Qiang Xue committed
142 143 144
				if (isset($params[$this->versionHeaderParam])) {
					if (in_array($params[$this->versionHeaderParam], $this->supportedVersions, true)) {
						$this->version = $params[$this->versionHeaderParam];
Qiang Xue committed
145
					} else {
Qiang Xue committed
146
						throw new UnsupportedMediaTypeHttpException('You are requesting an invalid version number.');
Qiang Xue committed
147 148 149 150 151 152 153
					}
				}
				return;
			}
		}

		if (!isset($types['*/*'])) {
Qiang Xue committed
154
			throw new UnsupportedMediaTypeHttpException('None of your requested content types is supported.');
Qiang Xue committed
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
		}
	}

	/**
	 * Declares the allowed HTTP verbs.
	 * Please refer to [[VerbFilter::actions]] on how to declare the allowed verbs.
	 * @return array the allowed HTTP verbs.
	 */
	protected function verbs()
	{
		return [];
	}

	/**
	 * Authenticates the user.
170
	 * This method implements the user authentication based on an access token sent through the `Authorization` HTTP header.
Qiang Xue committed
171 172 173 174
	 * @throws UnauthorizedHttpException if the user is not authenticated successfully
	 */
	protected function authenticate()
	{
Qiang Xue committed
175 176
		if (empty($this->authMethods)) {
			return;
177 178
		}

Qiang Xue committed
179 180 181 182 183 184 185 186 187
		$user = Yii::$app->getUser();
		$request = Yii::$app->getRequest();
		$response = Yii::$app->getResponse();
		foreach ($this->authMethods as $i => $auth) {
			$this->authMethods[$i] = $auth = Yii::createObject($auth);
			if (!$auth instanceof AuthInterface) {
				throw new InvalidConfigException(get_class($auth) . ' must implement yii\rest\AuthInterface');
			} elseif ($auth->authenticate($user, $request, $response) !== null) {
				return;
188
			}
Qiang Xue committed
189
		}
Qiang Xue committed
190 191 192 193

		/** @var AuthInterface $auth */
		$auth = reset($this->authMethods);
		$auth->handleFailure($response);
Qiang Xue committed
194 195
	}

196 197
	/**
	 * Ensures the rate limit is not exceeded.
Qiang Xue committed
198 199 200 201 202
	 *
	 * This method will use [[rateLimiter]] to check rate limit. In order to perform rate limiting check,
	 * the user must be authenticated and the user identity object (`Yii::$app->user->identity`) must
	 * implement [[RateLimitInterface]].
	 *
203 204 205 206 207
	 * @param \yii\base\Action $action the action to be executed
	 * @throws TooManyRequestsHttpException if the rate limit is exceeded.
	 */
	protected function checkRateLimit($action)
	{
Qiang Xue committed
208 209 210 211 212 213 214 215 216 217
		if (empty($this->rateLimiter)) {
			return;
		}

		$identity = Yii::$app->getUser()->getIdentity(false);
		if ($identity instanceof RateLimitInterface) {
			/** @var RateLimiter $rateLimiter */
			$rateLimiter = Yii::createObject($this->rateLimiter);
			$rateLimiter->check($identity, Yii::$app->getRequest(), Yii::$app->getResponse(), $action);
		}
218 219
	}

Qiang Xue committed
220 221 222 223 224 225 226 227 228 229 230
	/**
	 * Serializes the specified data.
	 * The default implementation will create a serializer based on the configuration given by [[serializer]].
	 * It then uses the serializer to serialize the given data.
	 * @param mixed $data the data to be serialized
	 * @return mixed the serialized data.
	 */
	protected function serializeData($data)
	{
		return Yii::createObject($this->serializer)->serialize($data);
	}
Qiang Xue committed
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246

	/**
	 * Checks the privilege of the current user.
	 *
	 * This method should be overridden to check whether the current user has the privilege
	 * to run the specified action against the specified data model.
	 * If the user does not have access, a [[ForbiddenHttpException]] should be thrown.
	 *
	 * @param string $action the ID of the action to be executed
	 * @param object $model the model to be accessed. If null, it means no specific model is being accessed.
	 * @param array $params additional parameters
	 * @throws ForbiddenHttpException if the user does not have access
	 */
	public function checkAccess($action, $model = null, $params = [])
	{
	}
Qiang Xue committed
247
}