Page MenuHomePhabricator

OAuth appears to have become more resource-hungry of late (m3api-oauth2 CI broken)
Closed, ResolvedPublic

Description

Sorry for the weird task title, I hope I can make it make sense. In m3api-oauth2, I have a CI job which runs tests against a local install of MediaWiki including the OAuth extension (so I can configure it with a shorter $wgOAuth2GrantExpirationInterval than on the Beta cluster; see T374562 previously). This test still worked on 15 September, but is now broken with a mixture of timeouts (PHP max_execution_time defaults to 30 seconds) and OOMKilled. However, interestingly enough, if I make CI check out an older commit (this change, but that’s just an arbitrary one I picked out of the Git log, and it looks like I went back at least a month more than should’ve been necessary), then CI passes again! So I thought this was worth flagging up here; cc @Tgr who’s been doing some work in this extension recently. (Note: it’s possible that the slowness and/or OOMness[?] only affects a mostly-default setup but is irrelevant in Wikimedia production.)

Event Timeline

Restricted Application added a subscriber: Aklapper. · View Herald Transcript

I’ll see if I can git bisect this down to a specific commit to blame…

Okay, git bisect says Deprecate $wgMWOAuthSharedUserIDs is the first bad commit. (I retried each CI job twice, i.e. three builds per commit, to hopefully rule out any flakiness. The results seem quite consistent.)

What $wgMWOAuthCentralWiki, $wgMWOAuthSharedUserIDs and $wgMWOAuthSharedUserSource settings are you using?

rEOAUc7c9d5f9b5f1: Deprecate $wgMWOAuthSharedUserIDs is a noop with CentralAuth. For a non-farm wiki it will replace some User::new* calls with LocalCentralIdLookup; maybe that's not cached as much as it should be?

But then I think for CI we would use the local lookup anyway, whether or not CentralAuth is installed, so not sure why the error doesn't occur in Wikimedia CI. Maybe it just has higher limits?

What $wgMWOAuthCentralWiki, $wgMWOAuthSharedUserIDs and $wgMWOAuthSharedUserSource settings are you using?

Whichever the default values are – the only changed OAuth settings in this CI setup are $wgOAuth2GrantExpirationInterval, $wgOAuth2PrivateKey and $wgOAuth2PublicKey.

But then I think for CI we would use the local lookup anyway, whether or not CentralAuth is installed, so not sure why the error doesn't occur in Wikimedia CI. Maybe it just has higher limits?

Could be… though I don’t see any significant difference in the CI times between Add Slovak special page aliases and Deprecate $wgMWOAuthSharedUserIDs, neither in the test build nor in gate-and-submit.

For a non-farm wiki it will replace some User::new* calls with LocalCentralIdLookup; maybe that's not cached as much as it should be?

Relevant task: T387105: LocalIdLookup should use a cache

Hm, I think OAuth might be not so much “resource-hungry” as “broken”? When running the m3api-oauth2 integration tests against a local wiki, with very little custom OAuth configuration, it encounters this error:

