Coverage for pass_import/managers/bitwarden.py: 99%
83 statements
« prev ^ index » next coverage.py v7.4.3, created at 2024-02-26 12:11 +0000
« prev ^ index » next coverage.py v7.4.3, created at 2024-02-26 12:11 +0000
1# -*- encoding: utf-8 -*-
2# pass import - Passwords importer swiss army knife
3# Copyright (C) 2017-2024 Alexandre PUJOL <alexandre@pujol.io>.
4#
6import json
8from pass_import.core import register_managers
9from pass_import.formats.csv import CSV
10from pass_import.formats.json import JSON
13class BitwardenCSV(CSV):
14 """Importer for Bitwarden in CSV format."""
15 name = 'bitwarden'
16 url = 'https://bitwarden.com'
17 hexport = 'Tools> Export Vault> File Format: .csv'
18 himport = 'pass import bitwarden file.csv'
19 keys = {
20 'title': 'name',
21 'password': 'login_password',
22 'login': 'login_username',
23 'url': 'login_uri',
24 'comments': 'notes',
25 'group': 'folder',
26 'otpauth': 'login_totp',
27 }
30class BitwardenOrgCSV(BitwardenCSV):
31 """Importer for Bitwarden in CSV format."""
32 default = False
33 keys = {
34 'title': 'name',
35 'password': 'login_password',
36 'login': 'login_username',
37 'url': 'login_uri',
38 'comments': 'notes',
39 'group': 'collections',
40 'otpauth': 'login_totp',
41 }
44class BitwardenJSON(JSON):
45 """Importer for Bitwarden in JSON format."""
46 name = 'bitwarden'
47 default = False
48 url = 'https://bitwarden.com'
49 hexport = 'Tools> Export Vault> File Format: .json'
50 himport = 'pass import bitwarden file.json'
51 ignore = {
52 'login', 'id', 'folderId', 'collectionIds', 'organizationId', 'type',
53 'favorite', 'secureNote'
54 }
55 nesting_keys = {'card', 'identity'}
56 key_group = 'folders'
57 key_group_id = 'folderId'
58 keys = {
59 'title': 'name',
60 'password': 'password',
61 'login': 'username',
62 'url': 'uris',
63 'otpauth': 'totp',
64 'comments': 'notes',
65 }
66 json_header = {
67 'encrypted': False,
68 'folders': [{
69 'id': str,
70 'name': str
71 }],
72 'items': [{
73 'id': str,
74 'folderId': str,
75 'type': int,
76 'name': str,
77 'favorite': bool,
78 }],
79 }
81 def _sortgroup(self, folders):
82 for entry in self.data:
83 groupid = entry.get('group', '')
84 entry['group'] = folders.get(groupid, '')
86 def parse(self):
87 """Parse Bitwarden JSON file."""
88 jsons = json.loads(self.file.read())
89 keys = self.invkeys()
90 folders = {}
91 for item in jsons.get(self.key_group, {}):
92 key = item.get('id', '')
93 folders[key] = item.get('name', '')
95 for item in jsons.get('items', {}):
96 entry = {}
97 if 'folder' in self.key_group_id:
98 entry['group'] = item.get(self.key_group_id, '')
99 else:
100 entry['group'] = item.get(self.key_group_id, [''])[0]
101 logins = item.get('login', {})
102 item.update(logins)
103 for key, value in item.items():
104 if key in self.ignore:
105 continue
107 if key == 'fields':
108 self._parse_custom_fields(entry, value)
109 elif key in self.nesting_keys:
110 self._parse_nested(entry, value)
111 else:
112 if value:
113 entry[keys.get(key, key)] = value
115 urls = entry.get('url')
116 if urls and isinstance(urls, list) and urls[0]:
117 entry['url'] = urls[0]['uri']
119 if len(urls) > 1:
120 index = 2
121 for url in urls[1:]:
122 entry[f'url{index}'] = url['uri']
123 index += 1
125 self.data.append(entry)
126 self._sortgroup(folders)
128 @staticmethod
129 def _parse_nested(destination_entry, nesting_source):
130 for key, value in nesting_source.items():
131 if key in destination_entry.keys():
132 key = f'{key}_'
133 if value:
134 destination_entry[key] = value
136 @staticmethod
137 def _parse_custom_fields(destination_entry, custom_fields):
138 for field in custom_fields:
139 name = field['name']
140 value = field['value']
141 if name in destination_entry.keys():
142 name = f'{name}_'
143 if value:
144 destination_entry[name] = value
147class BitwardenOrgJSON(BitwardenJSON):
148 """Importer for Bitwarden Organisation in JSON format."""
149 key_group = 'collections'
150 key_group_id = 'collectionIds'
151 json_header = {
152 'encrypted': False,
153 'collections': list,
154 'items': [{
155 'id': str,
156 'type': int,
157 'name': str,
158 'favorite': bool,
159 'collectionIds': list,
160 }],
161 }
164register_managers(BitwardenCSV, BitwardenJSON,
165 BitwardenOrgCSV, BitwardenOrgJSON)