Skip to content

Instantly share code, notes, and snippets.

@fqxp
Created July 13, 2020 09:14
Show Gist options
  • Save fqxp/c798f3ed1e4416a2ba3b2105ee800902 to your computer and use it in GitHub Desktop.
Save fqxp/c798f3ed1e4416a2ba3b2105ee800902 to your computer and use it in GitHub Desktop.
bitwarden2pass
#!/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