Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 88 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
0.00% |
0 / 88 |
|
0.00% |
0 / 11 |
702 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
onRegistration | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
12 | |||
onExtensionFunctions | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
6 | |||
onChangeTagCanCreate | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
onMergeAccountFromTo | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
doUserIdMerge | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
2 | |||
onListDefinedTags | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onChangeTagsListActive | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getUsedConsumerTags | |
0.00% |
0 / 37 |
|
0.00% |
0 / 1 |
72 | |||
onSetupAfterCache | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
onApiRsdServiceApis | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\OAuth\Backend; |
4 | |
5 | use MediaWiki\Api\Hook\ApiRsdServiceApisHook; |
6 | use MediaWiki\ChangeTags\Hook\ChangeTagCanCreateHook; |
7 | use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook; |
8 | use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook; |
9 | use MediaWiki\Extension\OAuth\Frontend\OAuthLogFormatter; |
10 | use MediaWiki\Hook\SetupAfterCacheHook; |
11 | use MediaWiki\Status\Status; |
12 | use MediaWiki\Storage\NameTableAccessException; |
13 | use MediaWiki\Storage\NameTableStore; |
14 | use MediaWiki\User\User; |
15 | use MediaWiki\WikiMap\WikiMap; |
16 | use Wikimedia\Rdbms\IConnectionProvider; |
17 | |
18 | /** |
19 | * Class containing hooked functions for an OAuth environment |
20 | */ |
21 | class Hooks implements |
22 | ApiRsdServiceApisHook, |
23 | ChangeTagsListActiveHook, |
24 | ChangeTagCanCreateHook, |
25 | ListDefinedTagsHook, |
26 | SetupAfterCacheHook |
27 | { |
28 | |
29 | /** @var NameTableStore */ |
30 | private $changeTagDefStore; |
31 | |
32 | /** @var IConnectionProvider */ |
33 | private $connectionProvider; |
34 | |
35 | /** |
36 | * @param NameTableStore $changeTagDefStore |
37 | * @param IConnectionProvider $connectionProvider |
38 | */ |
39 | public function __construct( NameTableStore $changeTagDefStore, IConnectionProvider $connectionProvider |
40 | ) { |
41 | $this->changeTagDefStore = $changeTagDefStore; |
42 | $this->connectionProvider = $connectionProvider; |
43 | } |
44 | |
45 | /** |
46 | * Called right after configuration variables have been set. |
47 | */ |
48 | public static function onRegistration() { |
49 | global $wgOAuth2PrivateKey, $wgOAuth2PublicKey; |
50 | |
51 | // Set $wgOAuth2PrivateKey and $wgOAuth2PublicKey for Wikimedia Jenkins, PHPUnit. |
52 | if ( defined( 'MW_PHPUNIT_TEST' ) || defined( 'MW_QUIBBLE_CI' ) ) { |
53 | $wgOAuth2PrivateKey = <<<EOK |
54 | -----BEGIN RSA PRIVATE KEY----- |
55 | MIIBOwIBAAJBAMBGXQYJ2lXzLuQkRlWoqYJvSnNGfRvPBUVsbHfFPyCr8i6jBPcO |
56 | vtMLFMRAaq4quRDFgQ7YQLvKTqjpN+bo7RECAwEAAQJBAKP3XTzZCihhyYskpBZI |
57 | TsW8wnCrm+UrFgOuApHg04S3oeUXpNApxxGy+EX0aBsVoPBuisyBjiJDIFssdgJa |
58 | IwECIQDuMipv8QOzA9qJPPpXZCQQN6znXjSE3jZhrBH879SDBQIhAM6lgY0lWB0N |
59 | lhQZWtM8jRcxtJUFrApEizE6WFxj/LedAiEAyINgaAVqiMror3iugNyi4ygLHGWY |
60 | LnVlMAmKxvMZYQUCIAYTeb6ztWaNSrdmk3QYmLFw5bVoCEn4//q/k2+MBRdFAiA2 |
61 | MJWJuom6IpoP0UrM/gJbwGxwgZymb4jL+sKFoIqGmA== |
62 | -----END RSA PRIVATE KEY----- |
63 | EOK; |
64 | $wgOAuth2PublicKey = <<<EOK |
65 | -----BEGIN PUBLIC KEY----- |
66 | MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMBGXQYJ2lXzLuQkRlWoqYJvSnNGfRvP |
67 | BUVsbHfFPyCr8i6jBPcOvtMLFMRAaq4quRDFgQ7YQLvKTqjpN+bo7RECAwEAAQ== |
68 | -----END PUBLIC KEY----- |
69 | EOK; |
70 | } |
71 | } |
72 | |
73 | public static function onExtensionFunctions() { |
74 | global $wgLogTypes, $wgLogNames, |
75 | $wgLogHeaders, $wgLogActionsHandlers, $wgActionFilteredLogs; |
76 | |
77 | if ( Utils::isCentralWiki() ) { |
78 | $wgLogTypes[] = 'mwoauthconsumer'; |
79 | $wgLogNames['mwoauthconsumer'] = 'mwoauthconsumer-consumer-logpage'; |
80 | $wgLogHeaders['mwoauthconsumer'] = 'mwoauthconsumer-consumer-logpagetext'; |
81 | $wgLogActionsHandlers['mwoauthconsumer/*'] = [ |
82 | 'class' => OAuthLogFormatter::class, |
83 | 'services' => [ |
84 | 'LinkRenderer', |
85 | 'TitleFactory', |
86 | 'UserEditTracker', |
87 | ], |
88 | ]; |
89 | $wgActionFilteredLogs['mwoauthconsumer'] = [ |
90 | 'approve' => [ 'approve' ], |
91 | 'create-owner-only' => [ 'create-owner-only' ], |
92 | 'disable' => [ 'disable' ], |
93 | 'propose' => [ 'propose' ], |
94 | 'propose-autoapproved' => [ 'propose-autoapproved' ], |
95 | 'reenable' => [ 'reenable' ], |
96 | 'reject' => [ 'reject' ], |
97 | 'update' => [ 'update' ], |
98 | ]; |
99 | } |
100 | } |
101 | |
102 | /** |
103 | * Reserve change tags that look like an OAuth change tag. |
104 | * |
105 | * @param string $tag |
106 | * @param User|null $user |
107 | * @param Status &$status |
108 | */ |
109 | public function onChangeTagCanCreate( $tag, $user, &$status ) { |
110 | if ( Utils::isReservedTagName( $tag ) ) { |
111 | $status->fatal( 'mwoauth-tag-reserved' ); |
112 | } |
113 | } |
114 | |
115 | public static function onMergeAccountFromTo( User $oUser, User $nUser ) { |
116 | global $wgMWOAuthSharedUserIDs; |
117 | |
118 | if ( !$wgMWOAuthSharedUserIDs ) { |
119 | $oldid = $oUser->getId(); |
120 | $newid = $nUser->getId(); |
121 | if ( $oldid && $newid ) { |
122 | self::doUserIdMerge( $oldid, $newid ); |
123 | } |
124 | } |
125 | |
126 | return true; |
127 | } |
128 | |
129 | protected static function doUserIdMerge( $oldid, $newid ) { |
130 | $dbw = Utils::getCentralDB( DB_PRIMARY ); |
131 | // Merge any consumers register to this user |
132 | $dbw->newUpdateQueryBuilder() |
133 | ->update( 'oauth_registered_consumer' ) |
134 | ->set( [ 'oarc_user_id' => $newid ] ) |
135 | ->where( [ 'oarc_user_id' => $oldid ] ) |
136 | ->caller( __METHOD__ ) |
137 | ->execute(); |
138 | // Delete any acceptance tokens by the old user ID |
139 | $dbw->newDeleteQueryBuilder() |
140 | ->deleteFrom( 'oauth_accepted_consumer' ) |
141 | ->where( [ 'oaac_user_id' => $oldid ] ) |
142 | ->caller( __METHOD__ ) |
143 | ->execute(); |
144 | } |
145 | |
146 | public function onListDefinedTags( &$tags ) { |
147 | return $this->getUsedConsumerTags( false, $tags ); |
148 | } |
149 | |
150 | public function onChangeTagsListActive( &$tags ) { |
151 | return $this->getUsedConsumerTags( true, $tags ); |
152 | } |
153 | |
154 | /** |
155 | * List tags that should show as defined/active on Special:Tags |
156 | * |
157 | * Handles both the ChangeTagsListActive and ListDefinedTags hooks. Only |
158 | * lists those tags that are actually in use on the local wiki, to avoid |
159 | * flooding Special:Tags with tags for consumers that will never be making |
160 | * logged actions. |
161 | * |
162 | * @param bool $activeOnly true for ChangeTagsListActive, false for ListDefinedTags |
163 | * @param array &$tags |
164 | * @return bool |
165 | */ |
166 | private function getUsedConsumerTags( $activeOnly, &$tags ) { |
167 | // Step 1: Get the list of (active) consumers' tags for this wiki |
168 | $db = Utils::getCentralDB( DB_REPLICA ); |
169 | $conds = [ |
170 | $db->expr( 'oarc_wiki', '=', [ '*', WikiMap::getCurrentWikiId() ] ), |
171 | 'oarc_deleted' => 0, |
172 | ]; |
173 | if ( $activeOnly ) { |
174 | $conds[] = $db->expr( 'oarc_stage', '=', [ Consumer::STAGE_APPROVED, Consumer::STAGE_PROPOSED ] ); |
175 | } |
176 | $res = $db->newSelectQueryBuilder() |
177 | ->select( 'oarc_id' ) |
178 | ->from( 'oauth_registered_consumer' ) |
179 | ->where( $conds ) |
180 | ->caller( __METHOD__ ) |
181 | ->fetchResultSet(); |
182 | $allTags = []; |
183 | foreach ( $res as $row ) { |
184 | $allTags[] = Utils::getTagName( $row->oarc_id ); |
185 | } |
186 | |
187 | // Step 2: Return only those that are in use. |
188 | $tagIds = []; |
189 | foreach ( $allTags as $tag ) { |
190 | try { |
191 | $tagIds[] = $this->changeTagDefStore->getId( $tag ); |
192 | } catch ( NameTableAccessException $ex ) { |
193 | continue; |
194 | } |
195 | } |
196 | if ( $tagIds === [] ) { |
197 | // Nothing to add, return |
198 | return true; |
199 | } |
200 | $conditions = [ 'ct_tag_id' => $tagIds ]; |
201 | $field = 'ct_tag_id'; |
202 | |
203 | if ( $allTags ) { |
204 | $db = $this->connectionProvider->getReplicaDatabase(); |
205 | |
206 | $res = $db->newSelectQueryBuilder() |
207 | ->select( $field ) |
208 | ->distinct() |
209 | ->from( 'change_tag' ) |
210 | ->where( $conditions ) |
211 | ->caller( __METHOD__ ) |
212 | ->fetchResultSet(); |
213 | foreach ( $res as $row ) { |
214 | $tags[] = $this->changeTagDefStore->getName( intval( $row->ct_tag_id ) ); |
215 | } |
216 | } |
217 | |
218 | return true; |
219 | } |
220 | |
221 | public function onSetupAfterCache() { |
222 | global $wgMWOAuthCentralWiki, $wgMWOAuthSharedUserIDs; |
223 | |
224 | if ( $wgMWOAuthCentralWiki === false ) { |
225 | // Treat each wiki as its own "central wiki" as there is no actual one |
226 | $wgMWOAuthCentralWiki = WikiMap::getCurrentWikiId(); |
227 | } else { |
228 | // There is actually a central wiki, requiring global user IDs via hook |
229 | $wgMWOAuthSharedUserIDs = true; |
230 | } |
231 | } |
232 | |
233 | public function onApiRsdServiceApis( &$apis ) { |
234 | $apis['MediaWiki']['settings']['OAuth'] = true; |
235 | } |
236 | } |