Loading lib/Command/Migrate2FASecrets.php +51 −52 Original line number Diff line number Diff line Loading @@ -8,27 +8,24 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use OCA\TwoFactorTOTP\Db\TotpSecretMapper; use OCP\Security\ICrypto; use OCP\IDBConnection; use OCP\IUserManager; use OCP\IUser; use OCA\EcloudAccounts\Db\SSOMapper; use OCA\EcloudAccounts\Exception\DbConnectionParamsException; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Connection; class Migrate2FASecrets extends Command { private TotpSecretMapper $totpSecretMapper; private SSOMapper $ssoMapper; private IDBConnection $dbConn; private Connection $ssoDbConn; private ICrypto $crypto; private IUserManager $userManager; private OutputInterface $commandOutput; private const TOTP_SECRET_TABLE = 'twofactor_totp_secrets'; public function __construct(TotpSecretMapper $totpSecretMapper, IDBConnection $dbConn, ICrypto $crypto, SSOMapper $ssoMapper, IUserManager $userManager) { $this->totpSecretMapper = $totpSecretMapper; public function __construct(IDBConnection $dbConn, ICrypto $crypto, SSOMapper $ssoMapper, IUserManager $userManager) { $this->ssoMapper = $ssoMapper; $this->userManager = $userManager; $this->dbConn = $dbConn; Loading Loading @@ -82,15 +79,12 @@ class Migrate2FASecrets extends Command { protected function execute(InputInterface $input, OutputInterface $output): int { try { $this->commandOutput = $output; $dbName = $input->getOption('sso-db-name'); $dbHost = $input->getOption('sso-db-host'); $dbPort = $input->getOption('sso-db-port'); $dbPassword = $input->getOption('sso-db-password'); $dbUser = $input->getOption('sso-db-user'); if (empty($dbName) || empty($dbHost) || empty($dbPort) || empty($dbPassword) || empty($dbUser)) { throw new DbConnectionParamsException('Invalid database parameters!'); } $this->ssoDbConn = $this->getDatabaseConnection($dbName, $dbHost, $dbPort, $dbPassword, $dbUser); $usernames = []; Loading @@ -98,57 +92,51 @@ class Migrate2FASecrets extends Command { if (!empty($usernameList)) { $usernames = explode(',', $usernameList); } $ssoSecretEntries = $this->getSSOSecretEntries($usernames); foreach ($ssoSecretEntries as $username => $entry) { try { $this->ssoMapper->insertCredential($entry, $this->ssoDbConn); } catch(\Exception $e) { $output->writeln('Error inserting entry for user ' . $username . ' message: ' . $e->getMessage()); continue; } } $this->migrateUsers($usernames); return 0; } catch (\Exception $e) { $output->writeln($e->getMessage()); $this->commandOutput->writeln($e->getMessage()); return 1; } } private function getSSOSecretEntries(array $usernames) : array { if (!empty($usernames)) { $entries = []; foreach ($usernames as $username) { $user = $this->userManager->get($username); if (!$user instanceof IUser) { continue; } $dbSecret = $this->totpSecretMapper->getSecret($user); $decryptedSecret = $this->crypto->decrypt($dbSecret->getSecret()); $ssoUserId = $this->ssoMapper->getUserId($username, $this->ssoDbConn); $entries[$username] = $this->getSSOSecretEntry($decryptedSecret, $ssoUserId); } return $entries; } return $this->getAllSSOSecretEntries(); } private function getAllSSOSecretEntries() : array { /** * Migrate user secrets to the SSO database * * @return void */ private function migrateUsers(array $usernames = []) : void { $entries = []; $qb = $this->dbConn->getQueryBuilder(); $qb->select('user_id', 'secret') ->from(self::TOTP_SECRET_TABLE); if (!empty($usernames)) { $qb->where('user_id IN (:usernames)') ->setParameter('usernames', implode(',', $usernames)); } $result = $qb->execute(); while ($row = $result->fetch()) { try { $username = (string) $row['user_id']; $secret = (string) $row['secret']; $decryptedSecret = $this->crypto->decrypt($secret); $ssoUserId = $this->ssoMapper->getUserId($username, $this->ssoDbConn); $entries[$username] = $this->getSSOSecretEntry($decryptedSecret, $ssoUserId); $entry = $this->getSSOSecretEntry($decryptedSecret, $ssoUserId); $this->ssoMapper->insertCredential($entry, $this->ssoDbConn); } catch(\Exception $e) { $this->commandOutput->writeln('Error inserting entry for user ' . $username . ' message: ' . $e->getMessage()); continue; } } return $entries; } /** * Create secret entry compatible with Keycloak schema * * @return array */ private function getSSOSecretEntry(string $secret, string $ssoUserId) : array { $credentialEntry = [ Loading Loading @@ -176,7 +164,16 @@ class Migrate2FASecrets extends Command { return $credentialEntry; } private function getDatabaseConnection(string $dbName, string $dbHost, int $dbPort, string $dbPassword, string $dbUser) { /** * Attempt to connect to a non-NC database * * @return Connection */ private function getDatabaseConnection(string $dbName, string $dbHost, int $dbPort, string $dbPassword, string $dbUser) : Connection { if (empty($dbName) || empty($dbHost) || empty($dbPort) || empty($dbPassword) || empty($dbUser)) { throw new DbConnectionParamsException('Invalid database parameters!'); } $params = [ 'dbname' => $dbName, 'user' => $dbUser, Loading @@ -189,11 +186,13 @@ class Migrate2FASecrets extends Command { return DriverManager::getConnection($params); } /* From https://www.uuidgenerator.net/dev-corner/php As keycloak generates random UUIDs using the java.util.UUID class which is RFC 4122 compliant /** * From https://www.uuidgenerator.net/dev-corner/php * As keycloak generates random UUIDs using the java.util.UUID class which is RFC 4122 compliant * * @return string */ private function randomUUID($data = null) { private function randomUUID($data = null) : string { // Generate 16 bytes (128 bits) of random data or use the data passed into the function. $data = $data ?? random_bytes(16); assert(strlen($data) == 16); Loading Loading
lib/Command/Migrate2FASecrets.php +51 −52 Original line number Diff line number Diff line Loading @@ -8,27 +8,24 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use OCA\TwoFactorTOTP\Db\TotpSecretMapper; use OCP\Security\ICrypto; use OCP\IDBConnection; use OCP\IUserManager; use OCP\IUser; use OCA\EcloudAccounts\Db\SSOMapper; use OCA\EcloudAccounts\Exception\DbConnectionParamsException; use Doctrine\DBAL\DriverManager; use Doctrine\DBAL\Connection; class Migrate2FASecrets extends Command { private TotpSecretMapper $totpSecretMapper; private SSOMapper $ssoMapper; private IDBConnection $dbConn; private Connection $ssoDbConn; private ICrypto $crypto; private IUserManager $userManager; private OutputInterface $commandOutput; private const TOTP_SECRET_TABLE = 'twofactor_totp_secrets'; public function __construct(TotpSecretMapper $totpSecretMapper, IDBConnection $dbConn, ICrypto $crypto, SSOMapper $ssoMapper, IUserManager $userManager) { $this->totpSecretMapper = $totpSecretMapper; public function __construct(IDBConnection $dbConn, ICrypto $crypto, SSOMapper $ssoMapper, IUserManager $userManager) { $this->ssoMapper = $ssoMapper; $this->userManager = $userManager; $this->dbConn = $dbConn; Loading Loading @@ -82,15 +79,12 @@ class Migrate2FASecrets extends Command { protected function execute(InputInterface $input, OutputInterface $output): int { try { $this->commandOutput = $output; $dbName = $input->getOption('sso-db-name'); $dbHost = $input->getOption('sso-db-host'); $dbPort = $input->getOption('sso-db-port'); $dbPassword = $input->getOption('sso-db-password'); $dbUser = $input->getOption('sso-db-user'); if (empty($dbName) || empty($dbHost) || empty($dbPort) || empty($dbPassword) || empty($dbUser)) { throw new DbConnectionParamsException('Invalid database parameters!'); } $this->ssoDbConn = $this->getDatabaseConnection($dbName, $dbHost, $dbPort, $dbPassword, $dbUser); $usernames = []; Loading @@ -98,57 +92,51 @@ class Migrate2FASecrets extends Command { if (!empty($usernameList)) { $usernames = explode(',', $usernameList); } $ssoSecretEntries = $this->getSSOSecretEntries($usernames); foreach ($ssoSecretEntries as $username => $entry) { try { $this->ssoMapper->insertCredential($entry, $this->ssoDbConn); } catch(\Exception $e) { $output->writeln('Error inserting entry for user ' . $username . ' message: ' . $e->getMessage()); continue; } } $this->migrateUsers($usernames); return 0; } catch (\Exception $e) { $output->writeln($e->getMessage()); $this->commandOutput->writeln($e->getMessage()); return 1; } } private function getSSOSecretEntries(array $usernames) : array { if (!empty($usernames)) { $entries = []; foreach ($usernames as $username) { $user = $this->userManager->get($username); if (!$user instanceof IUser) { continue; } $dbSecret = $this->totpSecretMapper->getSecret($user); $decryptedSecret = $this->crypto->decrypt($dbSecret->getSecret()); $ssoUserId = $this->ssoMapper->getUserId($username, $this->ssoDbConn); $entries[$username] = $this->getSSOSecretEntry($decryptedSecret, $ssoUserId); } return $entries; } return $this->getAllSSOSecretEntries(); } private function getAllSSOSecretEntries() : array { /** * Migrate user secrets to the SSO database * * @return void */ private function migrateUsers(array $usernames = []) : void { $entries = []; $qb = $this->dbConn->getQueryBuilder(); $qb->select('user_id', 'secret') ->from(self::TOTP_SECRET_TABLE); if (!empty($usernames)) { $qb->where('user_id IN (:usernames)') ->setParameter('usernames', implode(',', $usernames)); } $result = $qb->execute(); while ($row = $result->fetch()) { try { $username = (string) $row['user_id']; $secret = (string) $row['secret']; $decryptedSecret = $this->crypto->decrypt($secret); $ssoUserId = $this->ssoMapper->getUserId($username, $this->ssoDbConn); $entries[$username] = $this->getSSOSecretEntry($decryptedSecret, $ssoUserId); $entry = $this->getSSOSecretEntry($decryptedSecret, $ssoUserId); $this->ssoMapper->insertCredential($entry, $this->ssoDbConn); } catch(\Exception $e) { $this->commandOutput->writeln('Error inserting entry for user ' . $username . ' message: ' . $e->getMessage()); continue; } } return $entries; } /** * Create secret entry compatible with Keycloak schema * * @return array */ private function getSSOSecretEntry(string $secret, string $ssoUserId) : array { $credentialEntry = [ Loading Loading @@ -176,7 +164,16 @@ class Migrate2FASecrets extends Command { return $credentialEntry; } private function getDatabaseConnection(string $dbName, string $dbHost, int $dbPort, string $dbPassword, string $dbUser) { /** * Attempt to connect to a non-NC database * * @return Connection */ private function getDatabaseConnection(string $dbName, string $dbHost, int $dbPort, string $dbPassword, string $dbUser) : Connection { if (empty($dbName) || empty($dbHost) || empty($dbPort) || empty($dbPassword) || empty($dbUser)) { throw new DbConnectionParamsException('Invalid database parameters!'); } $params = [ 'dbname' => $dbName, 'user' => $dbUser, Loading @@ -189,11 +186,13 @@ class Migrate2FASecrets extends Command { return DriverManager::getConnection($params); } /* From https://www.uuidgenerator.net/dev-corner/php As keycloak generates random UUIDs using the java.util.UUID class which is RFC 4122 compliant /** * From https://www.uuidgenerator.net/dev-corner/php * As keycloak generates random UUIDs using the java.util.UUID class which is RFC 4122 compliant * * @return string */ private function randomUUID($data = null) { private function randomUUID($data = null) : string { // Generate 16 bytes (128 bits) of random data or use the data passed into the function. $data = $data ?? random_bytes(16); assert(strlen($data) == 16); Loading