def test_merge_folder_manifests(): my_device = DeviceID("b@b") other_device = DeviceID("a@a") parent = EntryID() v1 = LocalFolderManifest.new_placeholder( my_device, parent=parent).to_remote(author=other_device) # Initial base manifest m1 = LocalFolderManifest.from_remote(v1, empty_filter) assert merge_manifests(my_device, empty_filter, m1) == m1 # Local change m2 = m1.evolve_children_and_mark_updated({"a": EntryID()}, empty_filter) assert merge_manifests(my_device, empty_filter, m2) == m2 # Successful upload v2 = m2.to_remote(author=my_device) m3 = merge_manifests(my_device, empty_filter, m2, v2) assert m3 == LocalFolderManifest.from_remote(v2, empty_filter) # Two local changes m4 = m3.evolve_children_and_mark_updated({"b": EntryID()}, empty_filter) assert merge_manifests(my_device, empty_filter, m4) == m4 m5 = m4.evolve_children_and_mark_updated({"c": EntryID()}, empty_filter) assert merge_manifests(my_device, empty_filter, m4) == m4 # M4 has been successfully uploaded v3 = m4.to_remote(author=my_device) m6 = merge_manifests(my_device, empty_filter, m5, v3) assert m6 == m5.evolve(base=v3) # The remote has changed v4 = v3.evolve(version=4, children={ "d": EntryID(), **v3.children }, author=other_device) m7 = merge_manifests(my_device, empty_filter, m6, v4) assert m7.base_version == 4 assert sorted(m7.children) == ["a", "b", "c", "d"] assert m7.need_sync # Successful upload v5 = m7.to_remote(author=my_device) m8 = merge_manifests(my_device, empty_filter, m7, v5) assert m8 == LocalFolderManifest.from_remote(v5, empty_filter) # The remote has changed v6 = v5.evolve(version=6, children={ "e": EntryID(), **v5.children }, author=other_device) m9 = merge_manifests(my_device, empty_filter, m8, v6) assert m9 == LocalFolderManifest.from_remote(v6, empty_filter)
async def test_path_info_remote_loader_exceptions(monkeypatch, alice_workspace, alice): manifest, _ = await alice_workspace.transactions._get_manifest_from_path( FsPath("/foo/bar")) async with alice_workspace.local_storage.lock_entry_id(manifest.id): await alice_workspace.local_storage.clear_manifest(manifest.id) vanilla_file_manifest_deserialize = BaseRemoteManifest._deserialize def mocked_file_manifest_deserialize(*args, **kwargs): return vanilla_file_manifest_deserialize( *args, **kwargs).evolve(**manifest_modifiers) monkeypatch.setattr(BaseRemoteManifest, "_deserialize", mocked_file_manifest_deserialize) manifest_modifiers = {"id": EntryID()} with pytest.raises(FSError) as exc: await alice_workspace.path_info(FsPath("/foo/bar")) assert f"Invalid entry ID: expected `{manifest.id}`, got `{manifest_modifiers['id']}`" in str( exc.value) manifest_modifiers = {"version": 4} with pytest.raises(FSError) as exc: await alice_workspace.path_info(FsPath("/foo/bar")) assert "Invalid version: expected `1`, got `4`" in str(exc.value) manifest_modifiers = {"author": DeviceID("mallory@pc1")} with pytest.raises(FSError) as exc: await alice_workspace.path_info(FsPath("/foo/bar")) assert "Invalid author: expected `alice@dev1`, got `mallory@pc1`" in str( exc.value)
def __init__(self) -> None: super().__init__() self.oracle = open(tmpdir / "oracle.txt", "w+b") self.manifest = LocalFileManifest.new_placeholder( DeviceID.new(), parent=EntryID(), blocksize=8 ) self.storage = Storage()
def _from_url_parse_and_consume_params(cls, params): kwargs = super()._from_url_parse_and_consume_params(params) value = params.pop("action", ()) if len(value) != 1: raise ValueError("Missing mandatory `action` param") if value[0] != "claim_device": raise ValueError("Expected `action=claim_device` value") value = params.pop("device_id", ()) if len(value) != 1: raise ValueError("Missing mandatory `device_id` param") try: kwargs["device_id"] = DeviceID(value[0]) except ValueError as exc: raise ValueError("Invalid `device_id` param value") from exc value = params.pop("token", ()) if len(value) > 0: try: kwargs["token"] = value[0] except ValueError: raise ValueError("Invalid `token` param value") return kwargs
def validate(self, string, pos): try: if len(string) == 0: return QValidator.Intermediate, string, pos DeviceID(string) return QValidator.Acceptable, string, pos except ValueError: return QValidator.Invalid, string, pos
async def get(self, organization_id: OrganizationID, recipient: UserID, offset: int) -> List[Tuple[DeviceID, DateTime, bytes]]: async with self.dbh.pool.acquire() as conn: data = await conn.fetch( *_q_get_messages(organization_id=organization_id, recipient=recipient, offset=offset)) return [(DeviceID(d[0]), d[1], d[2]) for d in data]
def test_merge_file_manifests(): my_device = DeviceID("b@b") other_device = DeviceID("a@a") parent = EntryID() v1 = LocalFileManifest.new_placeholder( my_device, parent=parent).to_remote(author=other_device) def evolve(m, n): chunk = Chunk.new(0, n).evolve_as_block(b"a" * n) blocks = ((chunk, ), ) return m1.evolve_and_mark_updated(size=n, blocks=blocks) # Initial base manifest m1 = LocalFileManifest.from_remote(v1) assert merge_manifests(my_device, empty_filter, m1) == m1 # Local change m2 = evolve(m1, 1) assert merge_manifests(my_device, empty_filter, m2) == m2 # Successful upload v2 = m2.to_remote(author=my_device) m3 = merge_manifests(my_device, empty_filter, m2, v2) assert m3 == LocalFileManifest.from_remote(v2) # Two local changes m4 = evolve(m3, 2) assert merge_manifests(my_device, empty_filter, m4) == m4 m5 = evolve(m4, 3) assert merge_manifests(my_device, empty_filter, m4) == m4 # M4 has been successfully uploaded v3 = m4.to_remote(author=my_device) m6 = merge_manifests(my_device, empty_filter, m5, v3) assert m6 == m5.evolve(base=v3) # The remote has changed v4 = v3.evolve(version=4, size=0, author=other_device) with pytest.raises(FSFileConflictError): merge_manifests(my_device, empty_filter, m6, v4)
def test_merge_manifests_with_a_placeholder(): my_device = DeviceID("b@b") other_device = DeviceID("a@a") parent = EntryID() m1 = LocalFolderManifest.new_placeholder(my_device, parent=parent) m2 = merge_manifests(my_device, empty_filter, m1) assert m2 == m1 v1 = m1.to_remote(author=my_device) m2a = merge_manifests(my_device, empty_filter, m1, v1) assert m2a == LocalFolderManifest.from_remote(v1, empty_filter) m2b = m1.evolve_children_and_mark_updated({"a": EntryID()}, empty_filter) m3b = merge_manifests(my_device, empty_filter, m2b, v1) assert m3b == m2b.evolve(base=v1) v2 = v1.evolve(version=2, author=other_device, children={"b": EntryID()}) m2c = m1.evolve_children_and_mark_updated({"a": EntryID()}, empty_filter) m3c = merge_manifests(my_device, empty_filter, m2c, v2) children = {**v2.children, **m2c.children} assert m3c == m2c.evolve(base=v2, children=children, updated=m3c.updated)
async def _get_user_invitation(conn, organization_id: OrganizationID, user_id: UserID): if await _user_exists(conn, organization_id, user_id): raise UserAlreadyExistsError(f"User `{user_id}` already exists") result = await conn.fetchrow( *_q_get_invitation(organization_id=organization_id, user_id=user_id) ) if not result: raise UserNotFoundError(user_id) return UserInvitation( user_id=user_id, creator=DeviceID(result["creator"]), created_on=result["created_on"] )
async def _get_device_invitation(conn, organization_id: OrganizationID, device_id: DeviceID) -> DeviceInvitation: if await _device_exists(conn, organization_id, device_id): raise UserAlreadyExistsError(f"Device `{device_id}` already exists") result = await conn.fetchrow(*_q_get_invitation( organization_id=organization_id, device_id=device_id)) if not result: raise UserNotFoundError(device_id) return DeviceInvitation(device_id=device_id, creator=DeviceID(result["creator"]), created_on=result["created_on"])
async def _get_user_devices(conn, organization_id: OrganizationID, user_id: UserID) -> Tuple[Device]: results = await conn.fetch( *_q_get_user_devices(organization_id=organization_id, user_id=user_id)) return tuple( Device( device_id=DeviceID(row["device_id"]), device_label=row["device_label"], device_certificate=row["device_certificate"], redacted_device_certificate=row["redacted_device_certificate"], device_certifier=row["device_certifier"], created_on=row["created_on"], ) for row in results)
def test_list_devices_support_legacy_file_with_meaningful_name(config_dir): # Legacy path might exceed the 256 characters limit in some cases (see issue #1356) # So we use the `\\?\` workaround: https://stackoverflow.com/a/57502760/2846140 if os.name == "nt": config_dir = Path("\\\\?\\" + str(config_dir.resolve())) # Device information user_id = uuid4().hex device_name = uuid4().hex organization_id = "Org" rvk_hash = (uuid4().hex)[:10] device_id = f"{user_id}@{device_name}" slug = f"{rvk_hash}#{organization_id}#{device_id}" human_label = "Billy Mc BillFace" human_email = "*****@*****.**" device_label = "My device" # Craft file data without the user_id, organization_id and # root_verify_key_hash fields key_file_data = packb({ "type": "password", "salt": b"12345", "ciphertext": b"whatever", "human_handle": (human_email.encode(), human_label.encode()), "device_label": device_label.encode(), }) key_file_path = get_devices_dir(config_dir) / slug / f"{slug}.keys" key_file_path.parent.mkdir(parents=True) key_file_path.write_bytes(key_file_data) devices = list_available_devices(config_dir) expected_device = AvailableDevice( key_file_path=key_file_path, organization_id=OrganizationID(organization_id), device_id=DeviceID(device_id), human_handle=HumanHandle(human_email, human_label), device_label=device_label, root_verify_key_hash=rvk_hash, ) assert devices == [expected_device]
async def test_user_create_human_handle_with_revoked_previous_one( alice_backend_sock, alice, bob, backend_data_binder): # First revoke bob await backend_data_binder.bind_revocation(user_id=bob.user_id, certifier=alice) # Now recreate another user with bob's human handle now = pendulum.now() bob2_device_id = DeviceID("bob2@dev1") user_certificate = UserCertificateContent( author=alice.device_id, timestamp=now, user_id=bob2_device_id.user_id, human_handle=bob.human_handle, public_key=bob.public_key, profile=UserProfile.STANDARD, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=now, device_id=bob2_device_id, device_label=bob. device_label, # Device label doesn't have to be unique verify_key=bob.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) user_certificate = user_certificate.dump_and_sign(alice.signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign( alice.signing_key) device_certificate = device_certificate.dump_and_sign(alice.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( alice.signing_key) rep = await user_create( alice_backend_sock, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=redacted_user_certificate, redacted_device_certificate=redacted_device_certificate, ) assert rep == {"status": "ok"}
async def test_user_create_human_handle_already_exists(alice_backend_sock, alice, bob): now = pendulum.now() bob2_device_id = DeviceID("bob2@dev1") user_certificate = UserCertificateContent( author=alice.device_id, timestamp=now, user_id=bob2_device_id.user_id, human_handle=bob.human_handle, public_key=bob.public_key, profile=UserProfile.STANDARD, ) redacted_user_certificate = user_certificate.evolve(human_handle=None) device_certificate = DeviceCertificateContent( author=alice.device_id, timestamp=now, device_id=bob2_device_id, device_label="dev2", verify_key=bob.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) user_certificate = user_certificate.dump_and_sign(alice.signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign( alice.signing_key) device_certificate = device_certificate.dump_and_sign(alice.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( alice.signing_key) rep = await user_create( alice_backend_sock, user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=redacted_user_certificate, redacted_device_certificate=redacted_device_certificate, ) assert rep == { "status": "already_exists", "reason": f"Human handle `{bob.human_handle}` already corresponds to a non-revoked user", }
def generate_new_device( organization_addr: BackendOrganizationAddr, profile: UserProfile = UserProfile.STANDARD, device_id: Optional[DeviceID] = None, human_handle: Optional[HumanHandle] = None, device_label: Optional[str] = None, signing_key: Optional[SigningKey] = None, private_key: Optional[PrivateKey] = None, ) -> LocalDevice: return LocalDevice( organization_addr=organization_addr, device_id=device_id or DeviceID.new(), device_label=device_label, human_handle=human_handle, signing_key=signing_key or SigningKey.generate(), private_key=private_key or PrivateKey.generate(), profile=profile, user_manifest_id=EntryID(), user_manifest_key=SecretKey.generate(), local_symkey=SecretKey.generate(), )
async def _register_new_device(cmds: BackendAuthenticatedCmds, author: LocalDevice, device_label: Optional[str]): new_device = LocalDevice( organization_addr=author.organization_addr, device_id=DeviceID(f"{author.user_id}@{DeviceName.new()}"), device_label=device_label, human_handle=author.human_handle, profile=author.profile, private_key=author.private_key, signing_key=SigningKey.generate(), user_manifest_id=author.user_manifest_id, user_manifest_key=author.user_manifest_key, local_symkey=author.local_symkey, ) now = pendulum_now() device_certificate = DeviceCertificateContent( author=author.device_id, timestamp=now, device_id=new_device.device_id, device_label=new_device.device_label, verify_key=new_device.verify_key, ) redacted_device_certificate = device_certificate.evolve(device_label=None) device_certificate = device_certificate.dump_and_sign(author.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( author.signing_key) rep = await cmds.device_create( device_certificate=device_certificate, redacted_device_certificate=redacted_device_certificate, ) if rep["status"] != "ok": raise RuntimeError(f"Cannot create device: {rep}") return new_device
def test_list_devices_support_legacy_file_without_labels(config_dir): # Craft file data without the labels fields key_file_data = packb({ "type": "password", "salt": b"12345", "ciphertext": b"whatever" }) slug = "9d84fbd57a#Org#Zack@PC1" key_file_path = fix_dir( get_devices_dir(config_dir) / slug / f"{slug}.keys") key_file_path.parent.mkdir(parents=True) key_file_path.write_bytes(key_file_data) devices = list_available_devices(config_dir) expected_device = AvailableDevice( key_file_path=key_file_path, organization_id=OrganizationID("Org"), device_id=DeviceID("Zack@PC1"), human_handle=None, device_label=None, root_verify_key_hash="9d84fbd57a", ) assert devices == [expected_device]
def test_device_id(raw): user_id, device_name = raw.split("@") device_id = DeviceID(raw) assert device_id == raw assert device_id.user_id == user_id assert device_id.device_name == device_name
def test_complete_scenario(): storage = Storage() with freeze_time("2000-01-01"): base = manifest = LocalFileManifest.new_placeholder( DeviceID.new(), parent=EntryID(), blocksize=16 ) assert manifest == base.evolve(size=0) with freeze_time("2000-01-02") as t2: manifest = storage.write(manifest, b"Hello ", 0) assert storage.read(manifest, 6, 0) == b"Hello " ((chunk0,),) = manifest.blocks assert manifest == base.evolve(size=6, blocks=((chunk0,),), updated=t2) assert chunk0 == Chunk(id=chunk0.id, start=0, stop=6, raw_offset=0, raw_size=6, access=None) assert storage[chunk0.id] == b"Hello " with freeze_time("2000-01-03") as t3: manifest = storage.write(manifest, b"world !", 6) assert storage.read(manifest, 13, 0) == b"Hello world !" ((_, chunk1),) = manifest.blocks assert manifest == base.evolve(size=13, blocks=((chunk0, chunk1),), updated=t3) assert chunk1 == Chunk(id=chunk1.id, start=6, stop=13, raw_offset=6, raw_size=7, access=None) assert storage[chunk1.id] == b"world !" with freeze_time("2000-01-04") as t4: manifest = storage.write(manifest, b"\n More kontent", 13) assert storage.read(manifest, 27, 0) == b"Hello world !\n More kontent" (_, _, chunk2), (chunk3,) = manifest.blocks assert storage[chunk2.id] == b"\n M" assert storage[chunk3.id] == b"ore kontent" assert manifest == base.evolve( size=27, blocks=((chunk0, chunk1, chunk2), (chunk3,)), updated=t4 ) with freeze_time("2000-01-05") as t5: manifest = storage.write(manifest, b"c", 20) assert storage.read(manifest, 27, 0) == b"Hello world !\n More content" chunk4, chunk5, chunk6 = manifest.blocks[1] assert chunk3.id == chunk4.id == chunk6.id assert storage[chunk5.id] == b"c" assert manifest == base.evolve( size=27, blocks=((chunk0, chunk1, chunk2), (chunk4, chunk5, chunk6)), updated=t5 ) with freeze_time("2000-01-06") as t6: manifest = storage.resize(manifest, 40) expected = b"Hello world !\n More content" + b"\x00" * 13 assert storage.read(manifest, 40, 0) == expected (_, _, _, chunk7), (chunk8,) = manifest.blocks[1:] assert storage[chunk7.id] == b"\x00" * 5 assert storage[chunk8.id] == b"\x00" * 8 assert manifest == base.evolve( size=40, blocks=((chunk0, chunk1, chunk2), (chunk4, chunk5, chunk6, chunk7), (chunk8,)), updated=t6, ) with freeze_time("2000-01-07") as t7: manifest = storage.resize(manifest, 25) expected = b"Hello world !\n More conte" assert storage.read(manifest, 25, 0) == expected ((_, _, chunk9),) = manifest.blocks[1:] assert chunk9.id == chunk6.id assert manifest == base.evolve( size=25, blocks=((chunk0, chunk1, chunk2), (chunk4, chunk5, chunk9)), updated=t7 ) with freeze_time("2000-01-08"): assert not manifest.is_reshaped() manifest = storage.reshape(manifest) expected = b"Hello world !\n More conte" assert storage.read(manifest, 25, 0) == expected assert manifest.is_reshaped() (chunk10,), (chunk11,) = manifest.blocks assert storage[chunk10.id] == b"Hello world !\n M" assert storage[chunk11.id] == b"ore conte" assert manifest == base.evolve(size=25, blocks=((chunk10,), (chunk11,)), updated=t7)
def test_bad_device_id(raw): with pytest.raises(ValueError): DeviceID(raw)
def load_slug(slug: str) -> Tuple[str, OrganizationID, DeviceID]: """ Raises: ValueError """ rvk_hash, raw_org_id, raw_device_id = slug.split("#") return rvk_hash, OrganizationID(raw_org_id), DeviceID(raw_device_id)
def _local_device_factory( base_device_id: Optional[str] = None, org: OrganizationFullData = coolorg, profile: Optional[UserProfile] = None, has_human_handle: bool = True, base_human_handle: Optional[str] = None, has_device_label: bool = True, base_device_label: Optional[str] = None, ): nonlocal count if not base_device_id: count += 1 base_device_id = f"user{count}@dev0" org_devices = devices[org.organization_id] device_id = DeviceID(base_device_id) assert not any(d for d in org_devices if d.device_id == device_id) if not has_device_label: assert base_device_label is None device_label = None elif not base_device_label: device_label = f"My {device_id.device_name} machine" else: device_label = base_device_label if not has_human_handle: assert base_human_handle is None human_handle = None elif base_human_handle: if isinstance(base_human_handle, HumanHandle): human_handle = base_human_handle else: match = re.match(r"(.*) <(.*)>", base_human_handle) if match: label, email = match.groups() else: label = base_human_handle email = f"{device_id.user_id}@example.com" human_handle = HumanHandle(email=email, label=label) else: name = device_id.user_id.capitalize() human_handle = HumanHandle( email=f"{device_id.user_id}@example.com", label=f"{name}y Mc{name}Face") parent_device = None try: # If the user already exists, we must retrieve it data parent_device = next(d for d in org_devices if d.user_id == device_id.user_id) if profile is not None and profile != parent_device.profile: raise ValueError( "profile is set but user already exists, with a different profile value." ) profile = parent_device.profile except StopIteration: profile = profile or UserProfile.STANDARD # Force each device to access the backend trough a different hostname so # tcp stream spy can switch offline certains while keeping the others online org_addr = addr_with_device_subdomain(org.addr, device_id) device = generate_new_device( org_addr, profile, device_id=device_id, human_handle=human_handle, device_label=device_label, ) if parent_device is not None: device = device.evolve( private_key=parent_device.private_key, user_manifest_id=parent_device.user_manifest_id, user_manifest_key=parent_device.user_manifest_key, ) org_devices.append(device) return device
"parsec://foo:42/org?action=claim_user&user_id=alice&token=123&rvk=dummy", "Invalid `rvk` param value", ), ], ) def test_backend_organization_claim_user_addr_bad_value( url, exc_msg, exported_verify_key): url = url.replace("<rvk>", exported_verify_key) with pytest.raises(ValueError) as exc: BackendOrganizationClaimUserAddr.from_url(url) assert str(exc.value) == exc_msg @pytest.mark.parametrize( "device_id,token", [(DeviceID("alice@dev"), "123"), (DeviceID("alice@dev"), None)], # Token is not mandatory ) def test_backend_organization_claim_device_addr_good(organization_addr, device_id, token): addr = BackendOrganizationClaimDeviceAddr.build(organization_addr, device_id, token) assert addr.hostname == organization_addr.hostname assert addr.port == organization_addr.port assert addr.use_ssl == organization_addr.use_ssl assert addr.organization_id == organization_addr.organization_id assert addr.root_verify_key == organization_addr.root_verify_key assert isinstance(addr.device_id, DeviceID) assert addr.device_id == device_id
async def do_create_new_user( self, author: LocalDevice, device_label: Optional[str], human_handle: Optional[HumanHandle], profile: UserProfile, ) -> None: device_id = DeviceID.new() try: now = pendulum_now() user_certificate = UserCertificateContent( author=author.device_id, timestamp=now, user_id=device_id.user_id, human_handle=human_handle, public_key=self._public_key, profile=profile, ) redacted_user_certificate = user_certificate.evolve( human_handle=None) device_certificate = DeviceCertificateContent( author=author.device_id, timestamp=now, device_id=device_id, device_label=device_label, verify_key=self._verify_key, ) redacted_device_certificate = device_certificate.evolve( device_label=None) user_certificate = user_certificate.dump_and_sign( author.signing_key) redacted_user_certificate = redacted_user_certificate.dump_and_sign( author.signing_key) device_certificate = device_certificate.dump_and_sign( author.signing_key) redacted_device_certificate = redacted_device_certificate.dump_and_sign( author.signing_key) except DataError as exc: raise InviteError( f"Cannot generate device certificate: {exc}") from exc rep = await self._cmds.user_create( user_certificate=user_certificate, device_certificate=device_certificate, redacted_user_certificate=redacted_user_certificate, redacted_device_certificate=redacted_device_certificate, ) if rep["status"] != "ok": raise InviteError(f"Cannot create device: {rep}") try: payload = InviteUserConfirmation( device_id=device_id, device_label=device_label, human_handle=human_handle, profile=profile, root_verify_key=author.root_verify_key, ).dump_and_encrypt(key=self._shared_secret_key) except DataError as exc: raise InviteError( "Cannot generate InviteUserConfirmation payload") from exc rep = await self._cmds.invite_4_greeter_communicate(token=self.token, payload=payload) _check_rep(rep, step_name="step 4 (confirmation exchange)") await self._cmds.invite_delete(token=self.token, reason=InvitationDeletedReason.FINISHED)
def test_max_bytes_size_device_id(data): with pytest.raises(ValueError): DeviceID(data)
def test_good_pattern_device_id(data): DeviceID(data)
from guardata.types import UUID4 from guardata.crypto import SecretKey, HashDigest from guardata.serde import fields, validate, post_load, OneOfSchema, pre_load from guardata.api.protocol import RealmRole, RealmRoleField, DeviceID from guardata.api.data.base import ( BaseData, BaseSchema, BaseAPISignedData, BaseSignedDataSchema, DataValidationError, ) from guardata.api.data.entry import EntryID, EntryIDField, EntryName, EntryNameField from enum import Enum LOCAL_AUTHOR_LEGACY_PLACEHOLDER = DeviceID( "LOCAL_AUTHOR_LEGACY_PLACEHOLDER@LOCAL_AUTHOR_LEGACY_PLACEHOLDER") class BlockID(UUID4): pass BlockIDField = fields.uuid_based_field_factory(BlockID) class ManifestType(Enum): FILE_MANIFEST = "file_manifest" FOLDER_MANIFEST = "folder_manifest" WORKSPACE_MANIFEST = "workspace_manifest" USER_MANIFEST = "user_manifest"