diff --git a/apps/settings/lib/AppInfo/Application.php b/apps/settings/lib/AppInfo/Application.php index 5faf7b5110b0c..74f09baadf820 100644 --- a/apps/settings/lib/AppInfo/Application.php +++ b/apps/settings/lib/AppInfo/Application.php @@ -10,7 +10,6 @@ namespace OCA\Settings\AppInfo; use OC\AppFramework\Utility\TimeFactory; -use OC\Authentication\Events\AppPasswordCreatedEvent; use OC\Authentication\Token\IProvider; use OC\Settings\Manager; use OCA\Settings\ConfigLexicon; @@ -89,6 +88,7 @@ use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCP\Authentication\Events\AppPasswordCreatedEvent; use OCP\Defaults; use OCP\Group\Events\GroupDeletedEvent; use OCP\Group\Events\UserAddedEvent; diff --git a/apps/settings/lib/Controller/AuthSettingsController.php b/apps/settings/lib/Controller/AuthSettingsController.php index ae2f2e3e2056f..8e0deeaa10fdc 100644 --- a/apps/settings/lib/Controller/AuthSettingsController.php +++ b/apps/settings/lib/Controller/AuthSettingsController.php @@ -25,10 +25,12 @@ use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Services\IAppConfig; +use OCP\Authentication\Events\AppPasswordCreatedEvent; use OCP\Authentication\Exceptions\ExpiredTokenException; use OCP\Authentication\Exceptions\InvalidTokenException; use OCP\Authentication\Exceptions\WipeTokenException; use OCP\Authentication\Token\IToken; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; @@ -54,6 +56,7 @@ public function __construct( private IConfig $serverConfig, private IL10N $l, private TokenScopes $tokenScopes, + private IEventDispatcher $eventDispatcher, ) { parent::__construct($appName, $request); } @@ -132,7 +135,7 @@ public function create(string $name = '', bool $qrcodeLogin = false): JSONRespon $tokenData['canDelete'] = true; $tokenData['canRename'] = true; - $this->publishActivity(Provider::APP_TOKEN_CREATED, $deviceToken->getId(), ['name' => $deviceToken->getName()]); + $this->eventDispatcher->dispatchTyped(new AppPasswordCreatedEvent($deviceToken)); return new JSONResponse([ 'token' => $token, diff --git a/apps/settings/lib/Listener/AppPasswordCreatedActivityListener.php b/apps/settings/lib/Listener/AppPasswordCreatedActivityListener.php index 72310feef170d..09928eb62c48c 100644 --- a/apps/settings/lib/Listener/AppPasswordCreatedActivityListener.php +++ b/apps/settings/lib/Listener/AppPasswordCreatedActivityListener.php @@ -10,9 +10,9 @@ namespace OCA\Settings\Listener; use BadMethodCallException; -use OC\Authentication\Events\AppPasswordCreatedEvent; use OCA\Settings\Activity\Provider; use OCP\Activity\IManager as IActivityManager; +use OCP\Authentication\Events\AppPasswordCreatedEvent; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\IUserSession; diff --git a/apps/settings/tests/Controller/AuthSettingsControllerTest.php b/apps/settings/tests/Controller/AuthSettingsControllerTest.php index b4b2201e7a78e..ba1b023c96117 100644 --- a/apps/settings/tests/Controller/AuthSettingsControllerTest.php +++ b/apps/settings/tests/Controller/AuthSettingsControllerTest.php @@ -25,7 +25,9 @@ use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Services\IAppConfig; +use OCP\Authentication\Events\AppPasswordCreatedEvent; use OCP\Authentication\Exceptions\WipeTokenException; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; @@ -48,6 +50,7 @@ class AuthSettingsControllerTest extends TestCase { private RemoteWipe&MockObject $remoteWipe; private IConfig&MockObject $serverConfig; private IL10N&MockObject $l; + private IEventDispatcher&MockObject $eventDispatcher; private string $uid = 'jane'; private AuthSettingsController $controller; @@ -66,6 +69,7 @@ protected function setUp(): void { /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); $this->l = $this->createMock(IL10N::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); $this->controller = new AuthSettingsController( 'core', @@ -82,6 +86,7 @@ protected function setUp(): void { $this->serverConfig, $this->l, new TokenScopes(), + $this->eventDispatcher, ); } @@ -124,7 +129,9 @@ public function testCreate(): void { ->method('jsonSerialize') ->willReturn(['dummy' => 'dummy', 'canDelete' => true]); - $this->mockActivityManager(); + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with($this->callback(static fn (AppPasswordCreatedEvent $event): bool => $event->getToken() === $deviceToken)); $expected = [ 'token' => $newToken, diff --git a/core/Command/User/AuthTokens/Add.php b/core/Command/User/AuthTokens/Add.php index c491ced011bf6..19319c828cfb6 100644 --- a/core/Command/User/AuthTokens/Add.php +++ b/core/Command/User/AuthTokens/Add.php @@ -9,10 +9,10 @@ namespace OC\Core\Command\User\AuthTokens; -use OC\Authentication\Events\AppPasswordCreatedEvent; use OC\Authentication\Token\IProvider; use OC\Authentication\Token\IToken; use OC\User\Manager as UserManager; +use OCP\Authentication\Events\AppPasswordCreatedEvent; use OCP\EventDispatcher\IEventDispatcher; use OCP\Security\ISecureRandom; use Symfony\Component\Console\Command\Command; diff --git a/core/Controller/AppPasswordController.php b/core/Controller/AppPasswordController.php index f2835b1cbe0eb..65771415bc0a8 100644 --- a/core/Controller/AppPasswordController.php +++ b/core/Controller/AppPasswordController.php @@ -9,7 +9,6 @@ namespace OC\Core\Controller; -use OC\Authentication\Events\AppPasswordCreatedEvent; use OC\Authentication\Token\IProvider; use OC\Authentication\Token\IToken; use OC\User\Session; @@ -23,6 +22,7 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSForbiddenException; use OCP\AppFramework\OCSController; +use OCP\Authentication\Events\AppPasswordCreatedEvent; use OCP\Authentication\Exceptions\CredentialsUnavailableException; use OCP\Authentication\Exceptions\InvalidTokenException; use OCP\Authentication\Exceptions\PasswordUnavailableException; diff --git a/core/Controller/ClientFlowLoginController.php b/core/Controller/ClientFlowLoginController.php index 06a6db515a123..eb5334b59ef81 100644 --- a/core/Controller/ClientFlowLoginController.php +++ b/core/Controller/ClientFlowLoginController.php @@ -7,7 +7,6 @@ namespace OC\Core\Controller; -use OC\Authentication\Events\AppPasswordCreatedEvent; use OC\Authentication\Exceptions\PasswordlessTokenException; use OC\Authentication\Token\IProvider; use OCA\OAuth2\Db\AccessToken; @@ -29,6 +28,7 @@ use OCP\AppFramework\Http\StandaloneTemplateResponse; use OCP\AppFramework\Services\IInitialState; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Authentication\Events\AppPasswordCreatedEvent; use OCP\Authentication\Exceptions\InvalidTokenException; use OCP\Authentication\Token\IToken; use OCP\Defaults; diff --git a/core/Service/LoginFlowV2Service.php b/core/Service/LoginFlowV2Service.php index 47707b909ecf6..54388b92745ca 100644 --- a/core/Service/LoginFlowV2Service.php +++ b/core/Service/LoginFlowV2Service.php @@ -20,6 +20,7 @@ use OC\Core\Exception\LoginFlowV2NotFoundException; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Authentication\Events\AppPasswordCreatedEvent; use OCP\Authentication\Exceptions\InvalidTokenException; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; @@ -179,6 +180,7 @@ public function flowDone(string $loginToken, string $sessionId, string $server, $data->setAppPassword($this->encryptPassword($appPassword, $data->getPublicKey())); $this->mapper->update($data); + $this->eventDispatcher->dispatchTyped(new AppPasswordCreatedEvent($generatedToken)); return true; } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 4467e4e6179c2..b09a39965e0ea 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -215,6 +215,7 @@ 'OCP\\App\\IAppManager' => $baseDir . '/lib/public/App/IAppManager.php', 'OCP\\App\\ManagerEvent' => $baseDir . '/lib/public/App/ManagerEvent.php', 'OCP\\Authentication\\Events\\AnyLoginFailedEvent' => $baseDir . '/lib/public/Authentication/Events/AnyLoginFailedEvent.php', + 'OCP\\Authentication\\Events\\AppPasswordCreatedEvent' => $baseDir . '/lib/public/Authentication/Events/AppPasswordCreatedEvent.php', 'OCP\\Authentication\\Events\\LoginFailedEvent' => $baseDir . '/lib/public/Authentication/Events/LoginFailedEvent.php', 'OCP\\Authentication\\Events\\TokenInvalidatedEvent' => $baseDir . '/lib/public/Authentication/Events/TokenInvalidatedEvent.php', 'OCP\\Authentication\\Exceptions\\CredentialsUnavailableException' => $baseDir . '/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php', @@ -1300,7 +1301,6 @@ 'OC\\Archive\\TAR' => $baseDir . '/lib/private/Archive/TAR.php', 'OC\\Archive\\ZIP' => $baseDir . '/lib/private/Archive/ZIP.php', 'OC\\Authentication\\Events\\ARemoteWipeEvent' => $baseDir . '/lib/private/Authentication/Events/ARemoteWipeEvent.php', - 'OC\\Authentication\\Events\\AppPasswordCreatedEvent' => $baseDir . '/lib/private/Authentication/Events/AppPasswordCreatedEvent.php', 'OC\\Authentication\\Events\\LoginFailed' => $baseDir . '/lib/private/Authentication/Events/LoginFailed.php', 'OC\\Authentication\\Events\\RemoteWipeFinished' => $baseDir . '/lib/private/Authentication/Events/RemoteWipeFinished.php', 'OC\\Authentication\\Events\\RemoteWipeStarted' => $baseDir . '/lib/private/Authentication/Events/RemoteWipeStarted.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index dac59af04087a..08d4195c60d68 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -256,6 +256,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\App\\IAppManager' => __DIR__ . '/../../..' . '/lib/public/App/IAppManager.php', 'OCP\\App\\ManagerEvent' => __DIR__ . '/../../..' . '/lib/public/App/ManagerEvent.php', 'OCP\\Authentication\\Events\\AnyLoginFailedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/AnyLoginFailedEvent.php', + 'OCP\\Authentication\\Events\\AppPasswordCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/AppPasswordCreatedEvent.php', 'OCP\\Authentication\\Events\\LoginFailedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/LoginFailedEvent.php', 'OCP\\Authentication\\Events\\TokenInvalidatedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/TokenInvalidatedEvent.php', 'OCP\\Authentication\\Exceptions\\CredentialsUnavailableException' => __DIR__ . '/../../..' . '/lib/public/Authentication/Exceptions/CredentialsUnavailableException.php', @@ -1341,7 +1342,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Archive\\TAR' => __DIR__ . '/../../..' . '/lib/private/Archive/TAR.php', 'OC\\Archive\\ZIP' => __DIR__ . '/../../..' . '/lib/private/Archive/ZIP.php', 'OC\\Authentication\\Events\\ARemoteWipeEvent' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/ARemoteWipeEvent.php', - 'OC\\Authentication\\Events\\AppPasswordCreatedEvent' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/AppPasswordCreatedEvent.php', 'OC\\Authentication\\Events\\LoginFailed' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/LoginFailed.php', 'OC\\Authentication\\Events\\RemoteWipeFinished' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/RemoteWipeFinished.php', 'OC\\Authentication\\Events\\RemoteWipeStarted' => __DIR__ . '/../../..' . '/lib/private/Authentication/Events/RemoteWipeStarted.php', diff --git a/lib/private/Authentication/Events/AppPasswordCreatedEvent.php b/lib/public/Authentication/Events/AppPasswordCreatedEvent.php similarity index 68% rename from lib/private/Authentication/Events/AppPasswordCreatedEvent.php rename to lib/public/Authentication/Events/AppPasswordCreatedEvent.php index b6c6c887d8986..a800d4de211ee 100644 --- a/lib/private/Authentication/Events/AppPasswordCreatedEvent.php +++ b/lib/public/Authentication/Events/AppPasswordCreatedEvent.php @@ -7,18 +7,29 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OC\Authentication\Events; +namespace OCP\Authentication\Events; use OCP\Authentication\Token\IToken; use OCP\EventDispatcher\Event; +/** + * Dispatched after an app password was created, from any place that creates one + * + * @since 36.0.0 + */ class AppPasswordCreatedEvent extends Event { + /** + * @since 36.0.0 + */ public function __construct( private IToken $token, ) { parent::__construct(); } + /** + * @since 36.0.0 + */ public function getToken(): IToken { return $this->token; } diff --git a/tests/Core/Service/LoginFlowV2ServiceUnitTest.php b/tests/Core/Service/LoginFlowV2ServiceUnitTest.php index 300626b9bc5d4..e85960ece8d74 100644 --- a/tests/Core/Service/LoginFlowV2ServiceUnitTest.php +++ b/tests/Core/Service/LoginFlowV2ServiceUnitTest.php @@ -20,6 +20,7 @@ use OC\Core\Service\LoginFlowV2Service; use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Authentication\Events\AppPasswordCreatedEvent; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\Log\Audit\CriticalActionPerformedEvent; @@ -392,10 +393,12 @@ public function testFlowDone(): void { ) ->willReturn($appToken); - $this->eventDispatcher->expects($this->once()) + $events = []; + $this->eventDispatcher->expects($this->exactly(2)) ->method('dispatchTyped') - ->with($this->callback(fn (CriticalActionPerformedEvent $event): bool => $event->getOperation() === 'auth.app_password.created' - && $event->getParameters() === ['tokenId' => 7, 'tokenName' => 'client_name'])); + ->willReturnCallback(static function (object $event) use (&$events): void { + $events[] = $event; + }); $result = $this->subjectUnderTest->flowDone( 'login_token', @@ -405,6 +408,12 @@ public function testFlowDone(): void { ); $this->assertTrue($result); + $this->assertInstanceOf(CriticalActionPerformedEvent::class, $events[0]); + $this->assertSame('auth.app_password.created', $events[0]->getOperation()); + $this->assertSame(['tokenId' => 7, 'tokenName' => 'client_name'], $events[0]->getParameters()); + $this->assertInstanceOf(AppPasswordCreatedEvent::class, $events[1]); + $this->assertSame($appToken, $events[1]->getToken()); + // app password is encrypted and must look like: // ZACZOOzxTpKz4+KXL5kZ/gCK0xvkaVi/8yzupAn6Ui6+5qCSKvfPKGgeDRKs0sivvSLzk/XSp811SZCZmH0Y3g== $this->assertMatchesRegularExpression('/[a-zA-Z\/0-9+=]+/', $loginFlowV2->getAppPassword());