Beispiel #1
0
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'))
Beispiel #2
0
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'))
Beispiel #3
0
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.')
Beispiel #5
0
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.')