Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
93.94% |
31 / 33 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
| MultiFormatUserIdentityLookup | |
93.94% |
31 / 33 |
|
33.33% |
1 / 3 |
15.05 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getUserIdentity | |
95.45% |
21 / 22 |
|
0.00% |
0 / 1 |
11 | |||
| parseUserDesignator | |
90.00% |
9 / 10 |
|
0.00% |
0 / 1 |
3.01 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * @license GPL-2.0-or-later |
| 4 | * @file |
| 5 | */ |
| 6 | |
| 7 | namespace MediaWiki\User; |
| 8 | |
| 9 | use MediaWiki\Config\ServiceOptions; |
| 10 | use MediaWiki\MainConfigNames; |
| 11 | use MediaWiki\Permissions\Authority; |
| 12 | use MediaWiki\Status\Status; |
| 13 | use MediaWiki\WikiMap\WikiMap; |
| 14 | use Wikimedia\IPUtils; |
| 15 | |
| 16 | /** |
| 17 | * A service to look up user identities based on the user input. |
| 18 | * This class is similar to UserIdentityLookup but allows to search for users using |
| 19 | * multiple input formats (e.g. user ID, user name, user name with interwiki suffix). |
| 20 | * |
| 21 | * This service is designed to be of higher level than UserIdentityLookup and handy in |
| 22 | * places like special pages or API modules where the input can be in multiple formats. |
| 23 | * Additionally, this service allows to check for user rights, e.g. whether a hidden user |
| 24 | * should be returned or not. |
| 25 | * |
| 26 | * @since 1.45 |
| 27 | * @ingroup User |
| 28 | */ |
| 29 | class MultiFormatUserIdentityLookup { |
| 30 | |
| 31 | /** @internal */ |
| 32 | public const CONSTRUCTOR_OPTIONS = [ |
| 33 | MainConfigNames::LocalDatabases, |
| 34 | MainConfigNames::UserrightsInterwikiDelimiter |
| 35 | ]; |
| 36 | |
| 37 | public function __construct( |
| 38 | private readonly ActorStoreFactory $actorStoreFactory, |
| 39 | private readonly UserFactory $userFactory, |
| 40 | private readonly ServiceOptions $options, |
| 41 | ) { |
| 42 | $this->options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS ); |
| 43 | } |
| 44 | |
| 45 | /** |
| 46 | * Looks up for a user identity based on the passed designator string. |
| 47 | * |
| 48 | * The designator can be in one of the following formats: |
| 49 | * - username (e.g. "Example"), |
| 50 | * - user ID (e.g. "#123"), |
| 51 | * - username or id with interwiki suffix (e.g. "Example@wiki", "#123@wiki"), where the separator |
| 52 | * between those two parts is defined by the `wgUserrightsInterwikiDelimiter` config option. |
| 53 | * |
| 54 | * For interwiki users, this service assures that the remote wiki exists and is considered |
| 55 | * a local database (i.e. is listed in `wgLocalDatabases`). |
| 56 | * @param string $designator |
| 57 | * @param Authority|null $viewer |
| 58 | * @return Status<UserIdentity> |
| 59 | */ |
| 60 | public function getUserIdentity( string $designator, ?Authority $viewer = null ): Status { |
| 61 | [ $name, $wikiId ] = $this->parseUserDesignator( $designator ); |
| 62 | |
| 63 | // Check if the wikiId is valid |
| 64 | if ( $wikiId !== UserIdentity::LOCAL ) { |
| 65 | $localDatabases = $this->options->get( MainConfigNames::LocalDatabases ); |
| 66 | if ( !in_array( $wikiId, $localDatabases ) ) { |
| 67 | return Status::newFatal( 'userrights-nodatabase', $wikiId ); |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | if ( $name === '' ) { |
| 72 | return Status::newFatal( 'nouserspecified' ); |
| 73 | } |
| 74 | |
| 75 | if ( IPUtils::isValid( $name ) ) { |
| 76 | return Status::newGood( UserIdentityValue::newAnonymous( $name, $wikiId ) ); |
| 77 | } |
| 78 | |
| 79 | $userIdentityLookup = $this->actorStoreFactory->getUserIdentityLookup( $wikiId ); |
| 80 | if ( $name[0] == '#' ) { |
| 81 | $id = intval( substr( $name, 1 ) ); |
| 82 | $user = $userIdentityLookup->getUserIdentityByUserId( $id ); |
| 83 | } else { |
| 84 | $user = $userIdentityLookup->getUserIdentityByName( $name ); |
| 85 | } |
| 86 | |
| 87 | if ( !$user ) { |
| 88 | return Status::newFatal( 'nosuchusershort', $designator ); |
| 89 | } |
| 90 | |
| 91 | // If an authority is specified, check if the viewer is allowed to see the user |
| 92 | // If they can't, pretend the user doesn't exist |
| 93 | if ( |
| 94 | $viewer !== null && |
| 95 | $user->getWikiId() === UserIdentity::LOCAL && |
| 96 | $this->userFactory->newFromUserIdentity( $user )->isHidden() && |
| 97 | !$viewer->isAllowed( 'hideuser' ) |
| 98 | ) { |
| 99 | return Status::newFatal( 'nosuchusershort', $designator ); |
| 100 | } |
| 101 | |
| 102 | return Status::newGood( $user ); |
| 103 | } |
| 104 | |
| 105 | /** |
| 106 | * Parses the user designator into the name and wiki parts. |
| 107 | * If the wikiId refers to local wiki, ensure that the wikiId is set to UserIdentity::LOCAL. |
| 108 | * @return array{0:string,1:string|false} [name, wikiId] |
| 109 | */ |
| 110 | private function parseUserDesignator( string $designator ): array { |
| 111 | $interwikiSeparator = $this->options->get( MainConfigNames::UserrightsInterwikiDelimiter ); |
| 112 | $designatorParts = explode( $interwikiSeparator, $designator ); |
| 113 | |
| 114 | $name = trim( $designator ); |
| 115 | $wikiId = UserIdentity::LOCAL; |
| 116 | if ( count( $designatorParts ) >= 2 ) { |
| 117 | $name = trim( $designatorParts[0] ); |
| 118 | $wikiId = trim( $designatorParts[1] ); |
| 119 | |
| 120 | if ( WikiMap::isCurrentWikiId( $wikiId ) ) { |
| 121 | $wikiId = UserIdentity::LOCAL; |
| 122 | } |
| 123 | } |
| 124 | return [ $name, $wikiId ]; |
| 125 | } |
| 126 | } |