Skip to content

Commit d5cfb3c

Browse files
committed
tests - search/edit items
1 parent fc98f27 commit d5cfb3c

3 files changed

Lines changed: 133 additions & 4 deletions

File tree

src/vaultwarden/clients/bitwarden.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
OrganizationCollection,
1212
OrgData,
1313
RegisterData,
14+
get_organization,
1415
)
1516
from vaultwarden.models.crypto import CryptoContext
1617
from vaultwarden.models.exception_models import BitwardenError
@@ -313,9 +314,9 @@ def select_items(
313314

314315
def create_item(
315316
self,
316-
item: "CipherDetails",
317-
organization: typing.Optional["Organization"],
318-
collections: list["OrganizationCollection"] | None,
317+
item: CipherDetails,
318+
organization: Organization,
319+
collections: list[OrganizationCollection],
319320
) -> "CipherDetails":
320321
if organization:
321322
assert organization and (
@@ -330,6 +331,7 @@ def create_item(
330331
mode="json",
331332
by_alias=True,
332333
context=CryptoContext(client=self, stack=[key]),
334+
exclude_none=True,
333335
),
334336
"collectionIds": [str(i.Id) for i in collections],
335337
}
@@ -338,12 +340,30 @@ def create_item(
338340
assert self.connect_token is not None
339341
key = self.connect_token.Key
340342
data = item.model_dump(
341-
by_alias=True,
342343
mode="json",
344+
by_alias=True,
343345
context=CryptoContext(client=self, stack=[key]),
344346
)
345347

346348
resp = self._api_request("POST", path, json=data)
347349
return CipherDetail.validate_json(
348350
resp.text, context=CryptoContext(client=self)
349351
)
352+
353+
def edit_item(self, item: CipherDetails) -> "CipherDetails":
354+
assert self.connect_token is not None
355+
path = f"/api/ciphers/{item.Id}"
356+
key = (
357+
self.connect_token.Key
358+
if item.OrganizationId is None
359+
else get_organization(self, item.OrganizationId).key()
360+
)
361+
data = item.model_dump(
362+
mode="json",
363+
by_alias=True,
364+
context=CryptoContext(client=self, stack=[key]),
365+
)
366+
resp = self._api_request("PUT", path, json=data)
367+
return CipherDetail.validate_json(
368+
resp.text, context=CryptoContext(client=self)
369+
)

src/vaultwarden/models/bitwarden.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,22 @@ def remove_collections(self, collections: list[UUID]):
358358
json={"collectionIds": dump},
359359
)
360360

361+
def collections(self):
362+
org: Organization | None = (
363+
get_organization(self._bitwarden_client, self.OrganizationId)
364+
if self.OrganizationId
365+
else None
366+
)
367+
if org is None:
368+
return []
369+
cd: dict[UUID, OrganizationCollection] = {
370+
o.Id: o for o in org.collections()
371+
}
372+
colls: list[OrganizationCollection] = [
373+
cd[i] for i in self.CollectionIds
374+
]
375+
return colls
376+
361377
def delete(self):
362378
return self.api_client.api_request("DELETE", f"api/ciphers/{self.Id}")
363379

@@ -411,6 +427,9 @@ def _attach(self, name: str, file: io.IOBase):
411427
def uri_match(self, name: str) -> bool:
412428
return False
413429

430+
def save(self):
431+
self._bitwarden_client.edit_item(self)
432+
414433

415434
class LoginData(BitwardenBaseModel):
416435
username: SecretString | None = None

tests/e2e/test_write.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,93 @@ def test_cleanup_users(admin: VaultwardenAdminClient):
252252
for i in admin.users():
253253
if i.Email.endswith("@example.org"):
254254
admin.delete(i.Id)
255+
256+
257+
SEARCH_ITEMS = [
258+
# ("http://default.com", "http://default.com", None),
259+
(
260+
"http://sub.basedomain.com",
261+
"http://basedomain.com",
262+
UriMatchDetection.BASEDOMAIN,
263+
),
264+
("http://host.com/a", "http://host.com", UriMatchDetection.HOST),
265+
(
266+
"http://startswith.com/a/b",
267+
"http://startswith.com/a",
268+
UriMatchDetection.STARTSWITH,
269+
),
270+
("http://re.com", r"^http://re\.c.m", UriMatchDetection.RE),
271+
("http://exact.com", "http://exact.com", UriMatchDetection.EXACT),
272+
]
273+
274+
275+
@pytest.fixture(
276+
params=SEARCH_ITEMS,
277+
ids=[urllib.parse.urlparse(url).hostname for url, *_ in SEARCH_ITEMS],
278+
)
279+
def logins(request, test_account, organization, collection):
280+
url, uri, match = request.param
281+
name = urllib.parse.urlparse(url).hostname
282+
data = LoginData.model_construct(
283+
name=name,
284+
password="test123",
285+
username="test",
286+
Uris=[UriMatch.model_construct(match=match, uri=uri)],
287+
)
288+
item = Login.model_construct(
289+
name=name,
290+
login=data,
291+
data=data,
292+
key=secrets.token_bytes(64),
293+
)
294+
test_account.create_item(item, organization, [collection])
295+
return url, uri, match
296+
297+
298+
def test_search(
299+
test_account: BitwardenAPIClient,
300+
organization: Organization,
301+
collection: OrganizationCollection,
302+
logins,
303+
):
304+
test_account.sync(force_refresh=True)
305+
url, uri, match = logins
306+
307+
r = list(
308+
test_account.search_items(
309+
url, organisations=[organization], collections=[collection]
310+
)
311+
)
312+
assert len(r) == 1, url
313+
assert r[0].Name == urllib.parse.urlparse(url).hostname
314+
315+
316+
def test_edit(
317+
test_account: BitwardenAPIClient,
318+
organization: Organization,
319+
collection: OrganizationCollection,
320+
logins,
321+
):
322+
test_account.sync(force_refresh=True)
323+
url, uri, match = logins
324+
325+
r = list(
326+
test_account.search_items(
327+
url, organisations=[organization], collections=[collection]
328+
)
329+
)
330+
assert len(r) == 1, url
331+
assert r[0].Name == urllib.parse.urlparse(url).hostname
332+
lo: Login = r[0]
333+
assert lo.Login.username == "test"
334+
lo.Login.username = lo.Login.password = "edit"
335+
lo.save()
336+
test_account.sync(force_refresh=True)
337+
r = list(
338+
test_account.search_items(
339+
url, organisations=[organization], collections=[collection]
340+
)
341+
)
342+
assert len(r) == 1, url
343+
lo: Login = r[0]
344+
assert lo.Login.username == lo.Login.password == "edit"

0 commit comments

Comments
 (0)