From d30428aaa95f7bb79f409208df53aeb92af32958 Mon Sep 17 00:00:00 2001 From: Peter Ringelmann Date: Fri, 28 Aug 2026 10:09:12 +0200 Subject: [PATCH 1/2] fix(core): accept deprecated IANA timezone aliases Signed-off-by: Peter Ringelmann --- .../lib/Controller/UsersController.php | 3 +- .../tests/Controller/UsersControllerTest.php | 86 +++++++++++++++++++ .../Login/SetUserTimezoneCommand.php | 3 +- .../Login/ALoginTestCommand.php | 4 +- .../Login/SetUserTimezoneCommandTest.php | 27 +++++- 5 files changed, 116 insertions(+), 7 deletions(-) diff --git a/apps/provisioning_api/lib/Controller/UsersController.php b/apps/provisioning_api/lib/Controller/UsersController.php index 1b76d1bc61e46..5dc6a3bac69ab 100644 --- a/apps/provisioning_api/lib/Controller/UsersController.php +++ b/apps/provisioning_api/lib/Controller/UsersController.php @@ -1132,7 +1132,8 @@ public function editUser(string $userId, string $key, string $value): DataRespon $this->config->setUserValue($targetUser->getUID(), 'core', 'locale', $value); break; case self::USER_FIELD_TIMEZONE: - if (!in_array($value, \DateTimeZone::listIdentifiers())) { + // Older browsers still report deprecated aliases like Europe/Kiev. + if (!in_array($value, \DateTimeZone::listIdentifiers(\DateTimeZone::ALL_WITH_BC))) { throw new OCSException($this->l10n->t('Invalid timezone'), 101); } $this->config->setUserValue($targetUser->getUID(), 'core', 'timezone', $value); diff --git a/apps/provisioning_api/tests/Controller/UsersControllerTest.php b/apps/provisioning_api/tests/Controller/UsersControllerTest.php index 84e4f15b4b913..b118c17566b29 100644 --- a/apps/provisioning_api/tests/Controller/UsersControllerTest.php +++ b/apps/provisioning_api/tests/Controller/UsersControllerTest.php @@ -2618,6 +2618,92 @@ public function testEditUserAdminEditChangeLanguageInvalidLanguage(): void { $this->assertEquals([], $this->api->editUser('UserToEdit', 'language', 'ru')->getData()); } + public static function dataEditUserSelfEditChangeTimezone(): array { + return [ + 'primary identifier' => ['Europe/Kyiv'], + 'backward compatible alias' => ['Europe/Kiev'], + 'legacy region alias' => ['US/Eastern'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataEditUserSelfEditChangeTimezone')] + public function testEditUserSelfEditChangeTimezone(string $timezone): void { + $loggedInUser = $this->createMock(IUser::class); + $loggedInUser + ->expects($this->any()) + ->method('getUID') + ->willReturn('UserToEdit'); + $targetUser = $this->createMock(IUser::class); + $this->config->expects($this->once()) + ->method('setUserValue') + ->with('UserToEdit', 'core', 'timezone', $timezone); + $this->userSession + ->expects($this->once()) + ->method('getUser') + ->willReturn($loggedInUser); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('UserToEdit') + ->willReturn($targetUser); + $this->groupManager + ->expects($this->atLeastOnce()) + ->method('isAdmin') + ->with('UserToEdit') + ->willReturn(false); + $targetUser + ->expects($this->any()) + ->method('getUID') + ->willReturn('UserToEdit'); + + $backend = $this->createMock(UserInterface::class); + $targetUser + ->expects($this->any()) + ->method('getBackend') + ->willReturn($backend); + + $this->assertEquals([], $this->api->editUser('UserToEdit', 'timezone', $timezone)->getData()); + } + + public function testEditUserSelfEditChangeTimezoneInvalid(): void { + $this->expectException(OCSException::class); + + $loggedInUser = $this->createMock(IUser::class); + $loggedInUser + ->expects($this->any()) + ->method('getUID') + ->willReturn('UserToEdit'); + $targetUser = $this->createMock(IUser::class); + $this->config->expects($this->never()) + ->method('setUserValue'); + $this->userSession + ->expects($this->once()) + ->method('getUser') + ->willReturn($loggedInUser); + $this->userManager + ->expects($this->once()) + ->method('get') + ->with('UserToEdit') + ->willReturn($targetUser); + $this->groupManager + ->expects($this->atLeastOnce()) + ->method('isAdmin') + ->with('UserToEdit') + ->willReturn(false); + $targetUser + ->expects($this->any()) + ->method('getUID') + ->willReturn('UserToEdit'); + + $backend = $this->createMock(UserInterface::class); + $targetUser + ->expects($this->any()) + ->method('getBackend') + ->willReturn($backend); + + $this->api->editUser('UserToEdit', 'timezone', 'Mars/Olympus_Mons'); + } + public function testEditUserSubadminUserAccessible(): void { $this->config ->expects($this->once()) diff --git a/lib/private/Authentication/Login/SetUserTimezoneCommand.php b/lib/private/Authentication/Login/SetUserTimezoneCommand.php index ea80fbfc7143f..52df3b29eaafb 100644 --- a/lib/private/Authentication/Login/SetUserTimezoneCommand.php +++ b/lib/private/Authentication/Login/SetUserTimezoneCommand.php @@ -42,6 +42,7 @@ public function process(LoginData $loginData): LoginResult { } private function isValidTimezone(?string $value): bool { - return $value && in_array($value, \DateTimeZone::listIdentifiers()); + // Older browsers still report deprecated aliases like Europe/Kiev. + return $value && in_array($value, \DateTimeZone::listIdentifiers(\DateTimeZone::ALL_WITH_BC)); } } diff --git a/tests/lib/Authentication/Login/ALoginTestCommand.php b/tests/lib/Authentication/Login/ALoginTestCommand.php index da29e74e68300..c1c9feea2bb04 100644 --- a/tests/lib/Authentication/Login/ALoginTestCommand.php +++ b/tests/lib/Authentication/Login/ALoginTestCommand.php @@ -90,14 +90,14 @@ protected function getLoggedInLoginDataWithRedirectUrl(): LoginData { return $data; } - protected function getLoggedInLoginDataWithTimezone(): LoginData { + protected function getLoggedInLoginDataWithTimezone(?string $timezone = null): LoginData { $data = new LoginData( $this->request, $this->username, $this->password, true, null, - $this->timezone, + $timezone ?? $this->timezone, $this->timeZoneOffset ); $data->setUser($this->user); diff --git a/tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php b/tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php index 473d14daddfa3..0a52a54f862ec 100644 --- a/tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php +++ b/tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php @@ -44,8 +44,17 @@ public function testProcessNoTimezoneSet(): void { $this->assertTrue($result->isSuccess()); } - public function testProcess(): void { - $data = $this->getLoggedInLoginDataWithTimezone(); + public static function dataAcceptedTimezone(): array { + return [ + 'primary identifier' => ['Europe/Vienna'], + 'backward compatible alias' => ['Europe/Kiev'], + 'legacy region alias' => ['US/Eastern'], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataAcceptedTimezone')] + public function testProcess(string $timezone): void { + $data = $this->getLoggedInLoginDataWithTimezone($timezone); $this->user->expects($this->once()) ->method('getUID') ->willReturn($this->username); @@ -64,7 +73,7 @@ public function testProcess(): void { $this->username, 'core', 'timezone', - $this->timezone + $timezone ); $this->session->expects($this->once()) ->method('set') @@ -78,6 +87,18 @@ public function testProcess(): void { $this->assertTrue($result->isSuccess()); } + public function testProcessUnknownTimezone(): void { + $data = $this->getLoggedInLoginDataWithTimezone('Mars/Olympus_Mons'); + $this->config->expects($this->never()) + ->method('setUserValue'); + $this->session->expects($this->never()) + ->method('set'); + + $result = $this->cmd->process($data); + + $this->assertTrue($result->isSuccess()); + } + public function testProcessAlreadySet(): void { $data = $this->getLoggedInLoginDataWithTimezone(); $this->user->expects($this->once()) From c24a7d641848491b521086d41bc4ed837f287a91 Mon Sep 17 00:00:00 2001 From: Peter Ringelmann Date: Fri, 28 Aug 2026 10:34:54 +0200 Subject: [PATCH 2/2] test(core): pick timezone aliases from the running platform Signed-off-by: Peter Ringelmann --- .../tests/Controller/UsersControllerTest.php | 24 +++++++++++++++---- .../Login/SetUserTimezoneCommandTest.php | 22 ++++++++++++++--- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/apps/provisioning_api/tests/Controller/UsersControllerTest.php b/apps/provisioning_api/tests/Controller/UsersControllerTest.php index b118c17566b29..a326d23d30538 100644 --- a/apps/provisioning_api/tests/Controller/UsersControllerTest.php +++ b/apps/provisioning_api/tests/Controller/UsersControllerTest.php @@ -2618,16 +2618,32 @@ public function testEditUserAdminEditChangeLanguageInvalidLanguage(): void { $this->assertEquals([], $this->api->editUser('UserToEdit', 'language', 'ru')->getData()); } + /** + * Debian and Ubuntu ship the tz database's backward links in a separate + * tzdata-legacy package, so pick an alias this platform actually knows + * instead of hardcoding one. + */ + private static function findBackwardCompatibleTimezone(): ?string { + $aliases = array_diff( + \DateTimeZone::listIdentifiers(\DateTimeZone::ALL_WITH_BC), + \DateTimeZone::listIdentifiers(), + ); + return $aliases === [] ? null : reset($aliases); + } + public static function dataEditUserSelfEditChangeTimezone(): array { return [ - 'primary identifier' => ['Europe/Kyiv'], - 'backward compatible alias' => ['Europe/Kiev'], - 'legacy region alias' => ['US/Eastern'], + 'primary identifier' => ['Europe/Vienna'], + 'backward compatible alias' => [self::findBackwardCompatibleTimezone()], ]; } #[\PHPUnit\Framework\Attributes\DataProvider('dataEditUserSelfEditChangeTimezone')] - public function testEditUserSelfEditChangeTimezone(string $timezone): void { + public function testEditUserSelfEditChangeTimezone(?string $timezone): void { + if ($timezone === null) { + $this->markTestSkipped('No backward compatible timezone aliases in this platform\'s tz database'); + } + $loggedInUser = $this->createMock(IUser::class); $loggedInUser ->expects($this->any()) diff --git a/tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php b/tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php index 0a52a54f862ec..f5b9aec6fcef5 100644 --- a/tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php +++ b/tests/lib/Authentication/Login/SetUserTimezoneCommandTest.php @@ -44,16 +44,32 @@ public function testProcessNoTimezoneSet(): void { $this->assertTrue($result->isSuccess()); } + /** + * Debian and Ubuntu ship the tz database's backward links in a separate + * tzdata-legacy package, so pick an alias this platform actually knows + * instead of hardcoding one. + */ + private static function findBackwardCompatibleTimezone(): ?string { + $aliases = array_diff( + \DateTimeZone::listIdentifiers(\DateTimeZone::ALL_WITH_BC), + \DateTimeZone::listIdentifiers(), + ); + return $aliases === [] ? null : reset($aliases); + } + public static function dataAcceptedTimezone(): array { return [ 'primary identifier' => ['Europe/Vienna'], - 'backward compatible alias' => ['Europe/Kiev'], - 'legacy region alias' => ['US/Eastern'], + 'backward compatible alias' => [self::findBackwardCompatibleTimezone()], ]; } #[\PHPUnit\Framework\Attributes\DataProvider('dataAcceptedTimezone')] - public function testProcess(string $timezone): void { + public function testProcess(?string $timezone): void { + if ($timezone === null) { + $this->markTestSkipped('No backward compatible timezone aliases in this platform\'s tz database'); + } + $data = $this->getLoggedInLoginDataWithTimezone($timezone); $this->user->expects($this->once()) ->method('getUID')