commit 9e617a6f1f5d515134a18733beec733798577bef Author: Simon Vieille Date: Thu Feb 29 12:49:03 2024 +0100 init diff --git a/README.md b/README.md new file mode 100644 index 0000000..94adbaa --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# Nextcloud Passwords to Bitwarden + +This project allows you to convert Nextcloud Passwords CSV to a Bitwarden JSON file. + +## Installation + +``` +git clone https://gitnet.fr/deblan/nextcloud_passwords_to_bitwarden +composer install +``` + +## How to + +### On Nextcloud + +* Go to "Personal settings" +* Set "English (US)" as language +* Go to "Passwords" +* Click on "More" +* Click on "Backup and restore" +* Choose "Backup or export" +* Choose "Predefined CSV" +* Check "Export Passwords" and "Export Folders" +* Click on "Export" +* Unzip the download file + +### On the project + +``` +php index.php /path/to/Folders.csv /path/to/Passwords.csv > bitwarden_passwords.json +``` + +### On Bitwarden/Vaultwarden + +* Go to tools +* Go to "Import data" +* Choose "Bitwarden (json)" as file format +* Choose the file "bitwarden_passwords.json" +* Click on "Import data" + +### Clean up datas + +* Remove the downloaded archive +* Remove extracted files +* Remove bitwarden_passwords.json diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b3d9a79 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "league/csv": "^9.15" + } +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..320f090 --- /dev/null +++ b/index.php @@ -0,0 +1,121 @@ +setHeaderOffset(0); + + return $csv->getRecords(); +} + +function usage(int $exitCode = 0, ?string $error = null): void +{ + $usage = []; + + if (null !== $error) { + $usage[] = $error; + $usage[] = ''; + } + + $file = __FILE__; + + $usage[] = 'Usage'; + $usage[] = " php {$file} -h|--help"; + $usage[] = " php {$file} /path/to/Folders.csv /path/to/Passwords.csv"; + + file_put_contents('php://stderr', implode("\n", $usage)); + + exit($exitCode); +} + +function checkFile(?string $file): void +{ + if (null === $file) { + usage(1, 'File not defined'); + } + + if (!is_file($file) || !is_readable($file)) { + usage(1, 'File not found or not readable: '.$file); + } +} + +foreach ($argv as $value) { + if (in_array($value, ['-h', '--help'])) { + usage(); + } +} + +$folders = $argv[1] ?? null; +$passwords = $argv[2] ?? null; + +checkFile($folders); +checkFile($passwords); + +$datas = [ + 'encrypted' => false, + 'folders' => [], + 'items' => [], +]; + +foreach (getRecords($folders) as $item) { + $datas['folders'][] = [ + 'id' => $item['Id'], + 'name' => $item['Label'], + ]; +} + +foreach (getRecords($passwords) as $item) { + $fields = []; + + if (!empty($item['Custom Fields'])) { + $elements = explode("\n", trim($item['Custom Fields'])); + + if (!empty($elements)) { + foreach ($elements as $element) { + preg_match('/^(.*), (.*): (.*)$/iU', $element, $match); + + $fields[] = [ + 'name' => $match[1], + 'value' => $match[3], + 'linkedId' => null, + 'type' => match ($match[2]) { + 'secret' => 1, + default => 0, + }, + ]; + } + } + } + + $datas['items'][] = [ + 'passwordHistory' => null, + 'revisionDate' => null, + 'creationDate' => null, + 'deletedDate' => null, + 'id' => $item['Id'], + 'organizationId' => null, + 'folderId' => $item['Folder Id'], + 'type' => 1, + 'reprompt' => 0, + 'name' => $item['Label'], + 'notes' => $item['Notes'], + 'favorite' => 'true' === $item['Favorite'], + 'fields' => $fields, + 'login' => [ + 'fido2Credentials' => [], + 'uris' => [[ + 'match' => null, + 'uri' => $item['Url'], + ]], + 'username' => $item['Username'], + 'password' => $item['Password'], + 'topt' => null, + ], + ]; +} + +echo json_encode($datas);