diff --git a/README.md b/README.md index c6a22f7..eb1f7a7 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,12 @@ $git clone https://github.com/Pitenager/passbolt-python-api.git $cd passbolt-python-api.git/ - $pip install passbolt-python-api + $pip install passbolt-python-api ## Dependencies - - Python3 - - GPG (also known as GnuPG) software +- Python3 +- GPG (also known as GnuPG) software ## Configuration @@ -23,8 +23,8 @@ Fill the config.ini file with the following contents. USER_PRIVATE_KEY_FILE = PASSPHRASE = - ## CLI usage + usage: passbolt_manager.py [-h] [-c CHANGE] [-u UPLOAD] [-d DELETE] [-r READ] Client to operate Stratio's Passbolt server @@ -40,11 +40,11 @@ Fill the config.ini file with the following contents. >>>import passboltapi >>>passbolt = passboltapi.PassboltAPI(config_path="config.ini") - + # Now you may do any get, post, put and delete request. >>>r = passbolt.get(url="/resources.json?api-version=v2") >>>r = passbolt.post(self.server_url + url, json=data) - + # One can also use it as context manager >>>with passboltapi.PassboltAPI(config_path="config.ini") as passbolt: @@ -57,19 +57,18 @@ To import new keys: >>>import passboltapi >>>passbolt = passboltapi.PassboltAPI(config_path="config.ini", new_keys=True) - + To delete old keys and import only the new ones. >>>import passboltapi >>>passbolt = passboltapi.PassboltAPI(config_path="config.ini", new_keys=True, delete_old_keys=True) -Recommended to do: Do not keep private and public files. +Recommended to do: Do not keep private and public files. Rather just import them using gpg command one time and delete those files. $gpg --import public.asc $gpg --batch --import private.asc - ### Passbolt API For more API related questions, visit Passbolt API documentation: diff --git a/passbolt_manager.py b/passbolt_manager.py index 6e0f695..e0afa8d 100644 --- a/passbolt_manager.py +++ b/passbolt_manager.py @@ -1,10 +1,12 @@ -import passboltapi -from colorama import Fore, init -import sys -import time import argparse -import string import random +import string +import sys +import time + +from colorama import Fore, init + +import passboltapi def banner(): @@ -27,32 +29,37 @@ def banner(): def login(): try: - print(Fore.CYAN + "[*] Trying to authenticate against Passbolt server...") + print(Fore.CYAN + + "[*] Trying to authenticate against Passbolt server...") - passbolt = passboltapi.PassboltAPI(config_path="config.ini", new_keys=True, delete_old_keys=True) - uuid = passbolt.get(url="/resources.json?api-version=v2")["header"]["id"] + passbolt = passboltapi.PassboltAPI(config_path="config.ini", + new_keys=True, + delete_old_keys=True) + uuid = passbolt.get( + url="/resources.json?api-version=v2")["header"]["id"] print(Fore.GREEN + "[-] Authenticated") - return passbolt,uuid + return passbolt, uuid except Exception as e: print(Fore.RED + "[!] Error: " + str(e)) sys.exit(1) + def generate_password(): try: print(Fore.CYAN + "[*] Generating random password...") - #Create alphanumerical from string constants - printable = f'{string.ascii_letters}{string.digits}{string.punctuation}' + # Create alphanumerical from string constants + printable = f"{string.ascii_letters}{string.digits}{string.punctuation}" - #Convert printable from string to list and shuffle + # Convert printable from string to list and shuffle printable = list(printable) random.shuffle(printable) - #Generate random password and convert to string + # Generate random password and convert to string random_password = random.choices(printable, k=16) - random_password = ''.join(random_password) + random_password = "".join(random_password) return random_password @@ -60,43 +67,47 @@ def generate_password(): print(Fore.RED + "[!] Error: " + str(e)) sys.exit(1) -def read(passbolt,name): + +def read(passbolt, name): try: print(Fore.CYAN + f"[*] Reading resource {name}...") for i in passbolt.get(url="/resources.json?api-version=v2")["body"]: if i["name"] == name: - resource = passbolt.get("/secrets/resource/{}.json?api-version=v2".format(i["id"])) + resource = passbolt.get( + "/secrets/resource/{}.json?api-version=v2".format(i["id"])) i["password"] = passbolt.decrypt(resource["body"]["data"]) print(i) break - + except Exception as e: print(Fore.RED + "[!] Error: " + str(e)) sys.exit(1) -def upload(passbolt,uuid,name): + +def upload(passbolt, uuid, name): try: pwd = generate_password() encrypted_pass = passbolt.encrypt(pwd) - json_data = { + json_data = { "name": name, "description": f"(Automated) {name} password", "secrets": [{ "user_id": uuid, "data": encrypted_pass - }] + }], } print(Fore.CYAN + "[*] Uploading new password...") - passbolt.post(url="/resources.json?api-version=v2",data=json_data) + passbolt.post(url="/resources.json?api-version=v2", data=json_data) print(Fore.GREEN + "[-] Password uploaded") except Exception as e: print(Fore.RED + "[!] Error: " + str(e)) sys.exit(1) + def change(passbolt, name): try: pwd = generate_password() @@ -106,16 +117,20 @@ def change(passbolt, name): for i in passbolt.get(url="/resources.json?api-version=v2")["body"]: if i["name"] == name: - json_data = { - "name": name, - "description": f"(Automated) {name} password", + json_data = { + "name": + name, + "description": + f"(Automated) {name} password", "secrets": [{ "user_id": i["created_by"], "data": encrypted_pass - }] + }], } resourceId = i["id"] - passbolt.put(url=f"/resources/{resourceId}.json?api-version=v2",data=json_data) + passbolt.put( + url=f"/resources/{resourceId}.json?api-version=v2", + data=json_data) print(Fore.GREEN + f"[-] Password changed") @@ -123,33 +138,35 @@ def change(passbolt, name): print(Fore.RED + "[!] Error: " + str(e)) sys.exit(1) -def delete(passbolt,name): + +def delete(passbolt, name): try: print(Fore.CYAN + f"[*] Deleting resource {name}...") for i in passbolt.get(url="/resources.json?api-version=v2")["body"]: if i["name"] == name: resourceId = i["id"] - break + break passbolt.delete(url=f"/resources/{resourceId}.json?api-version=v2") print(Fore.GREEN + f"[-] Resource {name} deleted") except Exception as e: print(Fore.RED + "[!] Error: " + str(e)) sys.exit(1) + def main(args): try: - if (len(sys.argv) <= 1): - print(Fore.RED+"[!] Error: Should specify at least one argument") + if len(sys.argv) <= 1: + print(Fore.RED + "[!] Error: Should specify at least one argument") sys.exit(1) else: - passbolt,uuid = login() - if (args.change): + passbolt, uuid = login() + if args.change: change(passbolt, args.change) - elif (args.upload): + elif args.upload: upload(passbolt, uuid, args.upload) - elif (args.delete): + elif args.delete: delete(passbolt, args.delete) - elif (args.read): + elif args.read: read(passbolt, args.read) else: print(Fore.RED + "[!] Error: Invalid argument") @@ -164,16 +181,46 @@ def main(args): print(Fore.RED + "[!] Error: " + str(e)) sys.exit(1) -if __name__== "__main__": + +if __name__ == "__main__": init(autoreset=True) banner() - #Parameters - parser = argparse.ArgumentParser(description="Client to operate Stratio's Passbolt server") - parser.add_argument('-c', '--change', metavar="CHANGE", dest='change', default=False, help="Change an existing password in Passbolt") - parser.add_argument('-u', '--upload', metavar="UPLOAD", dest='upload', default=False, help="Upload new password to Passbolt") - parser.add_argument('-d', '--delete', metavar="DELETE", dest='delete', default=False, help="Delete an existing password in Passbolt") - parser.add_argument('-r', '--read', metavar="READ", dest='read', default=False, help="Read an existing password in Passbolt") + # Parameters + parser = argparse.ArgumentParser( + description="Client to operate Stratio's Passbolt server") + parser.add_argument( + "-c", + "--change", + metavar="CHANGE", + dest="change", + default=False, + help="Change an existing password in Passbolt", + ) + parser.add_argument( + "-u", + "--upload", + metavar="UPLOAD", + dest="upload", + default=False, + help="Upload new password to Passbolt", + ) + parser.add_argument( + "-d", + "--delete", + metavar="DELETE", + dest="delete", + default=False, + help="Delete an existing password in Passbolt", + ) + parser.add_argument( + "-r", + "--read", + metavar="READ", + dest="read", + default=False, + help="Read an existing password in Passbolt", + ) args = parser.parse_args() - main(args) \ No newline at end of file + main(args) diff --git a/passboltapi/__init__.py b/passboltapi/__init__.py index ee24990..171fc74 100644 --- a/passboltapi/__init__.py +++ b/passboltapi/__init__.py @@ -1,14 +1,14 @@ -import requests import configparser -import gnupg import urllib.parse +import gnupg +import requests + LOGIN_URL = "/auth/login.json" VERIFY_URL = "/auth/verify.json" class PassboltAPI: - def __init__(self, config_path, new_keys=False, delete_old_keys=False): """ :param config_path: Path to the config file. @@ -30,13 +30,19 @@ def __init__(self, config_path, new_keys=False, delete_old_keys=False): self._import_gpg_keys() try: self.gpg_fingerprint = [ - i for i in self.gpg.list_keys() if i["fingerprint"] == self.config["PASSBOLT"]["USER_FINGERPRINT"] + i for i in self.gpg.list_keys() if i["fingerprint"] == + self.config["PASSBOLT"]["USER_FINGERPRINT"] ][0]["fingerprint"] except IndexError: - raise Exception("GPG public key could not be found. Check: gpg --list-keys") - - if self.config["PASSBOLT"]["USER_FINGERPRINT"] not in [i["fingerprint"] for i in self.gpg.list_keys(True)]: - raise Exception("GPG private key could not be found. Check: gpg --list-secret-keys") + raise Exception( + "GPG public key could not be found. Check: gpg --list-keys") + + if self.config["PASSBOLT"]["USER_FINGERPRINT"] not in [ + i["fingerprint"] for i in self.gpg.list_keys(True) + ]: + raise Exception( + "GPG private key could not be found. Check: gpg --list-secret-keys" + ) self._login() def __enter__(self): @@ -55,42 +61,57 @@ def _delete_old_keys(self): def _import_gpg_keys(self): if not self.config["PASSBOLT"]["USER_PUBLIC_KEY_FILE"]: - raise ValueError("Missing value for USER_PUBLIC_KEY_FILE in config.ini") + raise ValueError( + "Missing value for USER_PUBLIC_KEY_FILE in config.ini") if not self.config["PASSBOLT"]["USER_PRIVATE_KEY_FILE"]: - raise ValueError("Missing value for USER_PRIVATE_KEY_FILE in config.ini") - self.gpg.import_keys(open(self.config["PASSBOLT"]["USER_PUBLIC_KEY_FILE"], "r").read()) - self.gpg.import_keys(open(self.config["PASSBOLT"]["USER_PRIVATE_KEY_FILE"], "r").read()) + raise ValueError( + "Missing value for USER_PRIVATE_KEY_FILE in config.ini") + self.gpg.import_keys( + open(self.config["PASSBOLT"]["USER_PUBLIC_KEY_FILE"], "r").read()) + self.gpg.import_keys( + open(self.config["PASSBOLT"]["USER_PRIVATE_KEY_FILE"], "r").read()) def _login(self): - r = self.requests_session.post(self.server_url + LOGIN_URL, json={ - "gpg_auth": {"keyid": self.gpg_fingerprint}}) + r = self.requests_session.post( + self.server_url + LOGIN_URL, + json={"gpg_auth": { + "keyid": self.gpg_fingerprint + }}, + ) encrypted_token = r.headers["X-GPGAuth-User-Auth-Token"] encrypted_token = urllib.parse.unquote(encrypted_token) encrypted_token = encrypted_token.replace("\+", " ") token = self.decrypt(encrypted_token) - self.requests_session.post(self.server_url + LOGIN_URL, json={ - "gpg_auth": { - "keyid": self.gpg_fingerprint, - "user_token_result": token + self.requests_session.post( + self.server_url + LOGIN_URL, + json={ + "gpg_auth": { + "keyid": self.gpg_fingerprint, + "user_token_result": token + }, }, - }) + ) def encrypt(self, text): - return str(self.gpg.encrypt( - data=text, - recipients=self.gpg_fingerprint, - always_trust=True - )) + return str( + self.gpg.encrypt(data=text, + recipients=self.gpg_fingerprint, + always_trust=True)) def decrypt(self, text): - return str(self.gpg.decrypt( - text, - always_trust=True, - passphrase=str(self.config["PASSBOLT"]["PASSPHRASE"]) - )) + return str( + self.gpg.decrypt( + text, + always_trust=True, + passphrase=str(self.config["PASSBOLT"]["PASSPHRASE"]), + )) def get_headers(self): - return {"X-CSRF-Token": self.requests_session.cookies['csrfToken'] if 'csrfToken' in self.requests_session.cookies else ''} + return { + "X-CSRF-Token": + self.requests_session.cookies["csrfToken"] + if "csrfToken" in self.requests_session.cookies else "" + } def get_server_public_key(self): r = self.requests_session.get(self.server_url + VERIFY_URL) @@ -101,18 +122,21 @@ def get(self, url): return r.json() def post(self, url, data): - r = self.requests_session.post(self.server_url + url, json=data, headers=self.get_headers()) + r = self.requests_session.post(self.server_url + url, + json=data, + headers=self.get_headers()) return r.json() def put(self, url, data): - r = self.requests_session.put(self.server_url + url, json=data, headers=self.get_headers()) + r = self.requests_session.put(self.server_url + url, + json=data, + headers=self.get_headers()) return r.json() def delete(self, url): - r = self.requests_session.delete(self.server_url + url, headers=self.get_headers()) + r = self.requests_session.delete(self.server_url + url, + headers=self.get_headers()) return r.json() def close_session(self): self.requests_session.close() - - diff --git a/setup.py b/setup.py index fc18806..aca3c6a 100644 --- a/setup.py +++ b/setup.py @@ -18,19 +18,18 @@ # rm -rf ./dist && python3 setup.py sdist bdist_wheel && python3 -m twine upload dist/* # -from setuptools import setup, find_packages +from setuptools import find_packages, setup # Usage: python setup.py sdist bdist_wheel links = [] # for repo urls (dependency_links) -with open('requirements.txt') as fp: +with open("requirements.txt") as fp: install_requires = fp.read() DESCRIPTION = "A python client for Passbolt." VERSION = "0.1.2" - with open("README.md", "r") as fh: LONG_DESCRIPTION = fh.read() @@ -41,10 +40,10 @@ author_email="shubham.dipt@gmail.com", description=DESCRIPTION, long_description=LONG_DESCRIPTION, - long_description_content_type='text/markdown', + long_description_content_type="text/markdown", url="https://github.com/shubhamdipt/passbolt-python-api", - license=open('LICENSE').read(), - packages=['passboltapi'], + license=open("LICENSE").read(), + packages=["passboltapi"], platforms=["any"], classifiers=( "Programming Language :: Python :: 3", @@ -53,4 +52,4 @@ ), install_requires=install_requires, dependency_links=links, -) \ No newline at end of file +) diff --git a/test.py b/test.py index 8bed6c6..f9c9efb 100644 --- a/test.py +++ b/test.py @@ -8,7 +8,7 @@ def get_my_passwords(passbolt_obj): "id": i["id"], "name": i["name"], "username": i["username"], - "uri": i["uri"] + "uri": i["uri"], }) print(i) for i in result: @@ -30,5 +30,5 @@ def main(): get_my_passwords(passbolt_obj=passbolt) -if __name__ == '__main__': - main() \ No newline at end of file +if __name__ == "__main__": + main()