1<!DOCTYPE html>
2<html><head><title>Internal error - TheodenWiki</title><meta name="color-scheme" content="light dark" /><style>body { font-family: sans-serif; margin: 0; padding: 0.5em 2em; }</style></head><body>
3<div dir=ltr><div class="cdx-message--error cdx-message cdx-message--block"><span class="cdx-message__icon"></span><div class="cdx-message__content"><p>[e4b55be3600ba0fb56e03574] /w/api.php?formatversion=2&amp;assert=user&amp;action=query&amp;meta=userinfo&amp;format=json Error: Xdebug has detected a possible infinite loop, and aborted your script with a stack depth of &#039;8192&#039; frames</p><p>Backtrace:</p><p>from /home/lucas/git/wikimedia/mediawiki/includes/libs/Rdbms/Database/Domain/DatabaseDomain.php(45)<br />
4#0 /home/lucas/git/wikimedia/mediawiki/includes/libs/Rdbms/Database/Domain/DatabaseDomain.php(45): is_string()<br />
5#1 /home/lucas/git/wikimedia/mediawiki/includes/libs/Rdbms/Database/Domain/DatabaseDomain.php(96): Wikimedia\Rdbms\DatabaseDomain-&gt;__construct()<br />
6#2 /home/lucas/git/wikimedia/mediawiki/includes/libs/Rdbms/Database/DBConnRef.php(80): Wikimedia\Rdbms\DatabaseDomain::newFromId()<br />
7#3 /home/lucas/git/wikimedia/mediawiki/includes/libs/Rdbms/LoadBalancer/LoadBalancer.php(795): Wikimedia\Rdbms\DBConnRef-&gt;__construct()<br />
8#4 /home/lucas/git/wikimedia/mediawiki/includes/libs/Rdbms/LBFactory/LBFactory.php(596): Wikimedia\Rdbms\LoadBalancer-&gt;getConnection()<br />
9#5 /home/lucas/git/wikimedia/mediawiki/includes/libs/Rdbms/LBFactory/LBFactory.php(565): Wikimedia\Rdbms\LBFactory-&gt;getMappedDatabase()<br />
10#6 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/Backend/Utils.php(75): Wikimedia\Rdbms\LBFactory-&gt;getReplicaDatabase()<br />
11#7 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/Repository/DatabaseRepository.php(16): MediaWiki\Extension\OAuth\Backend\Utils::getCentralDB()<br />
12#8 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/Repository/AccessTokenRepository.php(99): MediaWiki\Extension\OAuth\Repository\DatabaseRepository-&gt;getDB()<br />
13#9 /home/lucas/git/wikimedia/mediawiki/vendor/league/oauth2-server/src/AuthorizationValidators/BearerTokenValidator.php(115): MediaWiki\Extension\OAuth\Repository\AccessTokenRepository-&gt;isAccessTokenRevoked()<br />
14#10 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/BearerTokenValidator.php(14): League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator-&gt;validateAuthorization()<br />
15#11 /home/lucas/git/wikimedia/mediawiki/vendor/league/oauth2-server/src/ResourceServer.php(84): MediaWiki\Extension\OAuth\BearerTokenValidator-&gt;validateAuthorization()<br />
16#12 /home/lucas/git/wikimedia/mediawiki/vendor/league/oauth2-server/src/Middleware/ResourceServerMiddleware.php(43): League\OAuth2\Server\ResourceServer-&gt;validateAuthenticatedRequest()<br />
17#13 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/ResourceServer.php(85): League\OAuth2\Server\Middleware\ResourceServerMiddleware-&gt;__invoke()<br />
18#14 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/SessionProvider.php(279): MediaWiki\Extension\OAuth\ResourceServer-&gt;verify()<br />
19#15 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/SessionProvider.php(109): MediaWiki\Extension\OAuth\SessionProvider-&gt;verifyOAuth2Request()<br />
20#16 /home/lucas/git/wikimedia/mediawiki/includes/Session/SessionManager.php(569): MediaWiki\Extension\OAuth\SessionProvider-&gt;provideSessionInfo()<br />
21#17 /home/lucas/git/wikimedia/mediawiki/includes/Session/SessionManager.php(136): MediaWiki\Session\SessionManager-&gt;getSessionInfoForRequest()<br />
22#18 /home/lucas/git/wikimedia/mediawiki/includes/Request/WebRequest.php(860): MediaWiki\Session\SessionManager-&gt;getSessionForRequest()<br />
23#19 /home/lucas/git/wikimedia/mediawiki/includes/Permissions/PermissionManager.php(1572): MediaWiki\Request\WebRequest-&gt;getSession()<br />
24#20 /home/lucas/git/wikimedia/mediawiki/includes/Permissions/PermissionManager.php(1514): MediaWiki\Permissions\PermissionManager-&gt;getUserPermissions()<br />
25#21 /home/lucas/git/wikimedia/mediawiki/includes/Permissions/UserAuthority.php(271): MediaWiki\Permissions\PermissionManager-&gt;userHasRight()<br />
26#22 /home/lucas/git/wikimedia/mediawiki/includes/Permissions/UserAuthority.php(130): MediaWiki\Permissions\UserAuthority-&gt;internalAllowed()<br />
27#23 /home/lucas/git/wikimedia/mediawiki/includes/User/User.php(2129): MediaWiki\Permissions\UserAuthority-&gt;isAllowed()<br />
28#24 /home/lucas/git/wikimedia/mediawiki/includes/User/CentralId/LocalIdLookup.php(104): MediaWiki\User\User-&gt;isAllowed()<br />
29#25 /home/lucas/git/wikimedia/mediawiki/includes/User/CentralId/CentralIdLookup.php(238): MediaWiki\User\CentralId\LocalIdLookup-&gt;lookupCentralIds()<br />
30#26 /home/lucas/git/wikimedia/mediawiki/includes/User/CentralId/CentralIdLookup.php(316): MediaWiki\User\CentralId\CentralIdLookup-&gt;nameFromCentralId()<br />
31#27 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/Backend/Utils.php(292): MediaWiki\User\CentralId\CentralIdLookup-&gt;localUserFromCentralId()<br />
32#28 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/ResourceServer.php(180): MediaWiki\Extension\OAuth\Backend\Utils::getLocalUserFromCentralId()<br />
33#29 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/ResourceServer.php(157): MediaWiki\Extension\OAuth\ResourceServer-&gt;setUser()<br />
34#30 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/ResourceServer.php(89): MediaWiki\Extension\OAuth\ResourceServer-&gt;setVerifiedInfo()<br />
35#31 /home/lucas/git/wikimedia/mediawiki/vendor/league/oauth2-server/src/Middleware/ResourceServerMiddleware.php(54): MediaWiki\Extension\OAuth\ResourceServer-&gt;{closure:MediaWiki\Extension\OAuth\ResourceServer::verify():88}()<br />
36#32 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/ResourceServer.php(85): League\OAuth2\Server\Middleware\ResourceServerMiddleware-&gt;__invoke()<br />
37#33 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/SessionProvider.php(279): MediaWiki\Extension\OAuth\ResourceServer-&gt;verify()<br />
38#34 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/SessionProvider.php(109): MediaWiki\Extension\OAuth\SessionProvider-&gt;verifyOAuth2Request()<br />
39#35 /home/lucas/git/wikimedia/mediawiki/includes/Session/SessionManager.php(569): MediaWiki\Extension\OAuth\SessionProvider-&gt;provideSessionInfo()<br />
40#36 /home/lucas/git/wikimedia/mediawiki/includes/Session/SessionManager.php(136): MediaWiki\Session\SessionManager-&gt;getSessionInfoForRequest()<br />
41#37 /home/lucas/git/wikimedia/mediawiki/includes/Request/WebRequest.php(860): MediaWiki\Session\SessionManager-&gt;getSessionForRequest()<br />
42<!-- many, many, many stack frames later… -->
43#8169 /home/lucas/git/wikimedia/mediawiki/includes/Request/WebRequest.php(860): MediaWiki\Session\SessionManager-&gt;getSessionForRequest()<br />
44#8170 /home/lucas/git/wikimedia/mediawiki/includes/Permissions/PermissionManager.php(1572): MediaWiki\Request\WebRequest-&gt;getSession()<br />
45#8171 /home/lucas/git/wikimedia/mediawiki/includes/Permissions/PermissionManager.php(1514): MediaWiki\Permissions\PermissionManager-&gt;getUserPermissions()<br />
46#8172 /home/lucas/git/wikimedia/mediawiki/includes/Permissions/UserAuthority.php(271): MediaWiki\Permissions\PermissionManager-&gt;userHasRight()<br />
47#8173 /home/lucas/git/wikimedia/mediawiki/includes/Permissions/UserAuthority.php(130): MediaWiki\Permissions\UserAuthority-&gt;internalAllowed()<br />
48#8174 /home/lucas/git/wikimedia/mediawiki/includes/User/User.php(2129): MediaWiki\Permissions\UserAuthority-&gt;isAllowed()<br />
49#8175 /home/lucas/git/wikimedia/mediawiki/includes/User/CentralId/LocalIdLookup.php(104): MediaWiki\User\User-&gt;isAllowed()<br />
50#8176 /home/lucas/git/wikimedia/mediawiki/includes/User/CentralId/CentralIdLookup.php(238): MediaWiki\User\CentralId\LocalIdLookup-&gt;lookupCentralIds()<br />
51#8177 /home/lucas/git/wikimedia/mediawiki/includes/User/CentralId/CentralIdLookup.php(316): MediaWiki\User\CentralId\CentralIdLookup-&gt;nameFromCentralId()<br />
52#8178 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/Backend/Utils.php(292): MediaWiki\User\CentralId\CentralIdLookup-&gt;localUserFromCentralId()<br />
53#8179 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/ResourceServer.php(180): MediaWiki\Extension\OAuth\Backend\Utils::getLocalUserFromCentralId()<br />
54#8180 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/ResourceServer.php(157): MediaWiki\Extension\OAuth\ResourceServer-&gt;setUser()<br />
55#8181 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/ResourceServer.php(89): MediaWiki\Extension\OAuth\ResourceServer-&gt;setVerifiedInfo()<br />
56#8182 /home/lucas/git/wikimedia/mediawiki/vendor/league/oauth2-server/src/Middleware/ResourceServerMiddleware.php(54): MediaWiki\Extension\OAuth\ResourceServer-&gt;{closure:MediaWiki\Extension\OAuth\ResourceServer::verify():88}()<br />
57#8183 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/ResourceServer.php(85): League\OAuth2\Server\Middleware\ResourceServerMiddleware-&gt;__invoke()<br />
58#8184 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/SessionProvider.php(279): MediaWiki\Extension\OAuth\ResourceServer-&gt;verify()<br />
59#8185 /home/lucas/git/wikimedia/mediawiki/extensions/OAuth/src/SessionProvider.php(109): MediaWiki\Extension\OAuth\SessionProvider-&gt;verifyOAuth2Request()<br />
60#8186 /home/lucas/git/wikimedia/mediawiki/includes/Session/SessionManager.php(569): MediaWiki\Extension\OAuth\SessionProvider-&gt;provideSessionInfo()<br />
61#8187 /home/lucas/git/wikimedia/mediawiki/includes/Session/SessionManager.php(136): MediaWiki\Session\SessionManager-&gt;getSessionInfoForRequest()<br />
62#8188 /home/lucas/git/wikimedia/mediawiki/includes/Request/WebRequest.php(860): MediaWiki\Session\SessionManager-&gt;getSessionForRequest()<br />
63#8189 /home/lucas/git/wikimedia/mediawiki/includes/Setup.php(504): MediaWiki\Request\WebRequest-&gt;getSession()<br />
64#8190 /home/lucas/git/wikimedia/mediawiki/includes/WebStart.php(72): require_once(string)<br />
65#8191 /home/lucas/git/wikimedia/mediawiki/api.php(23): require(string)<br />
66#8192 {main}</p>
67</div></div></div></body></html>

