Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.04% covered (success)
98.04%
50 / 51
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
CentralAuthIdLookup
98.04% covered (success)
98.04%
50 / 51
87.50% covered (warning)
87.50%
7 / 8
25
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 lookupCentralIds
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
6
 lookupUserNames
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
6
 isAttached
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isOwned
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
4
 centralIdFromLocalUser
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isAttachedOn
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 getCentralUserInstance
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Extension\CentralAuth\User;
22
23use MediaWiki\Config\Config;
24use MediaWiki\DAO\WikiAwareEntity;
25use MediaWiki\Extension\CentralAuth\CentralAuthDatabaseManager;
26use MediaWiki\Extension\CentralAuth\Config\CAMainConfigNames;
27use MediaWiki\User\CentralId\CentralIdLookup;
28use MediaWiki\User\UserIdentity;
29use MediaWiki\WikiMap\WikiMap;
30use Wikimedia\Rdbms\DBAccessObjectUtils;
31use Wikimedia\Rdbms\IDBAccessObject;
32
33/**
34 * Look up central IDs using CentralAuth
35 */
36class CentralAuthIdLookup extends CentralIdLookup {
37
38    private Config $config;
39    private CentralAuthDatabaseManager $databaseManager;
40
41    public function __construct( Config $config, CentralAuthDatabaseManager $databaseManager ) {
42        $this->config = $config;
43        $this->databaseManager = $databaseManager;
44    }
45
46    /** @inheritDoc */
47    public function lookupCentralIds(
48        array $idToName, $audience = self::AUDIENCE_PUBLIC, $flags = IDBAccessObject::READ_NORMAL
49    ): array {
50        if ( !$idToName ) {
51            return [];
52        }
53
54        $audience = $this->checkAudience( $audience );
55        $fromPrimaryDb = DBAccessObjectUtils::hasFlags( $flags, IDBAccessObject::READ_LATEST );
56        $db = $this->databaseManager->getCentralDBFromRecency( $flags );
57
58        $res = $db->newSelectQueryBuilder()
59            ->queryInfo( CentralAuthUser::selectQueryInfo() )
60            ->where( [ 'gu_id' => array_map( 'intval', array_keys( $idToName ) ) ] )
61            ->caller( __METHOD__ )
62            ->fetchResultSet();
63        foreach ( $res as $row ) {
64            $centralUser = CentralAuthUser::newFromRow( $row, [], $fromPrimaryDb );
65            if ( $centralUser->getHiddenLevelInt() === CentralAuthUser::HIDDEN_LEVEL_NONE
66                || $audience === null || $audience->isAllowed( 'centralauth-suppress' )
67            ) {
68                $idToName[$centralUser->getId()] = $centralUser->getName();
69            } else {
70                $idToName[$centralUser->getId()] = '';
71            }
72        }
73
74        return $idToName;
75    }
76
77    /** @inheritDoc */
78    public function lookupUserNames(
79        array $nameToId, $audience = self::AUDIENCE_PUBLIC, $flags = IDBAccessObject::READ_NORMAL
80    ): array {
81        if ( !$nameToId ) {
82            return [];
83        }
84
85        $audience = $this->checkAudience( $audience );
86        $fromPrimaryDb = DBAccessObjectUtils::hasFlags( $flags, IDBAccessObject::READ_LATEST );
87        $db = $this->databaseManager->getCentralDBFromRecency( $flags );
88
89        $res = $db->newSelectQueryBuilder()
90            ->queryInfo( CentralAuthUser::selectQueryInfo() )
91            ->where( [ 'gu_name' => array_map( 'strval', array_keys( $nameToId ) ) ] )
92            ->caller( __METHOD__ )
93            ->fetchResultSet();
94        foreach ( $res as $row ) {
95            $centralUser = CentralAuthUser::newFromRow( $row, [], $fromPrimaryDb );
96            if ( $centralUser->getHiddenLevelInt() === CentralAuthUser::HIDDEN_LEVEL_NONE
97                || $audience === null || $audience->isAllowed( 'centralauth-suppress' )
98            ) {
99                $nameToId[$centralUser->getName()] = $centralUser->getId();
100            }
101        }
102
103        return $nameToId;
104    }
105
106    /** @inheritDoc */
107    public function isAttached( UserIdentity $user, $wikiId = UserIdentity::LOCAL ): bool {
108        return self::isAttachedOn( $user, $wikiId, IDBAccessObject::READ_NORMAL );
109    }
110
111    /** @inheritDoc */
112    public function isOwned( UserIdentity $user, $wikiId = UserIdentity::LOCAL ): bool {
113        $user->assertWiki( $wikiId );
114
115        $centralUser = CentralAuthUser::getInstance( $user );
116
117        $strictMode = $this->config->get( CAMainConfigNames::CentralAuthStrict );
118        if ( $centralUser->exists() && !$user->isRegistered() && $strictMode ) {
119            // Even if the user doesn't exist locally, the username is reserved for the central user, as
120            // it will be automatically attached on login, and can't be taken by any other user (T371340).
121            // CentralAuthPrimaryAuthenticationProvider guarantees this.
122            return true;
123        }
124
125        return self::isAttachedOn( $user, $wikiId, IDBAccessObject::READ_NORMAL );
126    }
127
128    /** @inheritDoc */
129    public function centralIdFromLocalUser(
130        UserIdentity $user, $audience = self::AUDIENCE_PUBLIC, $flags = IDBAccessObject::READ_NORMAL
131    ): int {
132        // This is only an optimization to take advantage of cache in CentralAuthUser.
133        // The result should be the same as calling the parent method.
134        if ( self::isAttachedOn( $user, WikiAwareEntity::LOCAL, $flags ) ) {
135            return self::getCentralUserInstance( $user, $flags )->getId();
136        }
137
138        return 0;
139    }
140
141    /**
142     * Check whether an user is attached on the given wiki, reading from the primary DB if needed.
143     *
144     * @param UserIdentity $user The user whose attachment status to look up.
145     * @param string|false $wikiId The DB name of the wiki to check, or `false` to use the local wiki.
146     * @param int $flags Bitmask of IDBAccessObject::READ_* constants.
147     *
148     * @return bool `true` if the given user is attached to a central user on the given wiki,
149     * `false` otherwise.
150     */
151    private static function isAttachedOn( UserIdentity $user, $wikiId, int $flags ): bool {
152        $wikiId = $wikiId ?: WikiMap::getCurrentWikiId();
153        $centralUser = self::getCentralUserInstance( $user, $flags );
154
155        return $centralUser->exists() && $centralUser->attachedOn( $wikiId );
156    }
157
158    /**
159     * Get a potentially cached central user instance for the given user, reading from the primary DB if needed.
160     *
161     * @param UserIdentity $user The user to fetch the corresponding central user for.
162     * @param int $flags Bitmask of IDBAccessObject::READ_* constants.
163     *
164     * @return CentralAuthUser
165     */
166    private static function getCentralUserInstance( UserIdentity $user, int $flags ): CentralAuthUser {
167        return $flags & IDBAccessObject::READ_LATEST
168            ? CentralAuthUser::getPrimaryInstance( $user )
169            : CentralAuthUser::getInstance( $user );
170    }
171
172}