Add PasswordFactory to MediaWikiServices
[lhc/web/wiklou.git] / tests / phpunit / includes / api / ApiLoginTest.php
1 <?php
2
3 use MediaWiki\MediaWikiServices;
4 use Wikimedia\TestingAccessWrapper;
5
6 /**
7 * @group API
8 * @group Database
9 * @group medium
10 *
11 * @covers ApiLogin
12 */
13 class ApiLoginTest extends ApiTestCase {
14
15 /**
16 * Test result of attempted login with an empty username
17 */
18 public function testApiLoginNoName() {
19 $session = [
20 'wsTokenSecrets' => [ 'login' => 'foobar' ],
21 ];
22 $data = $this->doApiRequest( [ 'action' => 'login',
23 'lgname' => '', 'lgpassword' => self::$users['sysop']->getPassword(),
24 'lgtoken' => (string)( new MediaWiki\Session\Token( 'foobar', '' ) )
25 ], $session );
26 $this->assertEquals( 'Failed', $data[0]['login']['result'] );
27 }
28
29 public function testApiLoginBadPass() {
30 global $wgServer;
31
32 $user = self::$users['sysop'];
33 $userName = $user->getUser()->getName();
34 $user->getUser()->logout();
35
36 if ( !isset( $wgServer ) ) {
37 $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
38 }
39 $ret = $this->doApiRequest( [
40 "action" => "login",
41 "lgname" => $userName,
42 "lgpassword" => "bad",
43 ] );
44
45 $result = $ret[0];
46
47 $this->assertNotInternalType( "bool", $result );
48 $a = $result["login"]["result"];
49 $this->assertEquals( "NeedToken", $a );
50
51 $token = $result["login"]["token"];
52
53 $ret = $this->doApiRequest(
54 [
55 "action" => "login",
56 "lgtoken" => $token,
57 "lgname" => $userName,
58 "lgpassword" => "badnowayinhell",
59 ],
60 $ret[2]
61 );
62
63 $result = $ret[0];
64
65 $this->assertNotInternalType( "bool", $result );
66 $a = $result["login"]["result"];
67
68 $this->assertEquals( 'Failed', $a );
69 }
70
71 public function testApiLoginGoodPass() {
72 global $wgServer;
73
74 if ( !isset( $wgServer ) ) {
75 $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
76 }
77
78 $user = self::$users['sysop'];
79 $userName = $user->getUser()->getName();
80 $password = $user->getPassword();
81 $user->getUser()->logout();
82
83 $ret = $this->doApiRequest( [
84 "action" => "login",
85 "lgname" => $userName,
86 "lgpassword" => $password,
87 ]
88 );
89
90 $result = $ret[0];
91 $this->assertNotInternalType( "bool", $result );
92 $this->assertNotInternalType( "null", $result["login"] );
93
94 $a = $result["login"]["result"];
95 $this->assertEquals( "NeedToken", $a );
96 $token = $result["login"]["token"];
97
98 $ret = $this->doApiRequest(
99 [
100 "action" => "login",
101 "lgtoken" => $token,
102 "lgname" => $userName,
103 "lgpassword" => $password,
104 ],
105 $ret[2]
106 );
107
108 $result = $ret[0];
109
110 $this->assertNotInternalType( "bool", $result );
111 $a = $result["login"]["result"];
112
113 $this->assertEquals( "Success", $a );
114 }
115
116 /**
117 * @group Broken
118 */
119 public function testApiLoginGotCookie() {
120 $this->markTestIncomplete( "The server can't do external HTTP requests, "
121 . "and the internal one won't give cookies" );
122
123 global $wgServer, $wgScriptPath;
124
125 if ( !isset( $wgServer ) ) {
126 $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
127 }
128 $user = self::$users['sysop'];
129 $userName = $user->getUser()->getName();
130 $password = $user->getPassword();
131
132 $req = MWHttpRequest::factory( self::$apiUrl . "?action=login&format=xml",
133 [ "method" => "POST",
134 "postData" => [
135 "lgname" => $userName,
136 "lgpassword" => $password
137 ]
138 ],
139 __METHOD__
140 );
141 $req->execute();
142
143 libxml_use_internal_errors( true );
144 $sxe = simplexml_load_string( $req->getContent() );
145 $this->assertNotInternalType( "bool", $sxe );
146 $this->assertThat( $sxe, $this->isInstanceOf( SimpleXMLElement::class ) );
147 $this->assertNotInternalType( "null", $sxe->login[0] );
148
149 $a = $sxe->login[0]->attributes()->result[0];
150 $this->assertEquals( ' result="NeedToken"', $a->asXML() );
151 $token = (string)$sxe->login[0]->attributes()->token;
152
153 $req->setData( [
154 "lgtoken" => $token,
155 "lgname" => $userName,
156 "lgpassword" => $password ] );
157 $req->execute();
158
159 $cj = $req->getCookieJar();
160 $serverName = parse_url( $wgServer, PHP_URL_HOST );
161 $this->assertNotEquals( false, $serverName );
162 $serializedCookie = $cj->serializeToHttpRequest( $wgScriptPath, $serverName );
163 $this->assertNotEquals( '', $serializedCookie );
164 $this->assertRegExp(
165 '/_session=[^;]*; .*UserID=[0-9]*; .*UserName=' . $user->userName . '; .*Token=/',
166 $serializedCookie
167 );
168 }
169
170 public function testRunLogin() {
171 $user = self::$users['sysop'];
172 $userName = $user->getUser()->getName();
173 $password = $user->getPassword();
174
175 $data = $this->doApiRequest( [
176 'action' => 'login',
177 'lgname' => $userName,
178 'lgpassword' => $password ] );
179
180 $this->assertArrayHasKey( "login", $data[0] );
181 $this->assertArrayHasKey( "result", $data[0]['login'] );
182 $this->assertEquals( "NeedToken", $data[0]['login']['result'] );
183 $token = $data[0]['login']['token'];
184
185 $data = $this->doApiRequest( [
186 'action' => 'login',
187 "lgtoken" => $token,
188 "lgname" => $userName,
189 "lgpassword" => $password ], $data[2] );
190
191 $this->assertArrayHasKey( "login", $data[0] );
192 $this->assertArrayHasKey( "result", $data[0]['login'] );
193 $this->assertEquals( "Success", $data[0]['login']['result'] );
194 }
195
196 public function testBotPassword() {
197 global $wgServer, $wgSessionProviders;
198
199 if ( !isset( $wgServer ) ) {
200 $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
201 }
202
203 $this->setMwGlobals( [
204 'wgSessionProviders' => array_merge( $wgSessionProviders, [
205 [
206 'class' => MediaWiki\Session\BotPasswordSessionProvider::class,
207 'args' => [ [ 'priority' => 40 ] ],
208 ]
209 ] ),
210 'wgEnableBotPasswords' => true,
211 'wgBotPasswordsDatabase' => false,
212 'wgCentralIdLookupProvider' => 'local',
213 'wgGrantPermissions' => [
214 'test' => [ 'read' => true ],
215 ],
216 ] );
217
218 // Make sure our session provider is present
219 $manager = TestingAccessWrapper::newFromObject( MediaWiki\Session\SessionManager::singleton() );
220 if ( !isset( $manager->sessionProviders[MediaWiki\Session\BotPasswordSessionProvider::class] ) ) {
221 $tmp = $manager->sessionProviders;
222 $manager->sessionProviders = null;
223 $manager->sessionProviders = $tmp + $manager->getProviders();
224 }
225 $this->assertNotNull(
226 MediaWiki\Session\SessionManager::singleton()->getProvider(
227 MediaWiki\Session\BotPasswordSessionProvider::class
228 ),
229 'sanity check'
230 );
231
232 $user = self::$users['sysop'];
233 $centralId = CentralIdLookup::factory()->centralIdFromLocalUser( $user->getUser() );
234 $this->assertNotEquals( 0, $centralId, 'sanity check' );
235
236 $password = 'ngfhmjm64hv0854493hsj5nncjud2clk';
237 $passwordFactory = MediaWikiServices::getInstance()->getPasswordFactory();
238 // A is unsalted MD5 (thus fast) ... we don't care about security here, this is test only
239 $passwordHash = $passwordFactory->newFromPlaintext( $password );
240
241 $dbw = wfGetDB( DB_MASTER );
242 $dbw->insert(
243 'bot_passwords',
244 [
245 'bp_user' => $centralId,
246 'bp_app_id' => 'foo',
247 'bp_password' => $passwordHash->toString(),
248 'bp_token' => '',
249 'bp_restrictions' => MWRestrictions::newDefault()->toJson(),
250 'bp_grants' => '["test"]',
251 ],
252 __METHOD__
253 );
254
255 $lgName = $user->getUser()->getName() . BotPassword::getSeparator() . 'foo';
256
257 $ret = $this->doApiRequest( [
258 'action' => 'login',
259 'lgname' => $lgName,
260 'lgpassword' => $password,
261 ] );
262
263 $result = $ret[0];
264 $this->assertNotInternalType( 'bool', $result );
265 $this->assertNotInternalType( 'null', $result['login'] );
266
267 $a = $result['login']['result'];
268 $this->assertEquals( 'NeedToken', $a );
269 $token = $result['login']['token'];
270
271 $ret = $this->doApiRequest( [
272 'action' => 'login',
273 'lgtoken' => $token,
274 'lgname' => $lgName,
275 'lgpassword' => $password,
276 ], $ret[2] );
277
278 $result = $ret[0];
279 $this->assertNotInternalType( 'bool', $result );
280 $a = $result['login']['result'];
281
282 $this->assertEquals( 'Success', $a );
283 }
284
285 public function testLoginWithNoSameOriginSecurity() {
286 $this->setTemporaryHook( 'RequestHasSameOriginSecurity',
287 function () {
288 return false;
289 }
290 );
291
292 $result = $this->doApiRequest( [
293 'action' => 'login',
294 ] )[0]['login'];
295
296 $this->assertSame( [
297 'result' => 'Aborted',
298 'reason' => 'Cannot log in when the same-origin policy is not applied.',
299 ], $result );
300 }
301 }