Page MenuHomeWickedGov Phorge

Utils.php
No OneTemporary

Size
13 KB
Referenced Files
None
Subscribers
None

Utils.php

<?php
namespace MediaWiki\Extension\OAuth\Backend;
use MediaWiki\Context\RequestContext;
use MediaWiki\Deferred\AutoCommitUpdate;
use MediaWiki\Deferred\DeferredUpdates;
use MediaWiki\Extension\Notifications\Model\Event;
use MediaWiki\Extension\OAuth\Lib\OAuthSignatureMethodHmacSha1;
use MediaWiki\MediaWikiServices;
use MediaWiki\Request\WebRequest;
use MediaWiki\Title\Title;
use MediaWiki\User\CentralId\CentralIdLookup;
use MediaWiki\User\User;
use MediaWiki\WikiMap\WikiMap;
use MWException;
use Wikimedia\ObjectCache\BagOStuff;
use Wikimedia\Rdbms\IDatabase;
/**
* Static utility functions for OAuth
*
* @file
* @ingroup OAuth
*/
class Utils {
/**
* @return bool
*/
public static function isCentralWiki() {
global $wgMWOAuthCentralWiki;
return ( WikiMap::getCurrentWikiId() === $wgMWOAuthCentralWiki );
}
/**
* @return string|bool
*/
public static function getCentralWiki() {
global $wgMWOAuthCentralWiki;
return $wgMWOAuthCentralWiki;
}
/**
* @param int $index DB_PRIMARY/DB_REPLICA
* @return IDatabase
*/
public static function getCentralDB( $index ) {
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
// T244415: Use the primary database if there were changes
if ( $index === DB_REPLICA && $lbFactory->hasOrMadeRecentPrimaryChanges() ) {
$index = DB_PRIMARY;
}
$wikiId = self::getCentralWiki();
if ( WikiMap::isCurrentWikiId( $wikiId ) ) {
$wikiId = false;
}
return $lbFactory->getMainLB( $wikiId )->getConnection(
$index, [], $wikiId );
}
/**
* @return BagOStuff
*/
public static function getSessionCache() {
global $wgMWOAuthSessionCacheType;
global $wgSessionCacheType;
$sessionCacheType = $wgMWOAuthSessionCacheType ?? $wgSessionCacheType;
return MediaWikiServices::getInstance()
->getObjectCacheFactory()->getInstance( $sessionCacheType );
}
/**
* Get the cache type for OAuth 1.0 nonces
* @return BagOStuff
*/
public static function getNonceCache() {
global $wgMWOAuthNonceCacheType, $wgMWOAuthSessionCacheType, $wgSessionCacheType;
$cacheType = $wgMWOAuthNonceCacheType
?? $wgMWOAuthSessionCacheType ?? $wgSessionCacheType;
return MediaWikiServices::getInstance()
->getObjectCacheFactory()->getInstance( $cacheType );
}
/**
* @param IDatabase $db
* @return int[]
*/
public static function getConsumerStateCounts( IDatabase $db ) {
$res = $db->newSelectQueryBuilder()
->select( [ 'oarc_stage', 'count' => 'COUNT(*)' ] )
->from( 'oauth_registered_consumer' )
->groupBy( 'oarc_stage' )
->caller( __METHOD__ )
->fetchResultSet();
$table = [
Consumer::STAGE_APPROVED => 0,
Consumer::STAGE_DISABLED => 0,
Consumer::STAGE_EXPIRED => 0,
Consumer::STAGE_PROPOSED => 0,
Consumer::STAGE_REJECTED => 0,
];
foreach ( $res as $row ) {
$table[(int)$row->oarc_stage] = (int)$row->count;
}
return $table;
}
/**
* Get request headers.
* Sanitizes the output of apache_request_headers because
* we always want the keys to be Cased-Like-This and arh()
* returns the headers in the same case as they are in the
* request
* @return array Header name => value
*/
public static function getHeaders() {
$request = RequestContext::getMain()->getRequest();
$headers = $request->getAllHeaders();
$out = [];
foreach ( $headers as $key => $value ) {
$key = str_replace(
" ",
"-",
ucwords( strtolower( str_replace( "-", " ", $key ) ) )
);
$out[$key] = $value;
}
return $out;
}
/**
* Test this request for an OAuth Authorization header
* @param WebRequest $request the MediaWiki request
* @return bool true if a header was found
*/
public static function hasOAuthHeaders( WebRequest $request ) {
$header = $request->getHeader( 'Authorization' );
return $header !== false && strpos( $header, 'OAuth ' ) === 0;
}
/**
* Make a cache key for the given arguments, that (hopefully) won't clash with
* anything else in your cache
* @param string ...$args
* @return string
*/
public static function getCacheKey( ...$args ) {
global $wgMWOAuthCentralWiki;
return "OAUTH:$wgMWOAuthCentralWiki:" . implode( ':', $args );
}
/**
* @param IDatabase $dbw
* @return void
*/
public static function runAutoMaintenance( IDatabase $dbw ) {
global $wgMWOAuthRequestExpirationAge;
if ( $wgMWOAuthRequestExpirationAge <= 0 ) {
return;
}
$cutoff = time() - $wgMWOAuthRequestExpirationAge;
$fname = __METHOD__;
DeferredUpdates::addUpdate(
new AutoCommitUpdate(
$dbw,
__METHOD__,
static function ( IDatabase $dbw ) use ( $cutoff, $fname ) {
$dbw->newUpdateQueryBuilder()
->update( 'oauth_registered_consumer' )
->set( [
'oarc_stage' => Consumer::STAGE_EXPIRED,
'oarc_stage_timestamp' => $dbw->timestamp()
] )
->where( [
'oarc_stage' => Consumer::STAGE_PROPOSED,
$dbw->expr( 'oarc_stage_timestamp', '<', $dbw->timestamp( $cutoff ) )
] )
->caller( $fname )
->execute();
}
)
);
}
/**
* Get the pretty name of an OAuth wiki ID restriction value
*
* @param string $wikiId A wiki ID or '*'
* @return string
*/
public static function getWikiIdName( $wikiId ) {
if ( $wikiId === '*' ) {
return wfMessage( 'mwoauth-consumer-allwikis' )->text();
}
$host = WikiMap::getWikiName( $wikiId );
if ( strpos( $host, '.' ) ) {
// e.g. "en.wikipedia.org"
return $host;
}
return $wikiId;
}
/**
* Get the pretty names of all local wikis
*
* @return string[] associative array of local wiki names indexed by wiki ID
*/
public static function getAllWikiNames() {
global $wgConf;
$wikiNames = [];
foreach ( $wgConf->getLocalDatabases() as $dbname ) {
$name = self::getWikiIdName( $dbname );
if ( $name != $dbname ) {
$wikiNames[$dbname] = $name;
}
}
return $wikiNames;
}
/**
* Quickly get a new server with all the default configurations
*
* @return MWOAuthServer with default configurations
*/
public static function newMWOAuthServer() {
$store = static::newMWOAuthDataStore();
$server = new MWOAuthServer( $store );
$server->add_signature_method( new OAuthSignatureMethodHmacSha1() );
$server->add_signature_method( new MWOAuthSignatureMethodRsaSha1( $store ) );
return $server;
}
public static function newMWOAuthDataStore() {
$lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
$dbr = self::getCentralDB( DB_REPLICA );
$dbw = $lb->getServerCount() > 1 ? self::getCentralDB( DB_PRIMARY ) : null;
return new MWOAuthDataStore( $dbr, $dbw, self::getSessionCache(), self::getNonceCache() );
}
/**
* Given a central wiki user ID, get a central username
*
* @param int $userId
* @param bool|User|string $audience show hidden names based on this user, or false for public
* @throws MWException
* @return string|bool Username, false if not found, empty string if name is hidden
*/
public static function getCentralUserNameFromId( $userId, $audience = false ) {
global $wgMWOAuthSharedUserIDs, $wgMWOAuthSharedUserSource;
// global ID required via hook
if ( $wgMWOAuthSharedUserIDs ) {
$lookup = MediaWikiServices::getInstance()
->getCentralIdLookupFactory()
->getLookup( $wgMWOAuthSharedUserSource );
$name = $lookup->nameFromCentralId(
$userId,
$audience === 'raw'
? CentralIdLookup::AUDIENCE_RAW
: ( $audience ?: CentralIdLookup::AUDIENCE_PUBLIC )
);
if ( $name === null ) {
$name = false;
}
} else {
$name = '';
$user = User::newFromId( $userId );
$permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
if ( $audience === 'raw'
|| !$user->isHidden()
|| ( $audience instanceof User && $permissionManager->userHasRight( $audience, 'hideuser' ) )
) {
$name = $user->getName();
}
}
return $name;
}
/**
* Given a central wiki user ID, get a local User object
*
* @param int $userId
* @return User|false False if not found
*/
public static function getLocalUserFromCentralId( $userId ) {
global $wgMWOAuthSharedUserIDs, $wgMWOAuthSharedUserSource;
// global ID required via hook
if ( $wgMWOAuthSharedUserIDs ) {
$lookup = MediaWikiServices::getInstance()
->getCentralIdLookupFactory()
->getLookup( $wgMWOAuthSharedUserSource );
$user = $lookup->localUserFromCentralId( $userId );
if ( $user === null || !$lookup->isAttached( $user ) ) {
return false;
}
return User::newFromIdentity( $user );
}
return User::newFromId( $userId );
}
/**
* Given a local User object, get the user ID for that user on the central wiki
*
* @param User $user
* @return int|bool ID or false if not found
*/
public static function getCentralIdFromLocalUser( User $user ) {
global $wgMWOAuthSharedUserIDs, $wgMWOAuthSharedUserSource;
// global ID required via hook
if ( $wgMWOAuthSharedUserIDs ) {
// T227688 do not rely on array auto-creation for non-stdClass
if ( !isset( $user->oAuthUserData ) ) {
$user->oAuthUserData = [];
}
if ( isset( $user->oAuthUserData['centralId'] ) ) {
$id = $user->oAuthUserData['centralId'];
} else {
$lookup = MediaWikiServices::getInstance()
->getCentralIdLookupFactory()
->getLookup( $wgMWOAuthSharedUserSource );
if ( !$lookup->isAttached( $user ) ) {
$id = false;
} else {
$id = $lookup->centralIdFromLocalUser( $user );
if ( $id === 0 ) {
$id = false;
}
}
// Process cache the result to avoid queries
$user->oAuthUserData['centralId'] = $id;
}
} else {
$id = $user->getId();
}
return $id;
}
/**
* Given a username, get the user ID for that user on the central wiki.
* @param string $username
* @return int|bool ID or false if not found
*/
public static function getCentralIdFromUserName( $username ) {
global $wgMWOAuthSharedUserIDs, $wgMWOAuthSharedUserSource;
// global ID required via hook
if ( $wgMWOAuthSharedUserIDs ) {
$lookup = MediaWikiServices::getInstance()
->getCentralIdLookupFactory()
->getLookup( $wgMWOAuthSharedUserSource );
$id = $lookup->centralIdFromName( $username );
if ( $id === 0 ) {
$id = false;
}
} else {
$id = false;
$user = User::newFromName( $username );
if ( $user instanceof User && $user->getId() > 0 ) {
$id = $user->getId();
}
}
return $id;
}
/**
* Get the effective secret key/token to use for OAuth purposes.
*
* For example, the "secret key" and "access secret" values that are
* used for authenticating request should be the result of applying this
* function to the respective values stored in the DB. This means that
* a leak of DB values is not enough to impersonate consumers.
*
* @param string $secret
* @return string
*/
public static function hmacDBSecret( $secret ) {
global $wgOAuthSecretKey, $wgSecretKey;
$secretKey = $wgOAuthSecretKey ?? $wgSecretKey;
return $secretKey ? hash_hmac( 'sha1', $secret, $secretKey ) : $secret;
}
/**
* Get a link to the central wiki's user talk page of a user.
*
* @param string $username the username of the User Talk link
* @return string the (proto-relative, urlencoded) url of the central wiki's user talk page
*/
public static function getCentralUserTalk( $username ) {
global $wgMWOAuthCentralWiki, $wgMWOAuthSharedUserIDs;
if ( $wgMWOAuthSharedUserIDs ) {
$url = WikiMap::getForeignURL(
$wgMWOAuthCentralWiki,
"User_talk:$username"
);
} else {
$url = Title::makeTitleSafe( NS_USER_TALK, $username )->getFullURL();
}
return $url;
}
/**
* @param array $grants
* @return bool
*/
public static function grantsAreValid( array $grants ) {
// Remove our special grants before calling the core method
$grants = array_diff( $grants, [ 'mwoauth-authonly', 'mwoauth-authonlyprivate' ] );
return MediaWikiServices::getInstance()
->getGrantsInfo()
->grantsAreValid( $grants );
}
/**
* Given an OAuth consumer stage change event, find out who needs to be notified.
* Will be used as an EchoAttributeManager::ATTR_LOCATORS callback.
* @param Event $event
* @return User[]
*/
public static function locateUsersToNotify( Event $event ) {
$agent = $event->getAgent();
$owner = self::getLocalUserFromCentralId( $event->getExtraParam( 'owner-id' ) );
$users = [];
switch ( $event->getType() ) {
case 'oauth-app-propose':
// notify OAuth admins about new proposed apps
$oauthAdmins = self::getOAuthAdmins();
foreach ( $oauthAdmins as $admin ) {
if ( $admin->equals( $owner ) ) {
continue;
}
$users[$admin->getId()] = $admin;
}
break;
case 'oauth-app-update':
case 'oauth-app-approve':
case 'oauth-app-reject':
case 'oauth-app-disable':
case 'oauth-app-reenable':
// notify owner if someone else changed the status of the app
if ( !$owner->equals( $agent ) ) {
$users[$owner->getId()] = $owner;
}
break;
}
return $users;
}
/**
* Get the change tag name for a given consumer.
* @param int $consumerId
* @return string
*/
public static function getTagName( $consumerId ) {
return 'OAuth CID: ' . (int)$consumerId;
}
/**
* Check if a given change tag name should be reserved for this extension.
* @param string $tagName
* @return bool
*/
public static function isReservedTagName( $tagName ) {
return stripos( $tagName, 'oauth cid:' ) === 0;
}
/**
* Return a list of all OAuth admins (or the first 5000 in the unlikely case that there is more
* than that).
* Should be called on the central OAuth wiki.
* @return User[]
*/
protected static function getOAuthAdmins() {
global $wgOAuthGroupsToNotify;
if ( !$wgOAuthGroupsToNotify ) {
return [];
}
return iterator_to_array( User::findUsersByGroup( $wgOAuthGroupsToNotify ) );
}
}

File Metadata

Mime Type
text/x-php
Expires
Sat, May 16, 20:10 (2 h, 47 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
cb/f0/330b47f122f9ff55ea25c70eb23c
Default Alt Text
Utils.php (13 KB)

Event Timeline