* Added MWRestrictions as a class to check restrictions on a WebRequest, e.g.
to assert that the request comes from a particular IP range.
* Added bot passwords, a rights-restricted login mechanism for API-using bots.
+* Whitelisted the following HTML attributes for all elements in wikitext:
+ aria-describedby, aria-flowto, aria-label, aria-labelledby, aria-owns.
+* Removed "presentation" restriction on the HTML role attribute in wikitext.
+ All values are now allowed for the role attribute.
=== External library changes in 1.27 ===
global $wgOut;
if ( Hooks::run( 'EditPageBeforeConflictDiff', array( &$this, &$wgOut ) ) ) {
+ $stats = $wgOut->getContext()->getStats();
+ $stats->increment( 'edit.failures.conflict' );
+
$wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourdiff" );
$content1 = $this->toEditContent( $this->textbox1 );
* Take an array of attribute names and values and normalize or discard
* illegal values for the given whitelist.
*
- * - Discards attributes not the given whitelist
+ * - Discards attributes not on the given whitelist
* - Unsafe style attributes are discarded
* - Invalid id attributes are re-encoded
*
$value = Sanitizer::checkCss( $value );
}
+ # Escape HTML id attributes
if ( $attribute === 'id' ) {
$value = Sanitizer::escapeId( $value, 'noninitial' );
}
- # WAI-ARIA
- # http://www.w3.org/TR/wai-aria/
- # http://www.whatwg.org/html/elements.html#wai-aria
- # For now we only support role="presentation" until we work out what roles should be
- # usable by content and we ensure that our code explicitly rejects patterns that
- # violate HTML5's ARIA restrictions.
- if ( $attribute === 'role' && $value !== 'presentation' ) {
- continue;
+ # Escape HTML id reference lists
+ if ( $attribute === 'aria-describedby'
+ || $attribute === 'aria-flowto'
+ || $attribute === 'aria-labelledby'
+ || $attribute === 'aria-owns'
+ ) {
+ $value = Sanitizer::escapeIdReferenceList( $value, 'noninitial' );
}
// RDFa and microdata properties allow URLs, URIs and/or CURIs.
return $id;
}
+ /**
+ * Given a string containing a space delimited list of ids, escape each id
+ * to match ids escaped by the escapeId() function.
+ *
+ * @since 1.27
+ *
+ * @param string $referenceString Space delimited list of ids
+ * @param string|array $options String or array of strings (default is array()):
+ * 'noninitial': This is a non-initial fragment of an id, not a full id,
+ * so don't pay attention if the first character isn't valid at the
+ * beginning of an id. Only matters if $wgExperimentalHtmlIds is
+ * false.
+ * 'legacy': Behave the way the old HTML 4-based ID escaping worked even
+ * if $wgExperimentalHtmlIds is used, so we can generate extra
+ * anchors and links won't break.
+ * @return string
+ */
+ static function escapeIdReferenceList( $referenceString, $options = array() ) {
+ # Explode the space delimited list string into an array of tokens
+ $references = preg_split( '/\s+/', "{$referenceString}", -1, PREG_SPLIT_NO_EMPTY );
+
+ # Escape each token as an id
+ foreach ( $references as &$ref ) {
+ $ref = Sanitizer::escapeId( $ref, $options );
+ }
+
+ # Merge the array back to a space delimited list string
+ # If the array is empty, the result will be an empty string ('')
+ $referenceString = implode( ' ', $references );
+
+ return $referenceString;
+ }
+
/**
* Given a value, escape it so that it can be used as a CSS class and
* return it.
'title',
# WAI-ARIA
+ 'aria-describedby',
+ 'aria-flowto',
+ 'aria-label',
+ 'aria-labelledby',
+ 'aria-owns',
'role',
);
}
$cacheType = get_class( ObjectCache::getInstance( $wgSessionCacheType ) );
wfDebugLog(
+ 'caches',
"Session data will be stored in \"$cacheType\" cache with " .
"expiry $wgObjectCacheSessionExpiry seconds"
);
* @since 1.21
*/
public function exportSession() {
+ $session = MediaWiki\Session\SessionManager::getGlobalSession();
return array(
'ip' => $this->getRequest()->getIP(),
'headers' => $this->getRequest()->getAllHeaders(),
- 'sessionId' => MediaWiki\Session\SessionManager::getGlobalSession()->getId(),
+ 'sessionId' => $session->isPersistent() ? $session->getId() : '',
'userId' => $this->getUser()->getId()
);
}
if ( !$this->persist ) {
$this->persist = true;
$this->forcePersist = true;
+ $this->metaDirty = true;
$this->logger->debug( "SessionBackend $this->id force-persist due to persist()" );
$this->autosave();
} else {
'forceHTTPS' => $this->forceHTTPS,
'expires' => time() + $this->lifetime,
'loggedOut' => $this->loggedOut,
+ 'persisted' => $this->persist,
);
\Hooks::run( 'SessionMetadata', array( $this, &$metadata, $this->requests ) );
if ( !empty( $metadata['forceHTTPS'] ) && !$info->forceHTTPS() ) {
$newParams['forceHTTPS'] = true;
}
+ if ( !empty( $metadata['persisted'] ) && !$info->wasPersisted() ) {
+ $newParams['persisted'] = true;
+ }
if ( !$info->isIdSafe() ) {
$newParams['idIsSafe'] = true;
);
}
- /**
- * Test for support or lack of support for specific attributes in the attribute whitelist.
- */
- public static function provideAttributeSupport() {
- /** array( <attributes>, <expected>, <message> ) */
- return array(
- array(
- 'div',
- ' role="presentation"',
- ' role="presentation"',
- 'Support for WAI-ARIA\'s role="presentation".'
- ),
- array( 'div', ' role="main"', '', "Other WAI-ARIA roles are currently not supported." ),
- );
- }
-
- /**
- * @dataProvider provideAttributeSupport
- * @covers Sanitizer::fixTagAttributes
- */
- public function testAttributeSupport( $tag, $attributes, $expected, $message ) {
- $this->assertEquals( $expected,
- Sanitizer::fixTagAttributes( $attributes, $tag ),
- $message
- );
- }
-
/**
* @dataProvider provideEscapeHtmlAllowEntities
* @covers Sanitizer::escapeHtmlAllowEntities
array( '<script>foo</script>', '<script>foo</script>' ),
);
}
+
+ /**
+ * Test escapeIdReferenceList for consistency with escapeId
+ *
+ * @dataProvider provideEscapeIdReferenceList
+ * @covers Sanitizer::escapeIdReferenceList
+ */
+ public function testEscapeIdReferenceList( $referenceList, $id1, $id2 ) {
+ $this->assertEquals(
+ Sanitizer::escapeIdReferenceList( $referenceList, 'noninitial' ),
+ Sanitizer::escapeId( $id1, 'noninitial' )
+ . ' '
+ . Sanitizer::escapeId( $id2, 'noninitial' )
+ );
+ }
+
+ public static function provideEscapeIdReferenceList() {
+ /** array( <reference list>, <individual id 1>, <individual id 2> ) */
+ return array(
+ array( 'foo bar', 'foo', 'bar' ),
+ array( '#1 #2', '#1', '#2' ),
+ array( '+1 +2', '+1', '+2' ),
+ );
+ }
}
$oInfo = $context->exportSession();
$this->assertEquals( '127.0.0.1', $oInfo['ip'], "Correct initial IP address." );
$this->assertEquals( 0, $oInfo['userId'], "Correct initial user ID." );
+ $this->assertFalse( MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent(),
+ 'Global session isn\'t persistent to start' );
$user = User::newFromName( 'UnitTestContextUser' );
$user->addToDatabase();
$this->assertEquals( $oInfo['headers'], $info['headers'], "Correct restored headers." );
$this->assertEquals( $oInfo['sessionId'], $info['sessionId'], "Correct restored session ID." );
$this->assertEquals( $oInfo['userId'], $info['userId'], "Correct restored user ID." );
+ $this->assertFalse( MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent(),
+ 'Global session isn\'t persistent after restoring the context' );
}
}
$this->assertTrue( $info->forceHTTPS() );
$this->assertSame( array(), $logger->getBuffer() );
+ // "Persist" flag from session
+ $this->store->setSessionMeta( $id, $metadata );
+ $info = new SessionInfo( SessionInfo::MIN_PRIORITY, array(
+ 'provider' => $provider,
+ 'id' => $id,
+ 'userInfo' => $userInfo
+ ) );
+ $this->assertTrue( $loadSessionInfoFromStore( $info ) );
+ $this->assertFalse( $info->wasPersisted() );
+ $this->assertSame( array(), $logger->getBuffer() );
+
+ $this->store->setSessionMeta( $id, array( 'persisted' => true ) + $metadata );
+ $info = new SessionInfo( SessionInfo::MIN_PRIORITY, array(
+ 'provider' => $provider,
+ 'id' => $id,
+ 'userInfo' => $userInfo
+ ) );
+ $this->assertTrue( $loadSessionInfoFromStore( $info ) );
+ $this->assertTrue( $info->wasPersisted() );
+ $this->assertSame( array(), $logger->getBuffer() );
+
+ $this->store->setSessionMeta( $id, array( 'persisted' => false ) + $metadata );
+ $info = new SessionInfo( SessionInfo::MIN_PRIORITY, array(
+ 'provider' => $provider,
+ 'id' => $id,
+ 'userInfo' => $userInfo,
+ 'persisted' => true
+ ) );
+ $this->assertTrue( $loadSessionInfoFromStore( $info ) );
+ $this->assertTrue( $info->wasPersisted() );
+ $this->assertSame( array(), $logger->getBuffer() );
+
// Provider refreshSessionInfo() returning false
$info = new SessionInfo( SessionInfo::MIN_PRIORITY, array(
'provider' => $provider3,