This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * MediaWiki session provider base class |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @ingroup Session |
||
22 | */ |
||
23 | |||
24 | namespace MediaWiki\Session; |
||
25 | |||
26 | use Psr\Log\LoggerAwareInterface; |
||
27 | use Psr\Log\LoggerInterface; |
||
28 | use Config; |
||
29 | use Language; |
||
30 | use User; |
||
31 | use WebRequest; |
||
32 | |||
33 | /** |
||
34 | * A SessionProvider provides SessionInfo and support for Session |
||
35 | * |
||
36 | * A SessionProvider is responsible for taking a WebRequest and determining |
||
37 | * the authenticated session that it's a part of. It does this by returning an |
||
38 | * SessionInfo object with basic information about the session it thinks is |
||
39 | * associated with the request, namely the session ID and possibly the |
||
40 | * authenticated user the session belongs to. |
||
41 | * |
||
42 | * The SessionProvider also provides for updating the WebResponse with |
||
43 | * information necessary to provide the client with data that the client will |
||
44 | * send with later requests, and for populating the Vary and Key headers with |
||
45 | * the data necessary to correctly vary the cache on these client requests. |
||
46 | * |
||
47 | * An important part of the latter is indicating whether it even *can* tell the |
||
48 | * client to include such data in future requests, via the persistsSessionId() |
||
49 | * and canChangeUser() methods. The cases are (in order of decreasing |
||
50 | * commonness): |
||
51 | * - Cannot persist ID, no changing User: The request identifies and |
||
52 | * authenticates a particular local user, and the client cannot be |
||
53 | * instructed to include an arbitrary session ID with future requests. For |
||
54 | * example, OAuth or SSL certificate auth. |
||
55 | * - Can persist ID and can change User: The client can be instructed to |
||
56 | * return at least one piece of arbitrary data, that being the session ID. |
||
57 | * The user identity might also be given to the client, otherwise it's saved |
||
58 | * in the session data. For example, cookie-based sessions. |
||
59 | * - Can persist ID but no changing User: The request uniquely identifies and |
||
60 | * authenticates a local user, and the client can be instructed to return an |
||
61 | * arbitrary session ID with future requests. For example, HTTP Digest |
||
62 | * authentication might somehow use the 'opaque' field as a session ID |
||
63 | * (although getting MediaWiki to return 401 responses without breaking |
||
64 | * other stuff might be a challenge). |
||
65 | * - Cannot persist ID but can change User: I can't think of a way this |
||
66 | * would make sense. |
||
67 | * |
||
68 | * Note that many methods that are technically "cannot persist ID" could be |
||
69 | * turned into "can persist ID but not change User" using a session cookie, |
||
70 | * as implemented by ImmutableSessionProviderWithCookie. If doing so, different |
||
71 | * session cookie names should be used for different providers to avoid |
||
72 | * collisions. |
||
73 | * |
||
74 | * @ingroup Session |
||
75 | * @since 1.27 |
||
76 | * @see https://www.mediawiki.org/wiki/Manual:SessionManager_and_AuthManager |
||
77 | */ |
||
78 | abstract class SessionProvider implements SessionProviderInterface, LoggerAwareInterface { |
||
79 | |||
80 | /** @var LoggerInterface */ |
||
81 | protected $logger; |
||
82 | |||
83 | /** @var Config */ |
||
84 | protected $config; |
||
85 | |||
86 | /** @var SessionManager */ |
||
87 | protected $manager; |
||
88 | |||
89 | /** @var int Session priority. Used for the default newSessionInfo(), but |
||
90 | * could be used by subclasses too. |
||
91 | */ |
||
92 | protected $priority; |
||
93 | |||
94 | /** |
||
95 | * @note To fully initialize a SessionProvider, the setLogger(), |
||
96 | * setConfig(), and setManager() methods must be called (and should be |
||
97 | * called in that order). Failure to do so is liable to cause things to |
||
98 | * fail unexpectedly. |
||
99 | */ |
||
100 | public function __construct() { |
||
101 | $this->priority = SessionInfo::MIN_PRIORITY + 10; |
||
102 | } |
||
103 | |||
104 | public function setLogger( LoggerInterface $logger ) { |
||
105 | $this->logger = $logger; |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Set configuration |
||
110 | * @param Config $config |
||
111 | */ |
||
112 | public function setConfig( Config $config ) { |
||
113 | $this->config = $config; |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * Set the session manager |
||
118 | * @param SessionManager $manager |
||
119 | */ |
||
120 | public function setManager( SessionManager $manager ) { |
||
121 | $this->manager = $manager; |
||
122 | } |
||
123 | |||
124 | /** |
||
125 | * Get the session manager |
||
126 | * @return SessionManager |
||
127 | */ |
||
128 | public function getManager() { |
||
129 | return $this->manager; |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * Provide session info for a request |
||
134 | * |
||
135 | * If no session exists for the request, return null. Otherwise return an |
||
136 | * SessionInfo object identifying the session. |
||
137 | * |
||
138 | * If multiple SessionProviders provide sessions, the one with highest |
||
139 | * priority wins. In case of a tie, an exception is thrown. |
||
140 | * SessionProviders are encouraged to make priorities user-configurable |
||
141 | * unless only max-priority makes sense. |
||
142 | * |
||
143 | * @warning This will be called early in the MediaWiki setup process, |
||
144 | * before $wgUser, $wgLang, $wgOut, $wgParser, $wgTitle, and corresponding |
||
145 | * pieces of the main RequestContext are set up! If you try to use these, |
||
146 | * things *will* break. |
||
147 | * @note The SessionProvider must not attempt to auto-create users. |
||
148 | * MediaWiki will do this later (when it's safe) if the chosen session has |
||
149 | * a user with a valid name but no ID. |
||
150 | * @protected For use by \MediaWiki\Session\SessionManager only |
||
151 | * @param WebRequest $request |
||
152 | * @return SessionInfo|null |
||
153 | */ |
||
154 | abstract public function provideSessionInfo( WebRequest $request ); |
||
155 | |||
156 | /** |
||
157 | * Provide session info for a new, empty session |
||
158 | * |
||
159 | * Return null if such a session cannot be created. This base |
||
160 | * implementation assumes that it only makes sense if a session ID can be |
||
161 | * persisted and changing users is allowed. |
||
162 | * |
||
163 | * @protected For use by \MediaWiki\Session\SessionManager only |
||
164 | * @param string|null $id ID to force for the new session |
||
165 | * @return SessionInfo|null |
||
166 | * If non-null, must return true for $info->isIdSafe(); pass true for |
||
167 | * $data['idIsSafe'] to ensure this. |
||
168 | */ |
||
169 | public function newSessionInfo( $id = null ) { |
||
170 | if ( $this->canChangeUser() && $this->persistsSessionId() ) { |
||
171 | return new SessionInfo( $this->priority, [ |
||
172 | 'id' => $id, |
||
173 | 'provider' => $this, |
||
174 | 'persisted' => false, |
||
175 | 'idIsSafe' => true, |
||
176 | ] ); |
||
177 | } |
||
178 | return null; |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * Merge saved session provider metadata |
||
183 | * |
||
184 | * This method will be used to compare the metadata returned by |
||
185 | * provideSessionInfo() with the saved metadata (which has been returned by |
||
186 | * provideSessionInfo() the last time the session was saved), and merge the two |
||
187 | * into the new saved metadata, or abort if the current request is not a valid |
||
188 | * continuation of the session. |
||
189 | * |
||
190 | * The default implementation checks that anything in both arrays is |
||
191 | * identical, then returns $providedMetadata. |
||
192 | * |
||
193 | * @protected For use by \MediaWiki\Session\SessionManager only |
||
194 | * @param array $savedMetadata Saved provider metadata |
||
195 | * @param array $providedMetadata Provided provider metadata (from the SessionInfo) |
||
196 | * @return array Resulting metadata |
||
197 | * @throws MetadataMergeException If the metadata cannot be merged. |
||
198 | * Such exceptions will be handled by SessionManager and are a safe way of rejecting |
||
199 | * a suspicious or incompatible session. The provider is expected to write an |
||
200 | * appropriate message to its logger. |
||
201 | */ |
||
202 | public function mergeMetadata( array $savedMetadata, array $providedMetadata ) { |
||
203 | foreach ( $providedMetadata as $k => $v ) { |
||
204 | if ( array_key_exists( $k, $savedMetadata ) && $savedMetadata[$k] !== $v ) { |
||
205 | $e = new MetadataMergeException( "Key \"$k\" changed" ); |
||
206 | $e->setContext( [ |
||
207 | 'old_value' => $savedMetadata[$k], |
||
208 | 'new_value' => $v, |
||
209 | ] ); |
||
210 | throw $e; |
||
211 | } |
||
212 | } |
||
213 | return $providedMetadata; |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Validate a loaded SessionInfo and refresh provider metadata |
||
218 | * |
||
219 | * This is similar in purpose to the 'SessionCheckInfo' hook, and also |
||
220 | * allows for updating the provider metadata. On failure, the provider is |
||
221 | * expected to write an appropriate message to its logger. |
||
222 | * |
||
223 | * @protected For use by \MediaWiki\Session\SessionManager only |
||
224 | * @param SessionInfo $info Any changes by mergeMetadata() will already be reflected here. |
||
225 | * @param WebRequest $request |
||
226 | * @param array|null &$metadata Provider metadata, may be altered. |
||
227 | * @return bool Return false to reject the SessionInfo after all. |
||
228 | */ |
||
229 | public function refreshSessionInfo( SessionInfo $info, WebRequest $request, &$metadata ) { |
||
230 | return true; |
||
231 | } |
||
232 | |||
233 | /** |
||
234 | * Indicate whether self::persistSession() can save arbitrary session IDs |
||
235 | * |
||
236 | * If false, any session passed to self::persistSession() will have an ID |
||
237 | * that was originally provided by self::provideSessionInfo(). |
||
238 | * |
||
239 | * If true, the provider may be passed sessions with arbitrary session IDs, |
||
240 | * and will be expected to manipulate the request in such a way that future |
||
241 | * requests will cause self::provideSessionInfo() to provide a SessionInfo |
||
242 | * with that ID. |
||
243 | * |
||
244 | * For example, a session provider for OAuth would function by matching the |
||
245 | * OAuth headers to a particular user, and then would use self::hashToSessionId() |
||
246 | * to turn the user and OAuth client ID (and maybe also the user token and |
||
247 | * client secret) into a session ID, and therefore can't easily assign that |
||
248 | * user+client a different ID. Similarly, a session provider for SSL client |
||
249 | * certificates would function by matching the certificate to a particular |
||
250 | * user, and then would use self::hashToSessionId() to turn the user and |
||
251 | * certificate fingerprint into a session ID, and therefore can't easily |
||
252 | * assign a different ID either. On the other hand, a provider that saves |
||
253 | * the session ID into a cookie can easily just set the cookie to a |
||
254 | * different value. |
||
255 | * |
||
256 | * @protected For use by \MediaWiki\Session\SessionBackend only |
||
257 | * @return bool |
||
258 | */ |
||
259 | abstract public function persistsSessionId(); |
||
260 | |||
261 | /** |
||
262 | * Indicate whether the user associated with the request can be changed |
||
263 | * |
||
264 | * If false, any session passed to self::persistSession() will have a user |
||
265 | * that was originally provided by self::provideSessionInfo(). Further, |
||
266 | * self::provideSessionInfo() may only provide sessions that have a user |
||
267 | * already set. |
||
268 | * |
||
269 | * If true, the provider may be passed sessions with arbitrary users, and |
||
270 | * will be expected to manipulate the request in such a way that future |
||
271 | * requests will cause self::provideSessionInfo() to provide a SessionInfo |
||
272 | * with that ID. This can be as simple as not passing any 'userInfo' into |
||
273 | * SessionInfo's constructor, in which case SessionInfo will load the user |
||
274 | * from the saved session's metadata. |
||
275 | * |
||
276 | * For example, a session provider for OAuth or SSL client certificates |
||
277 | * would function by matching the OAuth headers or certificate to a |
||
278 | * particular user, and thus would return false here since it can't |
||
279 | * arbitrarily assign those OAuth credentials or that certificate to a |
||
280 | * different user. A session provider that shoves information into cookies, |
||
281 | * on the other hand, could easily do so. |
||
282 | * |
||
283 | * @protected For use by \MediaWiki\Session\SessionBackend only |
||
284 | * @return bool |
||
285 | */ |
||
286 | abstract public function canChangeUser(); |
||
287 | |||
288 | /** |
||
289 | * Returns the duration (in seconds) for which users will be remembered when |
||
290 | * Session::setRememberUser() is set. Null means setting the remember flag will |
||
291 | * have no effect (and endpoints should not offer that option). |
||
292 | * @return int|null |
||
293 | */ |
||
294 | public function getRememberUserDuration() { |
||
295 | return null; |
||
296 | } |
||
297 | |||
298 | /** |
||
299 | * Notification that the session ID was reset |
||
300 | * |
||
301 | * No need to persist here, persistSession() will be called if appropriate. |
||
302 | * |
||
303 | * @protected For use by \MediaWiki\Session\SessionBackend only |
||
304 | * @param SessionBackend $session Session to persist |
||
305 | * @param string $oldId Old session ID |
||
306 | * @codeCoverageIgnore |
||
307 | */ |
||
308 | public function sessionIdWasReset( SessionBackend $session, $oldId ) { |
||
309 | } |
||
310 | |||
311 | /** |
||
312 | * Persist a session into a request/response |
||
313 | * |
||
314 | * For example, you might set cookies for the session's ID, user ID, user |
||
315 | * name, and user token on the passed request. |
||
316 | * |
||
317 | * To correctly persist a user independently of the session ID, the |
||
318 | * provider should persist both the user ID (or name, but preferably the |
||
319 | * ID) and the user token. When reading the data from the request, it |
||
320 | * should construct a User object from the ID/name and then verify that the |
||
321 | * User object's token matches the token included in the request. Should |
||
322 | * the tokens not match, an anonymous user *must* be passed to |
||
323 | * SessionInfo::__construct(). |
||
324 | * |
||
325 | * When persisting a user independently of the session ID, |
||
326 | * $session->shouldRememberUser() should be checked first. If this returns |
||
327 | * false, the user token *must not* be saved to cookies. The user name |
||
328 | * and/or ID may be persisted, and should be used to construct an |
||
329 | * unverified UserInfo to pass to SessionInfo::__construct(). |
||
330 | * |
||
331 | * A backend that cannot persist sesison ID or user info should implement |
||
332 | * this as a no-op. |
||
333 | * |
||
334 | * @protected For use by \MediaWiki\Session\SessionBackend only |
||
335 | * @param SessionBackend $session Session to persist |
||
336 | * @param WebRequest $request Request into which to persist the session |
||
337 | */ |
||
338 | abstract public function persistSession( SessionBackend $session, WebRequest $request ); |
||
339 | |||
340 | /** |
||
341 | * Remove any persisted session from a request/response |
||
342 | * |
||
343 | * For example, blank and expire any cookies set by self::persistSession(). |
||
344 | * |
||
345 | * A backend that cannot persist sesison ID or user info should implement |
||
346 | * this as a no-op. |
||
347 | * |
||
348 | * @protected For use by \MediaWiki\Session\SessionManager only |
||
349 | * @param WebRequest $request Request from which to remove any session data |
||
350 | */ |
||
351 | abstract public function unpersistSession( WebRequest $request ); |
||
352 | |||
353 | /** |
||
354 | * Prevent future sessions for the user |
||
355 | * |
||
356 | * If the provider is capable of returning a SessionInfo with a verified |
||
357 | * UserInfo for the named user in some manner other than by validating |
||
358 | * against $user->getToken(), steps must be taken to prevent that from |
||
359 | * occurring in the future. This might add the username to a blacklist, or |
||
360 | * it might just delete whatever authentication credentials would allow |
||
361 | * such a session in the first place (e.g. remove all OAuth grants or |
||
362 | * delete record of the SSL client certificate). |
||
363 | * |
||
364 | * The intention is that the named account will never again be usable for |
||
365 | * normal login (i.e. there is no way to undo the prevention of access). |
||
366 | * |
||
367 | * Note that the passed user name might not exist locally (i.e. |
||
368 | * User::idFromName( $username ) === 0); the name should still be |
||
369 | * prevented, if applicable. |
||
370 | * |
||
371 | * @protected For use by \MediaWiki\Session\SessionManager only |
||
372 | * @param string $username |
||
373 | */ |
||
374 | public function preventSessionsForUser( $username ) { |
||
375 | if ( !$this->canChangeUser() ) { |
||
376 | throw new \BadMethodCallException( |
||
377 | __METHOD__ . ' must be implmented when canChangeUser() is false' |
||
378 | ); |
||
379 | } |
||
380 | } |
||
381 | |||
382 | /** |
||
383 | * Invalidate existing sessions for a user |
||
384 | * |
||
385 | * If the provider has its own equivalent of CookieSessionProvider's Token |
||
386 | * cookie (and doesn't use User::getToken() to implement it), it should |
||
387 | * reset whatever token it does use here. |
||
388 | * |
||
389 | * @protected For use by \MediaWiki\Session\SessionManager only |
||
390 | * @param User $user; |
||
0 ignored issues
–
show
|
|||
391 | */ |
||
392 | public function invalidateSessionsForUser( User $user ) { |
||
393 | } |
||
394 | |||
395 | /** |
||
396 | * Return the HTTP headers that need varying on. |
||
397 | * |
||
398 | * The return value is such that someone could theoretically do this: |
||
399 | * @code |
||
400 | * foreach ( $provider->getVaryHeaders() as $header => $options ) { |
||
401 | * $outputPage->addVaryHeader( $header, $options ); |
||
402 | * } |
||
403 | * @endcode |
||
404 | * |
||
405 | * @protected For use by \MediaWiki\Session\SessionManager only |
||
406 | * @return array |
||
407 | */ |
||
408 | public function getVaryHeaders() { |
||
409 | return []; |
||
410 | } |
||
411 | |||
412 | /** |
||
413 | * Return the list of cookies that need varying on. |
||
414 | * @protected For use by \MediaWiki\Session\SessionManager only |
||
415 | * @return string[] |
||
416 | */ |
||
417 | public function getVaryCookies() { |
||
418 | return []; |
||
419 | } |
||
420 | |||
421 | /** |
||
422 | * Get a suggested username for the login form |
||
423 | * @protected For use by \MediaWiki\Session\SessionBackend only |
||
424 | * @param WebRequest $request |
||
425 | * @return string|null |
||
426 | */ |
||
427 | public function suggestLoginUsername( WebRequest $request ) { |
||
428 | return null; |
||
429 | } |
||
430 | |||
431 | /** |
||
432 | * Fetch the rights allowed the user when the specified session is active. |
||
433 | * |
||
434 | * This is mainly meant for allowing the user to restrict access to the account |
||
435 | * by certain methods; you probably want to use this with MWGrants. The returned |
||
436 | * rights will be intersected with the user's actual rights. |
||
437 | * |
||
438 | * @param SessionBackend $backend |
||
439 | * @return null|string[] Allowed user rights, or null to allow all. |
||
440 | */ |
||
441 | public function getAllowedUserRights( SessionBackend $backend ) { |
||
442 | if ( $backend->getProvider() !== $this ) { |
||
443 | // Not that this should ever happen... |
||
444 | throw new \InvalidArgumentException( 'Backend\'s provider isn\'t $this' ); |
||
445 | } |
||
446 | |||
447 | return null; |
||
448 | } |
||
449 | |||
450 | /** |
||
451 | * @note Only override this if it makes sense to instantiate multiple |
||
452 | * instances of the provider. Value returned must be unique across |
||
453 | * configured providers. If you override this, you'll likely need to |
||
454 | * override self::describeMessage() as well. |
||
455 | * @return string |
||
456 | */ |
||
457 | public function __toString() { |
||
458 | return get_class( $this ); |
||
459 | } |
||
460 | |||
461 | /** |
||
462 | * Return a Message identifying this session type |
||
463 | * |
||
464 | * This default implementation takes the class name, lowercases it, |
||
465 | * replaces backslashes with dashes, and prefixes 'sessionprovider-' to |
||
466 | * determine the message key. For example, MediaWiki\Session\CookieSessionProvider |
||
467 | * produces 'sessionprovider-mediawiki-session-cookiesessionprovider'. |
||
468 | * |
||
469 | * @note If self::__toString() is overridden, this will likely need to be |
||
470 | * overridden as well. |
||
471 | * @warning This will be called early during MediaWiki startup. Do not |
||
472 | * use $wgUser, $wgLang, $wgOut, $wgParser, or their equivalents via |
||
473 | * RequestContext from this method! |
||
474 | * @return \Message |
||
475 | */ |
||
476 | protected function describeMessage() { |
||
477 | return wfMessage( |
||
478 | 'sessionprovider-' . str_replace( '\\', '-', strtolower( get_class( $this ) ) ) |
||
479 | ); |
||
480 | } |
||
481 | |||
482 | public function describe( Language $lang ) { |
||
483 | $msg = $this->describeMessage(); |
||
484 | $msg->inLanguage( $lang ); |
||
485 | if ( $msg->isDisabled() ) { |
||
486 | $msg = wfMessage( 'sessionprovider-generic', (string)$this )->inLanguage( $lang ); |
||
487 | } |
||
488 | return $msg->plain(); |
||
489 | } |
||
490 | |||
491 | public function whyNoSession() { |
||
492 | return null; |
||
493 | } |
||
494 | |||
495 | /** |
||
496 | * Hash data as a session ID |
||
497 | * |
||
498 | * Generally this will only be used when self::persistsSessionId() is false and |
||
499 | * the provider has to base the session ID on the verified user's identity |
||
500 | * or other static data. The SessionInfo should then typically have the |
||
501 | * 'forceUse' flag set to avoid persistent session failure if validation of |
||
502 | * the stored data fails. |
||
503 | * |
||
504 | * @param string $data |
||
505 | * @param string|null $key Defaults to $this->config->get( 'SecretKey' ) |
||
506 | * @return string |
||
507 | */ |
||
508 | final protected function hashToSessionId( $data, $key = null ) { |
||
509 | if ( !is_string( $data ) ) { |
||
510 | throw new \InvalidArgumentException( |
||
511 | '$data must be a string, ' . gettype( $data ) . ' was passed' |
||
512 | ); |
||
513 | } |
||
514 | if ( $key !== null && !is_string( $key ) ) { |
||
515 | throw new \InvalidArgumentException( |
||
516 | '$key must be a string or null, ' . gettype( $key ) . ' was passed' |
||
517 | ); |
||
518 | } |
||
519 | |||
520 | $hash = \MWCryptHash::hmac( "$this\n$data", $key ?: $this->config->get( 'SecretKey' ), false ); |
||
521 | if ( strlen( $hash ) < 32 ) { |
||
522 | // Should never happen, even md5 is 128 bits |
||
523 | // @codeCoverageIgnoreStart |
||
524 | throw new \UnexpectedValueException( 'Hash fuction returned less than 128 bits' ); |
||
525 | // @codeCoverageIgnoreEnd |
||
526 | } |
||
527 | if ( strlen( $hash ) >= 40 ) { |
||
528 | $hash = \Wikimedia\base_convert( $hash, 16, 32, 32 ); |
||
529 | } |
||
530 | return substr( $hash, -32 ); |
||
531 | } |
||
532 | |||
533 | } |
||
534 |
This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.
Consider the following example. The parameter
$italy
is not defined by the methodfinale(...)
.The most likely cause is that the parameter was removed, but the annotation was not.