Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
89.66% |
52 / 58 |
|
66.67% |
4 / 6 |
CRAP | |
0.00% |
0 / 1 |
AntiSpoofPreAuthenticationProvider | |
89.66% |
52 / 58 |
|
66.67% |
4 / 6 |
24.64 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getAuthenticationRequests | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
5 | |||
testForAccountCreation | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
testUserInternal | |
100.00% |
40 / 40 |
|
100.00% |
1 / 1 |
9 | |||
testUserForCreation | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
30 | |||
getSpoofUser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
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 | |
19 | namespace MediaWiki\Extension\AntiSpoof; |
20 | |
21 | use MediaWiki\Auth\AbstractPreAuthenticationProvider; |
22 | use MediaWiki\Auth\AuthenticationRequest; |
23 | use MediaWiki\Auth\AuthManager; |
24 | use MediaWiki\Language\RawMessage; |
25 | use MediaWiki\Permissions\PermissionManager; |
26 | use MediaWiki\User\User; |
27 | use MediaWiki\User\UserIdentity; |
28 | use Message; |
29 | use Psr\Log\LoggerInterface; |
30 | use Psr\Log\NullLogger; |
31 | use RequestContext; |
32 | use StatusValue; |
33 | |
34 | class AntiSpoofPreAuthenticationProvider extends AbstractPreAuthenticationProvider { |
35 | /** @var bool False effectively disables this provider, but spoofed names will still be logged. */ |
36 | protected $antiSpoofAccounts; |
37 | |
38 | /** @var PermissionManager */ |
39 | private $permissionManager; |
40 | |
41 | /** |
42 | * @param PermissionManager $permissionManager |
43 | * @param array $params Options: |
44 | * - antiSpoofAccounts: (bool) stop spoofed accounts from being created. When false, only log. |
45 | */ |
46 | public function __construct( PermissionManager $permissionManager, array $params = [] ) { |
47 | global $wgAntiSpoofAccounts; |
48 | |
49 | $params += [ 'antiSpoofAccounts' => $wgAntiSpoofAccounts ]; |
50 | |
51 | $this->antiSpoofAccounts = $params['antiSpoofAccounts']; |
52 | $this->permissionManager = $permissionManager; |
53 | } |
54 | |
55 | /** @inheritDoc */ |
56 | public function getAuthenticationRequests( $action, array $options ) { |
57 | $needed = false; |
58 | switch ( $action ) { |
59 | case AuthManager::ACTION_CREATE: |
60 | $user = User::newFromName( $options['username'] ) ?: new User(); |
61 | $needed = $this->antiSpoofAccounts |
62 | && $this->permissionManager->userHasAnyRight( $user, 'override-antispoof' ); |
63 | break; |
64 | } |
65 | |
66 | return $needed ? [ new AntiSpoofAuthenticationRequest() ] : []; |
67 | } |
68 | |
69 | /** @inheritDoc */ |
70 | public function testForAccountCreation( $user, $creator, array $reqs ) { |
71 | /** @var AntiSpoofAuthenticationRequest $req */ |
72 | $req = AuthenticationRequest::getRequestByClass( $reqs, AntiSpoofAuthenticationRequest::class ); |
73 | $override = $req && $req->ignoreAntiSpoof && $creator->isAllowed( 'override-antispoof' ); |
74 | |
75 | return $this->testUserInternal( $user, $override, $this->logger ); |
76 | } |
77 | |
78 | /** |
79 | * @param UserIdentity $user |
80 | * @param bool $override |
81 | * @param LoggerInterface $logger |
82 | * @return StatusValue |
83 | */ |
84 | private function testUserInternal( UserIdentity $user, $override, LoggerInterface $logger ) { |
85 | $name = $user->getName(); |
86 | $spoofUser = $this->getSpoofUser( $user ); |
87 | $mode = !$this->antiSpoofAccounts ? 'LOGGING ' : ( $override ? 'OVERRIDE ' : '' ); |
88 | $active = $this->antiSpoofAccounts && !$override; |
89 | |
90 | if ( $spoofUser->isLegal() ) { |
91 | $normalized = $spoofUser->getNormalized(); |
92 | $conflicts = $spoofUser->getConflicts(); |
93 | if ( !$conflicts ) { |
94 | $logger->debug( "{mode}PASS new account '{name}' [{normalized}]", [ |
95 | 'mode' => $mode, |
96 | 'name' => $name, |
97 | 'normalized' => $normalized, |
98 | ] ); |
99 | } else { |
100 | $logger->info( "{mode}CONFLICT new account '{name}' [{normalized}] spoofs {spoofs}", [ |
101 | 'mode' => $mode, |
102 | 'name' => $name, |
103 | 'normalized' => $normalized, |
104 | 'spoofs' => $conflicts, |
105 | ] ); |
106 | |
107 | if ( $active ) { |
108 | $list = []; |
109 | foreach ( $conflicts as $simUser ) { |
110 | $list[] = "* " . wfMessage( 'antispoof-conflict-item', $simUser )->plain(); |
111 | } |
112 | $list = implode( "\n", $list ); |
113 | |
114 | return StatusValue::newFatal( |
115 | 'antispoof-conflict', |
116 | $name, |
117 | Message::numParam( count( $conflicts ) ), |
118 | // Avoid forced wikitext escaping for params in the Status class |
119 | new RawMessage( $list ) |
120 | ); |
121 | } |
122 | } |
123 | } else { |
124 | $error = $spoofUser->getErrorStatus(); |
125 | $logger->info( "{mode}ILLEGAL new account '{name}' {error}", [ |
126 | 'mode' => $mode, |
127 | 'name' => $name, |
128 | 'error' => $error->getMessage( false, false, 'en' )->text(), |
129 | ] ); |
130 | if ( $active ) { |
131 | return StatusValue::newFatal( 'antispoof-name-illegal', $name, |
132 | $error->getMessage() ); |
133 | } |
134 | } |
135 | return StatusValue::newGood(); |
136 | } |
137 | |
138 | /** @inheritDoc */ |
139 | public function testUserForCreation( $user, $autocreate, array $options = [] ) { |
140 | $sv = StatusValue::newGood(); |
141 | |
142 | // For "cancreate" checks via the API, test if the current user could |
143 | // create the username. |
144 | if ( $this->antiSpoofAccounts && !$autocreate && empty( $options['creating'] ) && |
145 | !$this->permissionManager->userHasAnyRight( RequestContext::getMain()->getUser(), 'override-antispoof' ) |
146 | ) { |
147 | $sv->merge( $this->testUserInternal( $user, false, new NullLogger ) ); |
148 | } |
149 | |
150 | return $sv; |
151 | } |
152 | |
153 | /** |
154 | * @param UserIdentity $user |
155 | * @return SpoofUser |
156 | */ |
157 | protected function getSpoofUser( UserIdentity $user ) { |
158 | return new SpoofUser( $user->getName() ); |
159 | } |
160 | } |