MediaWiki  master
LocalPasswordPrimaryAuthenticationProvider.php
Go to the documentation of this file.
1 <?php
22 namespace MediaWiki\Auth;
23 
25 use User;
27 
35 {
36 
38  protected $loginOnly = false;
39 
41  private $loadBalancer;
42 
50  public function __construct( ILoadBalancer $loadBalancer, $params = [] ) {
51  parent::__construct( $params );
52  $this->loginOnly = !empty( $params['loginOnly'] );
53  $this->loadBalancer = $loadBalancer;
54  }
55 
63  protected function getPasswordResetData( $username, $row ) {
64  $now = (int)wfTimestamp();
65  $expiration = wfTimestampOrNull( TS_UNIX, $row->user_password_expires );
66  if ( $expiration === null || (int)$expiration >= $now ) {
67  return null;
68  }
69 
70  $grace = $this->config->get( 'PasswordExpireGrace' );
71  if ( (int)$expiration + $grace < $now ) {
72  $data = [
73  'hard' => true,
74  'msg' => \Status::newFatal( 'resetpass-expired' )->getMessage(),
75  ];
76  } else {
77  $data = [
78  'hard' => false,
79  'msg' => \Status::newFatal( 'resetpass-expired-soft' )->getMessage(),
80  ];
81  }
82 
83  return (object)$data;
84  }
85 
86  public function beginPrimaryAuthentication( array $reqs ) {
87  $req = AuthenticationRequest::getRequestByClass( $reqs, PasswordAuthenticationRequest::class );
88  if ( !$req ) {
90  }
91 
92  if ( $req->username === null || $req->password === null ) {
94  }
95 
96  $username = $this->userNameUtils->getCanonical( $req->username, UserNameUtils::RIGOR_USABLE );
97  if ( $username === false ) {
99  }
100 
101  $fields = [
102  'user_id', 'user_password', 'user_password_expires',
103  ];
104 
105  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
106  $row = $dbr->selectRow(
107  'user',
108  $fields,
109  [ 'user_name' => $username ],
110  __METHOD__
111  );
112  if ( !$row ) {
113  // Do not reveal whether its bad username or
114  // bad password to prevent username enumeration
115  // on private wikis. (T134100)
116  return $this->failResponse( $req );
117  }
118 
119  $oldRow = clone $row;
120  // Check for *really* old password hashes that don't even have a type
121  // The old hash format was just an md5 hex hash, with no type information
122  if ( preg_match( '/^[0-9a-f]{32}$/', $row->user_password ) ) {
123  $row->user_password = ":B:{$row->user_id}:{$row->user_password}";
124  }
125 
126  $status = $this->checkPasswordValidity( $username, $req->password );
127  if ( !$status->isOK() ) {
128  // Fatal, can't log in
129  return AuthenticationResponse::newFail( $status->getMessage() );
130  }
131 
132  $pwhash = $this->getPassword( $row->user_password );
133  if ( !$pwhash->verify( $req->password ) ) {
134  if ( $this->config->get( 'LegacyEncoding' ) ) {
135  // Some wikis were converted from ISO 8859-1 to UTF-8, the passwords can't be converted
136  // Check for this with iconv
137  $cp1252Password = iconv( 'UTF-8', 'WINDOWS-1252//TRANSLIT', $req->password );
138  if ( $cp1252Password === $req->password || !$pwhash->verify( $cp1252Password ) ) {
139  return $this->failResponse( $req );
140  }
141  } else {
142  return $this->failResponse( $req );
143  }
144  }
145 
146  // @codeCoverageIgnoreStart
147  if ( $this->getPasswordFactory()->needsUpdate( $pwhash ) ) {
148  $newHash = $this->getPasswordFactory()->newFromPlaintext( $req->password );
149  $fname = __METHOD__;
150  \DeferredUpdates::addCallableUpdate( function () use ( $newHash, $oldRow, $fname ) {
151  $dbw = $this->loadBalancer->getConnectionRef( DB_PRIMARY );
152  $dbw->update(
153  'user',
154  [ 'user_password' => $newHash->toString() ],
155  [
156  'user_id' => $oldRow->user_id,
157  'user_password' => $oldRow->user_password
158  ],
159  $fname
160  );
161  } );
162  }
163  // @codeCoverageIgnoreEnd
164 
165  $this->setPasswordResetFlag( $username, $status, $row );
166 
167  return AuthenticationResponse::newPass( $username );
168  }
169 
170  public function testUserCanAuthenticate( $username ) {
171  $username = $this->userNameUtils->getCanonical( $username, UserNameUtils::RIGOR_USABLE );
172  if ( $username === false ) {
173  return false;
174  }
175 
176  $dbr = $this->loadBalancer->getConnectionRef( DB_REPLICA );
177  $row = $dbr->selectRow(
178  'user',
179  [ 'user_password' ],
180  [ 'user_name' => $username ],
181  __METHOD__
182  );
183  if ( !$row ) {
184  return false;
185  }
186 
187  // Check for *really* old password hashes that don't even have a type
188  // The old hash format was just an md5 hex hash, with no type information
189  if ( preg_match( '/^[0-9a-f]{32}$/', $row->user_password ) ) {
190  return true;
191  }
192 
193  return !$this->getPassword( $row->user_password ) instanceof \InvalidPassword;
194  }
195 
196  public function testUserExists( $username, $flags = User::READ_NORMAL ) {
197  $username = $this->userNameUtils->getCanonical( $username, UserNameUtils::RIGOR_USABLE );
198  if ( $username === false ) {
199  return false;
200  }
201 
202  list( $db, $options ) = \DBAccessObjectUtils::getDBOptions( $flags );
203  return (bool)$this->loadBalancer->getConnectionRef( $db )->selectField(
204  [ 'user' ],
205  'user_id',
206  [ 'user_name' => $username ],
207  __METHOD__,
208  $options
209  );
210  }
211 
213  AuthenticationRequest $req, $checkData = true
214  ) {
215  // We only want to blank the password if something else will accept the
216  // new authentication data, so return 'ignore' here.
217  if ( $this->loginOnly ) {
218  return \StatusValue::newGood( 'ignored' );
219  }
220 
221  if ( get_class( $req ) === PasswordAuthenticationRequest::class ) {
222  if ( !$checkData ) {
223  return \StatusValue::newGood();
224  }
225 
226  $username = $this->userNameUtils->getCanonical( $req->username, UserNameUtils::RIGOR_USABLE );
227  if ( $username !== false ) {
228  $row = $this->loadBalancer->getConnectionRef( DB_PRIMARY )->selectRow(
229  'user',
230  [ 'user_id' ],
231  [ 'user_name' => $username ],
232  __METHOD__
233  );
234  if ( $row ) {
235  $sv = \StatusValue::newGood();
236  if ( $req->password !== null ) {
237  if ( $req->password !== $req->retype ) {
238  $sv->fatal( 'badretype' );
239  } else {
240  $sv->merge( $this->checkPasswordValidity( $username, $req->password ) );
241  }
242  }
243  return $sv;
244  }
245  }
246  }
247 
248  return \StatusValue::newGood( 'ignored' );
249  }
250 
252  $username = $req->username !== null ?
253  $this->userNameUtils->getCanonical( $req->username, UserNameUtils::RIGOR_USABLE ) : false;
254  if ( $username === false ) {
255  return;
256  }
257 
258  $pwhash = null;
259 
260  if ( get_class( $req ) === PasswordAuthenticationRequest::class ) {
261  if ( $this->loginOnly ) {
262  $pwhash = $this->getPasswordFactory()->newFromCiphertext( null );
263  $expiry = null;
264  } else {
265  $pwhash = $this->getPasswordFactory()->newFromPlaintext( $req->password );
266  $expiry = $this->getNewPasswordExpiry( $username );
267  }
268  }
269 
270  if ( $pwhash ) {
271  $dbw = $this->loadBalancer->getConnectionRef( DB_PRIMARY );
272  $dbw->update(
273  'user',
274  [
275  'user_password' => $pwhash->toString(),
276  'user_password_expires' => $dbw->timestampOrNull( $expiry ),
277  ],
278  [ 'user_name' => $username ],
279  __METHOD__
280  );
281  }
282  }
283 
284  public function accountCreationType() {
285  return $this->loginOnly ? self::TYPE_NONE : self::TYPE_CREATE;
286  }
287 
288  public function testForAccountCreation( $user, $creator, array $reqs ) {
289  $req = AuthenticationRequest::getRequestByClass( $reqs, PasswordAuthenticationRequest::class );
290 
291  $ret = \StatusValue::newGood();
292  if ( !$this->loginOnly && $req && $req->username !== null && $req->password !== null ) {
293  if ( $req->password !== $req->retype ) {
294  $ret->fatal( 'badretype' );
295  } else {
296  $ret->merge(
297  $this->checkPasswordValidity( $user->getName(), $req->password )
298  );
299  }
300  }
301  return $ret;
302  }
303 
304  public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) {
305  if ( $this->accountCreationType() === self::TYPE_NONE ) {
306  throw new \BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' );
307  }
308 
309  $req = AuthenticationRequest::getRequestByClass( $reqs, PasswordAuthenticationRequest::class );
310  if ( $req && $req->username !== null && $req->password !== null ) {
311  // Nothing we can do besides claim it, because the user isn't in
312  // the DB yet
313  if ( $req->username !== $user->getName() ) {
314  $req = clone $req;
315  $req->username = $user->getName();
316  }
317  $ret = AuthenticationResponse::newPass( $req->username );
318  $ret->createRequest = $req;
319  return $ret;
320  }
322  }
323 
324  public function finishAccountCreation( $user, $creator, AuthenticationResponse $res ) {
325  if ( $this->accountCreationType() === self::TYPE_NONE ) {
326  throw new \BadMethodCallException( 'Shouldn\'t call this when accountCreationType() is NONE' );
327  }
328 
329  // Now that the user is in the DB, set the password on it.
330  $this->providerChangeAuthenticationData( $res->createRequest );
331 
332  return null;
333  }
334 }
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\$loginOnly
bool $loginOnly
If true, this instance is for legacy logins only.
Definition: LocalPasswordPrimaryAuthenticationProvider.php:38
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\$loadBalancer
ILoadBalancer $loadBalancer
Definition: LocalPasswordPrimaryAuthenticationProvider.php:41
MediaWiki\Auth\PrimaryAuthenticationProvider\TYPE_CREATE
const TYPE_CREATE
Provider can create accounts.
Definition: PrimaryAuthenticationProvider.php:77
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\accountCreationType
accountCreationType()
Fetch the account-creation type.
Definition: LocalPasswordPrimaryAuthenticationProvider.php:284
MediaWiki\Auth\PrimaryAuthenticationProvider\TYPE_NONE
const TYPE_NONE
Provider cannot create or link to accounts.
Definition: PrimaryAuthenticationProvider.php:81
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider\getPassword
getPassword( $hash)
Get a Password object from the hash.
Definition: AbstractPasswordPrimaryAuthenticationProvider.php:71
MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider\failResponse
failResponse(PasswordAuthenticationRequest $req)
Return the appropriate response for failure.
Definition: AbstractPasswordPrimaryAuthenticationProvider.php:87
MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider
Basic framework for a primary authentication provider that uses passwords.
Definition: AbstractPasswordPrimaryAuthenticationProvider.php:37
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1668
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
$res
$res
Definition: testCompression.php:57
MediaWiki\Auth\AuthenticationRequest\getRequestByClass
static getRequestByClass(array $reqs, $class, $allowSubclasses=false)
Select a request by class name.
Definition: AuthenticationRequest.php:272
$dbr
$dbr
Definition: testCompression.php:54
MediaWiki\Auth\AuthenticationResponse\newAbstain
static newAbstain()
Definition: AuthenticationResponse.php:170
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\__construct
__construct(ILoadBalancer $loadBalancer, $params=[])
Definition: LocalPasswordPrimaryAuthenticationProvider.php:50
MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider\getNewPasswordExpiry
getNewPasswordExpiry( $username)
Get expiration date for a new password, if any.
Definition: AbstractPasswordPrimaryAuthenticationProvider.php:160
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\providerAllowsAuthenticationDataChange
providerAllowsAuthenticationDataChange(AuthenticationRequest $req, $checkData=true)
Validate a change of authentication data (e.g.
Definition: LocalPasswordPrimaryAuthenticationProvider.php:212
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider
A primary authentication provider that uses the password field in the 'user' table.
Definition: LocalPasswordPrimaryAuthenticationProvider.php:35
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\testUserCanAuthenticate
testUserCanAuthenticate( $username)
Test whether the named user can authenticate with this provider.Should return true if the provider ha...
Definition: LocalPasswordPrimaryAuthenticationProvider.php:170
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\beginPrimaryAuthentication
beginPrimaryAuthentication(array $reqs)
Start an authentication flow.
Definition: LocalPasswordPrimaryAuthenticationProvider.php:86
MediaWiki\Auth\AuthenticationResponse
This is a value object to hold authentication response data.
Definition: AuthenticationResponse.php:37
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1684
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\finishAccountCreation
finishAccountCreation( $user, $creator, AuthenticationResponse $res)
Post-creation callback.Called after the user is added to the database, before secondary authenticatio...
Definition: LocalPasswordPrimaryAuthenticationProvider.php:324
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\testForAccountCreation
testForAccountCreation( $user, $creator, array $reqs)
Determine whether an account creation may begin.Called from AuthManager::beginAccountCreation()No nee...
Definition: LocalPasswordPrimaryAuthenticationProvider.php:288
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\providerChangeAuthenticationData
providerChangeAuthenticationData(AuthenticationRequest $req)
Change or remove authentication data (e.g.
Definition: LocalPasswordPrimaryAuthenticationProvider.php:251
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\beginPrimaryAccountCreation
beginPrimaryAccountCreation( $user, $creator, array $reqs)
Start an account creation flow.
Definition: LocalPasswordPrimaryAuthenticationProvider.php:304
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
DB_PRIMARY
const DB_PRIMARY
Definition: defines.php:27
MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider\setPasswordResetFlag
setPasswordResetFlag( $username, Status $status, $data=null)
Check if the password should be reset.
Definition: AbstractPasswordPrimaryAuthenticationProvider.php:122
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\getPasswordResetData
getPasswordResetData( $username, $row)
Check if the password has expired and needs a reset.
Definition: LocalPasswordPrimaryAuthenticationProvider.php:63
MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider\checkPasswordValidity
checkPasswordValidity( $username, $password)
Check that the password is valid.
Definition: AbstractPasswordPrimaryAuthenticationProvider.php:107
MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider\testUserExists
testUserExists( $username, $flags=User::READ_NORMAL)
Test whether the named user exists.
Definition: LocalPasswordPrimaryAuthenticationProvider.php:196
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:42
MediaWiki\Auth\AuthenticationResponse\newFail
static newFail(Message $msg)
Definition: AuthenticationResponse.php:146
MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider\getPasswordFactory
getPasswordFactory()
Definition: AbstractPasswordPrimaryAuthenticationProvider.php:56
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:68
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
Definition: DeferredUpdates.php:145
MediaWiki\Auth
Definition: AbstractAuthenticationProvider.php:22
MediaWiki\Auth\AuthenticationResponse\newPass
static newPass( $username=null)
Definition: AuthenticationResponse.php:134
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:38