1<?php 2 3namespace MediaWiki\Auth; 4 5use MediaWiki\MediaWikiServices; 6use Wikimedia\TestingAccessWrapper; 7 8/** 9 * @group AuthManager 10 * @covers \MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider 11 */ 12class AbstractPasswordPrimaryAuthenticationProviderTest extends \MediaWikiIntegrationTestCase { 13 public function testConstructor() { 14 $provider = $this->getMockForAbstractClass( 15 AbstractPasswordPrimaryAuthenticationProvider::class 16 ); 17 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 18 $this->assertTrue( $providerPriv->authoritative ); 19 20 $provider = $this->getMockForAbstractClass( 21 AbstractPasswordPrimaryAuthenticationProvider::class, 22 [ [ 'authoritative' => false ] ] 23 ); 24 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 25 $this->assertFalse( $providerPriv->authoritative ); 26 } 27 28 public function testGetPasswordFactory() { 29 $provider = $this->getMockForAbstractClass( 30 AbstractPasswordPrimaryAuthenticationProvider::class 31 ); 32 $provider->setConfig( MediaWikiServices::getInstance()->getMainConfig() ); 33 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 34 35 $obj = $providerPriv->getPasswordFactory(); 36 $this->assertInstanceOf( \PasswordFactory::class, $obj ); 37 $this->assertSame( $obj, $providerPriv->getPasswordFactory() ); 38 } 39 40 public function testGetPassword() { 41 $provider = $this->getMockForAbstractClass( 42 AbstractPasswordPrimaryAuthenticationProvider::class 43 ); 44 $provider->setConfig( MediaWikiServices::getInstance()->getMainConfig() ); 45 $provider->setLogger( new \Psr\Log\NullLogger() ); 46 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 47 48 $obj = $providerPriv->getPassword( null ); 49 $this->assertInstanceOf( \Password::class, $obj ); 50 51 $obj = $providerPriv->getPassword( 'invalid' ); 52 $this->assertInstanceOf( \Password::class, $obj ); 53 } 54 55 public function testGetNewPasswordExpiry() { 56 $config = new \HashConfig; 57 $provider = $this->getMockForAbstractClass( 58 AbstractPasswordPrimaryAuthenticationProvider::class 59 ); 60 $provider->setConfig( new \MultiConfig( [ 61 $config, 62 MediaWikiServices::getInstance()->getMainConfig() 63 ] ) ); 64 $provider->setLogger( new \Psr\Log\NullLogger() ); 65 $provider->setHookContainer( MediaWikiServices::getInstance()->getHookContainer() ); 66 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 67 68 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 'ResetPasswordExpiration' => [] ] ); 69 70 $config->set( 'PasswordExpirationDays', 0 ); 71 $this->assertNull( $providerPriv->getNewPasswordExpiry( 'UTSysop' ) ); 72 73 $config->set( 'PasswordExpirationDays', 5 ); 74 $this->assertEqualsWithDelta( 75 time() + 5 * 86400, 76 wfTimestamp( TS_UNIX, $providerPriv->getNewPasswordExpiry( 'UTSysop' ) ), 77 2 /* Fuzz */ 78 ); 79 80 $this->mergeMwGlobalArrayValue( 'wgHooks', [ 81 'ResetPasswordExpiration' => [ function ( $user, &$expires ) { 82 $this->assertSame( 'UTSysop', $user->getName() ); 83 $expires = '30001231235959'; 84 } ] 85 ] ); 86 $this->assertSame( '30001231235959', $providerPriv->getNewPasswordExpiry( 'UTSysop' ) ); 87 } 88 89 public function testCheckPasswordValidity() { 90 $uppCalled = 0; 91 $uppStatus = \Status::newGood( [] ); 92 $this->setMwGlobals( [ 93 'wgPasswordPolicy' => [ 94 'policies' => [ 95 'default' => [ 96 'Check' => true, 97 ], 98 ], 99 'checks' => [ 100 'Check' => function () use ( &$uppCalled, &$uppStatus ) { 101 $uppCalled++; 102 return $uppStatus; 103 }, 104 ], 105 ] 106 ] ); 107 108 $provider = $this->getMockForAbstractClass( 109 AbstractPasswordPrimaryAuthenticationProvider::class 110 ); 111 $provider->setConfig( MediaWikiServices::getInstance()->getMainConfig() ); 112 $provider->setLogger( new \Psr\Log\NullLogger() ); 113 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 114 115 $this->assertEquals( $uppStatus, $providerPriv->checkPasswordValidity( 'foo', 'bar' ) ); 116 117 $uppStatus->fatal( 'arbitrary-warning' ); 118 $this->assertEquals( $uppStatus, $providerPriv->checkPasswordValidity( 'foo', 'bar' ) ); 119 } 120 121 public function testSetPasswordResetFlag() { 122 $config = new \HashConfig( [ 123 'InvalidPasswordReset' => true, 124 ] ); 125 126 $services = MediaWikiServices::getInstance(); 127 $manager = new AuthManager( 128 new \FauxRequest(), 129 $services->getMainConfig(), 130 $services->getObjectFactory(), 131 $services->getPermissionManager(), 132 $services->getHookContainer() 133 ); 134 135 $provider = $this->getMockForAbstractClass( 136 AbstractPasswordPrimaryAuthenticationProvider::class 137 ); 138 $provider->setConfig( $config ); 139 $provider->setLogger( new \Psr\Log\NullLogger() ); 140 $provider->setManager( $manager ); 141 $provider->setHookContainer( $services->getHookContainer() ); 142 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 143 144 $manager->removeAuthenticationSessionData( null ); 145 $status = \Status::newGood(); 146 $providerPriv->setPasswordResetFlag( 'Foo', $status ); 147 $this->assertNull( $manager->getAuthenticationSessionData( 'reset-pass' ) ); 148 149 $manager->removeAuthenticationSessionData( null ); 150 $status = \Status::newGood( [ 'suggestChangeOnLogin' => true ] ); 151 $status->error( 'testing' ); 152 $providerPriv->setPasswordResetFlag( 'Foo', $status ); 153 $ret = $manager->getAuthenticationSessionData( 'reset-pass' ); 154 $this->assertNotNull( $ret ); 155 $this->assertSame( 'resetpass-validity-soft', $ret->msg->getKey() ); 156 $this->assertFalse( $ret->hard ); 157 158 $config->set( 'InvalidPasswordReset', false ); 159 $manager->removeAuthenticationSessionData( null ); 160 $providerPriv->setPasswordResetFlag( 'Foo', $status ); 161 $ret = $manager->getAuthenticationSessionData( 'reset-pass' ); 162 $this->assertNull( $ret ); 163 } 164 165 public function testFailResponse() { 166 $provider = $this->getMockForAbstractClass( 167 AbstractPasswordPrimaryAuthenticationProvider::class, 168 [ [ 'authoritative' => false ] ] 169 ); 170 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 171 172 $req = new PasswordAuthenticationRequest; 173 174 $ret = $providerPriv->failResponse( $req ); 175 $this->assertSame( AuthenticationResponse::ABSTAIN, $ret->status ); 176 177 $provider = $this->getMockForAbstractClass( 178 AbstractPasswordPrimaryAuthenticationProvider::class, 179 [ [ 'authoritative' => true ] ] 180 ); 181 $providerPriv = TestingAccessWrapper::newFromObject( $provider ); 182 183 $req->password = ''; 184 $ret = $providerPriv->failResponse( $req ); 185 $this->assertSame( AuthenticationResponse::FAIL, $ret->status ); 186 $this->assertSame( 'wrongpasswordempty', $ret->message->getKey() ); 187 188 $req->password = 'X'; 189 $ret = $providerPriv->failResponse( $req ); 190 $this->assertSame( AuthenticationResponse::FAIL, $ret->status ); 191 $this->assertSame( 'wrongpassword', $ret->message->getKey() ); 192 } 193 194 /** 195 * @dataProvider provideGetAuthenticationRequests 196 * @param string $action 197 * @param array $response 198 */ 199 public function testGetAuthenticationRequests( $action, $response ) { 200 $provider = $this->getMockForAbstractClass( 201 AbstractPasswordPrimaryAuthenticationProvider::class 202 ); 203 204 $this->assertEquals( $response, $provider->getAuthenticationRequests( $action, [] ) ); 205 } 206 207 public static function provideGetAuthenticationRequests() { 208 return [ 209 [ AuthManager::ACTION_LOGIN, [ new PasswordAuthenticationRequest() ] ], 210 [ AuthManager::ACTION_CREATE, [ new PasswordAuthenticationRequest() ] ], 211 [ AuthManager::ACTION_LINK, [] ], 212 [ AuthManager::ACTION_CHANGE, [ new PasswordAuthenticationRequest() ] ], 213 [ AuthManager::ACTION_REMOVE, [ new PasswordAuthenticationRequest() ] ], 214 ]; 215 } 216 217 public function testProviderRevokeAccessForUser() { 218 $req = new PasswordAuthenticationRequest; 219 $req->action = AuthManager::ACTION_REMOVE; 220 $req->username = 'foo'; 221 $req->password = null; 222 223 $provider = $this->getMockForAbstractClass( 224 AbstractPasswordPrimaryAuthenticationProvider::class 225 ); 226 $provider->expects( $this->once() ) 227 ->method( 'providerChangeAuthenticationData' ) 228 ->with( $this->equalTo( $req ) ); 229 230 $provider->providerRevokeAccessForUser( 'foo' ); 231 } 232 233} 234