1 | <?php |
||||||
2 | /** |
||||||
3 | * @link https://www.yiiframework.com/ |
||||||
4 | * @copyright Copyright (c) 2008 Yii Software LLC |
||||||
5 | * @license https://www.yiiframework.com/license/ |
||||||
6 | */ |
||||||
7 | |||||||
8 | namespace yii\web; |
||||||
9 | |||||||
10 | use Yii; |
||||||
11 | use yii\base\Component; |
||||||
12 | use yii\base\InvalidConfigException; |
||||||
13 | use yii\base\InvalidValueException; |
||||||
14 | use yii\di\Instance; |
||||||
15 | use yii\rbac\CheckAccessInterface; |
||||||
16 | |||||||
17 | /** |
||||||
18 | * User is the class for the `user` application component that manages the user authentication status. |
||||||
19 | * |
||||||
20 | * You may use [[isGuest]] to determine whether the current user is a guest or not. |
||||||
21 | * If the user is a guest, the [[identity]] property would return `null`. Otherwise, it would |
||||||
22 | * be an instance of [[IdentityInterface]]. |
||||||
23 | * |
||||||
24 | * You may call various methods to change the user authentication status: |
||||||
25 | * |
||||||
26 | * - [[login()]]: sets the specified identity and remembers the authentication status in session and cookie; |
||||||
27 | * - [[logout()]]: marks the user as a guest and clears the relevant information from session and cookie; |
||||||
28 | * - [[setIdentity()]]: changes the user identity without touching session or cookie |
||||||
29 | * (this is best used in stateless RESTful API implementation). |
||||||
30 | * |
||||||
31 | * Note that User only maintains the user authentication status. It does NOT handle how to authenticate |
||||||
32 | * a user. The logic of how to authenticate a user should be done in the class implementing [[IdentityInterface]]. |
||||||
33 | * You are also required to set [[identityClass]] with the name of this class. |
||||||
34 | * |
||||||
35 | * User is configured as an application component in [[\yii\web\Application]] by default. |
||||||
36 | * You can access that instance via `Yii::$app->user`. |
||||||
37 | * |
||||||
38 | * You can modify its configuration by adding an array to your application config under `components` |
||||||
39 | * as it is shown in the following example: |
||||||
40 | * |
||||||
41 | * ```php |
||||||
42 | * 'user' => [ |
||||||
43 | * 'identityClass' => 'app\models\User', // User must implement the IdentityInterface |
||||||
44 | * 'enableAutoLogin' => true, |
||||||
45 | * // 'loginUrl' => ['user/login'], |
||||||
46 | * // ... |
||||||
47 | * ] |
||||||
48 | * ``` |
||||||
49 | * |
||||||
50 | * @property-read string|int|null $id The unique identifier for the user. If `null`, it means the user is a |
||||||
51 | * guest. |
||||||
52 | * @property IdentityInterface|null $identity The identity object associated with the currently logged-in |
||||||
53 | * user. `null` is returned if the user is not logged in (not authenticated). |
||||||
54 | * @property-read bool $isGuest Whether the current user is a guest. |
||||||
55 | * @property string $returnUrl The URL that the user should be redirected to after login. Note that the type |
||||||
56 | * of this property differs in getter and setter. See [[getReturnUrl()]] and [[setReturnUrl()]] for details. |
||||||
57 | * |
||||||
58 | * @author Qiang Xue <[email protected]> |
||||||
59 | * @since 2.0 |
||||||
60 | * @phpstan-template T of IdentityInterface |
||||||
61 | * @psalm-template T of IdentityInterface |
||||||
62 | * @phpstan-property T|null $identity |
||||||
63 | * @psalm-property T|null $identity |
||||||
64 | */ |
||||||
65 | class User extends Component |
||||||
66 | { |
||||||
67 | const EVENT_BEFORE_LOGIN = 'beforeLogin'; |
||||||
68 | const EVENT_AFTER_LOGIN = 'afterLogin'; |
||||||
69 | const EVENT_BEFORE_LOGOUT = 'beforeLogout'; |
||||||
70 | const EVENT_AFTER_LOGOUT = 'afterLogout'; |
||||||
71 | |||||||
72 | /** |
||||||
73 | * @var string the class name of the [[identity]] object. |
||||||
74 | * @phpstan-var class-string<T> |
||||||
75 | * @psalm-var class-string<T> |
||||||
76 | */ |
||||||
77 | public $identityClass; |
||||||
78 | /** |
||||||
79 | * @var bool whether to enable cookie-based login. Defaults to `false`. |
||||||
80 | * Note that this property will be ignored if [[enableSession]] is `false`. |
||||||
81 | */ |
||||||
82 | public $enableAutoLogin = false; |
||||||
83 | /** |
||||||
84 | * @var bool whether to use session to persist authentication status across multiple requests. |
||||||
85 | * You set this property to be `false` if your application is stateless, which is often the case |
||||||
86 | * for RESTful APIs. |
||||||
87 | */ |
||||||
88 | public $enableSession = true; |
||||||
89 | /** |
||||||
90 | * @var string|array|null the URL for login when [[loginRequired()]] is called. |
||||||
91 | * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL. |
||||||
92 | * The first element of the array should be the route to the login action, and the rest of |
||||||
93 | * the name-value pairs are GET parameters used to construct the login URL. For example, |
||||||
94 | * |
||||||
95 | * ```php |
||||||
96 | * ['site/login', 'ref' => 1] |
||||||
97 | * ``` |
||||||
98 | * |
||||||
99 | * If this property is `null`, a 403 HTTP exception will be raised when [[loginRequired()]] is called. |
||||||
100 | */ |
||||||
101 | public $loginUrl = ['site/login']; |
||||||
102 | /** |
||||||
103 | * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is `true`. |
||||||
104 | * @see Cookie |
||||||
105 | */ |
||||||
106 | public $identityCookie = ['name' => '_identity', 'httpOnly' => true]; |
||||||
107 | /** |
||||||
108 | * @var int|null the number of seconds in which the user will be logged out automatically if the user |
||||||
109 | * remains inactive. If this property is not set, the user will be logged out after |
||||||
110 | * the current session expires (c.f. [[Session::timeout]]). |
||||||
111 | * Note that this will not work if [[enableAutoLogin]] is `true`. |
||||||
112 | */ |
||||||
113 | public $authTimeout; |
||||||
114 | /** |
||||||
115 | * @var CheckAccessInterface|string|array|null The access checker object to use for checking access or the application |
||||||
116 | * component ID of the access checker. |
||||||
117 | * If not set the application auth manager will be used. |
||||||
118 | * @since 2.0.9 |
||||||
119 | */ |
||||||
120 | public $accessChecker; |
||||||
121 | /** |
||||||
122 | * @var int|null the number of seconds in which the user will be logged out automatically |
||||||
123 | * regardless of activity. |
||||||
124 | * Note that this will not work if [[enableAutoLogin]] is `true`. |
||||||
125 | */ |
||||||
126 | public $absoluteAuthTimeout; |
||||||
127 | /** |
||||||
128 | * @var bool whether to automatically renew the identity cookie each time a page is requested. |
||||||
129 | * This property is effective only when [[enableAutoLogin]] is `true`. |
||||||
130 | * When this is `false`, the identity cookie will expire after the specified duration since the user |
||||||
131 | * is initially logged in. When this is `true`, the identity cookie will expire after the specified duration |
||||||
132 | * since the user visits the site the last time. |
||||||
133 | * @see enableAutoLogin |
||||||
134 | */ |
||||||
135 | public $autoRenewCookie = true; |
||||||
136 | /** |
||||||
137 | * @var string the session variable name used to store the value of [[id]]. |
||||||
138 | */ |
||||||
139 | public $idParam = '__id'; |
||||||
140 | /** |
||||||
141 | * @var string the session variable name used to store authentication key. |
||||||
142 | * @since 2.0.41 |
||||||
143 | */ |
||||||
144 | public $authKeyParam = '__authKey'; |
||||||
145 | /** |
||||||
146 | * @var string the session variable name used to store the value of expiration timestamp of the authenticated state. |
||||||
147 | * This is used when [[authTimeout]] is set. |
||||||
148 | */ |
||||||
149 | public $authTimeoutParam = '__expire'; |
||||||
150 | /** |
||||||
151 | * @var string the session variable name used to store the value of absolute expiration timestamp of the authenticated state. |
||||||
152 | * This is used when [[absoluteAuthTimeout]] is set. |
||||||
153 | */ |
||||||
154 | public $absoluteAuthTimeoutParam = '__absoluteExpire'; |
||||||
155 | /** |
||||||
156 | * @var string the session variable name used to store the value of [[returnUrl]]. |
||||||
157 | */ |
||||||
158 | public $returnUrlParam = '__returnUrl'; |
||||||
159 | /** |
||||||
160 | * @var array MIME types for which this component should redirect to the [[loginUrl]]. |
||||||
161 | * @since 2.0.8 |
||||||
162 | */ |
||||||
163 | public $acceptableRedirectTypes = ['text/html', 'application/xhtml+xml']; |
||||||
164 | |||||||
165 | private $_access = []; |
||||||
166 | |||||||
167 | |||||||
168 | /** |
||||||
169 | * Initializes the application component. |
||||||
170 | */ |
||||||
171 | 115 | public function init() |
|||||
172 | { |
||||||
173 | 115 | parent::init(); |
|||||
174 | |||||||
175 | 115 | if ($this->identityClass === null) { |
|||||
176 | throw new InvalidConfigException('User::identityClass must be set.'); |
||||||
177 | } |
||||||
178 | 115 | if ($this->enableAutoLogin && !isset($this->identityCookie['name'])) { |
|||||
179 | throw new InvalidConfigException('User::identityCookie must contain the "name" element.'); |
||||||
180 | } |
||||||
181 | 115 | if ($this->accessChecker !== null) { |
|||||
182 | 1 | $this->accessChecker = Instance::ensure($this->accessChecker, '\yii\rbac\CheckAccessInterface'); |
|||||
183 | } |
||||||
184 | } |
||||||
185 | |||||||
186 | private $_identity = false; |
||||||
187 | |||||||
188 | /** |
||||||
189 | * Returns the identity object associated with the currently logged-in user. |
||||||
190 | * When [[enableSession]] is true, this method may attempt to read the user's authentication data |
||||||
191 | * stored in session and reconstruct the corresponding identity object, if it has not done so before. |
||||||
192 | * @param bool $autoRenew whether to automatically renew authentication status if it has not been done so before. |
||||||
193 | * This is only useful when [[enableSession]] is true. |
||||||
194 | * @return IdentityInterface|null the identity object associated with the currently logged-in user. |
||||||
195 | * `null` is returned if the user is not logged in (not authenticated). |
||||||
196 | * @see login() |
||||||
197 | * @see logout() |
||||||
198 | * @phpstan-return T|null |
||||||
199 | * @psalm-return T|null |
||||||
200 | */ |
||||||
201 | 96 | public function getIdentity($autoRenew = true) |
|||||
202 | { |
||||||
203 | 96 | if ($this->_identity === false) { |
|||||
204 | 40 | if ($this->enableSession && $autoRenew) { |
|||||
205 | try { |
||||||
206 | 38 | $this->_identity = null; |
|||||
207 | 38 | $this->renewAuthStatus(); |
|||||
208 | 1 | } catch (\Exception $e) { |
|||||
209 | 1 | $this->_identity = false; |
|||||
210 | 1 | throw $e; |
|||||
211 | } catch (\Throwable $e) { |
||||||
212 | $this->_identity = false; |
||||||
213 | 37 | throw $e; |
|||||
214 | } |
||||||
215 | } else { |
||||||
216 | 2 | return null; |
|||||
217 | } |
||||||
218 | } |
||||||
219 | |||||||
220 | 93 | return $this->_identity; |
|||||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||||||
221 | } |
||||||
222 | |||||||
223 | /** |
||||||
224 | * Sets the user identity object. |
||||||
225 | * |
||||||
226 | * Note that this method does not deal with session or cookie. You should usually use [[switchIdentity()]] |
||||||
227 | * to change the identity of the current user. |
||||||
228 | * |
||||||
229 | * @param IdentityInterface|null $identity the identity object associated with the currently logged user. |
||||||
230 | * If null, it means the current user will be a guest without any associated identity. |
||||||
231 | * @throws InvalidValueException if `$identity` object does not implement [[IdentityInterface]]. |
||||||
232 | * @phpstan-param T|null $identity |
||||||
233 | * @psalm-param T|null $identity |
||||||
234 | */ |
||||||
235 | 93 | public function setIdentity($identity) |
|||||
236 | { |
||||||
237 | 93 | if ($identity instanceof IdentityInterface) { |
|||||
238 | 62 | $this->_identity = $identity; |
|||||
239 | 42 | } elseif ($identity === null) { |
|||||
0 ignored issues
–
show
|
|||||||
240 | 42 | $this->_identity = null; |
|||||
241 | } else { |
||||||
242 | 1 | throw new InvalidValueException('The identity object must implement IdentityInterface.'); |
|||||
243 | } |
||||||
244 | 93 | $this->_access = []; |
|||||
245 | } |
||||||
246 | |||||||
247 | /** |
||||||
248 | * Logs in a user. |
||||||
249 | * |
||||||
250 | * After logging in a user: |
||||||
251 | * - the user's identity information is obtainable from the [[identity]] property |
||||||
252 | * |
||||||
253 | * If [[enableSession]] is `true`: |
||||||
254 | * - the identity information will be stored in session and be available in the next requests |
||||||
255 | * - in case of `$duration == 0`: as long as the session remains active or till the user closes the browser |
||||||
256 | * - in case of `$duration > 0`: as long as the session remains active or as long as the cookie |
||||||
257 | * remains valid by it's `$duration` in seconds when [[enableAutoLogin]] is set `true`. |
||||||
258 | * |
||||||
259 | * If [[enableSession]] is `false`: |
||||||
260 | * - the `$duration` parameter will be ignored |
||||||
261 | * |
||||||
262 | * @param IdentityInterface $identity the user identity (which should already be authenticated) |
||||||
263 | * @param int $duration number of seconds that the user can remain in logged-in status, defaults to `0` |
||||||
264 | * @return bool whether the user is logged in |
||||||
265 | * @phpstan-param T $identity |
||||||
266 | * @psalm-param T $identity |
||||||
267 | */ |
||||||
268 | 43 | public function login(IdentityInterface $identity, $duration = 0) |
|||||
269 | { |
||||||
270 | 43 | if ($this->beforeLogin($identity, false, $duration)) { |
|||||
271 | 43 | $this->switchIdentity($identity, $duration); |
|||||
272 | 43 | $id = $identity->getId(); |
|||||
273 | 43 | $ip = Yii::$app->getRequest()->getUserIP(); |
|||||
0 ignored issues
–
show
The method
getUserIP() does not exist on yii\console\Request . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
274 | 43 | if ($this->enableSession) { |
|||||
275 | 43 | $log = "User '$id' logged in from $ip with duration $duration."; |
|||||
276 | } else { |
||||||
277 | $log = "User '$id' logged in from $ip. Session not enabled."; |
||||||
278 | } |
||||||
279 | |||||||
280 | 43 | $this->regenerateCsrfToken(); |
|||||
281 | |||||||
282 | 43 | Yii::info($log, __METHOD__); |
|||||
283 | 43 | $this->afterLogin($identity, false, $duration); |
|||||
284 | } |
||||||
285 | |||||||
286 | 43 | return !$this->getIsGuest(); |
|||||
287 | } |
||||||
288 | |||||||
289 | /** |
||||||
290 | * Regenerates CSRF token |
||||||
291 | * |
||||||
292 | * @since 2.0.14.2 |
||||||
293 | */ |
||||||
294 | 43 | protected function regenerateCsrfToken() |
|||||
295 | { |
||||||
296 | 43 | $request = Yii::$app->getRequest(); |
|||||
297 | 43 | if ($request->enableCsrfCookie || $this->enableSession) { |
|||||
0 ignored issues
–
show
The property
enableCsrfCookie does not exist on yii\console\Request . Since you implemented __get , consider adding a @property annotation.
![]() |
|||||||
298 | 43 | $request->getCsrfToken(true); |
|||||
0 ignored issues
–
show
The method
getCsrfToken() does not exist on yii\console\Request . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
299 | } |
||||||
300 | } |
||||||
301 | |||||||
302 | /** |
||||||
303 | * Logs in a user by the given access token. |
||||||
304 | * This method will first authenticate the user by calling [[IdentityInterface::findIdentityByAccessToken()]] |
||||||
305 | * with the provided access token. If successful, it will call [[login()]] to log in the authenticated user. |
||||||
306 | * If authentication fails or [[login()]] is unsuccessful, it will return null. |
||||||
307 | * @param string $token the access token |
||||||
308 | * @param mixed $type the type of the token. The value of this parameter depends on the implementation. |
||||||
309 | * For example, [[\yii\filters\auth\HttpBearerAuth]] will set this parameter to be `yii\filters\auth\HttpBearerAuth`. |
||||||
310 | * @return IdentityInterface|null the identity associated with the given access token. Null is returned if |
||||||
311 | * the access token is invalid or [[login()]] is unsuccessful. |
||||||
312 | * @phpstan-return T|null |
||||||
313 | * @psalm-return T|null |
||||||
314 | */ |
||||||
315 | 42 | public function loginByAccessToken($token, $type = null) |
|||||
316 | { |
||||||
317 | /** |
||||||
318 | * @var IdentityInterface $class |
||||||
319 | * @phpstan-var class-string<T> $class |
||||||
320 | * @psalm-var class-string<T> $class |
||||||
321 | */ |
||||||
322 | 42 | $class = $this->identityClass; |
|||||
323 | 42 | $identity = $class::findIdentityByAccessToken($token, $type); |
|||||
324 | 42 | if ($identity && $this->login($identity)) { |
|||||
325 | 27 | return $identity; |
|||||
326 | } |
||||||
327 | |||||||
328 | 15 | return null; |
|||||
329 | } |
||||||
330 | |||||||
331 | /** |
||||||
332 | * Logs in a user by cookie. |
||||||
333 | * |
||||||
334 | * This method attempts to log in a user using the ID and authKey information |
||||||
335 | * provided by the [[identityCookie|identity cookie]]. |
||||||
336 | */ |
||||||
337 | 2 | protected function loginByCookie() |
|||||
338 | { |
||||||
339 | 2 | $data = $this->getIdentityAndDurationFromCookie(); |
|||||
340 | 2 | if (isset($data['identity'], $data['duration'])) { |
|||||
341 | 1 | $identity = $data['identity']; |
|||||
342 | 1 | $duration = $data['duration']; |
|||||
343 | 1 | if ($this->beforeLogin($identity, true, $duration)) { |
|||||
344 | 1 | $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0); |
|||||
345 | 1 | $id = $identity->getId(); |
|||||
346 | 1 | $ip = Yii::$app->getRequest()->getUserIP(); |
|||||
347 | 1 | Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__); |
|||||
348 | 1 | $this->afterLogin($identity, true, $duration); |
|||||
349 | } |
||||||
350 | } |
||||||
351 | } |
||||||
352 | |||||||
353 | /** |
||||||
354 | * Logs out the current user. |
||||||
355 | * This will remove authentication-related session data. |
||||||
356 | * If `$destroySession` is true, all session data will be removed. |
||||||
357 | * @param bool $destroySession whether to destroy the whole session. Defaults to true. |
||||||
358 | * This parameter is ignored if [[enableSession]] is false. |
||||||
359 | * @return bool whether the user is logged out |
||||||
360 | */ |
||||||
361 | 1 | public function logout($destroySession = true) |
|||||
362 | { |
||||||
363 | 1 | $identity = $this->getIdentity(); |
|||||
364 | 1 | if ($identity !== null && $this->beforeLogout($identity)) { |
|||||
365 | 1 | $this->switchIdentity(null); |
|||||
366 | 1 | $id = $identity->getId(); |
|||||
367 | 1 | $ip = Yii::$app->getRequest()->getUserIP(); |
|||||
368 | 1 | Yii::info("User '$id' logged out from $ip.", __METHOD__); |
|||||
369 | 1 | if ($destroySession && $this->enableSession) { |
|||||
370 | Yii::$app->getSession()->destroy(); |
||||||
0 ignored issues
–
show
The method
getSession() does not exist on yii\console\Application . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
371 | } |
||||||
372 | 1 | $this->afterLogout($identity); |
|||||
373 | } |
||||||
374 | |||||||
375 | 1 | return $this->getIsGuest(); |
|||||
376 | } |
||||||
377 | |||||||
378 | /** |
||||||
379 | * Returns a value indicating whether the user is a guest (not authenticated). |
||||||
380 | * @return bool whether the current user is a guest. |
||||||
381 | * @see getIdentity() |
||||||
382 | */ |
||||||
383 | 44 | public function getIsGuest() |
|||||
384 | { |
||||||
385 | 44 | return $this->getIdentity() === null; |
|||||
386 | } |
||||||
387 | |||||||
388 | /** |
||||||
389 | * Returns a value that uniquely represents the user. |
||||||
390 | * @return string|int|null the unique identifier for the user. If `null`, it means the user is a guest. |
||||||
391 | * @see getIdentity() |
||||||
392 | */ |
||||||
393 | 84 | public function getId() |
|||||
394 | { |
||||||
395 | 84 | $identity = $this->getIdentity(); |
|||||
396 | |||||||
397 | 84 | return $identity !== null ? $identity->getId() : null; |
|||||
398 | } |
||||||
399 | |||||||
400 | /** |
||||||
401 | * Returns the URL that the browser should be redirected to after successful login. |
||||||
402 | * |
||||||
403 | * This method reads the return URL from the session. It is usually used by the login action which |
||||||
404 | * may call this method to redirect the browser to where it goes after successful authentication. |
||||||
405 | * |
||||||
406 | * @param string|array|null $defaultUrl the default return URL in case it was not set previously. |
||||||
407 | * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to. |
||||||
408 | * Please refer to [[setReturnUrl()]] on accepted format of the URL. |
||||||
409 | * @return string the URL that the user should be redirected to after login. |
||||||
410 | * @see loginRequired() |
||||||
411 | */ |
||||||
412 | 3 | public function getReturnUrl($defaultUrl = null) |
|||||
413 | { |
||||||
414 | 3 | $url = Yii::$app->getSession()->get($this->returnUrlParam, $defaultUrl); |
|||||
415 | 3 | if (is_array($url)) { |
|||||
416 | if (isset($url[0])) { |
||||||
417 | return Yii::$app->getUrlManager()->createUrl($url); |
||||||
418 | } |
||||||
419 | |||||||
420 | $url = null; |
||||||
421 | } |
||||||
422 | |||||||
423 | 3 | return $url === null ? Yii::$app->getHomeUrl() : $url; |
|||||
0 ignored issues
–
show
The method
getHomeUrl() does not exist on yii\console\Application . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
424 | } |
||||||
425 | |||||||
426 | /** |
||||||
427 | * Remembers the URL in the session so that it can be retrieved back later by [[getReturnUrl()]]. |
||||||
428 | * @param string|array $url the URL that the user should be redirected to after login. |
||||||
429 | * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL. |
||||||
430 | * The first element of the array should be the route, and the rest of |
||||||
431 | * the name-value pairs are GET parameters used to construct the URL. For example, |
||||||
432 | * |
||||||
433 | * ```php |
||||||
434 | * ['admin/index', 'ref' => 1] |
||||||
435 | * ``` |
||||||
436 | */ |
||||||
437 | 3 | public function setReturnUrl($url) |
|||||
438 | { |
||||||
439 | 3 | Yii::$app->getSession()->set($this->returnUrlParam, $url); |
|||||
440 | } |
||||||
441 | |||||||
442 | /** |
||||||
443 | * Redirects the user browser to the login page. |
||||||
444 | * |
||||||
445 | * Before the redirection, the current URL (if it's not an AJAX url) will be kept as [[returnUrl]] so that |
||||||
446 | * the user browser may be redirected back to the current page after successful login. |
||||||
447 | * |
||||||
448 | * Make sure you set [[loginUrl]] so that the user browser can be redirected to the specified login URL after |
||||||
449 | * calling this method. |
||||||
450 | * |
||||||
451 | * Note that when [[loginUrl]] is set, calling this method will NOT terminate the application execution. |
||||||
452 | * |
||||||
453 | * @param bool $checkAjax whether to check if the request is an AJAX request. When this is true and the request |
||||||
454 | * is an AJAX request, the current URL (for AJAX request) will NOT be set as the return URL. |
||||||
455 | * @param bool $checkAcceptHeader whether to check if the request accepts HTML responses. Defaults to `true`. When this is true and |
||||||
456 | * the request does not accept HTML responses the current URL will not be SET as the return URL. Also instead of |
||||||
457 | * redirecting the user an ForbiddenHttpException is thrown. This parameter is available since version 2.0.8. |
||||||
458 | * @return Response the redirection response if [[loginUrl]] is set |
||||||
459 | * @throws ForbiddenHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set or a redirect is |
||||||
460 | * not applicable. |
||||||
461 | */ |
||||||
462 | 2 | public function loginRequired($checkAjax = true, $checkAcceptHeader = true) |
|||||
463 | { |
||||||
464 | 2 | $request = Yii::$app->getRequest(); |
|||||
465 | 2 | $canRedirect = !$checkAcceptHeader || $this->checkRedirectAcceptable(); |
|||||
466 | if ( |
||||||
467 | 2 | $this->enableSession |
|||||
468 | 2 | && $request->getIsGet() |
|||||
0 ignored issues
–
show
The method
getIsGet() does not exist on yii\console\Request . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
469 | 2 | && (!$checkAjax || !$request->getIsAjax()) |
|||||
0 ignored issues
–
show
The method
getIsAjax() does not exist on yii\console\Request . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
470 | && $canRedirect |
||||||
471 | ) { |
||||||
472 | 1 | $this->setReturnUrl($request->getAbsoluteUrl()); |
|||||
0 ignored issues
–
show
The method
getAbsoluteUrl() does not exist on yii\console\Request . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
473 | } |
||||||
474 | 2 | if ($this->loginUrl !== null && $canRedirect) { |
|||||
475 | 1 | $loginUrl = (array) $this->loginUrl; |
|||||
476 | 1 | if ($loginUrl[0] !== Yii::$app->requestedRoute) { |
|||||
477 | 1 | return Yii::$app->getResponse()->redirect($this->loginUrl); |
|||||
0 ignored issues
–
show
The method
redirect() does not exist on yii\console\Response . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
478 | } |
||||||
479 | } |
||||||
480 | 2 | throw new ForbiddenHttpException(Yii::t('yii', 'Login Required')); |
|||||
481 | } |
||||||
482 | |||||||
483 | /** |
||||||
484 | * This method is called before logging in a user. |
||||||
485 | * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event. |
||||||
486 | * If you override this method, make sure you call the parent implementation |
||||||
487 | * so that the event is triggered. |
||||||
488 | * @param IdentityInterface $identity the user identity information |
||||||
489 | * @param bool $cookieBased whether the login is cookie-based |
||||||
490 | * @param int $duration number of seconds that the user can remain in logged-in status. |
||||||
491 | * If 0, it means login till the user closes the browser or the session is manually destroyed. |
||||||
492 | * @return bool whether the user should continue to be logged in |
||||||
493 | * @phpstan-param T $identity |
||||||
494 | * @psalm-param T $identity |
||||||
495 | */ |
||||||
496 | 43 | protected function beforeLogin($identity, $cookieBased, $duration) |
|||||
497 | { |
||||||
498 | 43 | $event = new UserEvent([ |
|||||
499 | 43 | 'identity' => $identity, |
|||||
500 | 43 | 'cookieBased' => $cookieBased, |
|||||
501 | 43 | 'duration' => $duration, |
|||||
502 | 43 | ]); |
|||||
503 | 43 | $this->trigger(self::EVENT_BEFORE_LOGIN, $event); |
|||||
504 | |||||||
505 | 43 | return $event->isValid; |
|||||
506 | } |
||||||
507 | |||||||
508 | /** |
||||||
509 | * This method is called after the user is successfully logged in. |
||||||
510 | * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event. |
||||||
511 | * If you override this method, make sure you call the parent implementation |
||||||
512 | * so that the event is triggered. |
||||||
513 | * @param IdentityInterface $identity the user identity information |
||||||
514 | * @param bool $cookieBased whether the login is cookie-based |
||||||
515 | * @param int $duration number of seconds that the user can remain in logged-in status. |
||||||
516 | * If 0, it means login till the user closes the browser or the session is manually destroyed. |
||||||
517 | * @phpstan-param T $identity |
||||||
518 | * @psalm-param T $identity |
||||||
519 | */ |
||||||
520 | 43 | protected function afterLogin($identity, $cookieBased, $duration) |
|||||
521 | { |
||||||
522 | 43 | $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent([ |
|||||
523 | 43 | 'identity' => $identity, |
|||||
524 | 43 | 'cookieBased' => $cookieBased, |
|||||
525 | 43 | 'duration' => $duration, |
|||||
526 | 43 | ])); |
|||||
527 | } |
||||||
528 | |||||||
529 | /** |
||||||
530 | * This method is invoked when calling [[logout()]] to log out a user. |
||||||
531 | * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event. |
||||||
532 | * If you override this method, make sure you call the parent implementation |
||||||
533 | * so that the event is triggered. |
||||||
534 | * @param IdentityInterface $identity the user identity information |
||||||
535 | * @return bool whether the user should continue to be logged out |
||||||
536 | * @phpstan-param T $identity |
||||||
537 | * @psalm-param T $identity |
||||||
538 | */ |
||||||
539 | 1 | protected function beforeLogout($identity) |
|||||
540 | { |
||||||
541 | 1 | $event = new UserEvent([ |
|||||
542 | 1 | 'identity' => $identity, |
|||||
543 | 1 | ]); |
|||||
544 | 1 | $this->trigger(self::EVENT_BEFORE_LOGOUT, $event); |
|||||
545 | |||||||
546 | 1 | return $event->isValid; |
|||||
547 | } |
||||||
548 | |||||||
549 | /** |
||||||
550 | * This method is invoked right after a user is logged out via [[logout()]]. |
||||||
551 | * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event. |
||||||
552 | * If you override this method, make sure you call the parent implementation |
||||||
553 | * so that the event is triggered. |
||||||
554 | * @param IdentityInterface $identity the user identity information |
||||||
555 | * @phpstan-param T $identity |
||||||
556 | * @psalm-param T $identity |
||||||
557 | */ |
||||||
558 | 1 | protected function afterLogout($identity) |
|||||
559 | { |
||||||
560 | 1 | $this->trigger(self::EVENT_AFTER_LOGOUT, new UserEvent([ |
|||||
561 | 1 | 'identity' => $identity, |
|||||
562 | 1 | ])); |
|||||
563 | } |
||||||
564 | |||||||
565 | /** |
||||||
566 | * Renews the identity cookie. |
||||||
567 | * This method will set the expiration time of the identity cookie to be the current time |
||||||
568 | * plus the originally specified cookie duration. |
||||||
569 | */ |
||||||
570 | protected function renewIdentityCookie() |
||||||
571 | { |
||||||
572 | $name = $this->identityCookie['name']; |
||||||
573 | $value = Yii::$app->getRequest()->getCookies()->getValue($name); |
||||||
0 ignored issues
–
show
The method
getCookies() does not exist on yii\console\Request . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
574 | if ($value !== null) { |
||||||
575 | $data = json_decode($value, true); |
||||||
576 | if (is_array($data) && isset($data[2])) { |
||||||
577 | $cookie = Yii::createObject(array_merge($this->identityCookie, [ |
||||||
578 | 'class' => 'yii\web\Cookie', |
||||||
579 | 'value' => $value, |
||||||
580 | 'expire' => time() + (int) $data[2], |
||||||
581 | ])); |
||||||
582 | Yii::$app->getResponse()->getCookies()->add($cookie); |
||||||
0 ignored issues
–
show
The method
getCookies() does not exist on yii\console\Response . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
583 | } |
||||||
584 | } |
||||||
585 | } |
||||||
586 | |||||||
587 | /** |
||||||
588 | * Sends an identity cookie. |
||||||
589 | * This method is used when [[enableAutoLogin]] is true. |
||||||
590 | * It saves [[id]], [[IdentityInterface::getAuthKey()|auth key]], and the duration of cookie-based login |
||||||
591 | * information in the cookie. |
||||||
592 | * @param IdentityInterface $identity |
||||||
593 | * @param int $duration number of seconds that the user can remain in logged-in status. |
||||||
594 | * @see loginByCookie() |
||||||
595 | * @phpstan-param T $identity |
||||||
596 | * @psalm-param T $identity |
||||||
597 | */ |
||||||
598 | 2 | protected function sendIdentityCookie($identity, $duration) |
|||||
599 | { |
||||||
600 | 2 | $cookie = Yii::createObject(array_merge($this->identityCookie, [ |
|||||
601 | 2 | 'class' => 'yii\web\Cookie', |
|||||
602 | 2 | 'value' => json_encode([ |
|||||
603 | 2 | $identity->getId(), |
|||||
604 | 2 | $identity->getAuthKey(), |
|||||
605 | 2 | $duration, |
|||||
606 | 2 | ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), |
|||||
607 | 2 | 'expire' => time() + $duration, |
|||||
608 | 2 | ])); |
|||||
609 | 2 | Yii::$app->getResponse()->getCookies()->add($cookie); |
|||||
610 | } |
||||||
611 | |||||||
612 | /** |
||||||
613 | * Determines if an identity cookie has a valid format and contains a valid auth key. |
||||||
614 | * This method is used when [[enableAutoLogin]] is true. |
||||||
615 | * This method attempts to authenticate a user using the information in the identity cookie. |
||||||
616 | * @return array|null Returns an array of 'identity' and 'duration' if valid, otherwise null. |
||||||
617 | * @see loginByCookie() |
||||||
618 | * @since 2.0.9 |
||||||
619 | */ |
||||||
620 | 2 | protected function getIdentityAndDurationFromCookie() |
|||||
621 | { |
||||||
622 | 2 | $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']); |
|||||
623 | 2 | if ($value === null) { |
|||||
624 | return null; |
||||||
625 | } |
||||||
626 | 2 | $data = json_decode($value, true); |
|||||
627 | 2 | if (is_array($data) && count($data) == 3) { |
|||||
628 | 1 | list($id, $authKey, $duration) = $data; |
|||||
629 | /** @var IdentityInterface $class */ |
||||||
630 | 1 | $class = $this->identityClass; |
|||||
631 | 1 | $identity = $class::findIdentity($id); |
|||||
632 | 1 | if ($identity !== null) { |
|||||
633 | 1 | if (!$identity instanceof IdentityInterface) { |
|||||
0 ignored issues
–
show
|
|||||||
634 | throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface."); |
||||||
635 | 1 | } elseif (!$identity->validateAuthKey($authKey)) { |
|||||
0 ignored issues
–
show
The expression
$identity->validateAuthKey($authKey) of type boolean|null is loosely compared to false ; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.
If an expression can have both $a = canBeFalseAndNull();
// Instead of
if ( ! $a) { }
// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
![]() |
|||||||
636 | $ip = Yii::$app->getRequest()->getUserIP(); |
||||||
637 | Yii::warning("Invalid cookie auth key attempted for user '$id' from $ip: $authKey", __METHOD__); |
||||||
638 | } else { |
||||||
639 | 1 | return ['identity' => $identity, 'duration' => $duration]; |
|||||
640 | } |
||||||
641 | } |
||||||
642 | } |
||||||
643 | 2 | $this->removeIdentityCookie(); |
|||||
644 | 2 | return null; |
|||||
645 | } |
||||||
646 | |||||||
647 | /** |
||||||
648 | * Removes the identity cookie. |
||||||
649 | * This method is used when [[enableAutoLogin]] is true. |
||||||
650 | * @since 2.0.9 |
||||||
651 | */ |
||||||
652 | 2 | protected function removeIdentityCookie() |
|||||
653 | { |
||||||
654 | 2 | Yii::$app->getResponse()->getCookies()->remove(Yii::createObject(array_merge($this->identityCookie, [ |
|||||
655 | 2 | 'class' => 'yii\web\Cookie', |
|||||
656 | 2 | ]))); |
|||||
657 | } |
||||||
658 | |||||||
659 | /** |
||||||
660 | * Switches to a new identity for the current user. |
||||||
661 | * |
||||||
662 | * When [[enableSession]] is true, this method may use session and/or cookie to store the user identity information, |
||||||
663 | * according to the value of `$duration`. Please refer to [[login()]] for more details. |
||||||
664 | * |
||||||
665 | * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]] |
||||||
666 | * when the current user needs to be associated with the corresponding identity information. |
||||||
667 | * |
||||||
668 | * @param IdentityInterface|null $identity the identity information to be associated with the current user. |
||||||
669 | * If null, it means switching the current user to be a guest. |
||||||
670 | * @param int $duration number of seconds that the user can remain in logged-in status. |
||||||
671 | * This parameter is used only when `$identity` is not null. |
||||||
672 | * @phpstan-param T|null $identity |
||||||
673 | * @psalm-param T|null $identity |
||||||
674 | */ |
||||||
675 | 45 | public function switchIdentity($identity, $duration = 0) |
|||||
676 | { |
||||||
677 | 45 | $this->setIdentity($identity); |
|||||
678 | |||||||
679 | 45 | if (!$this->enableSession) { |
|||||
680 | return; |
||||||
681 | } |
||||||
682 | |||||||
683 | /* Ensure any existing identity cookies are removed. */ |
||||||
684 | 45 | if ($this->enableAutoLogin && ($this->autoRenewCookie || $identity === null)) { |
|||||
685 | 1 | $this->removeIdentityCookie(); |
|||||
686 | } |
||||||
687 | |||||||
688 | 45 | $session = Yii::$app->getSession(); |
|||||
689 | 45 | $session->regenerateID(true); |
|||||
690 | 45 | $session->remove($this->idParam); |
|||||
691 | 45 | $session->remove($this->authTimeoutParam); |
|||||
692 | 45 | $session->remove($this->authKeyParam); |
|||||
693 | |||||||
694 | 45 | if ($identity) { |
|||||
695 | 43 | $session->set($this->idParam, $identity->getId()); |
|||||
696 | 43 | $session->set($this->authKeyParam, $identity->getAuthKey()); |
|||||
697 | 43 | if ($this->authTimeout !== null) { |
|||||
698 | 2 | $session->set($this->authTimeoutParam, time() + $this->authTimeout); |
|||||
699 | } |
||||||
700 | 43 | if ($this->absoluteAuthTimeout !== null) { |
|||||
701 | $session->set($this->absoluteAuthTimeoutParam, time() + $this->absoluteAuthTimeout); |
||||||
702 | } |
||||||
703 | 43 | if ($this->enableAutoLogin && $duration > 0) { |
|||||
704 | 2 | $this->sendIdentityCookie($identity, $duration); |
|||||
705 | } |
||||||
706 | } |
||||||
707 | } |
||||||
708 | |||||||
709 | /** |
||||||
710 | * Updates the authentication status using the information from session and cookie. |
||||||
711 | * |
||||||
712 | * This method will try to determine the user identity using the [[idParam]] session variable. |
||||||
713 | * |
||||||
714 | * If [[authTimeout]] is set, this method will refresh the timer. |
||||||
715 | * |
||||||
716 | * If the user identity cannot be determined by session, this method will try to [[loginByCookie()|login by cookie]] |
||||||
717 | * if [[enableAutoLogin]] is true. |
||||||
718 | */ |
||||||
719 | 38 | protected function renewAuthStatus() |
|||||
720 | { |
||||||
721 | 38 | $session = Yii::$app->getSession(); |
|||||
722 | 38 | $id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null; |
|||||
723 | |||||||
724 | 38 | if ($id === null) { |
|||||
725 | 31 | $identity = null; |
|||||
726 | } else { |
||||||
727 | /** @var IdentityInterface $class */ |
||||||
728 | 8 | $class = $this->identityClass; |
|||||
729 | 8 | $identity = $class::findIdentity($id); |
|||||
730 | 7 | if ($identity === null) { |
|||||
731 | 2 | $this->switchIdentity(null); |
|||||
732 | } |
||||||
733 | } |
||||||
734 | |||||||
735 | 37 | if ($identity !== null) { |
|||||
736 | 5 | $authKey = $session->get($this->authKeyParam); |
|||||
737 | 5 | if ($authKey !== null && !$identity->validateAuthKey($authKey)) { |
|||||
0 ignored issues
–
show
The expression
$identity->validateAuthKey($authKey) of type boolean|null is loosely compared to false ; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.
If an expression can have both $a = canBeFalseAndNull();
// Instead of
if ( ! $a) { }
// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
![]() |
|||||||
738 | 1 | $identity = null; |
|||||
739 | 1 | $ip = Yii::$app->getRequest()->getUserIP(); |
|||||
740 | 1 | Yii::warning("Invalid session auth key attempted for user '$id' from $ip: $authKey", __METHOD__); |
|||||
741 | } |
||||||
742 | } |
||||||
743 | |||||||
744 | 37 | $this->setIdentity($identity); |
|||||
745 | |||||||
746 | 37 | if ($identity !== null && ($this->authTimeout !== null || $this->absoluteAuthTimeout !== null)) { |
|||||
747 | 2 | $expire = $this->authTimeout !== null ? $session->get($this->authTimeoutParam) : null; |
|||||
748 | 2 | $expireAbsolute = $this->absoluteAuthTimeout !== null ? $session->get($this->absoluteAuthTimeoutParam) : null; |
|||||
749 | 2 | if ($expire !== null && $expire < time() || $expireAbsolute !== null && $expireAbsolute < time()) { |
|||||
0 ignored issues
–
show
|
|||||||
750 | 1 | $this->logout(false); |
|||||
751 | 2 | } elseif ($this->authTimeout !== null) { |
|||||
752 | 2 | $session->set($this->authTimeoutParam, time() + $this->authTimeout); |
|||||
753 | } |
||||||
754 | } |
||||||
755 | |||||||
756 | 37 | if ($this->enableAutoLogin) { |
|||||
757 | 2 | if ($this->getIsGuest()) { |
|||||
758 | 2 | $this->loginByCookie(); |
|||||
759 | 1 | } elseif ($this->autoRenewCookie) { |
|||||
760 | $this->renewIdentityCookie(); |
||||||
761 | } |
||||||
762 | } |
||||||
763 | } |
||||||
764 | |||||||
765 | /** |
||||||
766 | * Checks if the user can perform the operation as specified by the given permission. |
||||||
767 | * |
||||||
768 | * Note that you must configure "authManager" application component in order to use this method. |
||||||
769 | * Otherwise it will always return false. |
||||||
770 | * |
||||||
771 | * @param string $permissionName the name of the permission (e.g. "edit post") that needs access check. |
||||||
772 | * @param array $params name-value pairs that would be passed to the rules associated |
||||||
773 | * with the roles and permissions assigned to the user. |
||||||
774 | * @param bool $allowCaching whether to allow caching the result of access check. |
||||||
775 | * When this parameter is true (default), if the access check of an operation was performed |
||||||
776 | * before, its result will be directly returned when calling this method to check the same |
||||||
777 | * operation. If this parameter is false, this method will always call |
||||||
778 | * [[\yii\rbac\CheckAccessInterface::checkAccess()]] to obtain the up-to-date access result. Note that this |
||||||
779 | * caching is effective only within the same request and only works when `$params = []`. |
||||||
780 | * @return bool whether the user can perform the operation as specified by the given permission. |
||||||
781 | */ |
||||||
782 | 23 | public function can($permissionName, $params = [], $allowCaching = true) |
|||||
783 | { |
||||||
784 | 23 | if ($allowCaching && empty($params) && isset($this->_access[$permissionName])) { |
|||||
785 | return $this->_access[$permissionName]; |
||||||
786 | } |
||||||
787 | 23 | if (($accessChecker = $this->getAccessChecker()) === null) { |
|||||
788 | return false; |
||||||
789 | } |
||||||
790 | 23 | $access = $accessChecker->checkAccess($this->getId(), $permissionName, $params); |
|||||
791 | 23 | if ($allowCaching && empty($params)) { |
|||||
792 | 10 | $this->_access[$permissionName] = $access; |
|||||
793 | } |
||||||
794 | |||||||
795 | 23 | return $access; |
|||||
796 | } |
||||||
797 | |||||||
798 | /** |
||||||
799 | * Checks if the `Accept` header contains a content type that allows redirection to the login page. |
||||||
800 | * The login page is assumed to serve `text/html` or `application/xhtml+xml` by default. You can change acceptable |
||||||
801 | * content types by modifying [[acceptableRedirectTypes]] property. |
||||||
802 | * @return bool whether this request may be redirected to the login page. |
||||||
803 | * @see acceptableRedirectTypes |
||||||
804 | * @since 2.0.8 |
||||||
805 | */ |
||||||
806 | 2 | public function checkRedirectAcceptable() |
|||||
807 | { |
||||||
808 | 2 | $acceptableTypes = Yii::$app->getRequest()->getAcceptableContentTypes(); |
|||||
0 ignored issues
–
show
The method
getAcceptableContentTypes() does not exist on yii\console\Request . Since you implemented __call , consider adding a @method annotation.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
809 | 2 | if (empty($acceptableTypes) || (count($acceptableTypes) === 1 && array_keys($acceptableTypes)[0] === '*/*')) { |
|||||
810 | 1 | return true; |
|||||
811 | } |
||||||
812 | |||||||
813 | 2 | foreach ($acceptableTypes as $type => $params) { |
|||||
814 | 2 | if (in_array($type, $this->acceptableRedirectTypes, true)) { |
|||||
815 | 1 | return true; |
|||||
816 | } |
||||||
817 | } |
||||||
818 | |||||||
819 | 2 | return false; |
|||||
820 | } |
||||||
821 | |||||||
822 | /** |
||||||
823 | * Returns the access checker used for checking access. |
||||||
824 | * @return CheckAccessInterface |
||||||
825 | * @since 2.0.9 |
||||||
826 | */ |
||||||
827 | 23 | protected function getAccessChecker() |
|||||
828 | { |
||||||
829 | 23 | return $this->accessChecker !== null ? $this->accessChecker : Yii::$app->getAuthManager(); |
|||||
0 ignored issues
–
show
|
|||||||
830 | } |
||||||
831 | } |
||||||
832 |