async def api_vlob_update(self, client_ctx, msg): msg = vlob_update_serializer.req_load(msg) now = pendulum.now() if not timestamps_in_the_ballpark(msg["timestamp"], now): return {"status": "bad_timestamp", "reason": "Timestamp is out of date."} try: await self.update(client_ctx.organization_id, client_ctx.device_id, **msg) except VlobNotFoundError as exc: return vlob_update_serializer.rep_dump({"status": "not_found", "reason": str(exc)}) except VlobAccessError: return vlob_update_serializer.rep_dump({"status": "not_allowed"}) except VlobVersionError: return vlob_update_serializer.rep_dump({"status": "bad_version"}) except VlobTimestampError: return vlob_update_serializer.rep_dump({"status": "bad_timestamp"}) except VlobEncryptionRevisionError: return vlob_create_serializer.rep_dump({"status": "bad_encryption_revision"}) except VlobInMaintenanceError: return vlob_update_serializer.rep_dump({"status": "in_maintenance"}) return vlob_update_serializer.rep_dump({"status": "ok"})
async def upload_manifest(self, entry_id: EntryID, manifest: BaseRemoteManifest): """ Raises: FSError FSRemoteSyncError FSBackendOfflineError FSWorkspaceInMaintenance FSBadEncryptionRevision """ assert manifest.author == self.device.device_id assert timestamps_in_the_ballpark(manifest.timestamp, pendulum_now()) workspace_entry = self.get_workspace_entry() try: ciphered = manifest.dump_sign_and_encrypt( key=workspace_entry.key, author_signkey=self.device.signing_key) except DataError as exc: raise FSError(f"Cannot encrypt vlob: {exc}") from exc # Upload the vlob if manifest.version == 1: await self._vlob_create(workspace_entry.encryption_revision, entry_id, ciphered, manifest.timestamp) else: await self._vlob_update( workspace_entry.encryption_revision, entry_id, ciphered, manifest.timestamp, manifest.version, )
async def api_user_revoke(self, client_ctx, msg): if client_ctx.profile != UserProfile.ADMIN: return { "status": "not_allowed", "reason": f"User `{client_ctx.device_id.user_id}` is not admin", } msg = user_revoke_serializer.req_load(msg) try: data = RevokedUserCertificateContent.verify_and_load( msg["revoked_user_certificate"], author_verify_key=client_ctx.verify_key, expected_author=client_ctx.device_id, ) except DataError as exc: return { "status": "invalid_certification", "reason": f"Invalid certification data ({exc}).", } if not timestamps_in_the_ballpark(data.timestamp, pendulum.now()): return { "status": "invalid_certification", "reason": "Invalid timestamp in certification.", } if data.user_id == client_ctx.user_id: return { "status": "not_allowed", "reason": "Cannot do self-revocation" } try: await self.revoke_user( organization_id=client_ctx.organization_id, user_id=data.user_id, revoked_user_certificate=msg["revoked_user_certificate"], revoked_user_certifier=data.author, revoked_on=data.timestamp, ) except UserNotFoundError: return {"status": "not_found"} except UserAlreadyRevokedError: return { "status": "already_revoked", "reason": f"User `{data.user_id}` already revoked" } return user_revoke_serializer.rep_dump({"status": "ok"})
async def api_realm_start_reencryption_maintenance(self, client_ctx, msg): msg = realm_start_reencryption_maintenance_serializer.req_load(msg) now = pendulum.now() if not timestamps_in_the_ballpark(msg["timestamp"], now): return { "status": "bad_timestamp", "reason": "Timestamp is out of date." } try: await self.start_reencryption_maintenance( client_ctx.organization_id, client_ctx.device_id, **msg) except RealmAccessError: return realm_start_reencryption_maintenance_serializer.rep_dump( {"status": "not_allowed"}) except RealmNotFoundError as exc: return realm_start_reencryption_maintenance_serializer.rep_dump({ "status": "not_found", "reason": str(exc) }) except RealmEncryptionRevisionError: return realm_start_reencryption_maintenance_serializer.rep_dump( {"status": "bad_encryption_revision"}) except RealmParticipantsMismatchError as exc: return realm_start_reencryption_maintenance_serializer.rep_dump({ "status": "participants_mismatch", "reason": str(exc) }) except RealmMaintenanceError as exc: return realm_start_reencryption_maintenance_serializer.rep_dump({ "status": "maintenance_error", "reason": str(exc) }) except RealmInMaintenanceError: return realm_start_reencryption_maintenance_serializer.rep_dump( {"status": "in_maintenance"}) return realm_start_reencryption_maintenance_serializer.rep_dump( {"status": "ok"})
async def api_organization_bootstrap(self, client_ctx, msg): msg = apiv1_organization_bootstrap_serializer.req_load(msg) bootstrap_token = msg["bootstrap_token"] root_verify_key = msg["root_verify_key"] try: u_data = UserCertificateContent.verify_and_load( msg["user_certificate"], author_verify_key=root_verify_key, expected_author=None) d_data = DeviceCertificateContent.verify_and_load( msg["device_certificate"], author_verify_key=root_verify_key, expected_author=None) ru_data = rd_data = None if "redacted_user_certificate" in msg: ru_data = UserCertificateContent.verify_and_load( msg["redacted_user_certificate"], author_verify_key=root_verify_key, expected_author=None, ) if "redacted_device_certificate" in msg: rd_data = DeviceCertificateContent.verify_and_load( msg["redacted_device_certificate"], author_verify_key=root_verify_key, expected_author=None, ) except DataError as exc: return { "status": "invalid_certification", "reason": f"Invalid certification data ({exc}).", } if u_data.profile != UserProfile.ADMIN: return { "status": "invalid_data", "reason": "Bootstrapping user must have admin profile.", } if u_data.timestamp != d_data.timestamp: return { "status": "invalid_data", "reason": "Device and user certificates must have the same timestamp.", } if u_data.user_id != d_data.device_id.user_id: return { "status": "invalid_data", "reason": "Device and user must have the same user ID.", } now = pendulum.now() if not timestamps_in_the_ballpark(u_data.timestamp, now): return { "status": "invalid_certification", "reason": "Invalid timestamp in certification.", } if ru_data: if ru_data.evolve(human_handle=u_data.human_handle) != u_data: return { "status": "invalid_data", "reason": "Redacted User certificate differs from User certificate.", } if ru_data.human_handle: return { "status": "invalid_data", "reason": "Redacted User certificate must not contain a human_handle field.", } if rd_data: if rd_data.evolve(device_label=d_data.device_label) != d_data: return { "status": "invalid_data", "reason": "Redacted Device certificate differs from Device certificate.", } if rd_data.device_label: return { "status": "invalid_data", "reason": "Redacted Device certificate must not contain a device_label field.", } if (rd_data and not ru_data) or (ru_data and not rd_data): return { "status": "invalid_data", "reason": "Redacted user&device certificate muste be provided together", } user = User( user_id=u_data.user_id, human_handle=u_data.human_handle, profile=u_data.profile, user_certificate=msg["user_certificate"], redacted_user_certificate=msg.get("redacted_user_certificate", msg["user_certificate"]), user_certifier=u_data.author, created_on=u_data.timestamp, ) first_device = Device( device_id=d_data.device_id, device_label=d_data.device_label, device_certificate=msg["device_certificate"], redacted_device_certificate=msg.get("redacted_device_certificate", msg["device_certificate"]), device_certifier=d_data.author, created_on=d_data.timestamp, ) try: await self.bootstrap(client_ctx.organization_id, user, first_device, bootstrap_token, root_verify_key) except OrganizationAlreadyBootstrappedError: return {"status": "already_bootstrapped"} except (OrganizationNotFoundError, OrganizationInvalidBootstrapTokenError): return {"status": "not_found"} # Note: we let OrganizationFirstUserCreationError bobbles up given # it should not occurs under normal circumstances # Finally notify webhook await self.webhooks.on_organization_bootstrap( organization_id=client_ctx.organization_id, device_id=first_device.device_id, device_label=first_device.device_label, human_email=user.human_handle.email if user.human_handle else None, human_label=user.human_handle.label if user.human_handle else None, ) return apiv1_organization_bootstrap_serializer.rep_dump( {"status": "ok"})
async def api_device_create(self, client_ctx, msg): msg = device_create_serializer.req_load(msg) try: data = DeviceCertificateContent.verify_and_load( msg["device_certificate"], author_verify_key=client_ctx.verify_key, expected_author=client_ctx.device_id, ) redacted_data = None if msg["redacted_device_certificate"]: redacted_data = DeviceCertificateContent.verify_and_load( msg["redacted_device_certificate"], author_verify_key=client_ctx.verify_key, expected_author=client_ctx.device_id, ) except DataError as exc: return { "status": "invalid_certification", "reason": f"Invalid certification data ({exc}).", } if not timestamps_in_the_ballpark(data.timestamp, pendulum.now()): return { "status": "invalid_certification", "reason": "Invalid timestamp in certification.", } if data.device_id.user_id != client_ctx.user_id: return { "status": "bad_user_id", "reason": "Device must be handled by it own user." } if redacted_data: if redacted_data.evolve(device_label=data.device_label) != data: return { "status": "invalid_data", "reason": "Redacted Device certificate differs from Device certificate.", } if redacted_data.device_label: return { "status": "invalid_data", "reason": "Redacted Device certificate must not contain a device_label field.", } try: device = Device( device_id=data.device_id, device_label=data.device_label, device_certificate=msg["device_certificate"], redacted_device_certificate=msg["redacted_device_certificate"] or msg["device_certificate"], device_certifier=data.author, created_on=data.timestamp, ) await self.create_device(client_ctx.organization_id, device) except UserAlreadyExistsError as exc: return {"status": "already_exists", "reason": str(exc)} return device_create_serializer.rep_dump({"status": "ok"})
async def _api_user_create(self, client_ctx, msg): try: d_data = DeviceCertificateContent.verify_and_load( msg["device_certificate"], author_verify_key=client_ctx.verify_key, expected_author=client_ctx.device_id, ) u_data = UserCertificateContent.verify_and_load( msg["user_certificate"], author_verify_key=client_ctx.verify_key, expected_author=client_ctx.device_id, ) ru_data = UserCertificateContent.verify_and_load( msg["redacted_user_certificate"], author_verify_key=client_ctx.verify_key, expected_author=client_ctx.device_id, ) rd_data = DeviceCertificateContent.verify_and_load( msg["redacted_device_certificate"], author_verify_key=client_ctx.verify_key, expected_author=client_ctx.device_id, ) except DataError as exc: return { "status": "invalid_certification", "reason": f"Invalid certification data ({exc}).", } if u_data.timestamp != d_data.timestamp: return { "status": "invalid_data", "reason": "Device and User certificates must have the same timestamp.", } now = pendulum.now() if not timestamps_in_the_ballpark(u_data.timestamp, now): return { "status": "invalid_certification", "reason": "Invalid timestamp in certificate.", } if u_data.user_id != d_data.device_id.user_id: return { "status": "invalid_data", "reason": "Device and User must have the same user ID.", } if ru_data.evolve(human_handle=u_data.human_handle) != u_data: return { "status": "invalid_data", "reason": "Redacted User certificate differs from User certificate.", } if ru_data.human_handle: return { "status": "invalid_data", "reason": "Redacted User certificate must not contain a human_handle field.", } if rd_data.evolve(device_label=d_data.device_label) != d_data: return { "status": "invalid_data", "reason": "Redacted Device certificate differs from Device certificate.", } if rd_data.device_label: return { "status": "invalid_data", "reason": "Redacted Device certificate must not contain a device_label field.", } try: user = User( user_id=u_data.user_id, human_handle=u_data.human_handle, profile=u_data.profile, user_certificate=msg["user_certificate"], redacted_user_certificate=msg["redacted_user_certificate"] or msg["user_certificate"], user_certifier=u_data.author, created_on=u_data.timestamp, ) first_device = Device( device_id=d_data.device_id, device_label=d_data.device_label, device_certificate=msg["device_certificate"], redacted_device_certificate=msg["redacted_device_certificate"] or msg["device_certificate"], device_certifier=d_data.author, created_on=d_data.timestamp, ) await self.create_user(client_ctx.organization_id, user, first_device) except UserAlreadyExistsError as exc: return {"status": "already_exists", "reason": str(exc)} return {"status": "ok"}
async def api_realm_update_roles(self, client_ctx, msg): msg = realm_update_roles_serializer.req_load(msg) try: data = RealmRoleCertificateContent.verify_and_load( msg["role_certificate"], author_verify_key=client_ctx.verify_key, expected_author=client_ctx.device_id, ) except DataError as exc: return { "status": "invalid_certification", "reason": f"Invalid certification data ({exc}).", } now = pendulum.now() if not timestamps_in_the_ballpark(data.timestamp, now): return { "status": "invalid_certification", "reason": "Invalid timestamp in certification.", } granted_role = RealmGrantedRole( certificate=msg["role_certificate"], realm_id=data.realm_id, user_id=data.user_id, role=data.role, granted_by=data.author, granted_on=data.timestamp, ) if granted_role.granted_by.user_id == granted_role.user_id: return { "status": "invalid_data", "reason": "Realm role certificate cannot be self-signed.", } try: await self.update_roles(client_ctx.organization_id, granted_role, msg["recipient_message"]) except RealmRoleAlreadyGranted: return realm_update_roles_serializer.rep_dump( {"status": "already_granted"}) except RealmAccessError: return realm_update_roles_serializer.rep_dump( {"status": "not_allowed"}) except RealmIncompatibleProfileError as exc: return realm_update_roles_serializer.rep_dump({ "status": "incompatible_profile", "reason": str(exc) }) except RealmNotFoundError as exc: return realm_update_roles_serializer.rep_dump({ "status": "not_found", "reason": str(exc) }) except RealmInMaintenanceError: return realm_update_roles_serializer.rep_dump( {"status": "in_maintenance"}) return realm_update_roles_serializer.rep_dump({"status": "ok"})
async def api_realm_create(self, client_ctx, msg): if client_ctx.profile == UserProfile.OUTSIDER: return { "status": "not_allowed", "reason": "Outsider user cannot create realm" } msg = realm_create_serializer.req_load(msg) try: data = RealmRoleCertificateContent.verify_and_load( msg["role_certificate"], author_verify_key=client_ctx.verify_key, expected_author=client_ctx.device_id, ) except DataError as exc: return { "status": "invalid_certification", "reason": f"Invalid certification data ({exc}).", } now = pendulum.now() if not timestamps_in_the_ballpark(data.timestamp, now): return { "status": "invalid_certification", "reason": "Invalid timestamp in certification.", } granted_role = RealmGrantedRole( certificate=msg["role_certificate"], realm_id=data.realm_id, user_id=data.user_id, role=data.role, granted_by=data.author, granted_on=data.timestamp, ) if granted_role.granted_by.user_id != granted_role.user_id: return { "status": "invalid_data", "reason": "Initial realm role certificate must be self-signed.", } if granted_role.role != RealmRole.OWNER: return { "status": "invalid_data", "reason": "Initial realm role certificate must set OWNER role.", } try: await self.create(client_ctx.organization_id, granted_role) except RealmNotFoundError as exc: return realm_create_serializer.rep_dump({ "status": "not_found", "reason": str(exc) }) except RealmAlreadyExistsError: return realm_create_serializer.rep_dump( {"status": "already_exists"}) return realm_create_serializer.rep_dump({"status": "ok"})