diff --git a/core/Command/User/ResetPassword.php b/core/Command/User/ResetPassword.php
index 813c38fe1d539..d02f92dd4e2b2 100644
--- a/core/Command/User/ResetPassword.php
+++ b/core/Command/User/ResetPassword.php
@@ -9,7 +9,10 @@
namespace OC\Core\Command\User;
use OC\Core\Command\Base;
+use OC\Core\Events\BeforePasswordResetEvent;
+use OC\Core\Events\PasswordResetEvent;
use OCP\App\IAppManager;
+use OCP\EventDispatcher\IEventDispatcher;
use OCP\IUser;
use OCP\IUserManager;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
@@ -25,6 +28,7 @@ class ResetPassword extends Base {
public function __construct(
protected IUserManager $userManager,
private IAppManager $appManager,
+ private IEventDispatcher $eventDispatcher,
) {
parent::__construct();
}
@@ -118,6 +122,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
try {
+ $this->eventDispatcher->dispatchTyped(new BeforePasswordResetEvent($user, $password));
$success = $user->setPassword($password);
} catch (\Exception $e) {
$output->writeln('' . $e->getMessage() . '');
@@ -125,6 +130,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
if ($success) {
+ $this->eventDispatcher->dispatchTyped(new PasswordResetEvent($user, $password));
$output->writeln('Successfully reset password for ' . $username . '');
} else {
$output->writeln('Error while resetting password!');
diff --git a/tests/Core/Command/User/ResetPasswordTest.php b/tests/Core/Command/User/ResetPasswordTest.php
new file mode 100644
index 0000000000000..a9f4cff40f1ae
--- /dev/null
+++ b/tests/Core/Command/User/ResetPasswordTest.php
@@ -0,0 +1,117 @@
+createMock(IUser::class);
+ $user->expects(self::once())
+ ->method('setPassword')
+ ->with('NewPassword')
+ ->willReturnCallback(static function () use (&$sequence, $passwordChangeSucceeds): bool {
+ $sequence[] = 'setPassword';
+ return $passwordChangeSucceeds;
+ });
+
+ $userManager = $this->createMock(IUserManager::class);
+ $userManager->expects(self::once())
+ ->method('get')
+ ->with('alice')
+ ->willReturn($user);
+
+ $eventDispatcher = $this->createMock(IEventDispatcher::class);
+ $eventDispatcher->expects(self::exactly(count($expectedSequence) - 1))
+ ->method('dispatchTyped')
+ ->willReturnCallback(static function (object $event) use (&$sequence, $user): void {
+ if ($event instanceof BeforePasswordResetEvent) {
+ self::assertSame($user, $event->getUser());
+ self::assertSame('NewPassword', $event->getPassword());
+ $sequence[] = 'beforeReset';
+ return;
+ }
+
+ self::assertInstanceOf(PasswordResetEvent::class, $event);
+ self::assertSame($user, $event->getUser());
+ self::assertSame('NewPassword', $event->getPassword());
+ $sequence[] = 'afterReset';
+ });
+
+ $input = $this->createMock(InputInterface::class);
+ $input->method('getArgument')
+ ->with('user')
+ ->willReturn('alice');
+ $input->method('getOption')
+ ->with('password-from-env')
+ ->willReturn(true);
+
+ $output = $this->createMock(OutputInterface::class);
+ $output->expects(self::once())
+ ->method('writeln')
+ ->with($expectedOutput);
+
+ $command = new ResetPassword(
+ $userManager,
+ $this->createStub(IAppManager::class),
+ $eventDispatcher,
+ );
+
+ $previousPassword = getenv('NC_PASS');
+ putenv('NC_PASS=NewPassword');
+
+ try {
+ $exitCode = self::invokePrivate($command, 'execute', [$input, $output]);
+ } finally {
+ if ($previousPassword === false) {
+ putenv('NC_PASS');
+ } else {
+ putenv('NC_PASS=' . $previousPassword);
+ }
+ }
+
+ self::assertSame($expectedExitCode, $exitCode);
+ self::assertSame($expectedSequence, $sequence);
+ }
+
+ public static function passwordResetResults(): array {
+ return [
+ 'success dispatches both reset events around the password change' => [
+ true,
+ 0,
+ ['beforeReset', 'setPassword', 'afterReset'],
+ 'Successfully reset password for alice',
+ ],
+ 'failure does not dispatch the after-reset event' => [
+ false,
+ 1,
+ ['beforeReset', 'setPassword'],
+ 'Error while resetting password!',
+ ],
+ ];
+ }
+}