owncloud/core

View on GitHub
apps/files_external/appinfo/Migrations/Version20220329110116.php

Summary

Maintainability
C
1 day
Test Coverage
<?php
namespace OCA\files_external\Migrations;

use OC\NeedsUpdateException;
use OCA\Files_External\Lib\Backend\SFTP;
use OCA\Files_External\Lib\Auth\PublicKey\RSA;
use OCA\Files_External\Lib\RSAStore;
use OCP\Migration\ISimpleMigration;
use OCP\Migration\IOutput;
use OCP\Files\External\Service\IGlobalStoragesService;
use OCP\Files\External\IStorageConfig;
use OCP\ILogger;
use OCP\IConfig;
use phpseclib3\Crypt\RSA as RSACrypt;

class Version20220329110116 implements ISimpleMigration {
    /** @var IGlobalStoragesService */
    private $storageService;
    /** @var ILogger */
    private $logger;
    /** @var IConfig */
    private $config;

    public function __construct(IGlobalStoragesService $storageService, ILogger $logger, IConfig $config) {
        $this->storageService = $storageService;
        $this->logger = $logger;
        $this->config = $config;
    }
    /**
     * @param IOutput $out
     */
    public function run(IOutput $out) {
        if (!$this->config->getSystemValue('installed', false)) {
            // Skip the migration for new installations -> nothing to migrate
            return;
        }

        $this->loadFSApps();
        \OC_Util::setupFS();  // this should load additional backends and auth mechanisms
        $storageConfigs = $this->storageService->getStorageForAllUsers();
        $pass = $this->config->getSystemValue('secret', '');

        $rsaStore = RSAStore::getGlobalInstance();
        foreach ($storageConfigs as $storageConfig) {
            if ($storageConfig->getBackend() instanceof SFTP && $storageConfig->getAuthMechanism() instanceof RSA) {
                $encPubKey = $storageConfig->getBackendOption('public_key');
                $encPrivKey = $storageConfig->getBackendOption('private_key');

                $pubKey = \base64_decode($encPubKey, true);
                $privKey = \base64_decode($encPrivKey, true);

                $configId = $storageConfig->getId();
                if ($pubKey === false || $privKey === false) {
                    $out->warning("Storage configuration with id = {$configId}: Cannot decode either public or private key, skipping");
                    continue;
                }

                try {
                    /** @phan-suppress-next-line PhanUndeclaredMethod */
                    $rsaKey = RSACrypt::load($privKey, $pass)->withHash('sha1');
                } catch (\phpseclib3\Exception\NoKeyLoadedException $e) {
                    $out->warning("Storage configuration with id = {$configId}: Cannot load private key, skipping");
                    continue;
                }

                $targetUserId = '';
                if ($storageConfig->getType() === IStorageConfig::MOUNT_TYPE_PERSONAl) {
                    $applicableUsers = $storageConfig->getApplicableUsers();
                    $targetUserId = $applicableUsers[0];  // it must have one user.
                }

                $token = $rsaStore->storeData($rsaKey, $targetUserId);
                $storageConfig->setBackendOption('public_key', $pubKey);
                $storageConfig->setBackendOption('private_key', $token);

                $this->storageService->updateStorage($storageConfig);
                $out->info("Storage configuration with id = {$configId}: keys migrated successfully");
            }
        }
    }

    /**
     * Load the FS apps. This is required because the FS apps might not be loaded during the
     * migration.
     */
    private function loadFSApps() {
        $enabledApps = \OC_App::getEnabledApps();
        foreach ($enabledApps as $enabledApp) {
            if ($enabledApp !== 'files_external' && \OC_App::isType($enabledApp, ['filesystem'])) {
                try {
                    \OC_App::loadApp($enabledApp);
                } catch (NeedsUpdateException $ex) {
                    if (\OC_App::updateApp($enabledApp)) {
                        // update successful.
                        // We can load the app without checking if the should upgrade or not.
                        \OC_App::loadApp($enabledApp, false);
                    } else {
                        $this->logger->error("Error during files_external migration. $enabledApp couldn't be loaded nor updated.", ['app' => 'files_external']);
                        $this->logger->logException($ex, ['app' => 'files_external']);
                        $this->logger->error("Mount points using $enabledApp might not be migrated properly. You might need to re-enter the passwords for those mount points", ['app' => 'files_external']);
                    }
                }
            }
        }
    }
}