Skip to content

Commit 649ad48

Browse files
Merge pull request #65134 from nextcloud/backport/64764/stable34
[stable34] fix(occ): dispatch password reset events for user:resetpassword
2 parents d905199 + 9df9df3 commit 649ad48

2 files changed

Lines changed: 123 additions & 0 deletions

File tree

‎core/Command/User/ResetPassword.php‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
namespace OC\Core\Command\User;
99

1010
use OC\Core\Command\Base;
11+
use OC\Core\Events\BeforePasswordResetEvent;
12+
use OC\Core\Events\PasswordResetEvent;
1113
use OCP\App\IAppManager;
14+
use OCP\EventDispatcher\IEventDispatcher;
1215
use OCP\IUser;
1316
use OCP\IUserManager;
1417
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
@@ -24,6 +27,7 @@ class ResetPassword extends Base {
2427
public function __construct(
2528
protected IUserManager $userManager,
2629
private IAppManager $appManager,
30+
private IEventDispatcher $eventDispatcher,
2731
) {
2832
parent::__construct();
2933
}
@@ -118,13 +122,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
118122

119123

120124
try {
125+
$this->eventDispatcher->dispatchTyped(new BeforePasswordResetEvent($user, $password));
121126
$success = $user->setPassword($password);
122127
} catch (\Exception $e) {
123128
$output->writeln('<error>' . $e->getMessage() . '</error>');
124129
return 1;
125130
}
126131

127132
if ($success) {
133+
$this->eventDispatcher->dispatchTyped(new PasswordResetEvent($user, $password));
128134
$output->writeln('<info>Successfully reset password for ' . $username . '</info>');
129135
} else {
130136
$output->writeln('<error>Error while resetting password!</error>');
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Tests\Core\Command\User;
11+
12+
use OC\Core\Command\User\ResetPassword;
13+
use OC\Core\Events\BeforePasswordResetEvent;
14+
use OC\Core\Events\PasswordResetEvent;
15+
use OCP\App\IAppManager;
16+
use OCP\EventDispatcher\IEventDispatcher;
17+
use OCP\IUser;
18+
use OCP\IUserManager;
19+
use Symfony\Component\Console\Input\InputInterface;
20+
use Symfony\Component\Console\Output\OutputInterface;
21+
use Test\TestCase;
22+
23+
class ResetPasswordTest extends TestCase {
24+
#[\PHPUnit\Framework\Attributes\DataProvider('passwordResetResults')]
25+
public function testPasswordResetEvents(
26+
bool $passwordChangeSucceeds,
27+
int $expectedExitCode,
28+
array $expectedSequence,
29+
string $expectedOutput,
30+
): void {
31+
$sequence = [];
32+
33+
$user = $this->createMock(IUser::class);
34+
$user->expects(self::once())
35+
->method('setPassword')
36+
->with('NewPassword')
37+
->willReturnCallback(static function () use (&$sequence, $passwordChangeSucceeds): bool {
38+
$sequence[] = 'setPassword';
39+
return $passwordChangeSucceeds;
40+
});
41+
42+
$userManager = $this->createMock(IUserManager::class);
43+
$userManager->expects(self::once())
44+
->method('get')
45+
->with('alice')
46+
->willReturn($user);
47+
48+
$eventDispatcher = $this->createMock(IEventDispatcher::class);
49+
$eventDispatcher->expects(self::exactly(count($expectedSequence) - 1))
50+
->method('dispatchTyped')
51+
->willReturnCallback(static function (object $event) use (&$sequence, $user): void {
52+
if ($event instanceof BeforePasswordResetEvent) {
53+
self::assertSame($user, $event->getUser());
54+
self::assertSame('NewPassword', $event->getPassword());
55+
$sequence[] = 'beforeReset';
56+
return;
57+
}
58+
59+
self::assertInstanceOf(PasswordResetEvent::class, $event);
60+
self::assertSame($user, $event->getUser());
61+
self::assertSame('NewPassword', $event->getPassword());
62+
$sequence[] = 'afterReset';
63+
});
64+
65+
$input = $this->createMock(InputInterface::class);
66+
$input->method('getArgument')
67+
->with('user')
68+
->willReturn('alice');
69+
$input->method('getOption')
70+
->with('password-from-env')
71+
->willReturn(true);
72+
73+
$output = $this->createMock(OutputInterface::class);
74+
$output->expects(self::once())
75+
->method('writeln')
76+
->with($expectedOutput);
77+
78+
$command = new ResetPassword(
79+
$userManager,
80+
$this->createStub(IAppManager::class),
81+
$eventDispatcher,
82+
);
83+
84+
$previousPassword = getenv('NC_PASS');
85+
putenv('NC_PASS=NewPassword');
86+
87+
try {
88+
$exitCode = self::invokePrivate($command, 'execute', [$input, $output]);
89+
} finally {
90+
if ($previousPassword === false) {
91+
putenv('NC_PASS');
92+
} else {
93+
putenv('NC_PASS=' . $previousPassword);
94+
}
95+
}
96+
97+
self::assertSame($expectedExitCode, $exitCode);
98+
self::assertSame($expectedSequence, $sequence);
99+
}
100+
101+
public static function passwordResetResults(): array {
102+
return [
103+
'success dispatches both reset events around the password change' => [
104+
true,
105+
0,
106+
['beforeReset', 'setPassword', 'afterReset'],
107+
'<info>Successfully reset password for alice</info>',
108+
],
109+
'failure does not dispatch the after-reset event' => [
110+
false,
111+
1,
112+
['beforeReset', 'setPassword'],
113+
'<error>Error while resetting password!</error>',
114+
],
115+
];
116+
}
117+
}

0 commit comments

Comments
 (0)