Example #1
0
    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"})
Example #2
0
    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,
            )
Example #3
0
    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"})
Example #4
0
    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"})
Example #5
0
    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"})
Example #6
0
    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"})
Example #7
0
    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"}
Example #8
0
    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"})
Example #9
0
    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"})