]> xn--ix-yja.es Git - alex.git/commitdiff
Add Bitwarden client
authoralex <alex@pdp7.net>
Tue, 17 Oct 2023 20:20:39 +0000 (22:20 +0200)
committeralex <alex@pdp7.net>
Tue, 17 Oct 2023 20:20:39 +0000 (22:20 +0200)
scripts/.gitignore [new file with mode: 0644]
scripts/p7s/__pycache__/__init__.cpython-39.pyc [deleted file]
scripts/p7s/appdirs.py [new file with mode: 0644]
scripts/p7s/bitwarden.py [new file with mode: 0644]
scripts/poetry.lock
scripts/pyproject.toml

diff --git a/scripts/.gitignore b/scripts/.gitignore
new file mode 100644 (file)
index 0000000..bee8a64
--- /dev/null
@@ -0,0 +1 @@
+__pycache__
diff --git a/scripts/p7s/__pycache__/__init__.cpython-39.pyc b/scripts/p7s/__pycache__/__init__.cpython-39.pyc
deleted file mode 100644 (file)
index 483c7b9..0000000
Binary files a/scripts/p7s/__pycache__/__init__.cpython-39.pyc and /dev/null differ
diff --git a/scripts/p7s/appdirs.py b/scripts/p7s/appdirs.py
new file mode 100644 (file)
index 0000000..9ff0ca8
--- /dev/null
@@ -0,0 +1,11 @@
+import appdirs
+import pathlib
+
+
+APPDIRS = appdirs.AppDirs("p7s", "alex@pdp7.net")
+
+
+def user_cache_dir():
+    r = pathlib.Path(APPDIRS.user_cache_dir)
+    r.mkdir(parents=True, exist_ok=True)
+    return r
diff --git a/scripts/p7s/bitwarden.py b/scripts/p7s/bitwarden.py
new file mode 100644 (file)
index 0000000..9ce4d44
--- /dev/null
@@ -0,0 +1,61 @@
+import contextlib
+import io
+import json
+import os
+import subprocess
+import zipfile
+
+import httpx
+
+from p7s import appdirs
+
+
+class Bitwarden():
+    def download(self, check_version=False):
+        self.bw_command = appdirs.user_cache_dir() / "bw"
+        if self.bw_command.exists() and not check_version:
+            return self.bw_command
+        r = httpx.get("https://vault.bitwarden.com/download/?app=cli&platform=linux")
+        location = r.headers["location"]
+        version = location.split("/")[7]
+        bw_versioned_command = appdirs.user_cache_dir() / f"bw-{version}"
+        if not bw_versioned_command.exists():
+            with zipfile.ZipFile(io.BytesIO(httpx.get(location, follow_redirects=True).content)) as zip:
+                with zip.open("bw") as zip_bw:
+                    bw_versioned_command.write_bytes(zip_bw.read())
+            bw_versioned_command.chmod(0o755)
+        self.bw_command.unlink(missing_ok=True)
+        self.bw_command.symlink_to(bw_versioned_command)
+
+    @contextlib.contextmanager
+    def login(self, server, email):
+        subprocess.run([self.bw_command, "config", "server", server], check=True)
+        status = self.status()["status"]
+        if status == "unauthenticated":
+            command = ["login", email]
+        elif status == "locked":
+            command = ["unlock"]
+        else:
+            assert False, f"unexpected status {status}"
+        command = subprocess.run([self.bw_command] + command, check=True, stdout=subprocess.PIPE, encoding="UTF8")
+        export_line = command.stdout.splitlines()[3]
+        session = export_line.split('"')[1]
+        os.environ["BW_SESSION"] = session
+        try:
+            yield self.bw_command
+            subprocess.run([self.bw_command, "lock"], check=True)
+        finally:
+            del os.environ["BW_SESSION"]
+
+    def status(self):
+        return json.loads(subprocess.run([self.bw_command, "status"], check=True, stdout=subprocess.PIPE).stdout)
+
+    def get_item(self, uuid):
+        return json.loads(subprocess.run([self.bw_command, "get", "item", uuid], check=True, stdout=subprocess.PIPE).stdout)
+
+
+def get_item(server, email, uuid):
+    b = Bitwarden()
+    b.download()
+    with b.login(server, email):
+        return b.get_item(uuid)
index 9c20220395002e0c388f4a39fb43c577b97a9051..fec983695a297d6a88c487ee23e8442180d478f1 100644 (file)
@@ -1,7 +1,149 @@
 # This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand.
