diff --git a/config.inc.php b/config.inc.php index 69fec4bf7..8e9618bb2 100644 --- a/config.inc.php +++ b/config.inc.php @@ -90,8 +90,6 @@ // Formerly-used OAuth tokens to permit reading identities from $oauthLegacyTokens = []; -$oauthMediaWikiCanonicalServer = "https://en.wikipedia.org"; - $useOauthSignup = true; $enforceOAuth = false; @@ -254,7 +252,6 @@ function wfDebugLog($section, $message) ->setOAuthConsumerToken($oauthConsumerToken) ->setOAuthLegacyConsumerTokens($oauthLegacyTokens) ->setOAuthConsumerSecret($oauthSecretToken) - ->setOauthMediaWikiCanonicalServer($oauthMediaWikiCanonicalServer) ->setDataClearInterval($dataclear_interval) ->setXffTrustedHostsFile($xff_trusted_hosts_file) ->setIrcNotificationsEnabled($ircBotNotificationsEnabled == 1) diff --git a/includes/Background/CreationTaskBase.php b/includes/Background/CreationTaskBase.php index 4ef64ce1d..02a1032f9 100644 --- a/includes/Background/CreationTaskBase.php +++ b/includes/Background/CreationTaskBase.php @@ -9,6 +9,7 @@ namespace Waca\Background; use Exception; +use Waca\DataObjects\Domain; use Waca\DataObjects\Request; use Waca\DataObjects\User; use Waca\ExceptionHandler; @@ -111,8 +112,14 @@ protected abstract function getMediaWikiClient(); protected function getMediaWikiHelper() { + /** @var Domain $domain */ + $domain = Domain::getById($this->getJob()->getDomain(), $this->getDatabase()); + if ($this->mwHelper === null) { - $this->mwHelper = new MediaWikiHelper($this->getMediaWikiClient(), $this->getSiteConfiguration()); + $this->mwHelper = new MediaWikiHelper( + $this->getMediaWikiClient(), + $this->getSiteConfiguration(), + $domain->getWikiApiPath()); } return $this->mwHelper; diff --git a/includes/Background/Task/WelcomeUserTask.php b/includes/Background/Task/WelcomeUserTask.php index 71908b5ac..ec8dd6082 100644 --- a/includes/Background/Task/WelcomeUserTask.php +++ b/includes/Background/Task/WelcomeUserTask.php @@ -9,6 +9,7 @@ namespace Waca\Background\Task; use Waca\Background\BackgroundTaskBase; +use Waca\DataObjects\Domain; use Waca\DataObjects\Request; use Waca\DataObjects\User; use Waca\DataObjects\WelcomeTemplate; @@ -27,6 +28,10 @@ public function execute() $database = $this->getDatabase(); $this->request = $this->getRequest(); $user = $this->getTriggerUser(); + + /** @var Domain $domain */ + $domain = Domain::getById($this->getJob()->getDomain(), $database); + $userPrefs = new PreferenceManager($database, $user->getId(), $this->request->getDomain()); $welcomeTemplate = $userPrefs->getPreference(PreferenceManager::PREF_WELCOMETEMPLATE); @@ -48,7 +53,7 @@ public function execute() $oauth = new OAuthUserHelper($user, $database, $this->getOauthProtocolHelper(), $this->getSiteConfiguration()); - $mediaWikiHelper = new MediaWikiHelper($oauth, $this->getSiteConfiguration()); + $mediaWikiHelper = new MediaWikiHelper($oauth, $this->getSiteConfiguration(), $domain->getWikiApiPath()); if ($this->request->getStatus() !== RequestStatus::CLOSED) { $this->markFailed('Request is currently open'); diff --git a/includes/DataObjects/Domain.php b/includes/DataObjects/Domain.php index c480000a7..85eeb31b0 100644 --- a/includes/DataObjects/Domain.php +++ b/includes/DataObjects/Domain.php @@ -94,6 +94,27 @@ public static function getByShortName(string $shortName, PdoDatabase $database) return $result; } + public static function getByApiPath(string $apiPath, PdoDatabase $database) + { + $statement = $database->prepare(<<execute([ + ':api' => $apiPath, + ]); + + /** @var RequestForm|false $result */ + $result = $statement->fetchObject(get_called_class()); + + if ($result !== false) { + $result->setDatabase($database); + } + + return $result; + } + public static function getAll(PdoDatabase $database) { $statement = $database->prepare("SELECT * FROM domain;"); $statement->execute(); diff --git a/includes/Helpers/BotMediaWikiClient.php b/includes/Helpers/BotMediaWikiClient.php index e90e4aa14..485a04de2 100644 --- a/includes/Helpers/BotMediaWikiClient.php +++ b/includes/Helpers/BotMediaWikiClient.php @@ -49,7 +49,7 @@ public function __construct(SiteConfiguration $siteConfiguration, Domain $domain ); } - public function doApiCall($apiParams, $method = 'GET') + public function doApiCall($apiParams, string $method, string $apiPath) { $this->ensureLoggedIn(); $apiParams['assert'] = 'user'; @@ -85,12 +85,13 @@ private function ensureLoggedIn() /** * @param $apiParams * @param $method + * @param $apiPath * * @return mixed * @throws ApplicationLogicException * @throws CurlException */ - private function callApi($apiParams, $method) + private function callApi($apiParams, $method, $apiPath) { $apiParams['format'] = 'json'; diff --git a/includes/Helpers/Interfaces/IMediaWikiClient.php b/includes/Helpers/Interfaces/IMediaWikiClient.php index 5d2384108..2c5416f0d 100644 --- a/includes/Helpers/Interfaces/IMediaWikiClient.php +++ b/includes/Helpers/Interfaces/IMediaWikiClient.php @@ -10,5 +10,5 @@ interface IMediaWikiClient { - function doApiCall($params, $method); + function doApiCall($params, string $method, string $apiPath); } \ No newline at end of file diff --git a/includes/Helpers/Interfaces/IOAuthProtocolHelper.php b/includes/Helpers/Interfaces/IOAuthProtocolHelper.php index 7ab329566..617796629 100644 --- a/includes/Helpers/Interfaces/IOAuthProtocolHelper.php +++ b/includes/Helpers/Interfaces/IOAuthProtocolHelper.php @@ -63,5 +63,5 @@ public function getIdentityTicket($oauthAccessToken, $oauthAccessSecret); * @throws CurlException * @throws Exception */ - public function apiCall($apiParams, $accessToken, $accessSecret, $method = 'GET'); + public function apiCall($apiParams, $accessToken, $accessSecret, $method = 'GET', string $apiPath); } \ No newline at end of file diff --git a/includes/Helpers/MediaWikiHelper.php b/includes/Helpers/MediaWikiHelper.php index 21391098d..f256681cf 100644 --- a/includes/Helpers/MediaWikiHelper.php +++ b/includes/Helpers/MediaWikiHelper.php @@ -23,17 +23,20 @@ class MediaWikiHelper * @var SiteConfiguration */ private $siteConfiguration; + private string $apiPath; /** * MediaWikiHelper constructor. * * @param IMediaWikiClient $mediaWikiClient * @param SiteConfiguration $siteConfiguration + * @param string $apiPath */ - public function __construct(IMediaWikiClient $mediaWikiClient, SiteConfiguration $siteConfiguration) + public function __construct(IMediaWikiClient $mediaWikiClient, SiteConfiguration $siteConfiguration, string $apiPath) { $this->mediaWikiClient = $mediaWikiClient; $this->siteConfiguration = $siteConfiguration; + $this->apiPath = $apiPath; } /** @@ -55,7 +58,7 @@ public function createAccount($username, $emailAddress, $reason) 'type' => 'createaccount', ); - $response = $this->mediaWikiClient->doApiCall($tokenParams, 'POST'); + $response = $this->mediaWikiClient->doApiCall($tokenParams, 'POST', $this->apiPath); if (isset($response->error)) { throw new MediaWikiApiException($response->error->code . ': ' . $response->error->info); @@ -83,7 +86,7 @@ public function createAccount($username, $emailAddress, $reason) $createParams['email'] = $emailAddress; $createParams['reason'] = $reason; - $createResponse = $this->mediaWikiClient->doApiCall($createParams, 'POST'); + $createResponse = $this->mediaWikiClient->doApiCall($createParams, 'POST', $this->apiPath); if (isset($createResponse->error)) { throw new MediaWikiApiException($response->error->code . ': ' . $response->error->info); @@ -123,7 +126,7 @@ public function addTalkPageMessage($username, $title, $summary, $message, $creat 'type' => 'csrf', ); - $response = $this->mediaWikiClient->doApiCall($tokenParams, 'POST'); + $response = $this->mediaWikiClient->doApiCall($tokenParams, 'POST', $this->apiPath); if (isset($response->error)) { throw new MediaWikiApiException($response->error->code . ': ' . $response->error->info); @@ -149,7 +152,7 @@ public function addTalkPageMessage($username, $title, $summary, $message, $creat $editParameters['createonly'] = true; } - $response = $this->mediaWikiClient->doApiCall($editParameters, 'POST'); + $response = $this->mediaWikiClient->doApiCall($editParameters, 'POST', $this->apiPath); if (!isset($response->edit)) { if (isset($response->error)) { @@ -176,7 +179,7 @@ public function getCreationFieldData(&$requiredFields, &$checkboxFields) 'amirequestsfor' => 'create', ); - $response = $this->mediaWikiClient->doApiCall($params, 'GET'); + $response = $this->mediaWikiClient->doApiCall($params, 'GET', $this->apiPath); if (isset($response->error)) { throw new MediaWikiApiException($response->error->code . ': ' . $response->error->info); @@ -250,7 +253,7 @@ public function checkAccountExists($username) 'ususers' => $username, ); - $apiResult = $this->mediaWikiClient->doApiCall($parameters, 'GET'); + $apiResult = $this->mediaWikiClient->doApiCall($parameters, 'GET', $this->apiPath); $entry = $apiResult->query->users[0]; $exists = !isset($entry->missing); @@ -277,7 +280,7 @@ public function getHtmlForWikiText($wikiText) 'text' => $wikiText, ); - $apiResult = $this->mediaWikiClient->doApiCall($parameters, 'GET'); + $apiResult = $this->mediaWikiClient->doApiCall($parameters, 'GET', $this->apiPath); return $apiResult->parse->text->{'*'}; } diff --git a/includes/Helpers/OAuthProtocolHelper.php b/includes/Helpers/OAuthProtocolHelper.php index a388bbe0b..de9c68e9f 100644 --- a/includes/Helpers/OAuthProtocolHelper.php +++ b/includes/Helpers/OAuthProtocolHelper.php @@ -63,9 +63,8 @@ public function getRequestToken() { /** @var Token $requestToken */ - // FIXME: domains! /** @var Domain $domain */ - $domain = Domain::getById(1, $this->database); + $domain = Domain::getCurrent($this->database); list($authUrl, $requestToken) = $this->getClient($domain)->initiate(); $this->authUrl = $authUrl; @@ -87,9 +86,8 @@ public function callbackCompleted($oauthRequestToken, $oauthRequestSecret, $oaut { $requestToken = new Token($oauthRequestToken, $oauthRequestSecret); - // FIXME: domains! /** @var Domain $domain */ - $domain = Domain::getById(1, $this->database); + $domain = Domain::getCurrent($this->database); return $this->getClient($domain)->complete($requestToken, $oauthVerifier); } @@ -99,9 +97,8 @@ public function callbackCompleted($oauthRequestToken, $oauthRequestSecret, $oaut */ public function getIdentityTicket($oauthAccessToken, $oauthAccessSecret) { - // FIXME: domains! /** @var Domain $domain */ - $domain = Domain::getById(1, $this->database); + $domain = Domain::getCurrent($this->database); return $this->getClient($domain)->identify(new Token($oauthAccessToken, $oauthAccessSecret)); } @@ -109,7 +106,7 @@ public function getIdentityTicket($oauthAccessToken, $oauthAccessSecret) /** * @inheritDoc */ - public function apiCall($apiParams, $accessToken, $accessSecret, $method = 'GET') + public function apiCall($apiParams, $accessToken, $accessSecret, $method = 'GET', string $apiPath) { $userToken = new Token($accessToken, $accessSecret); @@ -119,11 +116,10 @@ public function apiCall($apiParams, $accessToken, $accessSecret, $method = 'GET' throw new CurlException("Invalid API call"); } - // FIXME: domains! /** @var Domain $domain */ - $domain = Domain::getById(1, $this->database); + $domain = Domain::getByApiPath($apiPath, $this->database); - $url = $domain->getWikiApiPath(); + $url = $apiPath; $isPost = ($method === 'POST'); if ($method === 'GET') { diff --git a/includes/Helpers/OAuthUserHelper.php b/includes/Helpers/OAuthUserHelper.php index ce8b3a74b..f863a408d 100644 --- a/includes/Helpers/OAuthUserHelper.php +++ b/includes/Helpers/OAuthUserHelper.php @@ -11,6 +11,7 @@ use DateTimeImmutable; use MediaWiki\OAuthClient\Exception; use PDOStatement; +use Waca\DataObjects\Domain; use Waca\DataObjects\OAuthIdentity; use Waca\DataObjects\OAuthToken; use Waca\DataObjects\User; @@ -67,6 +68,8 @@ class OAuthUserHelper implements IMediaWikiClient private $legacyTokens; + private array $issuers; + #region Static methods public static function findUserByRequestToken($requestToken, PdoDatabase $database) @@ -355,13 +358,13 @@ public function getIdentity($expiredOk = false) return $this->identity; } - public function doApiCall($params, $method) + public function doApiCall($params, string $method, string $apiPath) { // Ensure we're logged in $params['assert'] = 'user'; $token = $this->loadAccessToken(); - return $this->oauthProtocolHelper->apiCall($params, $token->getToken(), $token->getSecret(), $method); + return $this->oauthProtocolHelper->apiCall($params, $token->getToken(), $token->getSecret(), $method, $apiPath); } /** @@ -372,6 +375,7 @@ public function doApiCall($params, $method) private function identityIsValid($expiredOk = false) { $this->loadIdentity(); + $this->loadTokenIssuers(); if ($this->identity === null) { return false; @@ -408,8 +412,17 @@ private function identityIsValid($expiredOk = false) } } - if ($this->identity->getIssuer() !== $this->siteConfiguration->getOauthMediaWikiCanonicalServer()) { - // token not issued by the right person + $issuerFound = false; + $ticketIssuer = $this->identity->getIssuer(); + foreach ($this->issuers as $issuer) { + if (substr($issuer, 0, strlen($ticketIssuer)) === $ticketIssuer) { + $issuerFound = true; + break; + } + } + + if (!$issuerFound) { + // Issuer not found in domains return false; } @@ -481,4 +494,9 @@ private function loadAccessToken() return $this->accessToken; } + + private function loadTokenIssuers() + { + $this->issuers = array_map( fn(Domain $d): string => $d->getWikiApiPath(), Domain::getAll($this->database)); + } } diff --git a/includes/Pages/PageWelcomeTemplateManagement.php b/includes/Pages/PageWelcomeTemplateManagement.php index 32f30e8af..eed03eb05 100644 --- a/includes/Pages/PageWelcomeTemplateManagement.php +++ b/includes/Pages/PageWelcomeTemplateManagement.php @@ -121,7 +121,7 @@ protected function view() $oauth = new OAuthUserHelper($currentUser, $database, $this->getOauthProtocolHelper(), $this->getSiteConfiguration()); - $mediaWikiHelper = new MediaWikiHelper($oauth, $this->getSiteConfiguration()); + $mediaWikiHelper = new MediaWikiHelper($oauth, $this->getSiteConfiguration(), $domain->getWikiApiPath()); $templateHtml = $mediaWikiHelper->getHtmlForWikiText($wikiText); diff --git a/includes/Security/ContentSecurityPolicyManager.php b/includes/Security/ContentSecurityPolicyManager.php index 3ace15b77..4e3a030b6 100644 --- a/includes/Security/ContentSecurityPolicyManager.php +++ b/includes/Security/ContentSecurityPolicyManager.php @@ -8,6 +8,7 @@ namespace Waca\Security; +use Waca\DataObjects\Domain; use Waca\SiteConfiguration; class ContentSecurityPolicyManager @@ -33,6 +34,7 @@ class ContentSecurityPolicyManager * @var SiteConfiguration */ private $configuration; + private ?Domain $currentDomain = null; /** * ContentSecurityPolicyManager constructor. @@ -53,6 +55,11 @@ public function getNonce() return $this->nonce; } + public function setDomain(Domain $domain) + { + $this->currentDomain = $domain; + } + public function getHeader(): string { $reportOnly = ''; @@ -82,8 +89,14 @@ public function getHeader(): string } break; case 'oauth': - $policyIsSet = true; - $constructedPolicy .= "{$this->configuration->getOauthMediaWikiCanonicalServer()} "; + if ($this->currentDomain !== null) { + $policyIsSet = true; + + $parts = parse_url($this->currentDomain->getWikiApiPath()); + $bareHost = "${parts['scheme']}://${parts['host']}"; + + $constructedPolicy .= "{$bareHost} "; + } break; default: $policyIsSet = true; diff --git a/includes/SiteConfiguration.php b/includes/SiteConfiguration.php index 610932908..a85473d95 100644 --- a/includes/SiteConfiguration.php +++ b/includes/SiteConfiguration.php @@ -693,26 +693,6 @@ public function getOauthIdentityGraceTime() return $this->oauthIdentityGraceTime; } - /** - * @param string $oauthMediaWikiCanonicalServer - * - * @return SiteConfiguration - */ - public function setOauthMediaWikiCanonicalServer($oauthMediaWikiCanonicalServer) - { - $this->oauthMediaWikiCanonicalServer = $oauthMediaWikiCanonicalServer; - - return $this; - } - - /** - * @return string - */ - public function getOauthMediaWikiCanonicalServer() - { - return $this->oauthMediaWikiCanonicalServer; - } - /** * @param string $creationBotUsername * diff --git a/includes/Tasks/InternalPageBase.php b/includes/Tasks/InternalPageBase.php index 5fbdc5f8f..1b342f675 100644 --- a/includes/Tasks/InternalPageBase.php +++ b/includes/Tasks/InternalPageBase.php @@ -10,6 +10,7 @@ use Exception; use PDO; +use Waca\DataObjects\Domain; use Waca\DataObjects\SiteNotice; use Waca\DataObjects\User; use Waca\Exceptions\AccessDeniedException; @@ -107,6 +108,10 @@ final public function finalisePage() $database = $this->getDatabase(); $currentUser = User::getCurrent($database); + if (!$currentUser->isCommunityUser()) { + $this->getCspManager()->setDomain(Domain::getCurrent($database)); + } + // Load in the badges for the navbar $this->setUpNavBarBadges($currentUser, $database);