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