Skip to content

Instantly share code, notes, and snippets.

@kevinji
Created September 24, 2018 12:29
Show Gist options
  • Save kevinji/4bd6d133552a597bc24ee4aebed16cf2 to your computer and use it in GitHub Desktop.
Save kevinji/4bd6d133552a597bc24ee4aebed16cf2 to your computer and use it in GitHub Desktop.
Properly escape & from 1password passwords and URLs using the command-line tool, op.
import json
import subprocess
import sys
def read_json(*args):
output = subprocess.check_output(*args)
return json.loads(output)
def pp(obj):
"""
Debugging function used to pretty-print JSON.
"""
print(json.dumps(obj, indent=2))
def find_login_uuid():
templates = read_json(["op", "list", "templates"])
for template in templates:
if template["name"] == "Login":
return template["uuid"]
sys.exit("uuid for Login not found")
def update_password(login):
"""
Replace & with & in passwords.
Returns whether the password was updated.
"""
for field in login["details"]["fields"]:
# Use `.get()` since designation may not exist.
if field.get("designation") != "password":
continue
new_password = field["value"].replace("&", "&")
if field["value"] != new_password:
field["value"] = new_password
return True
return False
return False
def update_url(login):
"""
Replace & with & in URLs.
Returns whether the URL was updated.
"""
new_url = login["overview"]["url"].replace("&", "&")
if login["overview"]["url"] != new_url:
login["overview"]["url"] = new_url
return True
return False
def encode(login):
# The `.rstrip()` is quite important: it strips out the trailing newline
# from `subprocess.check_output()`. Without it, `op create item Login <data>`
# raises the error "illegal base64 data at input byte _".
return subprocess.check_output(["op", "encode"],
input=json.dumps(login["details"],
separators=(",", ":")),
text=True).rstrip()
def run(*args):
subprocess.run(*args, check=True, stdout=subprocess.DEVNULL)
def main():
login_uuid = find_login_uuid()
items = read_json(["op", "list", "items"])
uuids = (item["uuid"] for item in items)
for uuid in uuids:
login = read_json(["op", "get", "item", uuid])
if login["templateUuid"] != login_uuid or login["trashed"] != "N":
continue
password_updated = update_password(login)
url_updated = update_url(login)
what_to_update = []
what_to_update += ["password"] if password_updated else []
what_to_update += ["url"] if url_updated else []
if not what_to_update:
continue
what_to_update = ", ".join(what_to_update)
print("Updating {} for {}...".format(what_to_update,
login["overview"]["title"]))
encoded_details = encode(login)
run(["op", "create", "item", "Login",
"--title={}".format(login["overview"]["title"]),
"--url={}".format(login["overview"]["url"]),
encoded_details])
# Delete the old version to avoid leaving a duplicate entry.
print("Deleting old version of {}...".format(login["overview"]["title"]))
run(["op", "delete", "item", uuid])
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment