From 4634b74e4269bda599b206d6a3df8bc9d595afc0 Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Tue, 8 Aug 2023 13:52:01 +0200 Subject: [PATCH 01/24] doc and changelog --- CHANGES.md | 3 +++ USAGE.md | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 652eb69..3f5e4e0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ ## CHANGES +### 1.0.56 +- Customizable auth payload support (2Factor, api auth) [Markus Kötter ]} + ### 1.0.55 - ensure requests is in requirements [kiorky] diff --git a/USAGE.md b/USAGE.md index e24f935..b7cf392 100644 --- a/USAGE.md +++ b/USAGE.md @@ -151,6 +151,44 @@ c.remove_user_from_collection(userOrEmail, colc) c.remove_user_from_organization(userOrEmail, orga) ``` +### Manipulating the login data structure via callback (2Factor) +This allows other login mechanisms such as totp or api key: + +example 1: + +```python + +def mfa2fa(loginpayload): + totp = pyotp.TOTP(otpseed) + loginpayload.update( + { + "twoFactorToken": str(totp.now()), + "twoFactorProvider": "0", + "twoFactorRemember": "0" + } + ) + return loginpayload + +client = Client(server, email, password, authentication_cb=mfa2fa) +``` + +example 1: + +```python +def api_key(loginpayload): + loginpayload.update( + { + "client_id": CLIENT_ID, + "client_secret": CLIENT_SECRET, + "scope": "api", + "grant_type": "client_credentials" + } + ) + return loginpayload + +client = Client(server, email, password, authentication_cb=api_key) +``` + ### encode the vaultwarden/bitwarden_rs key for autovalidating user ```sh base64 $BITWARDEN_RS_SERVER_DATA/rsa_key.der|tr -d '\n' From d8e6dcc799d13184416fad06b06e4edb49cae855 Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Tue, 8 Aug 2023 13:52:15 +0200 Subject: [PATCH 02/24] Release: 1.0.56 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cee87eb..4c600bd 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def read(*rnames): long_description = "\n\n".join([read(a) for a in READMES]) classifiers = ["Programming Language :: Python", "Topic :: Software Development"] name = "bitwardentools" -version = "1.0.55" +version = "1.0.56" src_dir = "src" req = re.compile("^(?!(-e|#))", flags=re.I | re.M) install_requires = [ From f476364794f3b8f4b2a07086da16eb688767607d Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Tue, 15 Apr 2025 22:47:12 +0200 Subject: [PATCH 03/24] Fix CI/CD --- Dockerfile | 7 ++++--- README.md | 8 ++++---- apt.txt | 19 +++++++++---------- docker-compose-build.yml | 2 +- docker-compose-test.yml | 4 ++-- requirements/requirements.txt | 2 +- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index 122de6b..c455c50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ # syntax=docker/dockerfile:1.3 -FROM corpusops/ubuntu-bare:20.04 +FROM corpusops/ubuntu-bare:22.04 WORKDIR /tmp/install ARG DEV_DEPENDENCIES_PATTERN='^#\s*dev dependencies' \ PY_VER=3.8 \ USER_NAME=app USER_UID=1000 USER_GROUP= USER_GID= \ USER_HOME=/w ARG PIP_SRC=$USER_HOME/lib -ENV USER_NAME=$USER_NAME USER_GROUP=${USER_GROUP:-$USER_NAME} USER_UID=$USER_UID USER_GID=${USER_GID:-${USER_UID}} USER_HOME=$USER_HOME PY_VER=${PY_VER:-} PIP_SRC=$PIP_SRC +ENV USER_NAME=$USER_NAME USER_GROUP=${USER_GROUP:-$USER_NAME} USER_UID=$USER_UID USER_GID=${USER_GID:-${USER_UID}} USER_HOME=$USER_HOME PY_VER=${PY_VER} PIP_SRC=$PIP_SRC # system dependendencies (pkgs, users, etc) ADD apt*.txt ./ @@ -16,6 +16,7 @@ RUN set -e \ useradd -s /bin/bash -d $USER_HOME -m -u $USER_UID -g $USER_UID $USER_NAME;fi \ && sed -i -re "s/(python-?)[0-9]\.[0-9]+/\1$PY_VER/g" apt.txt \ && apt update && apt install -y $(egrep -v "^#" apt.txt) \ + && git config --global --add safe.directory '*' \ && mkdir -pv "$PIP_SRC" && chown $USER_NAME "$PIP_SRC" \ && printf "$USER_NAME ALL=(ALL) NOPASSWD:ALL\n">/etc/sudoers.d/app \ && : end @@ -24,7 +25,7 @@ RUN set -e \ WORKDIR $USER_HOME # See https://github.com/pypa/setuptools/issues/3301 # ARG PIP_REQ=>=22 SETUPTOOLS_REQ=<60 \ -ARG PIP_REQ=>=22 SETUPTOOLS_REQ>=60 \ +ARG PIP_REQ=>=22 SETUPTOOLS_REQ=>=60 \ REQUIREMENTS=requirements/requirements.txt requirements/requirements-dev.txt ENV REQUIREMENTS=$REQUIREMENTS PIP_REQ=$PIP_REQ SETUPTOOLS_REQ=$SETUPTOOLS_REQ ADD --chown=app:app lib/ lib/ diff --git a/README.md b/README.md index 61d447b..765e88f 100644 --- a/README.md +++ b/README.md @@ -41,16 +41,16 @@ docker-compose run --rm app bash ``` ```bash -sed "/COMPOSE_FILE/d" .env -echo COMPOSE_FILE=docker-compose.yml:docker-compose-dev.yml" +sed -i -e "/COMPOSE_FILE/d" .env +echo COMPOSE_FILE=docker-compose.yml:docker-compose-dev.yml" >> .env docker-compose up -d --force-recreate docker-compose exec -U app bash ``` ### run tests ```bash -sed "/COMPOSE_FILE/d" .env -echo COMPOSE_FILE=docker-compose.yml:docker-compose-dev.yml:docker-compose-test.yml" +sed -i -e "/COMPOSE_FILE/d" .env +echo COMPOSE_FILE=docker-compose.yml:docker-compose-dev.yml:docker-compose-test.yml" >> .env docker-compose exec -U app app tox -e linting,coverage ``` diff --git a/apt.txt b/apt.txt index 341ae7f..fc780e5 100644 --- a/apt.txt +++ b/apt.txt @@ -3,25 +3,25 @@ openssh-client rsync # runtime dependencies python3 -python3.8 -python3.8-distutils -python3.8-lib2to3 -libpython3.8 +python3.10 +python3.10-distutils +python3.10-lib2to3 +libpython3.10 binutils ca-certificates curl gettext git less -libllvm10 +libllvm11 libsoup2.4-1 -llvm-10 +llvm-11 lsb-release sudo tzdata vim wget -python3.8-venv +python3.10-venv python3-virtualenv python3-distlib python3-pkg-resources @@ -32,7 +32,6 @@ build-essential gpg libgcc-9-dev libstdc++-9-dev -llvm-10-dev -python3.8-dev -python3-dev +llvm-11-dev +python3.10-dev software-properties-common diff --git a/docker-compose-build.yml b/docker-compose-build.yml index f7a210e..0b30556 100644 --- a/docker-compose-build.yml +++ b/docker-compose-build.yml @@ -4,5 +4,5 @@ services: build: context: "." args: - PY_VER: 3.8 + PY_VER: "3.10" REQUIREMENTS: requirements/requirements.txt requirements/requirements-dev.txt diff --git a/docker-compose-test.yml b/docker-compose-test.yml index bb89a99..ea21bab 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -8,7 +8,7 @@ x-bases: BITWARDEN_DB_IMAGE: corpusops/postgres:13 BITWARDEN_DOMAIN: bitwarden DATABASE_URL: postgresql://db:db@db/db - BITWARDEN_IMAGE: vaultwarden/server:1.21.0 + BITWARDEN_IMAGE: vaultwarden/server:1.29.1 DATA_FOLDER: /data DISABLE_ADMIN_TOKEN: "true" DOMAIN: http://bitwarden @@ -98,7 +98,7 @@ services: - mails:/mails:rw bitwarden: <<: [ *base ] - image: $BITWARDEN_IMAGE + image: ${BITWARDEN_IMAGE:-vaultwarden/server:1.29.1} hostname: bitwarden depends_on: [db, setup, mailcatcher] entrypoint: diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a334300..be95814 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,5 +1,5 @@ -e . --e git+https://github.com/corpusops/vaultier-cli.git#egg=vaultcli +-e git+https://github.com/corpusops/vaultier-cli.git##egg=vaultcli click hkdf From 1eb4f0ef16e7f8af1cefd6fd0ce0ad0c3df88335 Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Wed, 16 Apr 2025 22:47:12 +0200 Subject: [PATCH 04/24] Add traefik front in test --- .gitignore | 1 + docker-compose-test.yml | 28 ++++++++++++++++++++++------ gencert.sh | 11 +++++++++++ testreset.sh | 2 +- traefik.r.toml | 10 ++++++++++ traefik.toml | 10 ++++++++++ 6 files changed, 55 insertions(+), 7 deletions(-) create mode 100755 gencert.sh create mode 100644 traefik.r.toml create mode 100644 traefik.toml diff --git a/.gitignore b/.gitignore index f7e5db7..15ae211 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ .bash_history src/.coverage /dist +/.tox diff --git a/docker-compose-test.yml b/docker-compose-test.yml index ea21bab..b59f159 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -44,7 +44,7 @@ x-bases: SMTP_SSL: "false" SMTP_TIMEOUT: '15' WEBSOCKET_ENABLED: "true" -networks: {app_net: {driver: bridge, ipam: {config: [{subnet: "${BITWARDEN_NETWORK:-172.38.0}.0/24"}], driver: default}}} +networks: {bitwarden_net: {driver: bridge, ipam: {config: [{subnet: "${BITWARDEN_NETWORK:-172.38.0}.0/24"}], driver: default}}} services: setup: <<: [ *base ] @@ -72,13 +72,13 @@ services: rf while true;do printf "HTTP/1.1 200 OK\nContent-Length: 7\n\nstarted\n"|( nc -l -p 80 || /bin/true);done image: corpusops/postgres:13 - networks: {app_net: {ipv4_address: "${BITWARDEN_NETWORK:-172.38.0}.6"}} + networks: {bitwarden_net: {ipv4_address: "${BITWARDEN_NETWORK:-172.38.0}.6"}} volumes: - helpers:/helpers:rw db: <<: [ *base ] image: corpusops/postgres:13 - networks: {app_net: {ipv4_address: "${BITWARDEN_NETWORK:-172.38.0}.4"}} + networks: {bitwarden_net: {ipv4_address: "${BITWARDEN_NETWORK:-172.38.0}.4"}} security_opt: [seccomp=unconfined] volumes: - db:/var/lib/postgresql/data:rw @@ -92,7 +92,7 @@ services: -p -c MailHog' hostname: mailcatcher image: corpusops/mailhog - networks: {app_net: {ipv4_address: "${BITWARDEN_NETWORK:-172.38.0}.12"}} + networks: {bitwarden_net: {ipv4_address: "${BITWARDEN_NETWORK:-172.38.0}.12"}} user: root volumes: - mails:/mails:rw @@ -106,7 +106,7 @@ services: - -exc - 'while !(curl -s setup);do echo "not ready">&2;sleep 0.5;done && exec /start.sh "$$@"' - networks: {app_net: {ipv4_address: "${BITWARDEN_NETWORK:-172.38.0}.14"}} + networks: {bitwarden_net: {ipv4_address: "${BITWARDEN_NETWORK:-172.38.0}.14"}} volumes: - bitwarden:/data:rw - ./bitwarden:/conf:rw @@ -114,7 +114,7 @@ services: - logs:/logs:rw app: <<: [ *base ] - networks: {app_net: {}} + networks: {bitwarden_net: {}} depends_on: [bitwarden] entrypoint: - bash @@ -136,6 +136,22 @@ services: volumes: - "bitwarden:/bitwarden" - helpers:/helpers:rw + traefik: + image: "traefik:v2.10" + networks: {bitwarden_net: {}} + entrypoint: + - "sh" + - "-xec" + - |- + /entrypoint.sh --configFile=/app/traefik.toml + ports: + - "${BITWARDEN_PORT:-3010}:80" + - "${BITWARDEN_PORT:-3013}:8080" + - "${BITWARDEN_PORT:-3011}:443" + volumes: + - "./:/app:ro" + - "./traefik.toml:/traefik.toml" + - "./traefik.r.toml:/traefik.r.toml" volumes: bitwarden: {} mails: {} diff --git a/gencert.sh b/gencert.sh new file mode 100755 index 0000000..392f0a8 --- /dev/null +++ b/gencert.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh +set -ex +cd $(dirname $(readlink -f $0)) +apk update && apk add openssl +openssl req -nodes -x509 -sha256 -newkey rsa:4096 \ + -keyout local/cert.key \ + -out local/cert.crt \ + -days 356 \ + -subj "/C=NL/ST=Zuid Holland/L=Rotterdam/O=ACME Corp/OU=IT Dept/CN=example.org" \ + -addext "subjectAltName = DNS:localhost,DNS:wkbox2,DNS:localhost:3012,DNS:wkbox2:3012,DNS:vaultwarden:3011,DNS:vaultwarden:3012" +# vim:set et sts=4 ts=4 tw=80: diff --git a/testreset.sh b/testreset.sh index 5ce5011..3179564 100755 --- a/testreset.sh +++ b/testreset.sh @@ -5,5 +5,5 @@ docker-compose exec -T db bash -c \ docker-compose stop -t0 docker-compose rm -f docker-compose run --rm --entrypoint bash app -exc 'rm -rf /bitwarden/*' -docker-compose up -d --force-recreate --no-deps db setup bitwarden +docker-compose up -d --force-recreate --no-deps db setup bitwarden traefik docker-compose up -d --force-recreate --no-deps app diff --git a/traefik.r.toml b/traefik.r.toml new file mode 100644 index 0000000..791d04c --- /dev/null +++ b/traefik.r.toml @@ -0,0 +1,10 @@ +[http.routers.r] +entryPoints = ["web", "websecure"] +service = "s@file" +rule = "PathPrefix(`/`)" +tls=true + +[http.services.s.loadBalancer] +passHostHeader = true +[[http.services.s.loadBalancer.servers]] +url = "http://bitwarden/" diff --git a/traefik.toml b/traefik.toml new file mode 100644 index 0000000..1cc625f --- /dev/null +++ b/traefik.toml @@ -0,0 +1,10 @@ +[global] +checkNewVersion=false +sendAnonymousUsage=false + +[entryPoints] +web.address=":80" +websecure.address=":443" + +[providers.file] +filename="/app/traefik.r.toml" From be84e3c0d2fda595ca00cfbb38a6d099ece9d16b Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Thu, 17 Apr 2025 22:47:12 +0200 Subject: [PATCH 05/24] Fix new vaultwarden create_orga --- src/bitwardentools/client.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/bitwardentools/client.py b/src/bitwardentools/client.py index aa03e87..114f5a9 100644 --- a/src/bitwardentools/client.py +++ b/src/bitwardentools/client.py @@ -18,6 +18,7 @@ from copy import deepcopy from subprocess import run from time import time +from packaging import version as _version import requests from jwt import encode as jwt_encode @@ -96,6 +97,11 @@ "managePolicies": False, "manageUsers": False, } +IS_BITWARDEN_RE = re.compile('[0-9][0-9][0-9][0-9][.][0-9][0-9]?[.][0-9][0-9]?', flags=re.I|re.U|re.M) +API_CHANGES = { + '1.27.0': _version.parse('1.27.0'), +} + def uncapitzalize(s): @@ -719,6 +725,8 @@ def __init__( self.authentication_cb = authentication_cb if login: self.login() + self._is_vaultwarden = False + self._version = None @property def token(self): @@ -1435,6 +1443,20 @@ def edit_orgcollection(self, collection, token=None, **jsond): self.cache(obj) return obj + def version(self, force=False): + # bitwarden scheme is yyyy.mm.xx + # vaultwarden scheme is SEMVER + if force or not self._version: + try: + v = self.r('/api/version', method='get').json() + except Exception: + trace = traceback.format_exc() + L.error(trace) + v = '1.0.0' + self._is_vaultwarden = not bool(IS_BITWARDEN_RE.search(v)) + self._version = _version.parse(v) + return self._version, self._is_vaultwarden + def create_orgcollection( self, name, organizationId=None, orga=None, externalId=None, token=None, **jsond ): @@ -1442,7 +1464,11 @@ def create_orgcollection( token = self.get_token(token) _, k = self.get_organization_key(orga, token=token) encoded_name = bwcrypto.encrypt(bwcrypto.CIPHERS.sym, name, k) - data = {"externalId": [], "groups": [], "name": encoded_name} + v, i = self.version() + if i and (v > API_CHANGES['1.27.0']): + data = {"externalId": "", "groups": [], "users": [], "name": encoded_name} + else: + data = {"externalId": "", "groups": [], "name": encoded_name} log = "Creating :" if orga: log += f" in orga: {orga.name}/{orga.id}:" From 3fe4745ad23b0a7b6c52ca1098a8d19958aa9df5 Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Fri, 18 Apr 2025 22:47:12 +0200 Subject: [PATCH 06/24] Fix newer vaultwarden adduser --- src/bitwardentools/client.py | 3 +++ src/tests/test_client.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/bitwardentools/client.py b/src/bitwardentools/client.py index 114f5a9..5a2fc99 100644 --- a/src/bitwardentools/client.py +++ b/src/bitwardentools/client.py @@ -2703,6 +2703,9 @@ def add_user_to_organization( dcollections, readonly=readonly, hidepasswords=hidepasswords )["payloads"] u = f"/api/organizations/{orga.id}/users/invite" + v, i = self.version() + if i and (v > API_CHANGES['1.27.0']): + params.setdefault('groups', []) response = self.r(u, json=params, token=token) self.assert_bw_response(response) return response diff --git a/src/tests/test_client.py b/src/tests/test_client.py index 379765c..156e8b4 100755 --- a/src/tests/test_client.py +++ b/src/tests/test_client.py @@ -616,7 +616,10 @@ def test_set_organization_access(self): self.assertFalse(a1a["daccess"][self.user1[0].email]["hidePasswords"]) self.assertTrue(a1b["daccess"][self.user1[0].email]["hidePasswords"]) self.assertEqual( - strip_dict_data(ao1["daccess"][self.user1[0].email]), + strip_dict_data( + ao1["daccess"][self.user1[0].email], + skip=f'{DSKIP}|(g|G)roups|TwoFactorEnabled|ResetPasswordEnrolled|ExternalId|collections' + ), { "accessAll": False, "email": "foo@org.comsimple1", From e2f52b31081d416777c2fc1fb6fa72baa6a009e8 Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Sat, 19 Apr 2025 22:47:12 +0200 Subject: [PATCH 07/24] Fix newer vaultwarden patch --- src/bitwardentools/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bitwardentools/client.py b/src/bitwardentools/client.py index 5a2fc99..deb4abe 100644 --- a/src/bitwardentools/client.py +++ b/src/bitwardentools/client.py @@ -1433,6 +1433,9 @@ def edit_orgcollection(self, collection, token=None, **jsond): if not bwcrypto.SYM_ENCRYPTED_STRING_RE.match(data["name"]): _, k = self.get_organization_key(obj._orga, token=token) data["name"] = bwcrypto.encrypt(bwcrypto.CIPHERS.sym, data["name"], k) + v, i = self.version() + if i and (v > API_CHANGES['1.27.0']): + data.setdefault('users', []) obj = self._upload_object( f"/api/organizations/{obj._orga.id}/collections/{obj.id}", data, From 1ea5399198347eaab52f30a588c4b2d04ca4dca5 Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Sun, 20 Apr 2025 22:47:12 +0200 Subject: [PATCH 08/24] Fix newer vaultwarden set_org_access --- src/bitwardentools/client.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/bitwardentools/client.py b/src/bitwardentools/client.py index deb4abe..f93bcc9 100644 --- a/src/bitwardentools/client.py +++ b/src/bitwardentools/client.py @@ -2487,6 +2487,9 @@ def get_accesses(self, objs, sync=None, token=None): ) raise exc u = f"/api/organizations/{orga.id}/users/{ouid}" + v, i = self.version() + if i and (v > API_CHANGES['1.27.0']): + u += "?includeGroups=True&includeCollections=true" resp = self.r(u, token=token, method="get", json={}) try: self.assert_bw_response(resp) @@ -2815,13 +2818,19 @@ def set_organization_access( # set access on subsequent collections cremove = cdata.get("remove", remove) if cremove: + found = True try: uacl["daccess"][email][cid] except KeyError: - L.info( - f"{email} has no access to collection {collection.name}/{collection.id}, no removal" - ) - else: + try: + cacl = self.get_accesses(collection) + cacl["daccess"][email] + except KeyError: + found = False + L.info( + f"{email} has no access to collection {collection.name}/{collection.id}, no removal" + ) + if found: L.info(f"Removing {cid} from {email} collections") todo = True payload["removed"].add(cid) From 2dfe31f3e96d49ab5b2a403a4eff74c17ca7cec4 Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Mon, 21 Apr 2025 22:47:12 +0200 Subject: [PATCH 09/24] lint --- setup.py | 6 ++++-- src/bitwardentools/client.py | 28 ++++++++++++------------- src/bitwardentools/create_and_notify.py | 6 ++---- src/tests/test_client.py | 4 ++-- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/setup.py b/setup.py index 4c600bd..f415134 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,9 @@ def read(*rnames): src_dir = "src" req = re.compile("^(?!(-e|#))", flags=re.I | re.M) install_requires = [ - a.strip() for a in open("requirements/requirements.txt").read().splitlines() if req.search(a) and a.strip() + a.strip() + for a in open("requirements/requirements.txt").read().splitlines() + if req.search(a) and a.strip() ] extra_requires = {} candidates = {} @@ -51,7 +53,7 @@ def read(*rnames): author="kiorky", author_email="kiorky@cryptelium.net", url="https://github.com/corpusops/bitwardentools", - long_description_content_type='text/markdown', + long_description_content_type="text/markdown", license="GPL", packages=find_packages(src_dir), package_dir={"": src_dir}, diff --git a/src/bitwardentools/client.py b/src/bitwardentools/client.py index f93bcc9..ce27a91 100644 --- a/src/bitwardentools/client.py +++ b/src/bitwardentools/client.py @@ -18,10 +18,10 @@ from copy import deepcopy from subprocess import run from time import time -from packaging import version as _version import requests from jwt import encode as jwt_encode +from packaging import version as _version from bitwardentools import crypto as bwcrypto from bitwardentools.common import L, caseinsentive_key_search @@ -97,13 +97,14 @@ "managePolicies": False, "manageUsers": False, } -IS_BITWARDEN_RE = re.compile('[0-9][0-9][0-9][0-9][.][0-9][0-9]?[.][0-9][0-9]?', flags=re.I|re.U|re.M) +IS_BITWARDEN_RE = re.compile( + "[0-9][0-9][0-9][0-9][.][0-9][0-9]?[.][0-9][0-9]?", flags=re.I | re.U | re.M +) API_CHANGES = { - '1.27.0': _version.parse('1.27.0'), + "1.27.0": _version.parse("1.27.0"), } - def uncapitzalize(s): if not s or not isinstance(s, str): return s @@ -691,7 +692,7 @@ def __init__( login=True, cache=None, vaultier=False, - authentication_cb=None + authentication_cb=None, ): # goal is to allow shared cache amongst client instances # but also if we want totally isolated caches @@ -1050,7 +1051,6 @@ def get_organization(self, orga, sync=None, cache=None, token=None, complete=Non return self.finish_orga(v, token=token, cache=cache, complete=complete) except KeyError: pass - import pdb;pdb.set_trace() exc = OrganizationNotFound(f"No such organization found {orga}") exc.criteria = [orga] raise exc @@ -1434,8 +1434,8 @@ def edit_orgcollection(self, collection, token=None, **jsond): _, k = self.get_organization_key(obj._orga, token=token) data["name"] = bwcrypto.encrypt(bwcrypto.CIPHERS.sym, data["name"], k) v, i = self.version() - if i and (v > API_CHANGES['1.27.0']): - data.setdefault('users', []) + if i and (v > API_CHANGES["1.27.0"]): + data.setdefault("users", []) obj = self._upload_object( f"/api/organizations/{obj._orga.id}/collections/{obj.id}", data, @@ -1451,11 +1451,11 @@ def version(self, force=False): # vaultwarden scheme is SEMVER if force or not self._version: try: - v = self.r('/api/version', method='get').json() + v = self.r("/api/version", method="get").json() except Exception: trace = traceback.format_exc() L.error(trace) - v = '1.0.0' + v = "1.0.0" self._is_vaultwarden = not bool(IS_BITWARDEN_RE.search(v)) self._version = _version.parse(v) return self._version, self._is_vaultwarden @@ -1468,7 +1468,7 @@ def create_orgcollection( _, k = self.get_organization_key(orga, token=token) encoded_name = bwcrypto.encrypt(bwcrypto.CIPHERS.sym, name, k) v, i = self.version() - if i and (v > API_CHANGES['1.27.0']): + if i and (v > API_CHANGES["1.27.0"]): data = {"externalId": "", "groups": [], "users": [], "name": encoded_name} else: data = {"externalId": "", "groups": [], "name": encoded_name} @@ -2488,7 +2488,7 @@ def get_accesses(self, objs, sync=None, token=None): raise exc u = f"/api/organizations/{orga.id}/users/{ouid}" v, i = self.version() - if i and (v > API_CHANGES['1.27.0']): + if i and (v > API_CHANGES["1.27.0"]): u += "?includeGroups=True&includeCollections=true" resp = self.r(u, token=token, method="get", json={}) try: @@ -2710,8 +2710,8 @@ def add_user_to_organization( )["payloads"] u = f"/api/organizations/{orga.id}/users/invite" v, i = self.version() - if i and (v > API_CHANGES['1.27.0']): - params.setdefault('groups', []) + if i and (v > API_CHANGES["1.27.0"]): + params.setdefault("groups", []) response = self.r(u, json=params, token=token) self.assert_bw_response(response) return response diff --git a/src/bitwardentools/create_and_notify.py b/src/bitwardentools/create_and_notify.py index 9ae6988..abd5baf 100755 --- a/src/bitwardentools/create_and_notify.py +++ b/src/bitwardentools/create_and_notify.py @@ -20,9 +20,7 @@ @click.command() @click.option("--login") @click.option("--password", default="") -@click.option( - "--register-to", default=os.environ.get("BW_ORGAS_REGISTER_TO", "") -) +@click.option("--register-to", default=os.environ.get("BW_ORGAS_REGISTER_TO", "")) @click.option("--server", default=bitwardentools.SERVER) @click.option("--mail-lang", default=MAIL_LANG) @click.option("--tls", default=os.environ.get("BW_MAIL_TLS", "1")) @@ -90,7 +88,7 @@ def main( client.create_user( login, name=login.split("@")[0], password=password, auto_validate=True ) - for i in register_to.split(':'): + for i in register_to.split(":"): client.set_organization_access( login, i, access_level=bwclient.CollectionAccess.admin, accessAll=True ) diff --git a/src/tests/test_client.py b/src/tests/test_client.py index 156e8b4..19076b6 100755 --- a/src/tests/test_client.py +++ b/src/tests/test_client.py @@ -58,7 +58,7 @@ def wipe_users(self): for attr, email, password in self.get_users(): try: self.client.delete_user(email) - except (bwclient.UserNotFoundError): + except bwclient.UserNotFoundError: pass @classmethod @@ -618,7 +618,7 @@ def test_set_organization_access(self): self.assertEqual( strip_dict_data( ao1["daccess"][self.user1[0].email], - skip=f'{DSKIP}|(g|G)roups|TwoFactorEnabled|ResetPasswordEnrolled|ExternalId|collections' + skip=f"{DSKIP}|(g|G)roups|TwoFactorEnabled|ResetPasswordEnrolled|ExternalId|collections", ), { "accessAll": False, From b050076940884ef1cf0be597a0d7f9f90c058224 Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Tue, 22 Apr 2025 22:47:12 +0200 Subject: [PATCH 10/24] doc --- .env.test | 2 -- CHANGES.md | 9 ++++++++- README.md | 8 ++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.env.test b/.env.test index 46a92ef..d44b349 100644 --- a/.env.test +++ b/.env.test @@ -7,5 +7,3 @@ NO_NVM_INSTALL=1 NO_PIP_INSTALL=1 VERBOSE_NVM_INSTALL=1 COMPOSE_FILE=docker-compose.yml:docker-compose-dev.yml:docker-compose-build.yml:docker-compose-test.yml -BITWARDEN_IMAGE=vaultwarden/server:1.25.2 -# BITWARDEN_IMAGE=bitwardenrs/server-postgresql:1.18.0 diff --git a/CHANGES.md b/CHANGES.md index 3f5e4e0..15ea7f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,14 @@ ## CHANGES +### 1.0.57 +- QA & CI/CD fixes [kiorky] +- Fix newer vaultwarden patch [kiorky] +- Fix newer vaultwarden adduser [kiorky] +- Fix new vaultwarden create_orga [kiorky] +- Fix newer vaultwarden set_org_acces [kiorky] + ### 1.0.56 -- Customizable auth payload support (2Factor, api auth) [Markus Kötter ]} +- Customizable auth payload support (2Factor, api auth) [Markus Kötter ]) ### 1.0.55 - ensure requests is in requirements [kiorky] diff --git a/README.md b/README.md index 765e88f..a8c7640 100644 --- a/README.md +++ b/README.md @@ -42,16 +42,16 @@ docker-compose run --rm app bash ```bash sed -i -e "/COMPOSE_FILE/d" .env -echo COMPOSE_FILE=docker-compose.yml:docker-compose-dev.yml" >> .env +echo "COMPOSE_FILE=docker-compose.yml:docker-compose-dev.yml" >> .env docker-compose up -d --force-recreate -docker-compose exec -U app bash +docker-compose exec -u app app bash ``` ### run tests ```bash sed -i -e "/COMPOSE_FILE/d" .env -echo COMPOSE_FILE=docker-compose.yml:docker-compose-dev.yml:docker-compose-test.yml" >> .env -docker-compose exec -U app app tox -e linting,coverage +echo "COMPOSE_FILE=docker-compose.yml:docker-compose-dev.yml:docker-compose-test.yml" >> .env +docker-compose exec -u app app tox -e linting,coverage ``` ## Credits and bibliography From 04ed0260e7b89f39a7fe1ed66eff8ad4c550a171 Mon Sep 17 00:00:00 2001 From: Mathieu Le Marec - Pasquet Date: Sat, 30 Sep 2023 18:25:23 +0200 Subject: [PATCH 11/24] Release: 1.0.57 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f415134..5e4cc09 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ def read(*rnames): long_description = "\n\n".join([read(a) for a in READMES]) classifiers = ["Programming Language :: Python", "Topic :: Software Development"] name = "bitwardentools" -version = "1.0.56" +version = "1.0.57" src_dir = "src" req = re.compile("^(?!(-e|#))", flags=re.I | re.M) install_requires = [ From 74077f75a76b6b2b2bc705bdd679d8888cd87c95 Mon Sep 17 00:00:00 2001 From: MeIchthys Date: Fri, 17 Nov 2023 11:04:50 -0500 Subject: [PATCH 12/24] Minor readme fixes --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a8c7640..8ba03b5 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ -# tools for working with vaultwarden/bitwarden (_rs) and vaultier +# Tools for working with vaultwarden/bitwarden (_rs) and vaultier -This package containers a python3+ client for bitwarden which uses both a native python implementation but also wraps the official the official npm `@bitwarden/cli`. +This package containers a python3+ client for bitwarden which uses both a native python implementation but also wraps the official npm `@bitwarden/cli`. The ultimate goal is certainly only to rely on python implementation against the vaultwarden/bitwarden_rs server implementation. - [![.github/workflows/cicd.yml](https://github.com/corpusops/bitwardentools/actions/workflows/cicd.yml/badge.svg?branch=main)](https://github.com/corpusops/bitwardentools/actions/workflows/cicd.yml) ## Features -- api controllable client +- API controllable client - Create, Read, Update, Delete, on organizations, collection, ciphers, users (also disable/enable), and attachments - Attach Ciphers to organization collections -- Set access at orgas, collections and users levels. +- Set access at organization, collections and users levels - Download/Upload attachments to vault and organizations -- The client also integrate a thin wrapper to official npm CLI (see `call` mathod) -- Read [api](./src/bitwardentools/client.py) for longer details +- Integrates a thin wrapper around the official npm CLI (see `call` mathod) +- Read [api](./src/bitwardentools/client.py) for more details -## install as a python lib +## Install as a python lib ```bash pip install bitwardentools ``` @@ -47,14 +47,14 @@ docker-compose up -d --force-recreate docker-compose exec -u app app bash ``` -### run tests +### Run Tests ```bash sed -i -e "/COMPOSE_FILE/d" .env echo "COMPOSE_FILE=docker-compose.yml:docker-compose-dev.yml:docker-compose-test.yml" >> .env docker-compose exec -u app app tox -e linting,coverage ``` -## Credits and bibliography +## Credits and Bibliography - [gnunux](http://gnunux.info/) excellent articles: [1](http://gnunux.info/dotclear2/index.php?post/2020/10/11/%C3%89crire-un-client-Bitwarden-en-python-%3A-identifiant) [2](http://gnunux.info/dotclear2/index.php?post/2020/10/11/%C3%89crire-un-client-Bitwarden-en-python-%3A-cr%C3%A9er-une-organisation-et-une-collection) @@ -66,5 +66,5 @@ docker-compose exec -u app app tox -e linting,coverage - https://github.com/jcs/rubywarden -## Doc -see also [USAGE](./USAGE.md) (or read below on pypi) +## Docs +See [USAGE](./USAGE.md) (or read below on pypi) From 64435cfd6b7811dba191712e8132a8e5f0d80dfb Mon Sep 17 00:00:00 2001 From: Didier 'OdyX' Raboud Date: Wed, 17 Jul 2024 09:39:05 +0200 Subject: [PATCH 13/24] 2024.6 Bitwarden uses camelCase keys instead of PascalCase --- src/bitwardentools/client.py | 50 ++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/bitwardentools/client.py b/src/bitwardentools/client.py index ce27a91..eadffa3 100644 --- a/src/bitwardentools/client.py +++ b/src/bitwardentools/client.py @@ -145,17 +145,17 @@ def rewrite_acls_collection(i, skip=None): skip = re.compile(skip) if isinstance(i, dict): for v, k in { - "Data": "data", - "Id": "id", - "AccessAll": "accessAll", - "Email": "email", - "Name": "name", - "Status": "status", - "Collections": "collections", - "UserId": "userId", - "Type": "type", - "HidePasswords": "hidePasswords", - "ReadOnly": "readOnly", + "data": "data", + "id": "id", + "accessAll": "accessAll", + "email": "email", + "name": "name", + "status": "status", + "collections": "collections", + "userId": "userId", + "type": "type", + "hidePasswords": "hidePasswords", + "readOnly": "readOnly", }.items(): if skip and (skip.search(v) or skip.search(k)): i.pop(v, None) @@ -367,9 +367,9 @@ def get_types(t): def get_type(obj, default=""): if isinstance(obj, BWFactory): - objtyp = getattr(obj, "Object", getattr(obj, "object", "")) + objtyp = getattr(obj, "object", getattr(obj, "object", "")) elif isinstance(obj, dict): - objtyp = obj.get("Object", obj.get("object", "")) + objtyp = obj.get("object", obj.get("object", "")) else: objtyp = default return objtyp.lower() @@ -484,7 +484,7 @@ def construct( try: object_class_name = jsond["object"] except KeyError: - object_class_name = jsond["Object"] + object_class_name = jsond["object"] if object_class_name.lower().startswith("cipher"): try: typ = jsond["Type"] @@ -1008,9 +1008,9 @@ def get_organizations(self, sync=None, cache=None, token=None): assert _CACHE.get("sync") except AssertionError: sdata = self.api_sync(sync=sync) - for orga in sdata.get("Profile", {}).get("Organizations", []): + for orga in sdata.get("profile", {}).get("organizations", []): orga = deepcopy(orga) - orga["Object"] = "organization" + orga["object"] = "organization" obj = BWFactory.construct(orga, client=self, unmarshall=True) self.cache(obj) _CACHE["sync"] = True @@ -1394,12 +1394,12 @@ def get_organization_key(self, orga, token=None, sync=None): enc_okey = ( dict( [ - (a["Id"], a) - for a in sdata.get("Profile", {}).get("Organizations", []) + (a["id"], a) + for a in sdata.get("profile", {}).get("organizations", []) ] ) .get(orga.id, {}) - .get("Key", None) + .get("key", None) ) if enc_okey: break @@ -1514,7 +1514,7 @@ def get_collections(self, orga=None, sync=None, cache=None, token=None): assert _CACHE["sync"] except AssertionError: for enccol in ( - self.r("/api/collections", method="get").json().get("Data", []) + self.r("/api/collections", method="get").json().get("data", []) ): col = BWFactory.construct(enccol, client=self, unmarshall=True) _, colk = self.get_organization_key(col.organizationId, token=token) @@ -1702,12 +1702,12 @@ def get_ciphers( exc.response = resp raise exc dciphers = [] - for cipher in ciphers.get("Data", []): + for cipher in ciphers.get("data", []): try: dciphers.append(self.decrypt(cipher, token=token)) except bwcrypto.DecryptError: - self._broken_ciphers[cipher["Id"]] = cipher - L.info(f'Cant decrypt cipher {cipher["Id"]}, broken ?') + self._broken_ciphers[cipher["id"]] = cipher + L.info(f'Cant decrypt cipher {cipher["id"]}, broken ?') for cipher in dciphers: obj = BWFactory.construct(cipher, client=self, unmarshall=True) self.cache(obj, vaultier=vaultier) @@ -3187,9 +3187,9 @@ def confirm_invitation(self, orga, email, name=None, sync=None, token=None): user_id = acl["userId"] resp = self.r(f"/api/users/{user_id}/public-key", method="get") self.assert_bw_response(resp) - userorgkey = b64decode(resp.json()["PublicKey"]) + userorgkey = b64decode(resp.json()["publicKey"]) encoded_key = bwcrypto.encrypt_asym(orgkey[1], userorgkey) - payload = {"Key": encoded_key} + payload = {"key": encoded_key} try: u = f"/api/organizations/{orga.id}/users/{acl['id']}/confirm" resp = self.r(u, json=payload, token=token) From 099855ccfb3ff77c4794dd089d0d5528f6ecc366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Tue, 23 Jul 2024 15:34:34 +0200 Subject: [PATCH 14/24] Fix missing type/id lowercase object keys (cherry picked from commit ddc0d825f9cebc287f134760ffe3f2f20f7bea27) --- src/bitwardentools/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bitwardentools/client.py b/src/bitwardentools/client.py index eadffa3..d5c4da7 100644 --- a/src/bitwardentools/client.py +++ b/src/bitwardentools/client.py @@ -487,10 +487,10 @@ def construct( object_class_name = jsond["object"] if object_class_name.lower().startswith("cipher"): try: - typ = jsond["Type"] + typ = jsond["type"] object_class = SECRETS_CLASSES[typ] except KeyError: - L.error(f'Unkown cipher {jsond.get("Id", "")}') + L.error(f'Unkown cipher {jsond.get("id", "")}') else: object_class = get_obj(object_class_name) a = object_class( From 44b52ca53c58c76bd152d11fec141d3fdedab4ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Sat, 17 Aug 2024 12:24:27 +0200 Subject: [PATCH 15/24] tests - using vaultwarden 1.32 --- docker-compose-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose-test.yml b/docker-compose-test.yml index b59f159..b8a3b73 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -8,7 +8,7 @@ x-bases: BITWARDEN_DB_IMAGE: corpusops/postgres:13 BITWARDEN_DOMAIN: bitwarden DATABASE_URL: postgresql://db:db@db/db - BITWARDEN_IMAGE: vaultwarden/server:1.29.1 + BITWARDEN_IMAGE: vaultwarden/server:1.32.0 DATA_FOLDER: /data DISABLE_ADMIN_TOKEN: "true" DOMAIN: http://bitwarden From 7db3b4b61f7f24e3c22f65b390ddcf534fdedbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Sat, 17 Aug 2024 12:32:45 +0200 Subject: [PATCH 16/24] cicd - on PR --- .github/workflows/cicd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 34e6003..f012c08 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -3,6 +3,7 @@ on: inputs: RUNTESTS: {description: 'Run tests', required: false} push: + pull_request: schedule: [{cron: '1 0 1,15 * *'}] repository_dispatch: env: From 53bcc7f6d6f26721c5e4d6461dcdd011756135d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Sat, 17 Aug 2024 12:36:26 +0200 Subject: [PATCH 17/24] tests/ci - disable dockerhub login --- .github/workflows/cicd.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index f012c08..6563f69 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -39,11 +39,11 @@ jobs: then releasable=true;else releasable=false;fi echo "::set-output name=releasable::$releasable" id: v - - name: Login to Docker Hub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKER_HUB_USERNAME }} - password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} +# - name: Login to Docker Hub +# uses: docker/login-action@v1 +# with: +# username: ${{ secrets.DOCKER_HUB_USERNAME }} +# password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} - name: Activate docker experimental run: |- sudo bash -exc "service docker stop;python -c \ From 41e979aaf1474069649b210bf2ecf3c581e3b99f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Sat, 17 Aug 2024 12:38:58 +0200 Subject: [PATCH 18/24] tests/ci - install docker-compose --- .github/workflows/cicd.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 6563f69..7187818 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -56,6 +56,9 @@ jobs: run: |- for i in .env .env.local;do if [ -e $i.test ];then cp -v $i.test $i;fi;done printf "USER_UID=$(id -u)\nUSER_GID=$(id -g)\n">>.env + - name: Install docker-compose + run: |- + sudo apt update && sudo apt install -y docker-compose - name: Build dependant docker images if any run: if ( docker-compose config|egrep -q build:; );then docker-compose build;fi - name: Start stack From e25abfb8786aebcab087b839127e3767220b3b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Sat, 17 Aug 2024 12:51:47 +0200 Subject: [PATCH 19/24] tests/ci - .tox perms --- .github/workflows/cicd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 7187818..bfc94bd 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -78,6 +78,7 @@ jobs: for i in $http_services;do http_wait $i;done ( while true; do docker ps -a;docker-compose logs db bitwarden app;sleep 15;done )& $bash -exc '\ + sudo chmod 777 -R .tox ( while !(touch .tox/ready);do echo "app not ready ($(pwd))">&2;sleep 0.5;done \ && touch .tox/appready )&\ /cops_helpers/dockerize -wait file://$(pwd)/.tox/appready -timeout 180s;' From 09ea27c522ce8a2ab4263f9a2c6caef2d7fb1451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Sat, 17 Aug 2024 12:56:01 +0200 Subject: [PATCH 20/24] tests/ci - using vaultwarden 1.32.0 --- docker-compose-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose-test.yml b/docker-compose-test.yml index b8a3b73..a58ec76 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -98,7 +98,7 @@ services: - mails:/mails:rw bitwarden: <<: [ *base ] - image: ${BITWARDEN_IMAGE:-vaultwarden/server:1.29.1} + image: ${BITWARDEN_IMAGE:-vaultwarden/server:1.32.0} hostname: bitwarden depends_on: [db, setup, mailcatcher] entrypoint: From ba48367d5e222a74b2d7f9775992203a85b57ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Mon, 19 Aug 2024 18:50:57 +0200 Subject: [PATCH 21/24] camelCase --- src/tests/test_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/test_client.py b/src/tests/test_client.py index 19076b6..403dfbd 100755 --- a/src/tests/test_client.py +++ b/src/tests/test_client.py @@ -10,7 +10,7 @@ from bitwardentools import crypto as bwcrypto ORGA_TEST_ID = os.environ.get("ORGA_TEST_ID", "bitwardentoolstest") -DSKIP = "daccess|emails|^id|^Id|userId|Object|ContinuationToken" +DSKIP = "daccess|emails|^id|^Id|userId|(o|O)bject|(c|C)ontinuationToken" AL = bwclient.CollectionAccess @@ -618,7 +618,7 @@ def test_set_organization_access(self): self.assertEqual( strip_dict_data( ao1["daccess"][self.user1[0].email], - skip=f"{DSKIP}|(g|G)roups|TwoFactorEnabled|ResetPasswordEnrolled|ExternalId|collections", + skip=f"{DSKIP}|(g|G)roups|(t|T)woFactorEnabled|(r|R)esetPasswordEnrolled|(e|E)xternalId|collections", ), { "accessAll": False, From da98abd073497050e58202f55c7e11a62441447b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Fri, 23 Aug 2024 14:04:40 +0200 Subject: [PATCH 22/24] using the items dedicated key to decode the item --- src/bitwardentools/client.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/bitwardentools/client.py b/src/bitwardentools/client.py index d5c4da7..0029385 100644 --- a/src/bitwardentools/client.py +++ b/src/bitwardentools/client.py @@ -1638,6 +1638,16 @@ def decrypt( pass if orga: _, key = self.get_organization_key(orga) + + if (k := value.get("key", None)) is not None: + key = self.decrypt(k, + orga=orga, + key=key, + token=token, + recursion=recursion, + dictkey="key", + ) + for i, v in value.items(): nvalue[i] = self.decrypt( v, From fa870a4e09c11cd790c6e5da5090ba934801c7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Sat, 24 Aug 2024 01:26:08 +0200 Subject: [PATCH 23/24] client - key uses user_key or org_key when using item encryption keys --- src/bitwardentools/client.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/bitwardentools/client.py b/src/bitwardentools/client.py index 0029385..2a4354a 100644 --- a/src/bitwardentools/client.py +++ b/src/bitwardentools/client.py @@ -1626,8 +1626,10 @@ def decrypt( elif isinstance(value, dict): nvalue = type(value)() obj = get_type(value) + _key = None if obj and not orga and re.search("^passsword|note|attachment|cipher", obj): - key = token["user_key"] + _key = token["user_key"] + user_key = _key if orga is None: for i in "OrganizationId", "organizationId": if not value.get(i, None): @@ -1637,22 +1639,24 @@ def decrypt( except OrganizationNotFound: pass if orga: - _, key = self.get_organization_key(orga) + _, _key = self.get_organization_key(orga) + orga_key = _key if (k := value.get("key", None)) is not None: - key = self.decrypt(k, + nvalue["key"] = item_key = _key = self.decrypt(k, orga=orga, - key=key, + key=_key, token=token, - recursion=recursion, + recursion=None, dictkey="key", ) + root = len(recursion) == 0 for i, v in value.items(): nvalue[i] = self.decrypt( v, orga=orga, - key=key, + key=orga_key if i in ["key"] else key or _key, token=token, recursion=recursion, dictkey=i, From 8d1685de92fa14973eefd903bddf00bca8a54e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Mon, 26 Aug 2024 06:46:22 +0200 Subject: [PATCH 24/24] =?UTF-8?q?item=20specific=20keys=20=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/bitwardentools/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bitwardentools/client.py b/src/bitwardentools/client.py index 2a4354a..62c1916 100644 --- a/src/bitwardentools/client.py +++ b/src/bitwardentools/client.py @@ -1651,12 +1651,11 @@ def decrypt( dictkey="key", ) - root = len(recursion) == 0 for i, v in value.items(): nvalue[i] = self.decrypt( v, orga=orga, - key=orga_key if i in ["key"] else key or _key, + key=(orga_key or user_key) if i in ["key"] else key or _key, token=token, recursion=recursion, dictkey=i,