1<?php 2 3use MediaWiki\MediaWikiServices; 4use MediaWiki\User\UserIdentity; 5use MediaWiki\User\UserIdentityValue; 6 7/** 8 * Wraps the user object, so we can also retain full access to properties 9 * like password if we log in via the API. 10 */ 11class TestUser { 12 /** 13 * @var string 14 */ 15 private $username; 16 17 /** 18 * @var string 19 */ 20 private $password; 21 22 /** 23 * @var User 24 */ 25 private $user; 26 27 private function assertNotReal() { 28 global $wgDBprefix; 29 if ( 30 $wgDBprefix !== MediaWikiIntegrationTestCase::DB_PREFIX && 31 $wgDBprefix !== ParserTestRunner::DB_PREFIX 32 ) { 33 throw new MWException( "Can't create user on real database" ); 34 } 35 } 36 37 public function __construct( $username, $realname = 'Real Name', 38 $email = 'sample@example.com', $groups = [] 39 ) { 40 $this->assertNotReal(); 41 42 $this->username = $username; 43 $this->password = 'TestUser'; 44 45 $this->user = User::newFromName( $this->username ); 46 $this->user->load(); 47 48 // In an ideal world we'd have a new wiki (or mock data store) for every single test. 49 // But for now, we just need to create or update the user with the desired properties. 50 // we particularly need the new password, since we just generated it randomly. 51 // In core MediaWiki, there is no functionality to delete users, so this is the best we can do. 52 if ( !$this->user->isRegistered() ) { 53 // create the user 54 $this->user = User::createNew( 55 $this->username, [ 56 "email" => $email, 57 "real_name" => $realname 58 ] 59 ); 60 61 if ( !$this->user ) { 62 throw new MWException( "Error creating TestUser " . $username ); 63 } 64 } 65 66 // Update the user to use the password and other details 67 $this->setPassword( $this->password ); 68 $change = $this->setEmail( $email ) || 69 $this->setRealName( $realname ); 70 71 // Adjust groups by adding any missing ones and removing any extras 72 $currentGroups = $this->user->getGroups(); 73 foreach ( array_diff( $groups, $currentGroups ) as $group ) { 74 $this->user->addGroup( $group ); 75 } 76 foreach ( array_diff( $currentGroups, $groups ) as $group ) { 77 $this->user->removeGroup( $group ); 78 } 79 if ( $change ) { 80 // Disable CAS check before saving. The User object may have been initialized from cached 81 // information that may be out of whack with the database during testing. If tests were 82 // perfectly isolated, this would not happen. But if it does happen, let's just ignore the 83 // inconsistency, and just write the data we want - during testing, we are not worried 84 // about data loss. 85 $this->user->mTouched = ''; 86 $this->user->saveSettings(); 87 } 88 } 89 90 /** 91 * @param string $realname 92 * @return bool 93 */ 94 private function setRealName( $realname ) { 95 if ( $this->user->getRealName() !== $realname ) { 96 $this->user->setRealName( $realname ); 97 return true; 98 } 99 100 return false; 101 } 102 103 /** 104 * @param string $email 105 * @return bool 106 */ 107 private function setEmail( string $email ) { 108 if ( $this->user->getEmail() !== $email ) { 109 $this->user->setEmail( $email ); 110 return true; 111 } 112 113 return false; 114 } 115 116 /** 117 * @param string $password 118 */ 119 private function setPassword( $password ) { 120 self::setPasswordForUser( $this->user, $password ); 121 } 122 123 /** 124 * Set the password on a testing user 125 * 126 * This assumes we're still using the generic AuthManager config from 127 * PHPUnitMaintClass::finalSetup(), and just sets the password in the 128 * database directly. 129 * @param User $user 130 * @param string $password 131 */ 132 public static function setPasswordForUser( User $user, $password ) { 133 if ( !$user->getId() ) { 134 throw new MWException( "Passed User has not been added to the database yet!" ); 135 } 136 137 $dbw = wfGetDB( DB_MASTER ); 138 $row = $dbw->selectRow( 139 'user', 140 [ 'user_password' ], 141 [ 'user_id' => $user->getId() ], 142 __METHOD__ 143 ); 144 if ( !$row ) { 145 throw new MWException( "Passed User has an ID but is not in the database?" ); 146 } 147 148 $passwordFactory = MediaWikiServices::getInstance()->getPasswordFactory(); 149 if ( !$passwordFactory->newFromCiphertext( $row->user_password )->verify( $password ) ) { 150 $passwordHash = $passwordFactory->newFromPlaintext( $password ); 151 $dbw->update( 152 'user', 153 [ 'user_password' => $passwordHash->toString() ], 154 [ 'user_id' => $user->getId() ], 155 __METHOD__ 156 ); 157 } 158 } 159 160 /** 161 * @since 1.25 162 * @return User 163 */ 164 public function getUser() { 165 return $this->user; 166 } 167 168 /** 169 * @since 1.36 170 * @return UserIdentity 171 */ 172 public function getUserIdentity(): UserIdentity { 173 return new UserIdentityValue( $this->user->getId(), $this->user->getName() ); 174 } 175 176 /** 177 * @since 1.25 178 * @return string 179 */ 180 public function getPassword() { 181 return $this->password; 182 } 183} 184