(Note that this is after configuring xdebug.max_nesting_level=8192, which is a preposterously high value. XDebug’s default is 512 levels, which I had previously doubled for Peast; this is eight times more than that again and still not enough to break out of the infinite loop. I guess in CI, where XDebug probably isn’t installed, nothing breaks out of the loop and that’s where the timeout comes from?)

The only OAuth-related lines in my LocalSettings.php are:

wfLoadExtension( 'OAuth' );
$wgGroupPermissions['sysop']['mwoauthproposeconsumer'] = true;
$wgGroupPermissions['sysop']['mwoauthupdateownconsumer'] = true;
$wgGroupPermissions['sysop']['mwoauthmanageconsumer'] = true;
$wgGroupPermissions['sysop']['mwoauthsuppress'] = true;
$wgGroupPermissions['sysop']['mwoauthviewsuppressed'] = true;
$wgGroupPermissions['sysop']['mwoauthviewprivate'] = true;
$wgGroupPermissions['sysop']['mwoauthmanagemygrants'] = true;
$wgOAuth2PrivateKey = __DIR__ . '/private.key';
$wgOAuth2PublicKey = __DIR__ . '/public.key';
$wgMWOAuthSharedUserIDs = true; // temporary due to OAuth commit c7c9d5f9b5, until OAuth stops spamming deprecation warnings for the **default config** >.<                                                                                                                                           

