Skip to content

Commit c2d16be

Browse files
committed
fix(password) refactor recover password + add button to recover any user
1 parent f355313 commit c2d16be

File tree

6 files changed

+80
-147
lines changed

6 files changed

+80
-147
lines changed

includes/controllers/ApiController.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use YesWiki\Core\Exception\GroupNameDoesNotExistException;
1616
use YesWiki\Core\Exception\InvalidGroupNameException;
1717
use YesWiki\Core\Exception\UserEmailAlreadyUsedException;
18+
use YesWiki\Core\Exception\UserNameAlreadyUsedException;
1819
use YesWiki\Core\Exception\UserNameDoesNotExistException;
1920
use YesWiki\Core\Service\AclService;
2021
use YesWiki\Core\Service\ArchiveService;
@@ -188,6 +189,7 @@ public function createUser()
188189
{
189190
$this->denyAccessUnlessAdmin();
190191
$userController = $this->getService(UserController::class);
192+
$userManager = $this->getService(UserManager::class);
191193

192194
if (empty($_POST['name'])) {
193195
$code = Response::HTTP_BAD_REQUEST;
@@ -206,11 +208,7 @@ public function createUser()
206208
'email' => strval($_POST['email']),
207209
'password' => $this->wiki->generateRandomString(30),
208210
]);
209-
if (!boolval($this->wiki->config['contact_disable_email_for_password']) && !empty($user)) {
210-
$link = $userController->sendPasswordRecoveryEmail($user);
211-
} else {
212-
$link = '';
213-
}
211+
$link = $userManager->sendPasswordRecoveryEmail($user);
214212
$code = Response::HTTP_OK;
215213
$result = [
216214
'created' => [$user['name']],
@@ -341,7 +339,7 @@ public function createGroup()
341339
'name' => $group_name,
342340
'error' => $th->getMessage(),
343341
];
344-
} catch (UserNameDoesNotExistException|GroupNameDoesNotExistException $th) {
342+
} catch (UserNameDoesNotExistException | GroupNameDoesNotExistException $th) {
345343
$code = Response::HTTP_UNPROCESSABLE_ENTITY;
346344
$result = [
347345
'name' => $group_name,
@@ -386,7 +384,7 @@ public function updateGroup(string $group_name)
386384
'name' => $group_name,
387385
'error' => $th->getMessage(),
388386
];
389-
} catch (UserNameDoesNotExistException|GroupNameDoesNotExistException $th) {
387+
} catch (UserNameDoesNotExistException | GroupNameDoesNotExistException $th) {
390388
$code = Response::HTTP_UNPROCESSABLE_ENTITY;
391389
$result = [
392390
'name' => $group_name,

includes/controllers/UserController.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,6 @@ public function create(array $newValues): ?User
103103
return null;
104104
}
105105

106-
public function sendPasswordRecoveryEmail(User $user): string
107-
{
108-
if ($this->userManager->sendPasswordRecoveryEmail($user, _t('LOGIN_PASSWORD_FOR'))) {
109-
return $this->userManager->getUserLink();
110-
} else {
111-
return '';
112-
}
113-
}
114-
115106
/**
116107
* update user params
117108
* for e-mail check is existing e-mail.

includes/services/UserManager.php

Lines changed: 52 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ class UserManager implements UserProviderInterface, PasswordUpgraderInterface
3535
protected $securityController;
3636
protected $params;
3737
protected $tripleStore;
38-
protected $userlink;
3938

4039
private $getOneByNameCacheResults;
4140

@@ -56,24 +55,6 @@ public function __construct(
5655
$this->params = $params;
5756
$this->tripleStore = $tripleStore;
5857
$this->getOneByNameCacheResults = [];
59-
$this->userlink = '';
60-
}
61-
62-
private function arrayToUser(?array $userAsArray = null, bool $fillEmpty = false): ?User
63-
{
64-
if (empty($userAsArray)) {
65-
return null;
66-
}
67-
if ($fillEmpty) {
68-
foreach (User::PROPS_LIST as $key) {
69-
if (!array_key_exists($key, $userAsArray)) {
70-
$userAsArray[$key] = null;
71-
}
72-
}
73-
}
74-
75-
// be carefull the User::__construct is really strict about list of properties that should set
76-
return new User($userAsArray);
7758
}
7859

7960
public function userExist($name): bool
@@ -128,9 +109,8 @@ function ($userAsArray) {
128109
*/
129110
public function create($wikiNameOrUser, string $email = '', string $plainPassword = '')
130111
{
131-
$this->userlink = '';
132112
if ($this->securityController->isWikiHibernated()) {
133-
throw new \Exception(_t('WIKI_IN_HIBERNATION'));
113+
throw new Exception(_t('WIKI_IN_HIBERNATION'));
134114
}
135115

136116
if (is_array($wikiNameOrUser)) {
@@ -161,23 +141,23 @@ public function create($wikiNameOrUser, string $email = '', string $plainPasswor
161141
'signuptime' => '',
162142
];
163143
} else {
164-
throw new \Exception('First parameter of UserManager->create should be string or array!');
144+
throw new Exception('First parameter of UserManager->create should be string or array!');
165145
}
166146

167147
if (empty($wikiName)) {
168-
throw new \Exception("'Name' parameter of UserManager->create should not be empty!");
148+
throw new Exception("'Name' parameter of UserManager->create should not be empty!");
169149
}
170150
if (!empty($this->getOneByName($wikiName))) {
171151
throw new UserNameAlreadyUsedException();
172152
}
173153
if (empty($email)) {
174-
throw new \Exception("'email' parameter of UserManager->create should not be empty!");
154+
throw new Exception("'email' parameter of UserManager->create should not be empty!");
175155
}
176156
if (!empty($this->getOneByEmail($email))) {
177157
throw new UserEmailAlreadyUsedException();
178158
}
179159
if (empty($plainPassword)) {
180-
throw new \Exception("'password' parameter of UserManager->create should not be empty!");
160+
throw new Exception("'password' parameter of UserManager->create should not be empty!");
181161
}
182162

183163
unset($this->getOneByNameCacheResults[$wikiName]);
@@ -199,93 +179,52 @@ public function create($wikiNameOrUser, string $email = '', string $plainPasswor
199179
);
200180
}
201181

202-
/*
203-
* Password recovery process (AKA reset password)
204-
* 1. A key is generated using name, email alongside with other stuff.
205-
* 2. The triple (user's name, specific key "vocabulary",key) is stored in triples table.
206-
* 3. In order to update h·er·is password, the user must provided that key.
207-
* 4. The new password is accepted only if the key matches with the value in triples table.
208-
* 5. The corresponding row is removed from triples table.
182+
/** Part of the Password recovery process: Handles the password recovery email process
183+
*
184+
* Generates the password recovery key
185+
* Stores the (name, vocabulary, key) triple in triples table
186+
* Generates the recovery email
187+
* Sends it
188+
*
189+
* @param User $user
190+
* @return string The link sent to the user
209191
*/
210-
211-
protected function generateUserLink($user)
192+
public function sendPasswordRecoveryEmail(User $user)
212193
{
213194
// Generate the password recovery key
214195
$passwordHasher = $this->passwordHasherFactory->getPasswordHasher($user);
215196
$plainKey = $user['name'] . '_' . $user['email'] . random_bytes(16) . date('Y-m-d H:i:s');
216197
$hashedKey = $passwordHasher->hash($plainKey);
217-
$tripleStore = $this->wiki->services->get(TripleStore::class);
218198
// Erase the previous triples in the trible table
219-
$tripleStore->delete($user['name'], self::KEY_VOCABULARY, null, '', '');
199+
$this->tripleStore->delete($user['name'], self::KEY_VOCABULARY, null, '', '');
220200
// Store the (name, vocabulary, key) triple in triples table
221-
$tripleStore->create($user['name'], self::KEY_VOCABULARY, $hashedKey, '', '');
201+
$this->tripleStore->create($user['name'], self::KEY_VOCABULARY, $hashedKey, '', '');
222202

223-
// Generate the recovery email
224-
$this->userlink = $this->wiki->Href('', 'MotDePassePerdu', [
203+
// Generate the recovery link
204+
$link = $this->wiki->Href('', 'MotDePassePerdu', [
225205
'a' => 'recover',
226206
'email' => $hashedKey,
227207
'u' => base64_encode($user['name']),
228208
], false);
229-
}
230-
231-
/**
232-
* Part of the Password recovery process: Handles the password recovery email process.
233-
*
234-
* Generates the password recovery key
235-
* Stores the (name, vocabulary, key) triple in triples table
236-
* Generates the recovery email
237-
* Sends it
238-
*
239-
* @return bool True if OK or false if any problems
240-
*/
241-
public function sendPasswordRecoveryEmail(User $user, string $title): bool
242-
{
243-
$this->generateUserLink($user);
244-
$pieces = parse_url($this->params->get('base_url'));
245-
$domain = isset($pieces['host']) ? $pieces['host'] : '';
246-
247-
$message = _t('LOGIN_DEAR') . ' ' . $user['name'] . ",\n";
248-
$message .= _t('LOGIN_CLICK_FOLLOWING_LINK') . ' :' . "\n";
249-
$message .= '-----------------------' . "\n";
250-
$message .= $this->userlink . "\n";
251-
$message .= '-----------------------' . "\n";
252-
$message .= _t('LOGIN_THE_TEAM') . ' ' . $domain . "\n";
253-
254-
$subject = $title . ' ' . $domain;
255209

256210
// Send the email
257-
return send_mail($this->params->get('BAZ_ADRESSE_MAIL_ADMIN'), $this->params->get('BAZ_ADRESSE_MAIL_ADMIN'), $user['email'], $subject, $message);
258-
}
211+
if (!boolval($this->wiki->config['contact_disable_email_for_password'])) {
212+
$pieces = parse_url($this->params->get('base_url'));
213+
$domain = isset($pieces['host']) ? $pieces['host'] : '';
259214

260-
/**
261-
* Assessor for userlink field.
262-
*/
263-
public function getUserLink(): string
264-
{
265-
return $this->userlink;
266-
}
215+
$message = _t('LOGIN_DEAR') . ' ' . $user['name'] . ",\n";
216+
$message .= _t('LOGIN_CLICK_FOLLOWING_LINK') . ' :' . "\n";
217+
$message .= '-----------------------' . "\n";
218+
$message .= $link . "\n";
219+
$message .= '-----------------------' . "\n";
220+
$message .= _t('LOGIN_THE_TEAM') . ' ' . $domain . "\n";
267221

268-
/**
269-
* Assessor for userlink field.
270-
*/
271-
public function getLastUserLink(User $user): string
272-
{
273-
$passwordHasher = $this->passwordHasherFactory->getPasswordHasher($user);
274-
$plainKey = $user['name'] . '_' . $user['email'] . random_bytes(16) . date('Y-m-d H:i:s');
275-
$hashedKey = $passwordHasher->hash($plainKey);
276-
$tripleStore = $this->wiki->services->get(TripleStore::class);
277-
$key = $tripleStore->getOne($user['name'], self::KEY_VOCABULARY, '', '');
278-
if ($key != null) {
279-
$this->userlink = $this->wiki->Href('', 'MotDePassePerdu', [
280-
'a' => 'recover',
281-
'email' => $key,
282-
'u' => base64_encode($user['name']),
283-
], false);
284-
} else {
285-
$this->generateUserLink($user);
222+
$subject = _t('LOGIN_PASSWORD_LOST_FOR') . ' ' . $domain;
223+
224+
send_mail($this->params->get('BAZ_ADRESSE_MAIL_ADMIN'), $this->params->get('BAZ_ADRESSE_MAIL_ADMIN'), $user['email'], $subject, $message);
286225
}
287226

288-
return $this->userlink;
227+
return $link;
289228
}
290229

291230
/**
@@ -300,7 +239,7 @@ public function getLastUserLink(User $user): string
300239
public function update(User $user, array $newValues): bool
301240
{
302241
if ($this->securityController->isWikiHibernated()) {
303-
throw new \Exception(_t('WIKI_IN_HIBERNATION'));
242+
throw new Exception(_t('WIKI_IN_HIBERNATION'));
304243
}
305244
$newKeys = array_keys($newValues);
306245
$authorizedKeys = array_filter($newKeys, function ($key) {
@@ -378,10 +317,10 @@ public function delete(User $user)
378317
*/
379318
public function groupsWhereIsMember(User $user, bool $adminCheck = true)
380319
{
381-
$group_list = $this->tripleStore->getMatching(GROUP_PREFIX . '%', null, '%'.$user['name'].'%', 'LIKE', '=', 'LIKE');
320+
$group_list = $this->tripleStore->getMatching(GROUP_PREFIX . '%', null, '%' . $user['name'] . '%', 'LIKE', '=', 'LIKE');
382321
$prefix_len = strlen(GROUP_PREFIX);
383322
$list = array();
384-
foreach($group_list as $group) {
323+
foreach ($group_list as $group) {
385324
$list[] = substr($group['resource'], $prefix_len);
386325
}
387326
return $list;
@@ -542,4 +481,21 @@ public function logout()
542481
{
543482
$this->wiki->services->get(AuthController::class)->logout();
544483
}
484+
485+
private function arrayToUser(?array $userAsArray = null, bool $fillEmpty = false): ?User
486+
{
487+
if (empty($userAsArray)) {
488+
return null;
489+
}
490+
if ($fillEmpty) {
491+
foreach (User::PROPS_LIST as $key) {
492+
if (!array_key_exists($key, $userAsArray)) {
493+
$userAsArray[$key] = null;
494+
}
495+
}
496+
}
497+
498+
// be carefull the User::__construct is really strict about list of properties that should set
499+
return new User($userAsArray);
500+
}
545501
}

tools/login/actions/LostPasswordAction.php

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,16 @@ public function run()
6868
'type' => 'danger',
6969
'message' => _t('LOGIN_UNKNOWN_USER'),
7070
]);
71-
break;
7271
case 'successPage':
7372
return $renderedTitle . $this->render('@templates/alert-message.twig', [
7473
'type' => 'success',
7574
'message' => _t('LOGIN_MESSAGE_SENT'),
7675
]);
77-
break;
7876
case 'recoverSuccess':
7977
return $renderedTitle . $this->render('@templates/alert-message.twig', [
8078
'type' => 'success',
8179
'message' => _t('LOGIN_PASSWORD_WAS_RESET'),
8280
]);
83-
break;
8481
case 'recoverForm':
8582
if (isset($hash)) {
8683
$key = $hash;
@@ -95,13 +92,11 @@ public function run()
9592
'key' => $hash ?? $key,
9693
'inIframe' => (testUrlInIframe() == 'iframe'),
9794
]);
98-
break;
9995
case 'directDangerMessage':
10096
return $renderedTitle . $this->render('@templates/alert-message.twig', [
10197
'type' => 'danger',
10298
'message' => $message,
10399
]);
104-
break;
105100
case 'emailForm':
106101
default:
107102
return $this->render('@login/lost-password-email-form.twig', [
@@ -130,7 +125,7 @@ private function manageSubStep(int $subStep): ?User
130125
$user = $this->userManager->getOneByEmail($email);
131126
if (!empty($user)) {
132127
$this->typeOfRendering = 'successPage';
133-
$this->userManager->sendPasswordRecoveryEmail($user, _t('LOGIN_PASSWORD_LOST_FOR'));
128+
$this->userManager->sendPasswordRecoveryEmail($user);
134129
} else {
135130
$this->errorType = 'userNotFound';
136131
$this->typeOfRendering = 'userNotFound';
@@ -177,12 +172,10 @@ private function manageSubStep(int $subStep): ?User
177172
return $user ?? null;
178173
}
179174

180-
/** Part of the Password recovery process: sets the password to a new value if given the the proper recovery key (sent in a recovery email).
181-
*
175+
/**
182176
* In order to update h·er·is password, the user provides a key (sent using sendPasswordRecoveryEmail())
183177
* The new password is accepted only if the key matches with the value in triples table.
184178
* The corresponding row is the removed from triples table.
185-
* See Password recovery process above
186179
*
187180
* @param string $userName The user login
188181
* @param string $key The password recovery key (sent by email)
@@ -201,10 +194,9 @@ private function resetPassword(string $userName, string $key, string $password)
201194

202195
$user = $this->userManager->getOneByName($userName);
203196
if (empty($user)) {
204-
$this->error = false;
205197
$this->typeOfRendering = 'userNotFound';
206198

207-
return null;
199+
return false;
208200
}
209201
$this->authController->setPassword($user, $password);
210202
// Was able to update password => Remove the key from triples table
@@ -230,5 +222,4 @@ private function checkEmailKey(string $hash, string $user): bool
230222
// Pas de detournement possible car utilisation de _vocabulary/key ....
231223
return !is_null($this->tripleStore->exist($user, UserManager::KEY_VOCABULARY, $hash, '', ''));
232224
}
233-
/* End of Password recovery process (AKA reset password) */
234225
}

0 commit comments

Comments
 (0)