forked from rubenv/1pif-to-kdbx
-
Notifications
You must be signed in to change notification settings - Fork 0
/
convert.py
executable file
·157 lines (127 loc) · 4.69 KB
/
convert.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/usr/bin/env python3
import argparse
import datetime
import yaml
import onepif
import kpwriter
from os.path import splitext
parser = argparse.ArgumentParser(description="Convert 1Password 1PIF exports into a KeePass KDBX file.")
parser.add_argument("inpath", metavar="input.1pif", help="1Password export file/folder")
parser.add_argument("outfile", metavar="output.kdbx", nargs="?", help="Desired filename for KeePass file. If omitted, defaults to <input>.kdbx. Existing files WILL BE OVERWRITTEN!")
args = parser.parse_args()
# If no outfile given, use infile name
if not args.outfile:
fileparts = splitext(args.inpath)
args.outfile = "{}.kdbx".format(fileparts[0])
# If given outfile doesn't have .kdbx extension, add it
outparts = splitext(args.outfile)
if outparts[1] != ".kdbx":
args.outfile += ".kdbx"
# Open input file
print("Input file: {}".format(args.inpath))
opif = onepif.OnepifReader("{}/data.1pif".format(args.inpath))
# Open output file
print("Output file: {}".format(args.outfile))
kp = kpwriter.KpWriter(args.outfile, "test")
# Load record mappings from Yaml file
RECORD_MAP = yaml.load(open("mappings.yml", "rt"), Loader=yaml.SafeLoader)
uuid_map = {}
for item in opif:
# Fields that are not to be added as custom properties
fids_done = ["passwordHistory"]
# Determine group/folder
item_type_name = item.type_name
target_group_name = "{}s".format(item_type_name) # plural for group
if item.is_trash():
target_group_name = "Recycle Bin"
# Add entry to KeePass
entry = kp.add_entry(target_group_name, item.get_property("title").value)
fids_done.append("title")
# UUID - memorise for later linking (if supported by output format)?
uuid = item.get_property("uuid").value
uuid_map[uuid] = entry.uuid
fids_done.append("uuid")
# Icon
kp_icon = RECORD_MAP[item.type]["icon"]
kp.set_icon(kp_icon)
# URLs
location = item.get_property("location")
if location:
kp.add_url(location.value)
fids_done.append("location")
fids_done.append("locationKey")
urls = item.get_property("URLs")
if urls:
for u in urls.raw:
kp.add_url(u["url"])
fids_done.append("URLs")
url = item.get_property("URL")
if url:
kp.add_url(url.value)
fids_done.append("URL")
# Tags
kp.set_tags(item.get_tags())
fids_done.append("tags")
# TOTPs
totps = item.get_totps()
if totps:
for totp in totps:
kp.add_totp(totp[0], title=totp[1])
# Notes
notes_plain = item.get_property("notesPlain")
print("Notes: {}".format(repr(notes_plain)))
if notes_plain:
entry.notes = notes_plain.raw
fids_done.append("notesPlain")
# Dates
entry.ctime = datetime.datetime.fromtimestamp(item.get_property("createdAt").raw)
entry.mtime = datetime.datetime.fromtimestamp(item.get_property("updatedAt").raw)
fids_done.append("createdAt")
fids_done.append("updatedAt")
# Apply mappings from mappings.yml
for map_field in ["username", "password"]:
seek_fields = RECORD_MAP[item.type][map_field]
if not seek_fields:
continue
if type(seek_fields) is str:
seek_fields = [seek_fields]
for fid in seek_fields:
prop = item.get_property(fid)
if prop:
setattr(entry, map_field, prop.value)
fids_done.append(fid)
break
# Set remaining properties
for k in item.get_property_keys():
if k in ["Password"]:
# Forbidden name
continue
if k[:5] == "TOTP_":
# Skip OTPs as they're handled separately
continue
if k in RECORD_MAP["General"]["ignored"]:
# Skip ignored fields
continue
if k in fids_done:
# Skip fields processed elsewhere
continue
v = item.get_property(k)
if v.is_web_field:
kp.set_prop("KPH: {}".format(v.title), v.value, protected=v.is_protected)
else:
kp.set_prop(v.title, v.value, protected=v.is_protected)
# TODO: scope: Never = never suggest in browser (i.e. don't add KPH fields)
# AFTER ALL OTHER PROCESSING IS DONE: Password history
password_history = item.get_property("passwordHistory")
if password_history:
original_password = entry.password
original_mtime = entry.mtime
for p in password_history.raw:
d = datetime.datetime.fromtimestamp(p["time"])
entry.mtime = d
entry.password = p["value"]
entry.save_history()
# Restore original values
entry.password = original_password
entry.mtime = original_mtime
kp.save()