* Added a new hook, 'RejectParserCacheValue', which allows extensions to
reject an otherwise-successful parser cache lookup. The intent is to allow
extensions to manage the eviction of archaic HTML output from the cache.
-
+* (T68699) The expiration of the UserID and Token login cookies
+ ($wgExtendedLoginCookieExpiration) can be configured independently of the
+ expiration of all other cookies ($wgCookieExpiration).
==== External libraries ====
* Update es5-shim from v4.0.0 to v4.1.5.
*/
$wgCookieExpiration = 180 * 86400;
+/**
+ * The identifiers of the login cookies that can have their lifetimes
+ * extended independently of all other login cookies.
+ *
+ * @var string[]
+ */
+$wgExtendedLoginCookies = array( 'UserID', 'Token' );
+
+/**
+ * Default login cookie lifetime, in seconds. Setting
+ * $wgExtendLoginCookieExpiration to null will use $wgCookieExpiration to
+ * calculate the cookie lifetime. As with $wgCookieExpiration, 0 will make
+ * login cookies session-only.
+ */
+$wgExtendedLoginCookieExpiration = null;
+
/**
* Set to set an explicit domain on the login cookies eg, "justthis.domain.org"
* or ".any.subdomain.net"
$this->setCookie( $name, '', time() - 86400, $secure, $params );
}
+ /**
+ * Set an extended login cookie on the user's client. The expiry of the cookie
+ * is controlled by the $wgExtendedLoginCookieExpiration configuration
+ * variable.
+ *
+ * @see User::setCookie
+ *
+ * @param string $name Name of the cookie to set
+ * @param string $value Value to set
+ * @param bool $secure
+ * true: Force setting the secure attribute when setting the cookie
+ * false: Force NOT setting the secure attribute when setting the cookie
+ * null (default): Use the default ($wgCookieSecure) to set the secure attribute
+ */
+ protected function setExtendedLoginCookie( $name, $value, $secure ) {
+ global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
+
+ $exp = time();
+ $exp += $wgExtendedLoginCookieExpiration !== null
+ ? $wgExtendedLoginCookieExpiration
+ : $wgCookieExpiration;
+
+ $this->setCookie( $name, $value, $exp, $secure );
+ }
+
/**
* Set the default cookies for this session on the user's client.
*
* @param bool $rememberMe Whether to add a Token cookie for elongated sessions
*/
public function setCookies( $request = null, $secure = null, $rememberMe = false ) {
+ global $wgExtendedLoginCookies;
+
if ( $request === null ) {
$request = $this->getRequest();
}
foreach ( $cookies as $name => $value ) {
if ( $value === false ) {
$this->clearCookie( $name );
+ } elseif ( $rememberMe && in_array( $name, $wgExtendedLoginCookies ) ) {
+ $this->setExtendedLoginCookie( $name, $value, $secure );
} else {
$this->setCookie( $name, $value, 0, $secure, array(), $request );
}
function mainLoginForm( $msg, $msgtype = 'error' ) {
global $wgEnableEmail, $wgEnableUserEmail;
global $wgHiddenPrefs, $wgLoginLanguageSelector;
- global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
+ global $wgAuth, $wgEmailConfirmToEdit;
global $wgSecureLogin, $wgPasswordResetRoutes;
+ global $wgExtendedLoginCookieExpiration, $wgCookieExpiration;
$titleObj = $this->getPageTitle();
$user = $this->getUser();
$template->set( 'emailothers', $wgEnableUserEmail );
$template->set( 'canreset', $wgAuth->allowPasswordChange() );
$template->set( 'resetlink', $resetLink );
- $template->set( 'canremember', ( $wgCookieExpiration > 0 ) );
+ $template->set( 'canremember', $wgExtendedLoginCookieExpiration === null ? ( $wgCookieExpiration > 0 ) : ( $wgExtendedLoginCookieExpiration > 0 ) );
$template->set( 'usereason', $user->isLoggedIn() );
$template->set( 'remember', $this->mRemember );
$template->set( 'cansecurelogin', ( $wgSecureLogin === true ) );
'MockImageHandler' => "$testDir/phpunit/mocks/media/MockImageHandler.php",
'MockSvgHandler' => "$testDir/phpunit/mocks/media/MockSvgHandler.php",
'MockDjVuHandler' => "$testDir/phpunit/mocks/media/MockDjVuHandler.php",
+ 'MockWebRequest' => "$testDir/phpunit/mocks/MockWebRequest.php",
# tests/parser
'NewParserTest' => "$testDir/phpunit/includes/parser/NewParserTest.php",
$this->assertGreaterThan(
$touched, $user->getDBTouched(), "user_touched increased with casOnTouched() #2" );
}
+
+ public static function setExtendedLoginCookieDataProvider() {
+ $data = array();
+ $now = time();
+
+ $secondsInDay = 86400;
+
+ // Arbitrary durations, in units of days, to ensure it chooses the
+ // right one. There is a 5-minute grace period (see testSetExtendedLoginCookie)
+ // to work around slow tests, since we're not currently mocking time() for PHP.
+
+ $durationOne = $secondsInDay * 5;
+ $durationTwo = $secondsInDay * 29;
+ $durationThree = $secondsInDay * 17;
+
+ // If $wgExtendedLoginCookieExpiration is null, then the expiry passed to
+ // set cookie is time() + $wgCookieExpiration
+ $data[] = array(
+ null,
+ $durationOne,
+ $now + $durationOne,
+ );
+
+ // If $wgExtendedLoginCookieExpiration isn't null, then the expiry passed to
+ // set cookie is $now + $wgExtendedLoginCookieExpiration
+ $data[] = array(
+ $durationTwo,
+ $durationThree,
+ $now + $durationTwo,
+ );
+
+ return $data;
+ }
+
+ /**
+ * @dataProvider setExtendedLoginCookieDataProvider
+ * @covers User::getRequest
+ * @covers User::setCookie
+ * @backupGlobals enabled
+ */
+ public function testSetExtendedLoginCookie(
+ $extendedLoginCookieExpiration,
+ $cookieExpiration,
+ $expectedExpiry
+ ) {
+ $this->setMwGlobals( array(
+ 'wgExtendedLoginCookieExpiration' => $extendedLoginCookieExpiration,
+ 'wgCookieExpiration' => $cookieExpiration,
+ ) );
+
+ $response = $this->getMock( 'WebResponse' );
+ $setcookieSpy = $this->any();
+ $response->expects( $setcookieSpy )
+ ->method( 'setcookie' );
+
+ $request = new MockWebRequest( $response );
+ $user = new UserProxy( User::newFromSession( $request ) );
+ $user->setExtendedLoginCookie( 'name', 'value', true );
+
+ $setcookieInvocations = $setcookieSpy->getInvocations();
+ $setcookieInvocation = end( $setcookieInvocations );
+ $actualExpiry = $setcookieInvocation->parameters[ 2 ];
+
+ // TODO: ± 300 seconds compensates for
+ // slow-running tests. However, the dependency on the time
+ // function should be removed. This requires some way
+ // to mock/isolate User->setExtendedLoginCookie's call to time()
+ $this->assertEquals( $expectedExpiry, $actualExpiry, '', 300 );
+ }
+}
+
+class UserProxy extends User {
+
+ /**
+ * @var User
+ */
+ protected $user;
+
+ public function __construct( User $user ) {
+ $this->user = $user;
+ }
+
+ public function setExtendedLoginCookie( $name, $value, $secure ) {
+ $this->user->setExtendedLoginCookie( $name, $value, $secure );
+ }
}
--- /dev/null
+<?php
+
+/**
+ * A mock WebRequest.
+ *
+ * If the code under test accesses the response via the request (see
+ * WebRequest#response), then you might be able to use this mock to simplify
+ * your tests.
+ */
+class MockWebRequest extends WebRequest
+{
+ /**
+ * @var WebResponse
+ */
+ protected $response;
+
+ public function __construct( WebResponse $response ) {
+ parent::__construct();
+
+ $this->response = $response;
+ }
+
+ public function response() {
+ return $this->response;
+ }
+}