/** * @dataProvider provideAccountCreation * @param StatusValue $preTest * @param StatusValue $primaryTest * @param StatusValue $secondaryTest * @param array $primaryResponses * @param array $secondaryResponses * @param array $managerResponses */ public function testAccountCreation(StatusValue $preTest, $primaryTest, $secondaryTest, array $primaryResponses, array $secondaryResponses, array $managerResponses) { $creator = \User::newFromName('UTSysop'); $username = self::usernameForCreation(); $this->initializeManager(); // Set up lots of mocks... $req = $this->getMockForAbstractClass(AuthenticationRequest::class); $req->preTest = $preTest; $req->primaryTest = $primaryTest; $req->secondaryTest = $secondaryTest; $req->primary = $primaryResponses; $req->secondary = $secondaryResponses; $mocks = []; foreach (['pre', 'primary', 'secondary'] as $key) { $class = ucfirst($key) . 'AuthenticationProvider'; $mocks[$key] = $this->getMockForAbstractClass("MediaWiki\\Auth\\{$class}", [], "Mock{$class}"); $mocks[$key]->expects($this->any())->method('getUniqueId')->will($this->returnValue($key)); $mocks[$key]->expects($this->any())->method('testUserForCreation')->will($this->returnValue(StatusValue::newGood())); $mocks[$key]->expects($this->any())->method('testForAccountCreation')->will($this->returnCallback(function ($user, $creatorIn, $reqs) use($username, $creator, $req, $key) { $this->assertSame($username, $user->getName()); $this->assertSame($creator->getId(), $creatorIn->getId()); $this->assertSame($creator->getName(), $creatorIn->getName()); $foundReq = false; foreach ($reqs as $r) { $this->assertSame($username, $r->username); $foundReq = $foundReq || get_class($r) === get_class($req); } $this->assertTrue($foundReq, '$reqs contains $req'); $k = $key . 'Test'; return $req->{$k}; })); for ($i = 2; $i <= 3; $i++) { $mocks[$key . $i] = $this->getMockForAbstractClass("MediaWiki\\Auth\\{$class}", [], "Mock{$class}"); $mocks[$key . $i]->expects($this->any())->method('getUniqueId')->will($this->returnValue($key . $i)); $mocks[$key . $i]->expects($this->any())->method('testUserForCreation')->will($this->returnValue(StatusValue::newGood())); $mocks[$key . $i]->expects($this->atMost(1))->method('testForAccountCreation')->will($this->returnValue(StatusValue::newGood())); } } $mocks['primary']->expects($this->any())->method('accountCreationType')->will($this->returnValue(PrimaryAuthenticationProvider::TYPE_CREATE)); $mocks['primary']->expects($this->any())->method('testUserExists')->will($this->returnValue(false)); $ct = count($req->primary); $callback = $this->returnCallback(function ($user, $creator, $reqs) use($username, $req) { $this->assertSame($username, $user->getName()); $this->assertSame('UTSysop', $creator->getName()); $foundReq = false; foreach ($reqs as $r) { $this->assertSame($username, $r->username); $foundReq = $foundReq || get_class($r) === get_class($req); } $this->assertTrue($foundReq, '$reqs contains $req'); return array_shift($req->primary); }); $mocks['primary']->expects($this->exactly(min(1, $ct)))->method('beginPrimaryAccountCreation')->will($callback); $mocks['primary']->expects($this->exactly(max(0, $ct - 1)))->method('continuePrimaryAccountCreation')->will($callback); $ct = count($req->secondary); $callback = $this->returnCallback(function ($user, $creator, $reqs) use($username, $req) { $this->assertSame($username, $user->getName()); $this->assertSame('UTSysop', $creator->getName()); $foundReq = false; foreach ($reqs as $r) { $this->assertSame($username, $r->username); $foundReq = $foundReq || get_class($r) === get_class($req); } $this->assertTrue($foundReq, '$reqs contains $req'); return array_shift($req->secondary); }); $mocks['secondary']->expects($this->exactly(min(1, $ct)))->method('beginSecondaryAccountCreation')->will($callback); $mocks['secondary']->expects($this->exactly(max(0, $ct - 1)))->method('continueSecondaryAccountCreation')->will($callback); $abstain = AuthenticationResponse::newAbstain(); $mocks['primary2']->expects($this->any())->method('accountCreationType')->will($this->returnValue(PrimaryAuthenticationProvider::TYPE_LINK)); $mocks['primary2']->expects($this->any())->method('testUserExists')->will($this->returnValue(false)); $mocks['primary2']->expects($this->atMost(1))->method('beginPrimaryAccountCreation')->will($this->returnValue($abstain)); $mocks['primary2']->expects($this->never())->method('continuePrimaryAccountCreation'); $mocks['primary3']->expects($this->any())->method('accountCreationType')->will($this->returnValue(PrimaryAuthenticationProvider::TYPE_NONE)); $mocks['primary3']->expects($this->any())->method('testUserExists')->will($this->returnValue(false)); $mocks['primary3']->expects($this->never())->method('beginPrimaryAccountCreation'); $mocks['primary3']->expects($this->never())->method('continuePrimaryAccountCreation'); $mocks['secondary2']->expects($this->atMost(1))->method('beginSecondaryAccountCreation')->will($this->returnValue($abstain)); $mocks['secondary2']->expects($this->never())->method('continueSecondaryAccountCreation'); $mocks['secondary3']->expects($this->atMost(1))->method('beginSecondaryAccountCreation')->will($this->returnValue($abstain)); $mocks['secondary3']->expects($this->never())->method('continueSecondaryAccountCreation'); $this->preauthMocks = [$mocks['pre'], $mocks['pre2']]; $this->primaryauthMocks = [$mocks['primary3'], $mocks['primary'], $mocks['primary2']]; $this->secondaryauthMocks = [$mocks['secondary3'], $mocks['secondary'], $mocks['secondary2']]; $this->logger = new \TestLogger(true, function ($message, $level) { return $level === LogLevel::DEBUG ? null : $message; }); $expectLog = []; $this->initializeManager(true); $constraint = \PHPUnit_Framework_Assert::logicalOr($this->equalTo(AuthenticationResponse::PASS), $this->equalTo(AuthenticationResponse::FAIL)); $providers = array_merge($this->preauthMocks, $this->primaryauthMocks, $this->secondaryauthMocks); foreach ($providers as $p) { $p->postCalled = false; $p->expects($this->atMost(1))->method('postAccountCreation')->willReturnCallback(function ($user, $creator, $response) use($constraint, $p, $username) { $this->assertInstanceOf('User', $user); $this->assertSame($username, $user->getName()); $this->assertSame('UTSysop', $creator->getName()); $this->assertInstanceOf(AuthenticationResponse::class, $response); $this->assertThat($response->status, $constraint); $p->postCalled = $response->status; }); } // We're testing with $wgNewUserLog = false, so assert that it worked $dbw = wfGetDB(DB_MASTER); $maxLogId = $dbw->selectField('logging', 'MAX(log_id)', ['log_type' => 'newusers']); $first = true; $created = false; foreach ($managerResponses as $i => $response) { $success = $response instanceof AuthenticationResponse && $response->status === AuthenticationResponse::PASS; if ($i === 'created') { $created = true; $this->hook('LocalUserCreated', $this->once())->with($this->callback(function ($user) use($username) { return $user->getName() === $username; }), $this->equalTo(false)); $expectLog[] = [LogLevel::INFO, "Creating user {user} during account creation"]; } else { $this->hook('LocalUserCreated', $this->never()); } $ex = null; try { if ($first) { $userReq = new UsernameAuthenticationRequest(); $userReq->username = $username; $ret = $this->manager->beginAccountCreation($creator, [$userReq, $req], 'http://localhost/'); } else { $ret = $this->manager->continueAccountCreation([$req]); } if ($response instanceof \Exception) { $this->fail('Expected exception not thrown', "Response {$i}"); } } catch (\Exception $ex) { if (!$response instanceof \Exception) { throw $ex; } $this->assertEquals($response->getMessage(), $ex->getMessage(), "Response {$i}, exception"); $this->assertNull($this->request->getSession()->getSecret('AuthManager::accountCreationState'), "Response {$i}, exception, session state"); $this->unhook('LocalUserCreated'); return; } $this->unhook('LocalUserCreated'); $this->assertSame('http://localhost/', $req->returnToUrl); if ($success) { $this->assertNotNull($ret->loginRequest, "Response {$i}, login marker"); $this->assertContains($ret->loginRequest, $this->managerPriv->createdAccountAuthenticationRequests, "Response {$i}, login marker"); $expectLog[] = [LogLevel::INFO, "MediaWiki\\Auth\\AuthManager::continueAccountCreation: Account creation succeeded for {user}"]; // Set some fields in the expected $response that we couldn't // know in provideAccountCreation(). $response->username = $username; $response->loginRequest = $ret->loginRequest; } else { $this->assertNull($ret->loginRequest, "Response {$i}, login marker"); $this->assertSame([], $this->managerPriv->createdAccountAuthenticationRequests, "Response {$i}, login marker"); } $ret->message = $this->message($ret->message); $this->assertEquals($response, $ret, "Response {$i}, response"); if ($success || $response->status === AuthenticationResponse::FAIL) { $this->assertNull($this->request->getSession()->getSecret('AuthManager::accountCreationState'), "Response {$i}, session state"); foreach ($providers as $p) { $this->assertSame($response->status, $p->postCalled, "Response {$i}, post-auth callback called"); } } else { $this->assertNotNull($this->request->getSession()->getSecret('AuthManager::accountCreationState'), "Response {$i}, session state"); $this->assertEquals($ret->neededRequests, $this->manager->getAuthenticationRequests(AuthManager::ACTION_CREATE_CONTINUE), "Response {$i}, continuation check"); foreach ($providers as $p) { $this->assertFalse($p->postCalled, "Response {$i}, post-auth callback not called"); } } if ($created) { $this->assertNotEquals(0, \User::idFromName($username)); } else { $this->assertEquals(0, \User::idFromName($username)); } $first = false; } $this->assertSame($expectLog, $this->logger->getBuffer()); $this->assertSame($maxLogId, $dbw->selectField('logging', 'MAX(log_id)', ['log_type' => 'newusers'])); }