Skip to content

Commit afbb492

Browse files
authored
Merge pull request #207 from seibert-media/fix-keys
Miscellaneous fixes
2 parents b79ec27 + 2bd8ec1 commit afbb492

6 files changed

Lines changed: 52 additions & 13 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ dependencies = [
4545
]
4646

4747
# dynamic = ["version"] - Currently unsupported by uv_build
48-
version = '0.11.5' # Also change in teamvault/__version__.py
48+
version = '0.11.6' # Also change in teamvault/__version__.py
4949

5050
[dependency-groups]
5151
dev = [

teamvault/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.11.5" # Also change in pyproject.toml
1+
__version__ = "0.11.6" # Also change in pyproject.toml

teamvault/apps/secrets/api/views.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ def data_get(request, hashid):
167167
except SecretPermissionError:
168168
raise PermissionDenied
169169
if secret_revision.secret.content_type == ContentType.PASSWORD:
170+
if not isinstance(data, dict):
171+
return Response({'password': data})
170172
return Response({'password': data["password"]})
171173
elif secret_revision.secret.content_type == ContentType.FILE:
172174
return Response({'file': b64encode(data).decode('ascii')})

teamvault/apps/secrets/services/revision.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -202,16 +202,17 @@ def _build_revision(
202202
# merge missing fields for PASSWORD type
203203
if content_type == ContentType.PASSWORD and secret.current_revision:
204204
prev = secret.current_revision.peek_data(actor)
205-
payload.setdefault('password', prev.get('password'))
206-
if 'otp_key' in prev:
207-
for fld in ('otp_key', 'digits', 'algorithm'):
208-
payload.setdefault(fld, prev.get(fld))
209-
210-
sha_src = (
211-
payload['password']
212-
if content_type == ContentType.PASSWORD and 'password' in payload
213-
else dumps(payload, sort_keys=True)
214-
)
205+
if 'password' not in payload and 'password' in prev:
206+
payload['password'] = prev['password']
207+
208+
# Preserve OTP fields only when the previous revision actually had them.
209+
if 'otp_key' not in payload and 'otp_key' in prev:
210+
payload['otp_key'] = prev['otp_key']
211+
for fld in ('digits', 'algorithm'):
212+
if fld in prev:
213+
payload[fld] = prev[fld]
214+
215+
sha_src = dumps(payload, sort_keys=True)
215216
sha_sum = sha256(sha_src.encode()).hexdigest()
216217

217218
revision, created = SecretRevision.objects.get_or_create(

teamvault/apps/secrets/tests/models/test_secret_crud.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,42 @@ def test_update_file_payload_roundtrip(self):
150150
self.assertIsInstance(got, (bytes, bytearray))
151151
self.assertEqual(got, raw)
152152

153+
def test_otp_only_update_creates_new_revision(self):
154+
s: Secret = new_secret(self.owner, name="otp-add")
155+
rev_before = s.current_revision
156+
self.assertFalse(rev_before.otp_key_set)
157+
158+
changes_before = SecretChange.objects.filter(secret=s).count()
159+
160+
# OTP-only update: omit password, keep existing via merge logic.
161+
RevisionService.save_payload(
162+
secret=s,
163+
actor=self.owner,
164+
payload={
165+
"otp_key": "JBSWY3DPEHPK3PXP",
166+
"digits": "6",
167+
"algorithm": "SHA1",
168+
},
169+
)
170+
171+
s.refresh_from_db()
172+
rev_after = s.current_revision
173+
self.assertNotEqual(rev_after.id, rev_before.id)
174+
self.assertTrue(rev_after.otp_key_set)
175+
self.assertEqual(rev_after.get_data(self.owner)["otp_key"], "JBSWY3DPEHPK3PXP")
176+
177+
# Re-saving identical OTP-only payload should be a no-op.
178+
RevisionService.save_payload(
179+
secret=s,
180+
actor=self.owner,
181+
payload={
182+
"otp_key": "JBSWY3DPEHPK3PXP",
183+
"digits": "6",
184+
"algorithm": "SHA1",
185+
},
186+
)
187+
self.assertEqual(SecretChange.objects.filter(secret=s).count(), changes_before + 1)
188+
153189
def test_delete_marks_secret_and_denies_visibility_and_read(self):
154190
s: Secret = new_secret(self.owner, name="todelete", access_policy=AccessPolicy.ANY)
155191
s.status = SecretStatus.DELETED

teamvault/apps/settings/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from hashlib import sha1
55
from os import environ, umask
66
from os.path import exists, isfile
7-
from random import choice
7+
from secrets import choice
88
from string import ascii_letters, digits, punctuation
99
from urllib.parse import urlparse
1010

0 commit comments

Comments
 (0)