diff options
author | Richard Steinmetz <richard@steinmetz.cloud> | 2025-01-15 11:01:05 +0100 |
---|---|---|
committer | backportbot[bot] <backportbot[bot]@users.noreply.github.com> | 2025-02-05 21:18:45 +0000 |
commit | 2f4d646246e4d97b29c1cd3a2f8aa044a7c6289c (patch) | |
tree | 30f9c4ab934ba679aef722b04b418f6abc05c466 | |
parent | 6d1211c4340b77fc637598353e87e0e3b93d8003 (diff) | |
download | nextcloud-server-2f4d646246e4d97b29c1cd3a2f8aa044a7c6289c.tar.gz nextcloud-server-2f4d646246e4d97b29c1cd3a2f8aa044a7c6289c.zip |
fix(oauth2): adjust db schemas when migrating from owncloudbackport/50193/stable30
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
-rw-r--r-- | lib/private/Repair.php | 3 | ||||
-rw-r--r-- | lib/private/Repair/Owncloud/MigrateOauthTables.php | 99 |
2 files changed, 96 insertions, 6 deletions
diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 6c5ec65bf26..63a55c81cdb 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -7,7 +7,6 @@ */ namespace OC; -use OC\DB\Connection; use OC\DB\ConnectionAdapter; use OC\Repair\AddAppConfigLazyMigration; use OC\Repair\AddBruteForceCleanupJob; @@ -164,7 +163,7 @@ class Repair implements IOutput { \OC::$server->getUserManager(), \OC::$server->getConfig() ), - new MigrateOauthTables(\OC::$server->get(Connection::class)), + \OC::$server->get(MigrateOauthTables::class), new UpdateLanguageCodes(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()), new AddLogRotateJob(\OC::$server->getJobList()), new ClearFrontendCaches(\OC::$server->getMemCacheFactory(), \OCP\Server::get(JSCombiner::class)), diff --git a/lib/private/Repair/Owncloud/MigrateOauthTables.php b/lib/private/Repair/Owncloud/MigrateOauthTables.php index 600d3fd3ed7..07a955db181 100644 --- a/lib/private/Repair/Owncloud/MigrateOauthTables.php +++ b/lib/private/Repair/Owncloud/MigrateOauthTables.php @@ -5,11 +5,18 @@ */ namespace OC\Repair\Owncloud; +use OC\Authentication\Token\IProvider as ITokenProvider; use OC\DB\Connection; use OC\DB\SchemaWrapper; +use OCA\OAuth2\Db\AccessToken; +use OCA\OAuth2\Db\AccessTokenMapper; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Authentication\Token\IToken; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; +use OCP\Security\ICrypto; +use OCP\Security\ISecureRandom; class MigrateOauthTables implements IRepairStep { /** @var Connection */ @@ -18,7 +25,14 @@ class MigrateOauthTables implements IRepairStep { /** * @param Connection $db */ - public function __construct(Connection $db) { + public function __construct( + Connection $db, + private AccessTokenMapper $accessTokenMapper, + private ITokenProvider $tokenProvider, + private ISecureRandom $random, + private ITimeFactory $timeFactory, + private ICrypto $crypto, + ) { $this->db = $db; } @@ -36,14 +50,23 @@ class MigrateOauthTables implements IRepairStep { return; } - $output->info('Update the oauth2_access_tokens table schema.'); + // Create column and then migrate before handling unique index. + // So that we can distinguish between legacy (from oc) and new rows (from nc). $table = $schema->getTable('oauth2_access_tokens'); if (!$table->hasColumn('hashed_code')) { + $output->info('Prepare the oauth2_access_tokens table schema.'); $table->addColumn('hashed_code', 'string', [ 'notnull' => true, 'length' => 128, ]); + + // Regenerate schema after migrating to it + $this->db->migrateToSchema($schema->getWrappedSchema()); + $schema = new SchemaWrapper($this->db); } + + $output->info('Update the oauth2_access_tokens table schema.'); + $table = $schema->getTable('oauth2_access_tokens'); if (!$table->hasColumn('encrypted_token')) { $table->addColumn('encrypted_token', 'string', [ 'notnull' => true, @@ -51,11 +74,31 @@ class MigrateOauthTables implements IRepairStep { ]); } if (!$table->hasIndex('oauth2_access_hash_idx')) { + // Drop legacy access codes first to prevent integrity constraint violations + $qb = $this->db->getQueryBuilder(); + $qb->delete('oauth2_access_tokens') + ->where($qb->expr()->eq('hashed_code', $qb->createNamedParameter(''))); + $qb->executeStatement(); + $table->addUniqueIndex(['hashed_code'], 'oauth2_access_hash_idx'); } if (!$table->hasIndex('oauth2_access_client_id_idx')) { $table->addIndex(['client_id'], 'oauth2_access_client_id_idx'); } + if (!$table->hasColumn('token_id')) { + $table->addColumn('token_id', 'integer', [ + 'notnull' => true, + ]); + } + if ($table->hasColumn('expires')) { + $table->dropColumn('expires'); + } + if ($table->hasColumn('user_id')) { + $table->dropColumn('user_id'); + } + if ($table->hasColumn('token')) { + $table->dropColumn('token'); + } $output->info('Update the oauth2_clients table schema.'); $table = $schema->getTable('oauth2_clients'); @@ -99,10 +142,10 @@ class MigrateOauthTables implements IRepairStep { $table->addIndex(['client_identifier'], 'oauth2_client_id_idx'); } - $this->db->migrateToSchema($schema->getWrappedSchema()); - // Regenerate schema after migrating to it + $this->db->migrateToSchema($schema->getWrappedSchema()); $schema = new SchemaWrapper($this->db); + if ($schema->getTable('oauth2_clients')->hasColumn('identifier')) { $output->info("Move identifier column's data to the new client_identifier column."); // 1. Fetch all [id, identifier] couple. @@ -124,7 +167,10 @@ class MigrateOauthTables implements IRepairStep { $output->info('Drop the identifier column.'); $table = $schema->getTable('oauth2_clients'); $table->dropColumn('identifier'); + + // Regenerate schema after migrating to it $this->db->migrateToSchema($schema->getWrappedSchema()); + $schema = new SchemaWrapper($this->db); } $output->info('Delete clients (and their related access tokens) with the redirect_uri starting with oc:// or ending with *'); @@ -157,5 +203,50 @@ class MigrateOauthTables implements IRepairStep { $qbDeleteClients->expr()->iLike('redirect_uri', $qbDeleteClients->createNamedParameter('%*', IQueryBuilder::PARAM_STR)) ); $qbDeleteClients->executeStatement(); + + // Migrate legacy refresh tokens from oc + if ($schema->hasTable('oauth2_refresh_tokens')) { + $output->info('Migrate legacy oauth2 refresh tokens.'); + + $qbSelect = $this->db->getQueryBuilder(); + $qbSelect->select('*') + ->from('oauth2_refresh_tokens'); + $result = $qbSelect->executeQuery(); + $now = $this->timeFactory->now()->getTimestamp(); + $index = 0; + while ($row = $result->fetch()) { + $clientId = $row['client_id']; + $refreshToken = $row['token']; + + // Insert expired token so that it can be rotated on the next refresh + $accessToken = $this->random->generate(72, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); + $authToken = $this->tokenProvider->generateToken( + $accessToken, + $row['user_id'], + $row['user_id'], + null, + "oc_migrated_client${clientId}_t{$now}_i$index", + IToken::PERMANENT_TOKEN, + IToken::DO_NOT_REMEMBER, + ); + $authToken->setExpires($now - 3600); + $this->tokenProvider->updateToken($authToken); + + $accessTokenEntity = new AccessToken(); + $accessTokenEntity->setTokenId($authToken->getId()); + $accessTokenEntity->setClientId($clientId); + $accessTokenEntity->setHashedCode(hash('sha512', $refreshToken)); + $accessTokenEntity->setEncryptedToken($this->crypto->encrypt($accessToken, $refreshToken)); + $accessTokenEntity->setCodeCreatedAt($now); + $accessTokenEntity->setTokenCount(1); + $this->accessTokenMapper->insert($accessTokenEntity); + + $index++; + } + $result->closeCursor(); + + $schema->dropTable('oauth2_refresh_tokens'); + $schema->performDropTableCalls(); + } } } |