From d66d118069e46515312c4159e195e500cb5b8056 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 6 Mar 2026 15:58:43 +0000 Subject: [PATCH 1/3] fix(files): add name attribute to default view radio group Add name="default_view" to the NcRadioGroup component so the underlying radio buttons are properly grouped. This enables keyboard navigation between radio options using arrow keys, improving accessibility. Fixes nextcloud/server#58729 Signed-off-by: boris324 Co-Authored-By: Claude Opus 4.6 --- .../src/components/FilesAppSettings/FilesAppSettingsGeneral.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/files/src/components/FilesAppSettings/FilesAppSettingsGeneral.vue b/apps/files/src/components/FilesAppSettings/FilesAppSettingsGeneral.vue index 01212793d02a0..8f51ff352a107 100644 --- a/apps/files/src/components/FilesAppSettings/FilesAppSettingsGeneral.vue +++ b/apps/files/src/components/FilesAppSettings/FilesAppSettingsGeneral.vue @@ -37,6 +37,7 @@ const store = useUserConfigStore() From 8ea6b7e1ab43867c0ff507129e9384eb9d6ddb53 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 6 Mar 2026 17:48:29 +0000 Subject: [PATCH 2/3] fix: increase bucket_name column length to 63 chars AWS allows bucket names up to 63 characters per their naming rules, but the bucket_name column in oc_preview_locations was varchar(40). This updates the initial migration to use length 63 for fresh installs and adds a new migration to alter the column for existing installs. Fixes: #58755 Signed-off-by: boris324 Co-Authored-By: Claude Opus 4.6 --- .../Version33000Date20250819110529.php | 2 +- .../Version33000Date20260306120000.php | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 core/Migrations/Version33000Date20260306120000.php diff --git a/core/Migrations/Version33000Date20250819110529.php b/core/Migrations/Version33000Date20250819110529.php index 570a2ee72d429..41f3e37a8128d 100644 --- a/core/Migrations/Version33000Date20250819110529.php +++ b/core/Migrations/Version33000Date20250819110529.php @@ -31,7 +31,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt if (!$schema->hasTable('preview_locations')) { $table = $schema->createTable('preview_locations'); $table->addColumn('id', Types::BIGINT, ['autoincrement' => true, 'notnull' => true, 'length' => 20, 'unsigned' => true]); - $table->addColumn('bucket_name', Types::STRING, ['notnull' => true, 'length' => 40]); + $table->addColumn('bucket_name', Types::STRING, ['notnull' => true, 'length' => 63]); $table->addColumn('object_store_name', Types::STRING, ['notnull' => true, 'length' => 40]); $table->setPrimaryKey(['id']); } diff --git a/core/Migrations/Version33000Date20260306120000.php b/core/Migrations/Version33000Date20260306120000.php new file mode 100644 index 0000000000000..d86f7d5304d26 --- /dev/null +++ b/core/Migrations/Version33000Date20260306120000.php @@ -0,0 +1,40 @@ +hasTable('preview_locations')) { + $table = $schema->getTable('preview_locations'); + $column = $table->getColumn('bucket_name'); + + if ($column->getLength() < 63) { + $column->setLength(63); + } + } + + return $schema; + } +} From 275f57a10d8247d03313301801514bfedfb9cc8e Mon Sep 17 00:00:00 2001 From: root Date: Fri, 6 Mar 2026 18:46:15 +0000 Subject: [PATCH 3/3] fix(accounts): change default account property scopes from federated to local New users were created with displayname, email, avatar, and pronouns set to federated scope by default, exposing personal information to federated servers without explicit user consent. This changes all default property scopes to local, so user data stays private until the user explicitly opts into federation. Includes a repair step to migrate existing users who still have the old federated defaults on the affected properties. Fixes: #58646 Signed-off-by: boris324 Co-Authored-By: Claude Opus 4.6 --- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + lib/private/Accounts/AccountManager.php | 8 +- lib/private/Repair.php | 2 + .../NC33/FixDefaultAccountScopesToLocal.php | 92 ++++++++ tests/lib/Accounts/AccountManagerTest.php | 16 +- .../FixDefaultAccountScopesToLocalTest.php | 196 ++++++++++++++++++ 7 files changed, 305 insertions(+), 11 deletions(-) create mode 100644 lib/private/Repair/NC33/FixDefaultAccountScopesToLocal.php create mode 100644 tests/lib/Repair/NC33/FixDefaultAccountScopesToLocalTest.php diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 5b6de5ff356d1..66d4221f734c7 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -2072,6 +2072,7 @@ 'OC\\Repair\\NC29\\SanitizeAccountProperties' => $baseDir . '/lib/private/Repair/NC29/SanitizeAccountProperties.php', 'OC\\Repair\\NC29\\SanitizeAccountPropertiesJob' => $baseDir . '/lib/private/Repair/NC29/SanitizeAccountPropertiesJob.php', 'OC\\Repair\\NC30\\RemoveLegacyDatadirFile' => $baseDir . '/lib/private/Repair/NC30/RemoveLegacyDatadirFile.php', + 'OC\\Repair\\NC33\\FixDefaultAccountScopesToLocal' => $baseDir . '/lib/private/Repair/NC33/FixDefaultAccountScopesToLocal.php', 'OC\\Repair\\OldGroupMembershipShares' => $baseDir . '/lib/private/Repair/OldGroupMembershipShares.php', 'OC\\Repair\\Owncloud\\CleanPreviews' => $baseDir . '/lib/private/Repair/Owncloud/CleanPreviews.php', 'OC\\Repair\\Owncloud\\CleanPreviewsBackgroundJob' => $baseDir . '/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index fbee07dafc6b4..c8e75d7f60daa 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -2113,6 +2113,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Repair\\NC29\\SanitizeAccountProperties' => __DIR__ . '/../../..' . '/lib/private/Repair/NC29/SanitizeAccountProperties.php', 'OC\\Repair\\NC29\\SanitizeAccountPropertiesJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC29/SanitizeAccountPropertiesJob.php', 'OC\\Repair\\NC30\\RemoveLegacyDatadirFile' => __DIR__ . '/../../..' . '/lib/private/Repair/NC30/RemoveLegacyDatadirFile.php', + 'OC\\Repair\\NC33\\FixDefaultAccountScopesToLocal' => __DIR__ . '/../../..' . '/lib/private/Repair/NC33/FixDefaultAccountScopesToLocal.php', 'OC\\Repair\\OldGroupMembershipShares' => __DIR__ . '/../../..' . '/lib/private/Repair/OldGroupMembershipShares.php', 'OC\\Repair\\Owncloud\\CleanPreviews' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/CleanPreviews.php', 'OC\\Repair\\Owncloud\\CleanPreviewsBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/CleanPreviewsBackgroundJob.php', diff --git a/lib/private/Accounts/AccountManager.php b/lib/private/Accounts/AccountManager.php index 373a697a327a5..9099126615bdf 100644 --- a/lib/private/Accounts/AccountManager.php +++ b/lib/private/Accounts/AccountManager.php @@ -66,16 +66,16 @@ class AccountManager implements IAccountManager { */ public const DEFAULT_SCOPES = [ self::PROPERTY_ADDRESS => self::SCOPE_LOCAL, - self::PROPERTY_AVATAR => self::SCOPE_FEDERATED, + self::PROPERTY_AVATAR => self::SCOPE_LOCAL, self::PROPERTY_BIOGRAPHY => self::SCOPE_LOCAL, self::PROPERTY_BIRTHDATE => self::SCOPE_LOCAL, - self::PROPERTY_DISPLAYNAME => self::SCOPE_FEDERATED, - self::PROPERTY_EMAIL => self::SCOPE_FEDERATED, + self::PROPERTY_DISPLAYNAME => self::SCOPE_LOCAL, + self::PROPERTY_EMAIL => self::SCOPE_LOCAL, self::PROPERTY_FEDIVERSE => self::SCOPE_LOCAL, self::PROPERTY_HEADLINE => self::SCOPE_LOCAL, self::PROPERTY_ORGANISATION => self::SCOPE_LOCAL, self::PROPERTY_PHONE => self::SCOPE_LOCAL, - self::PROPERTY_PRONOUNS => self::SCOPE_FEDERATED, + self::PROPERTY_PRONOUNS => self::SCOPE_LOCAL, self::PROPERTY_ROLE => self::SCOPE_LOCAL, self::PROPERTY_TWITTER => self::SCOPE_LOCAL, self::PROPERTY_BLUESKY => self::SCOPE_LOCAL, diff --git a/lib/private/Repair.php b/lib/private/Repair.php index e35feda335773..f7546d04abd5e 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -42,6 +42,7 @@ use OC\Repair\NC25\AddMissingSecretJob; use OC\Repair\NC29\SanitizeAccountProperties; use OC\Repair\NC30\RemoveLegacyDatadirFile; +use OC\Repair\NC33\FixDefaultAccountScopesToLocal; use OC\Repair\OldGroupMembershipShares; use OC\Repair\Owncloud\CleanPreviews; use OC\Repair\Owncloud\DropAccountTermsTable; @@ -189,6 +190,7 @@ public static function getRepairSteps(): array { Server::get(SanitizeAccountProperties::class), Server::get(AddMovePreviewJob::class), Server::get(ConfigKeyMigration::class), + Server::get(FixDefaultAccountScopesToLocal::class), ]; } diff --git a/lib/private/Repair/NC33/FixDefaultAccountScopesToLocal.php b/lib/private/Repair/NC33/FixDefaultAccountScopesToLocal.php new file mode 100644 index 0000000000000..517b4d73ca525 --- /dev/null +++ b/lib/private/Repair/NC33/FixDefaultAccountScopesToLocal.php @@ -0,0 +1,92 @@ +connection->getQueryBuilder(); + $select->select('uid', 'data') + ->from('accounts'); + + $update = $this->connection->getQueryBuilder(); + $update->update('accounts') + ->set('data', $update->createParameter('data')) + ->where($update->expr()->eq('uid', $update->createParameter('uid'))); + + $result = $select->executeQuery(); + while ($row = $result->fetch()) { + $processed++; + $data = json_decode($row['data'], true); + if (!is_array($data)) { + continue; + } + + $changed = false; + foreach (self::AFFECTED_PROPERTIES as $property) { + if (isset($data[$property]['scope']) + && $data[$property]['scope'] === IAccountManager::SCOPE_FEDERATED + ) { + $data[$property]['scope'] = IAccountManager::SCOPE_LOCAL; + $changed = true; + } + } + + if ($changed) { + $update->setParameter('data', json_encode($data)); + $update->setParameter('uid', $row['uid']); + $update->executeStatement(); + $updated++; + } + } + $result->closeCursor(); + + $output->info("Processed $processed accounts, updated $updated accounts with local scope defaults."); + } +} diff --git a/tests/lib/Accounts/AccountManagerTest.php b/tests/lib/Accounts/AccountManagerTest.php index 8d66ba15ce15a..4e799458961ae 100644 --- a/tests/lib/Accounts/AccountManagerTest.php +++ b/tests/lib/Accounts/AccountManagerTest.php @@ -532,14 +532,14 @@ public function testAddMissingDefaults(): void { [ 'name' => IAccountManager::PROPERTY_DISPLAYNAME, 'value' => 'bob', - 'scope' => IAccountManager::SCOPE_FEDERATED, + 'scope' => IAccountManager::SCOPE_LOCAL, 'verified' => IAccountManager::NOT_VERIFIED, ], [ 'name' => IAccountManager::PROPERTY_EMAIL, 'value' => 'bob@bob.bob', - 'scope' => IAccountManager::SCOPE_FEDERATED, + 'scope' => IAccountManager::SCOPE_LOCAL, 'verified' => IAccountManager::NOT_VERIFIED, ], @@ -559,7 +559,7 @@ public function testAddMissingDefaults(): void { [ 'name' => IAccountManager::PROPERTY_AVATAR, - 'scope' => IAccountManager::SCOPE_FEDERATED + 'scope' => IAccountManager::SCOPE_LOCAL, ], [ @@ -628,7 +628,7 @@ public function testAddMissingDefaults(): void { [ 'name' => IAccountManager::PROPERTY_PRONOUNS, 'value' => '', - 'scope' => IAccountManager::SCOPE_FEDERATED, + 'scope' => IAccountManager::SCOPE_LOCAL, ], ]; $this->config->expects($this->once())->method('getSystemValue')->with('account_manager.default_property_scope', [])->willReturn([]); @@ -1014,10 +1014,12 @@ public static function dataSetDefaultPropertyScopes(): array { [ [], [ - IAccountManager::PROPERTY_DISPLAYNAME => IAccountManager::SCOPE_FEDERATED, + IAccountManager::PROPERTY_DISPLAYNAME => IAccountManager::SCOPE_LOCAL, IAccountManager::PROPERTY_ADDRESS => IAccountManager::SCOPE_LOCAL, - IAccountManager::PROPERTY_EMAIL => IAccountManager::SCOPE_FEDERATED, + IAccountManager::PROPERTY_EMAIL => IAccountManager::SCOPE_LOCAL, IAccountManager::PROPERTY_ROLE => IAccountManager::SCOPE_LOCAL, + IAccountManager::PROPERTY_AVATAR => IAccountManager::SCOPE_LOCAL, + IAccountManager::PROPERTY_PRONOUNS => IAccountManager::SCOPE_LOCAL, ] ], [ @@ -1039,7 +1041,7 @@ public static function dataSetDefaultPropertyScopes(): array { ], [ IAccountManager::PROPERTY_ADDRESS => IAccountManager::SCOPE_LOCAL, - IAccountManager::PROPERTY_EMAIL => IAccountManager::SCOPE_FEDERATED, + IAccountManager::PROPERTY_EMAIL => IAccountManager::SCOPE_LOCAL, IAccountManager::PROPERTY_ROLE => IAccountManager::SCOPE_PRIVATE, ] ], diff --git a/tests/lib/Repair/NC33/FixDefaultAccountScopesToLocalTest.php b/tests/lib/Repair/NC33/FixDefaultAccountScopesToLocalTest.php new file mode 100644 index 0000000000000..d1cfbad35f83a --- /dev/null +++ b/tests/lib/Repair/NC33/FixDefaultAccountScopesToLocalTest.php @@ -0,0 +1,196 @@ +connection = Server::get(IDBConnection::class); + $this->output = $this->createMock(IOutput::class); + $this->repair = new FixDefaultAccountScopesToLocal($this->connection); + } + + protected function tearDown(): void { + parent::tearDown(); + $query = $this->connection->getQueryBuilder(); + $query->delete('accounts') + ->where($query->expr()->like('uid', $query->createNamedParameter('test-fix-scope-%'))) + ->executeStatement(); + } + + private function insertAccount(string $uid, array $data): void { + $query = $this->connection->getQueryBuilder(); + $query->insert('accounts') + ->values([ + 'uid' => $query->createNamedParameter($uid), + 'data' => $query->createNamedParameter(json_encode($data)), + ]) + ->executeStatement(); + } + + private function getAccountData(string $uid): ?array { + $query = $this->connection->getQueryBuilder(); + $query->select('data') + ->from('accounts') + ->where($query->expr()->eq('uid', $query->createNamedParameter($uid))); + $result = $query->executeQuery(); + $row = $result->fetch(); + $result->closeCursor(); + if ($row === false) { + return null; + } + return json_decode($row['data'], true); + } + + public function testMigratesFederatedToLocal(): void { + $uid = 'test-fix-scope-federated'; + $data = [ + IAccountManager::PROPERTY_DISPLAYNAME => [ + 'value' => 'Test User', + 'scope' => IAccountManager::SCOPE_FEDERATED, + 'verified' => '0', + ], + IAccountManager::PROPERTY_EMAIL => [ + 'value' => 'test@example.com', + 'scope' => IAccountManager::SCOPE_FEDERATED, + 'verified' => '0', + ], + IAccountManager::PROPERTY_AVATAR => [ + 'value' => '', + 'scope' => IAccountManager::SCOPE_FEDERATED, + ], + IAccountManager::PROPERTY_PRONOUNS => [ + 'value' => '', + 'scope' => IAccountManager::SCOPE_FEDERATED, + ], + IAccountManager::PROPERTY_PHONE => [ + 'value' => '', + 'scope' => IAccountManager::SCOPE_LOCAL, + 'verified' => '0', + ], + IAccountManager::PROPERTY_ADDRESS => [ + 'value' => '', + 'scope' => IAccountManager::SCOPE_LOCAL, + 'verified' => '0', + ], + ]; + + $this->insertAccount($uid, $data); + $this->repair->run($this->output); + + $updatedData = $this->getAccountData($uid); + $this->assertNotNull($updatedData); + + // These should be changed from federated to local + $this->assertEquals(IAccountManager::SCOPE_LOCAL, $updatedData[IAccountManager::PROPERTY_DISPLAYNAME]['scope']); + $this->assertEquals(IAccountManager::SCOPE_LOCAL, $updatedData[IAccountManager::PROPERTY_EMAIL]['scope']); + $this->assertEquals(IAccountManager::SCOPE_LOCAL, $updatedData[IAccountManager::PROPERTY_AVATAR]['scope']); + $this->assertEquals(IAccountManager::SCOPE_LOCAL, $updatedData[IAccountManager::PROPERTY_PRONOUNS]['scope']); + + // These should remain unchanged + $this->assertEquals(IAccountManager::SCOPE_LOCAL, $updatedData[IAccountManager::PROPERTY_PHONE]['scope']); + $this->assertEquals(IAccountManager::SCOPE_LOCAL, $updatedData[IAccountManager::PROPERTY_ADDRESS]['scope']); + } + + public function testDoesNotChangePublishedScope(): void { + $uid = 'test-fix-scope-published'; + $data = [ + IAccountManager::PROPERTY_DISPLAYNAME => [ + 'value' => 'Public User', + 'scope' => IAccountManager::SCOPE_PUBLISHED, + 'verified' => '0', + ], + IAccountManager::PROPERTY_EMAIL => [ + 'value' => 'public@example.com', + 'scope' => IAccountManager::SCOPE_PUBLISHED, + 'verified' => '0', + ], + ]; + + $this->insertAccount($uid, $data); + $this->repair->run($this->output); + + $updatedData = $this->getAccountData($uid); + $this->assertNotNull($updatedData); + + // Published scope should NOT be changed + $this->assertEquals(IAccountManager::SCOPE_PUBLISHED, $updatedData[IAccountManager::PROPERTY_DISPLAYNAME]['scope']); + $this->assertEquals(IAccountManager::SCOPE_PUBLISHED, $updatedData[IAccountManager::PROPERTY_EMAIL]['scope']); + } + + public function testDoesNotChangeAlreadyLocalScope(): void { + $uid = 'test-fix-scope-local'; + $data = [ + IAccountManager::PROPERTY_DISPLAYNAME => [ + 'value' => 'Local User', + 'scope' => IAccountManager::SCOPE_LOCAL, + 'verified' => '0', + ], + IAccountManager::PROPERTY_EMAIL => [ + 'value' => 'local@example.com', + 'scope' => IAccountManager::SCOPE_LOCAL, + 'verified' => '0', + ], + ]; + + $this->insertAccount($uid, $data); + $this->repair->run($this->output); + + $updatedData = $this->getAccountData($uid); + $this->assertNotNull($updatedData); + + // Should remain local + $this->assertEquals(IAccountManager::SCOPE_LOCAL, $updatedData[IAccountManager::PROPERTY_DISPLAYNAME]['scope']); + $this->assertEquals(IAccountManager::SCOPE_LOCAL, $updatedData[IAccountManager::PROPERTY_EMAIL]['scope']); + } + + public function testDoesNotChangeNonAffectedProperties(): void { + $uid = 'test-fix-scope-phone-federated'; + $data = [ + IAccountManager::PROPERTY_PHONE => [ + 'value' => '+1234567890', + 'scope' => IAccountManager::SCOPE_FEDERATED, + 'verified' => '0', + ], + IAccountManager::PROPERTY_WEBSITE => [ + 'value' => 'https://example.com', + 'scope' => IAccountManager::SCOPE_FEDERATED, + 'verified' => '0', + ], + ]; + + $this->insertAccount($uid, $data); + $this->repair->run($this->output); + + $updatedData = $this->getAccountData($uid); + $this->assertNotNull($updatedData); + + // Phone and website were not in the old defaults that were federated, + // so they should remain unchanged (user chose federated deliberately) + $this->assertEquals(IAccountManager::SCOPE_FEDERATED, $updatedData[IAccountManager::PROPERTY_PHONE]['scope']); + $this->assertEquals(IAccountManager::SCOPE_FEDERATED, $updatedData[IAccountManager::PROPERTY_WEBSITE]['scope']); + } +}