Slightly more readable version of what appears to be the loop:

includes/Request/WebRequest.php(860): MediaWiki\Session\SessionManager->getSessionForRequest()
includes/Permissions/PermissionManager.php(1572): MediaWiki\Request\WebRequest->getSession()
includes/Permissions/PermissionManager.php(1514): MediaWiki\Permissions\PermissionManager->getUserPermissions()
includes/Permissions/UserAuthority.php(271): MediaWiki\Permissions\PermissionManager->userHasRight()
includes/Permissions/UserAuthority.php(130): MediaWiki\Permissions\UserAuthority->internalAllowed()
includes/User/User.php(2129): MediaWiki\Permissions\UserAuthority->isAllowed()
includes/User/CentralId/LocalIdLookup.php(104): MediaWiki\User\User->isAllowed()
includes/User/CentralId/CentralIdLookup.php(238): MediaWiki\User\CentralId\LocalIdLookup->lookupCentralIds()
includes/User/CentralId/CentralIdLookup.php(316): MediaWiki\User\CentralId\CentralIdLookup->nameFromCentralId()
extensions/OAuth/src/Backend/Utils.php(292): MediaWiki\User\CentralId\CentralIdLookup->localUserFromCentralId()
extensions/OAuth/src/ResourceServer.php(180): MediaWiki\Extension\OAuth\Backend\Utils::getLocalUserFromCentralId()
extensions/OAuth/src/ResourceServer.php(157): MediaWiki\Extension\OAuth\ResourceServer->setUser()
extensions/OAuth/src/ResourceServer.php(89): MediaWiki\Extension\OAuth\ResourceServer->setVerifiedInfo()
vendor/league/oauth2-server/src/Middleware/ResourceServerMiddleware.php(54): MediaWiki\Extension\OAuth\ResourceServer->{closure:MediaWiki\Extension\OAuth\ResourceServer::verify():88}()
extensions/OAuth/src/ResourceServer.php(85): League\OAuth2\Server\Middleware\ResourceServerMiddleware->__invoke()
extensions/OAuth/src/SessionProvider.php(279): MediaWiki\Extension\OAuth\ResourceServer->verify()
extensions/OAuth/src/SessionProvider.php(109): MediaWiki\Extension\OAuth\SessionProvider->verifyOAuth2Request()
includes/Session/SessionManager.php(569): MediaWiki\Extension\OAuth\SessionProvider->provideSessionInfo()
includes/Session/SessionManager.php(136): MediaWiki\Session\SessionManager->getSessionInfoForRequest()
includes/Request/WebRequest.php(860): MediaWiki\Session\SessionManager->getSessionForRequest()
<!-- there's a hole in my bucket… -->

