diff --git a/AUTHORS b/AUTHORS index fe478401fddb4..a816c2d1698be 100644 --- a/AUTHORS +++ b/AUTHORS @@ -86,6 +86,7 @@ - Carlos Cerrillo - Carlos Ferreira - Carsten Wiedmann + - Charles Taborin - Chih-Hsuan Yen - Christian <16852529+cviereck@users.noreply.github.com> - Christian Berendt diff --git a/apps/settings/lib/Controller/AuthSettingsController.php b/apps/settings/lib/Controller/AuthSettingsController.php index 7d1ff16027d17..ad63c1f3208c1 100644 --- a/apps/settings/lib/Controller/AuthSettingsController.php +++ b/apps/settings/lib/Controller/AuthSettingsController.php @@ -15,6 +15,7 @@ use OC\Authentication\Token\RemoteWipe; use OCA\Settings\Activity\Provider; use OCA\Settings\ConfigLexicon; +use OCP\Authentication\Events\AfterAuthTokenCreatedEvent; use OCP\Activity\IManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; @@ -26,6 +27,7 @@ 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; @@ -46,6 +48,7 @@ public function __construct( private ?string $userId, private IUserSession $userSession, private IManager $activityManager, + private IEventDispatcher $eventDispatcher, private IAppConfig $appConfig, private RemoteWipe $remoteWipe, private LoggerInterface $logger, @@ -117,6 +120,12 @@ public function create(string $name = '', bool $qrcodeLogin = false): JSONRespon } $token = $this->generateRandomDeviceToken(); + + // Allow apps to post-process the generated token before persisting it + $event = new AfterAuthTokenCreatedEvent($token); + $this->eventDispatcher->dispatchTyped($event); + $token = $event->getToken(); + $deviceToken = $this->tokenProvider->generateToken( $token, $this->userId, diff --git a/apps/settings/tests/Controller/AuthSettingsControllerTest.php b/apps/settings/tests/Controller/AuthSettingsControllerTest.php index 5d75a1aa09aef..f96f0b533442b 100644 --- a/apps/settings/tests/Controller/AuthSettingsControllerTest.php +++ b/apps/settings/tests/Controller/AuthSettingsControllerTest.php @@ -16,10 +16,12 @@ use OC\Authentication\Token\PublicKeyToken; use OC\Authentication\Token\RemoteWipe; use OCA\Settings\Controller\AuthSettingsController; +use OCP\Authentication\Events\AfterAuthTokenCreatedEvent; use OCP\Activity\IEvent; use OCP\Activity\IManager; use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Services\IAppConfig; +use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IL10N; use OCP\IRequest; @@ -38,6 +40,7 @@ class AuthSettingsControllerTest extends TestCase { private IUserSession&MockObject $userSession; private ISecureRandom&MockObject $secureRandom; private IManager&MockObject $activityManager; + private IEventDispatcher&MockObject $eventDispatcher; private IAppConfig&MockObject $appConfig; private RemoteWipe&MockObject $remoteWipe; private IConfig&MockObject $serverConfig; @@ -54,12 +57,13 @@ protected function setUp(): void { $this->userSession = $this->createMock(IUserSession::class); $this->secureRandom = $this->createMock(ISecureRandom::class); $this->activityManager = $this->createMock(IManager::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); $this->appConfig = $this->createMock(IAppConfig::class); $this->remoteWipe = $this->createMock(RemoteWipe::class); $this->serverConfig = $this->createMock(IConfig::class); + $this->l = $this->createMock(IL10N::class); /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); - $this->l = $this->createMock(IL10N::class); $this->controller = new AuthSettingsController( 'core', @@ -70,6 +74,7 @@ protected function setUp(): void { $this->uid, $this->userSession, $this->activityManager, + $this->eventDispatcher, $this->appConfig, $this->remoteWipe, $logger, @@ -108,6 +113,13 @@ public function testCreate(): void { ->willReturn('XXXXX'); $newToken = 'XXXXX-XXXXX-XXXXX-XXXXX-XXXXX'; + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with($this->callback(function (AfterAuthTokenCreatedEvent $event) use ($newToken) { + $this->assertSame($newToken, $event->getToken()); + return true; + })); + $this->tokenProvider->expects($this->once()) ->method('generateToken') ->with($newToken, $this->uid, 'User13', $password, $name, IToken::PERMANENT_TOKEN) @@ -143,8 +155,8 @@ public function testCreateDisabledBySystemConfig(): void { ->method('getToken'); $this->tokenProvider->expects($this->never()) ->method('getPassword'); - - + $this->eventDispatcher->expects($this->never()) + ->method('dispatchTyped'); $this->tokenProvider->expects($this->never()) ->method('generateToken'); @@ -154,6 +166,66 @@ public function testCreateDisabledBySystemConfig(): void { $this->assertEquals($expected, $this->controller->create($name)); } + public function testCreateTokenModifiedByEvent(): void { + $name = 'Pixel 8'; + $sessionToken = $this->createMock(IToken::class); + $deviceToken = $this->createMock(IToken::class); + $password = 'secret'; + + $this->serverConfig->method('getSystemValueBool') + ->with('auth_can_create_app_token', true) + ->willReturn(true); + $this->session->expects($this->once()) + ->method('getId') + ->willReturn('sessionid'); + $this->tokenProvider->expects($this->once()) + ->method('getToken') + ->with('sessionid') + ->willReturn($sessionToken); + $this->tokenProvider->expects($this->once()) + ->method('getPassword') + ->with($sessionToken, 'sessionid') + ->willReturn($password); + $sessionToken->expects($this->once()) + ->method('getLoginName') + ->willReturn('User99'); + + $this->secureRandom->expects($this->exactly(5)) + ->method('generate') + ->with(5, ISecureRandom::CHAR_HUMAN_READABLE) + ->willReturnOnConsecutiveCalls('AAAAA', 'BBBBB', 'CCCCC', 'DDDDD', 'EEEEE'); + $initialToken = 'AAAAA-BBBBB-CCCCC-DDDDD-EEEEE'; + + $this->eventDispatcher->expects($this->once()) + ->method('dispatchTyped') + ->with($this->callback(function (AfterAuthTokenCreatedEvent $event) use ($initialToken) { + $this->assertSame($initialToken, $event->getToken()); + $event->setToken('custom-token'); + return true; + })); + + $this->tokenProvider->expects($this->once()) + ->method('generateToken') + ->with('custom-token', $this->uid, 'User99', $password, $name, IToken::PERMANENT_TOKEN, null) + ->willReturn($deviceToken); + + $deviceToken->expects($this->once()) + ->method('jsonSerialize') + ->willReturn(['dummy' => 'dummy', 'canDelete' => true]); + + $this->mockActivityManager(); + + $expected = [ + 'token' => 'custom-token', + 'deviceToken' => ['dummy' => 'dummy', 'canDelete' => true, 'canRename' => true], + 'loginName' => 'User99', + ]; + + $response = $this->controller->create($name); + $this->assertInstanceOf(JSONResponse::class, $response); + $this->assertEquals($expected, $response->getData()); + } + public function testCreateSessionNotAvailable(): void { $name = 'personal phone'; diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 0cec259303926..39cd79a4cfa64 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -159,6 +159,7 @@ 'OCP\\App\\Events\\AppUpdateEvent' => $baseDir . '/lib/public/App/Events/AppUpdateEvent.php', 'OCP\\App\\IAppManager' => $baseDir . '/lib/public/App/IAppManager.php', 'OCP\\App\\ManagerEvent' => $baseDir . '/lib/public/App/ManagerEvent.php', + 'OCP\\Authentication\\Events\\AfterAuthTokenCreatedEvent' => $baseDir . '/lib/public/Authentication/Events/AfterAuthTokenCreatedEvent.php', 'OCP\\Authentication\\Events\\AnyLoginFailedEvent' => $baseDir . '/lib/public/Authentication/Events/AnyLoginFailedEvent.php', 'OCP\\Authentication\\Events\\LoginFailedEvent' => $baseDir . '/lib/public/Authentication/Events/LoginFailedEvent.php', 'OCP\\Authentication\\Events\\TokenInvalidatedEvent' => $baseDir . '/lib/public/Authentication/Events/TokenInvalidatedEvent.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index c07b6d127cc0b..0838740fb1514 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -11,32 +11,32 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 ); public static $prefixLengthsPsr4 = array ( - 'O' => + 'O' => array ( 'OC\\Core\\' => 8, 'OC\\' => 3, 'OCP\\' => 4, ), - 'N' => + 'N' => array ( 'NCU\\' => 4, ), ); public static $prefixDirsPsr4 = array ( - 'OC\\Core\\' => + 'OC\\Core\\' => array ( 0 => __DIR__ . '/../../..' . '/core', ), - 'OC\\' => + 'OC\\' => array ( 0 => __DIR__ . '/../../..' . '/lib/private', ), - 'OCP\\' => + 'OCP\\' => array ( 0 => __DIR__ . '/../../..' . '/lib/public', ), - 'NCU\\' => + 'NCU\\' => array ( 0 => __DIR__ . '/../../..' . '/lib/unstable', ), @@ -200,6 +200,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\App\\Events\\AppUpdateEvent' => __DIR__ . '/../../..' . '/lib/public/App/Events/AppUpdateEvent.php', 'OCP\\App\\IAppManager' => __DIR__ . '/../../..' . '/lib/public/App/IAppManager.php', 'OCP\\App\\ManagerEvent' => __DIR__ . '/../../..' . '/lib/public/App/ManagerEvent.php', + 'OCP\\Authentication\\Events\\AfterAuthTokenCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/AfterAuthTokenCreatedEvent.php', 'OCP\\Authentication\\Events\\AnyLoginFailedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/AnyLoginFailedEvent.php', 'OCP\\Authentication\\Events\\LoginFailedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/LoginFailedEvent.php', 'OCP\\Authentication\\Events\\TokenInvalidatedEvent' => __DIR__ . '/../../..' . '/lib/public/Authentication/Events/TokenInvalidatedEvent.php', diff --git a/lib/public/Authentication/Events/AfterAuthTokenCreatedEvent.php b/lib/public/Authentication/Events/AfterAuthTokenCreatedEvent.php new file mode 100644 index 0000000000000..b549be6c3b95d --- /dev/null +++ b/lib/public/Authentication/Events/AfterAuthTokenCreatedEvent.php @@ -0,0 +1,44 @@ +token; + } + + /** + * @since 34.0.0 + */ + public function setToken(string $token): void { + $this->token = $token; + } +} \ No newline at end of file