-package = []
+
+[[package]]
+name = "anyio"
+version = "4.0.0"
+description = "High level compatibility layer for multiple asynchronous event loop implementations"
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "anyio-4.0.0-py3-none-any.whl", hash = "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f"},
+    {file = "anyio-4.0.0.tar.gz", hash = "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a"},
+]
+
+[package.dependencies]
+exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
+idna = ">=2.8"
+sniffio = ">=1.1"
+
+[package.extras]
+doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)"]
+test = ["anyio[trio]", "coverage[toml] (>=7)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
+trio = ["trio (>=0.22)"]
+
+[[package]]
+name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "main"
+optional = false
+python-versions = "*"
+files = [
+    {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+    {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
+
+[[package]]
+name = "certifi"
+version = "2023.7.22"
+description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"},
+    {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"},
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.1.3"
+description = "Backport of PEP 654 (exception groups)"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"},
+    {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"},
+]
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "h11"
+version = "0.14.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+    {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
+]
+
+[[package]]
+name = "httpcore"
+version = "0.18.0"
+description = "A minimal low-level HTTP client."
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "httpcore-0.18.0-py3-none-any.whl", hash = "sha256:adc5398ee0a476567bf87467063ee63584a8bce86078bf748e48754f60202ced"},
+    {file = "httpcore-0.18.0.tar.gz", hash = "sha256:13b5e5cd1dca1a6636a6aaea212b19f4f85cd88c366a2b82304181b769aab3c9"},
+]
+
+[package.dependencies]
+anyio = ">=3.0,<5.0"
+certifi = "*"
+h11 = ">=0.13,<0.15"
+sniffio = ">=1.0.0,<2.0.0"
+
+[package.extras]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (>=1.0.0,<2.0.0)"]
+
+[[package]]
+name = "httpx"
+version = "0.25.0"
+description = "The next generation HTTP client."
+category = "main"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "httpx-0.25.0-py3-none-any.whl", hash = "sha256:181ea7f8ba3a82578be86ef4171554dd45fec26a02556a744db029a0a27b7100"},
+    {file = "httpx-0.25.0.tar.gz", hash = "sha256:47ecda285389cb32bb2691cc6e069e3ab0205956f681c5b2ad2325719751d875"},
+]
+
+[package.dependencies]
+certifi = "*"
+httpcore = ">=0.18.0,<0.19.0"
+idna = "*"
+sniffio = "*"
+
+[package.extras]
+brotli = ["brotli", "brotlicffi"]
+cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (>=1.0.0,<2.0.0)"]
+
+[[package]]
+name = "idna"
+version = "3.4"
+description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+files = [
+    {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+    {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.0"
+description = "Sniff out which async library your code is running under"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+    {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
+]
 
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.9"
-content-hash = "c595a0588c25d58f3e3834ad7169126836d262b925fe6ca9b5d540dcf301d254"
+content-hash = "a6328570e23a2739e1268265645d92a5d4cfb77f85586e54e9cdf4a161fd690d"
index ad8aef7ae5fedc7543bb60d9d22e654726d5f363..8e80de4c294b5b1dfa3d7de3895c431bdab8ed44 100644 (file)
@@ -6,6 +6,8 @@ authors = ["alex <alex@pdp7.net>"]
 
 [tool.poetry.dependencies]
 python = "^3.9"
+appdirs = "^1.4.4"
+httpx = "^0.25.0"
 
 
 [build-system]