If I understand correctly: PermissionManager asks for the $userObj->getRequest()->getSession()->getAllowedUserRights(); the session calls into OAuth; LocalIdLookup::lookupCentralId() checks for the audience’s hideuser right; and back into the PermissionManager we go. I’m not sure where this cycle should best be broken – you probably have a better idea about that. (And also, why this didn’t come up in CI…)

Thanks for tracking it down. Yeah that seems like a bug in Utils::getLocalUserFromCentralId().

Thanks, good shout – this patch appears to be sufficient to make the m3api-oauth2 tests pass against my local wiki:

diff --git i/src/Backend/Utils.php w/src/Backend/Utils.php
index 8a64797522..3e7607dcb4 100644
--- i/src/Backend/Utils.php
+++ w/src/Backend/Utils.php
@@ -289,7 +289,7 @@ public static function getCentralUserNameFromId( $userId, $audience = false ) {
 	 */
 	public static function getLocalUserFromCentralId( $userId ) {
 		$lookup = self::getCentralIdLookup();
-		$user = $lookup->localUserFromCentralId( $userId );
+		$user = $lookup->localUserFromCentralId( $userId, CentralIdLookup::AUDIENCE_RAW );
 		if ( $user === null || !$lookup->isAttached( $user ) ) {
 			return false;
 		}

That said, I have no clue whether this is correct / safe, or whether getLocalUserFromCentralId() should itself take an audience argument (like getCentralUserNameFromId() already does, for instance) and have the right caller pass that in.

FWIW this also affects CentralAuth wikis, but due to the way the different CentralIdLookup providers are implemented, on those wikis the loop only happens if the user is hidden (as CentralAuthIdLookup first does the query and then filters out the hidden users, while LocalIdLookup does the filtering in the query).

PermissionManager breaks loops for block checks, I wonder if we should do the same for session permission checks?

Change #1207819 had a related patch set uploaded (by Gergő Tisza; author: Gergő Tisza):

[mediawiki/extensions/OAuth@master] Use AUDIENCE_RAW in OAuth central user ID lookups

https://gerrit.wikimedia.org/r/1207819

Change #1207819 merged by jenkins-bot:

[mediawiki/extensions/OAuth@master] Use AUDIENCE_RAW in OAuth central user ID lookups

https://gerrit.wikimedia.org/r/1207819