class AttachmentTests3(KDBX3Tests): # get some things ready before testing def setUp(self): shutil.copy( os.path.join(base_dir, self.database), os.path.join(base_dir, 'test_attachment.kdbx') ) self.open() def open(self): self.kp = PyKeePass( os.path.join(base_dir, 'test_attachment.kdbx'), password=self.password, keyfile=os.path.join(base_dir, self.keyfile) ) def test_create_delete_binary(self): with self.assertRaises(BinaryError): self.kp.delete_binary(999) with self.assertRaises(BinaryError): e = self.kp.entries[0] e.add_attachment(filename='foo.txt', id=123) e.attachments[0].binary binary_id = self.kp.add_binary(b'Ronald McDonald Trump') self.kp.save() self.open() self.assertEqual(self.kp.binaries[binary_id], b'Ronald McDonald Trump') self.assertEqual(len(self.kp.attachments), 1) num_attach = len(self.kp.binaries) self.kp.delete_binary(binary_id) self.kp.save() self.open() self.assertEqual(len(self.kp.binaries), num_attach - 1) def test_attachment_reference_decrement(self): e = self.kp.entries[0] binary_id1 = self.kp.add_binary(b'foobar') binary_id2 = self.kp.add_binary(b'foobar2') attachment1 = e.add_attachment(binary_id1, 'foo.txt') attachment2 = e.add_attachment(binary_id2, 'foo.txt') self.kp.delete_binary(binary_id1) self.assertEqual(attachment2.id, binary_id2 - 1) def test_fields(self): e = self.kp.entries[0] binary_id = self.kp.add_binary(b'foobar') a = e.add_attachment(filename='test.txt', id=binary_id) self.assertEqual(a.data, b'foobar') self.assertEqual(a.id, binary_id) self.assertEqual(a.filename, 'test.txt') def tearDown(self): os.remove(os.path.join(base_dir, 'test_attachment.kdbx'))
class AttachmentTests3(KDBX3Tests): # get some things ready before testing def setUp(self): shutil.copy( os.path.join(base_dir, self.database), os.path.join(base_dir, 'test_attachment.kdbx') ) self.open() def open(self): self.kp = PyKeePass( os.path.join(base_dir, 'test_attachment.kdbx'), password=self.password, keyfile=os.path.join(base_dir, self.keyfile) ) def test_create_delete_attachment(self): attachment_id = self.kp.add_binary(b'Ronald McDonald Trump') self.kp.save() self.open() self.assertEqual(self.kp.binaries[-1], b'Ronald McDonald Trump') num_attach = len(self.kp.binaries) self.kp.delete_binary(len(self.kp.binaries) - 1) self.kp.save() self.open() self.assertEqual(len(self.kp.binaries), num_attach - 1) def test_attachment_reference_decrement(self): e = self.kp.entries[0] attachment_id1 = self.kp.add_binary(b'foobar') attachment_id2 = self.kp.add_binary(b'foobar2') attachment1 = e.add_attachment(attachment_id1, 'foo.txt') attachment2 = e.add_attachment(attachment_id2, 'foo.txt') self.kp.delete_binary(attachment_id1) self.assertEqual(attachment2.id, attachment_id2 - 1) def tearDown(self): os.remove(os.path.join(base_dir, 'test_attachment.kdbx'))
def bitwarden_to_keepass(args): try: kp = PyKeePass(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except FileNotFoundError: logging.info('KeePass database does not exist, creating a new one.') kp = create_database(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except CredentialsError as e: logging.error(f'Wrong password for KeePass database: {e}') return folders = subprocess.check_output( f'{quote(args.bw_path)} list folders --session {quote(args.bw_session)}', shell=True, encoding='utf8') folders = json.loads(folders) groups = {} for folder in folders: groups[folder['id']] = kp.add_group(kp.root_group, folder['name']) logging.info(f'Folders done ({len(groups)}).') items = subprocess.check_output( f'{quote(args.bw_path)} list items --session {quote(args.bw_session)}', shell=True, encoding='utf8') items = json.loads(items) logging.info(f'Starting to process {len(items)} items.') for item in items: if item['type'] in [ItemTypes.CARD, ItemTypes.IDENTITY]: logging.warning( f'Skipping credit card or identity item "{item["name"]}".') continue bw_item = Item(item) is_duplicate_title = False try: while True: entry_title = bw_item.get_name( ) if not is_duplicate_title else '{name} - ({item_id}'.format( name=bw_item.get_name(), item_id=bw_item.get_id()) try: entry = kp.add_entry( destination_group=groups[bw_item.get_folder_id()], title=entry_title, username=bw_item.get_username(), password=bw_item.get_password(), notes=bw_item.get_notes()) break except Exception as e: if 'already exists' in str(e): is_duplicate_title = True continue raise totp_secret, totp_settings = bw_item.get_totp() if totp_secret and totp_settings: entry.set_custom_property('TOTP Seed', totp_secret) entry.set_custom_property('TOTP Settings', totp_settings) for uri in bw_item.get_uris(): entry.url = uri['uri'] break # todo append additional uris to notes? for field in bw_item.get_custom_fields(): entry.set_custom_property(field['name'], field['value']) for attachment in bw_item.get_attachments(): attachment_tmp_path = f'/tmp/attachment/{attachment["fileName"]}' attachment_path = subprocess.check_output( f'{quote(args.bw_path)} get attachment' f' --raw {quote(attachment["id"])} ' f'--itemid {quote(bw_item.get_id())} ' f'--output {quote(attachment_tmp_path)} --session {quote(args.bw_session)}', shell=True, encoding='utf8').rstrip() attachment_id = kp.add_binary( open(attachment_path, 'rb').read()) entry.add_attachment(attachment_id, attachment['fileName']) os.remove(attachment_path) except Exception as e: logging.warning( f'Skipping item named "{item["name"]}" because of this error: {repr(e)}' ) continue logging.info('Saving changes to KeePass database.') kp.save() logging.info('Export completed.')
def bitwarden_to_keepass(args): try: kp = PyKeePass(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except FileNotFoundError: logging.info('KeePass database does not exist, creating a new one.') kp = create_database(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except CredentialsError as e: logging.error(f'Wrong password for KeePass database: {e}') return folders = subprocess.check_output( f'{quote(args.bw_path)} list folders --session {quote(args.bw_session)}', shell=True, encoding='utf8') folders = json.loads(folders) # sort folders so that in the case of nested folders, the parents would be guaranteed to show up before the children folders.sort(key=lambda x: x['name']) groups_by_id = {} groups_by_name = {} for folder in folders: # entries not associated with a folder should go under the root group if folder['id'] is None: groups_by_id[folder['id']] = kp.root_group continue parent_group = kp.root_group target_name = folder['name'] # check if this is a nested folder; set appropriate parent group if so folder_path_split = target_name.rsplit('/', maxsplit=1) if len(folder_path_split) > 1: parent_group = groups_by_name[folder_path_split[0]] target_name = folder_path_split[1] new_group = kp.add_group(parent_group, target_name) groups_by_id[folder['id']] = new_group groups_by_name[folder['name']] = new_group logging.info(f'Folders done ({len(groups_by_id)}).') items = subprocess.check_output( f'{quote(args.bw_path)} list items --session {quote(args.bw_session)}', shell=True, encoding='utf8') items = json.loads(items) logging.info(f'Starting to process {len(items)} items.') for item in items: bw_item = Item(item) is_duplicate_title = False try: while True: entry_title = bw_item.get_name( ) if not is_duplicate_title else '{name} - ({item_id}'.format( name=bw_item.get_name(), item_id=bw_item.get_id()) try: entry = kp.add_entry(destination_group=groups_by_id[ bw_item.get_folder_id()], title=entry_title, username=bw_item.get_username(), password=bw_item.get_password(), notes=bw_item.get_notes()) break except Exception as e: if 'already exists' in str(e): is_duplicate_title = True continue raise totp_secret, totp_settings = bw_item.get_totp() if totp_secret and totp_settings: entry.set_custom_property('TOTP Seed', totp_secret) entry.set_custom_property('TOTP Settings', totp_settings) for uri in bw_item.get_uris(): entry.url = uri['uri'] break # todo append additional uris to notes? for field in bw_item.get_custom_fields(): entry.set_custom_property(field['name'], field['value']) for card_field_name, card_field_val in bw_item.get_card().items(): if card_field_val is not None: entry.set_custom_property( f"{CUSTOM_FIELD_PREFIX}_CARD_{card_field_name}", card_field_val) for identity_field_name, identity_field_val in bw_item.get_identity( ).items(): if identity_field_val is not None: entry.set_custom_property( f"{CUSTOM_FIELD_PREFIX}_IDENTITY_{identity_field_name}", identity_field_val) for attachment in bw_item.get_attachments(): attachment_tmp_path = f'{args.tmp_path}/attachment/{attachment["fileName"]}' attachment_path = subprocess.check_output( f'{quote(args.bw_path)} get attachment' f' --raw {quote(attachment["id"])} ' f'--itemid {quote(bw_item.get_id())} ' f'--output {quote(attachment_tmp_path)} --session {quote(args.bw_session)}', shell=True, encoding='utf8').rstrip() attachment_id = kp.add_binary( open(attachment_path, 'rb').read()) entry.add_attachment(attachment_id, attachment['fileName']) os.remove(attachment_path) except Exception as e: logging.warning( f'Skipping item named "{item["name"]}" because of this error: {repr(e)}' ) continue logging.info('Saving changes to KeePass database.') kp.save() logging.info('Export completed.')
def bitwarden_to_keepass(args): try: kp = PyKeePass(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except FileNotFoundError: logging.info('KeePass database does not exist, creating a new one.') kp = create_database(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except CredentialsIntegrityError as e: logging.error(f'Wrong password for KeePass database: {e}') return folders = subprocess.check_output( f'{quote(args.bw_path)} list folders --session {quote(args.bw_session)}', shell=True, encoding='utf8') folders = json.loads(folders) groups = {} for folder in folders: groups[folder['id']] = kp.add_group(kp.root_group, folder['name']) logging.info(f'Folders done ({len(groups)}).') items = subprocess.check_output( f'{quote(args.bw_path)} list items --session {quote(args.bw_session)}', shell=True, encoding='utf8') items = json.loads(items) logging.info(f'Starting to process {len(items)} items.') for item in items: if item['type'] == ItemTypes.CARD: logging.warning(f'Skipping credit card item "{item["name"]}".') continue bw_item = Item(item) e = kp.add_entry(groups[bw_item.get_folder_id()], title=bw_item.get_name(), username=bw_item.get_username(), password=bw_item.get_password(), notes=bw_item.get_notes()) totp_secret, totp_settings = bw_item.get_totp() if totp_secret and totp_settings: e.set_custom_property('TOTP Seed', totp_secret) e.set_custom_property('TOTP Settings', totp_settings) for uri in bw_item.get_uris(): e.url = uri['uri'] break # todo append additional uris to notes? for field in bw_item.get_custom_fields(): e.set_custom_property(str(field['name']), field['value']) for attachment in bw_item.get_attachments(): attachment_tmp_path = f'./attachment_tmp/{attachment["fileName"]}' attachment_path = subprocess.check_output( f'{quote(args.bw_path)} get attachment' f' --raw {quote(attachment["id"])} ' f'--itemid {quote(bw_item.get_id())} ' f'--output {quote(attachment_tmp_path)} --session {quote(args.bw_session)}', shell=True, encoding='utf8').rstrip() attachment_id = kp.add_binary(open(attachment_path, 'rb').read()) e.add_attachment(attachment_id, attachment['fileName']) os.remove(attachment_path) logging.info('Saving changes to KeePass database.') kp.save() logging.info('Export completed.')