Created
July 13, 2020 09:14
-
-
Save fqxp/c798f3ed1e4416a2ba3b2105ee800902 to your computer and use it in GitHub Desktop.
bitwarden2pass
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# | |
# Prerequisites: | |
# * Install bitwarden-cli (ArchLinux: bitwarden-cli package) | |
# and run `bw login` and `bw unlock` and set the API TOKEN thats | |
# printed as a result. | |
# | |
# Usage: | |
# 1. Simply run `bitwarden2pass`. | |
# 2. Voilà! | |
import collections | |
from functools import lru_cache | |
import json | |
import os.path | |
import subprocess | |
import sys | |
PASS_BASE_FOLDER = 'bitwarden' | |
def main(): | |
sync_bitwarden() | |
items = list(retrieve_bitwarden_items()) | |
validate_paths(items) | |
i = 0 | |
for item in items: | |
i += 1 | |
sys.stdout.write('Inserting passwords ... ({} of {})\r'.format(i, len(items))) | |
insert_into_pass(item['path'], item['content']) | |
print() | |
def sync_bitwarden(): | |
run_command('bw sync') | |
def retrieve_bitwarden_items(): | |
try: | |
for item in read_command_json('bw list items'): | |
yield { | |
'path': build_path(item), | |
'content': build_content(item), | |
} | |
except Exception as e: | |
print('Error "{}" while processing {}'.format(e, item)) | |
raise | |
@lru_cache() | |
def retrieve_bitwarden_folders(): | |
folders = read_command_json('bw list folders') | |
return { | |
folder['id']: folder['name'] | |
for folder in folders | |
} | |
def read_command_json(command): | |
fd = subprocess.Popen( | |
command.split(), | |
stdout=subprocess.PIPE, | |
encoding='utf-8' | |
).stdout | |
return json.load(fd) | |
def run_command(command): | |
return subprocess.run( | |
command.split(), | |
check=True | |
) | |
def build_path(item): | |
folders = retrieve_bitwarden_folders() | |
if 'login' in item and 'username' in item['login']: | |
username = item['login']['username'] or '' | |
else: | |
username = '' | |
return os.path.join(*[ | |
'bitwarden', | |
folders[item['folderId']].replace(' ', '_'), | |
item['name'].strip(), | |
username, | |
]) | |
def build_content(item): | |
if 'login' in item and 'uris' in item['login'] and len(item['login']['uris']) > 0: | |
url = item['login']['uris'][0]['uri'] | |
else: | |
url = None | |
if 'login' in item and 'username' in item['login']: | |
username = item['login']['username'] or '' | |
else: | |
username = '' | |
if 'login' in item and 'password' in item['login']: | |
password = item['login']['password'] or '' | |
else: | |
password = '' | |
return '''{} | |
name: {} | |
url: {} | |
username: {} | |
notes: {} | |
'''.format( | |
password, | |
item['name'], | |
url or '', | |
username, | |
item['notes'] or '' | |
) | |
def validate_paths(items): | |
counter = collections.Counter(item['path'] for item in items) | |
duplicate_paths = [path for path, count in counter.items() if count > 1] | |
for path in duplicate_paths: | |
print('WARNING: Duplicate path "%s"' % path) | |
if len(duplicate_paths) > 0: | |
sys.exit(1) | |
def insert_into_pass(path, content): | |
process = subprocess.Popen( | |
['pass', 'insert', '--force', '--multiline', bytes(path, 'utf-8')], | |
stdin=subprocess.PIPE, | |
stdout=subprocess.PIPE) | |
process.communicate(bytes(content, 'utf-8')) | |
if process.returncode != 0: | |
print('WARNING: Couldn’t insert password %s' % path) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment