Example #1
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": f"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": f"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 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 #2
0
    async def api_realm_create(self, client_ctx, msg):
        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": f"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":
                f"Initial realm role certificate must be self-signed.",
            }
        if granted_role.role != RealmRole.OWNER:
            return {
                "status": "invalid_data",
                "reason":
                f"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"})
Example #3
0
    async def api_realm_update_roles(self, client_ctx, msg):
        # An OUTSIDER is allowed to create a realm (given he needs to have one
        # to store it user manifest). However he cannot be MANAGER or OWNER in
        # a shared realm as well.
        # Hence the only way for him to be OWNER is to create a realm, and in
        # this case he cannot share this realm with anyone.
        # On top of that, we don't have to fetch the user profile from the
        # database before checking it given it cannot be updated.
        if client_ctx.profile == UserProfile.OUTSIDER:
            return {
                "status": "not_allowed",
                "reason": "Outsider user cannot share realm"
            }

        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": f"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": f"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 #4
0
    async def _load_realm_role_certificates(self,
                                            realm_id: Optional[EntryID] = None
                                            ):
        rep = await self._backend_cmds("realm_get_role_certificates", realm_id
                                       or self.workspace_id)
        if rep["status"] == "not_allowed":
            # Seems we lost the access to the realm
            raise FSWorkspaceNoReadAccess(
                "Cannot get workspace roles: no read access")
        elif rep["status"] != "ok":
            raise FSError(
                f"Cannot retrieve workspace roles: `{rep['status']}`")

        try:
            # Must read unverified certificates to access metadata
            unsecure_certifs = sorted(
                [(RealmRoleCertificateContent.unsecure_load(uv_role), uv_role)
                 for uv_role in rep["certificates"]],
                key=lambda x: x[0].timestamp,
            )

            current_roles = {}
            owner_only = (RealmRole.OWNER, )
            owner_or_manager = (RealmRole.OWNER, RealmRole.MANAGER)

            # Now verify each certif
            for unsecure_certif, raw_certif in unsecure_certifs:
                author = await self.remote_device_manager.get_device(
                    unsecure_certif.author)

                RealmRoleCertificateContent.verify_and_load(
                    raw_certif,
                    author_verify_key=author.verify_key,
                    expected_author=author.device_id,
                )

                # Make sure author had the right to do this
                existing_user_role = current_roles.get(unsecure_certif.user_id)
                if not current_roles and unsecure_certif.user_id == author.device_id.user_id:
                    # First user is autosigned
                    needed_roles = (None, )
                elif (existing_user_role in owner_or_manager
                      or unsecure_certif.role in owner_or_manager):
                    needed_roles = owner_only
                else:
                    needed_roles = owner_or_manager
                if current_roles.get(
                        unsecure_certif.author.user_id) not in needed_roles:
                    raise FSError(
                        f"Invalid realm role certificates: "
                        f"{unsecure_certif.author} has not right to give "
                        f"{unsecure_certif.role} role to {unsecure_certif.user_id} "
                        f"on {unsecure_certif.timestamp}")

                if unsecure_certif.role is None:
                    current_roles.pop(unsecure_certif.user_id, None)
                else:
                    current_roles[
                        unsecure_certif.user_id] = unsecure_certif.role

        # Decryption error
        except DataError as exc:
            raise FSError(f"Invalid realm role certificates: {exc}") from exc

        # Now unsecure_certifs is no longer unsecure we have valided it items
        return [c for c, _ in unsecure_certifs], current_roles
Example #5
0
    async def api_realm_update_roles(self, client_ctx, msg):
        """
        This API call, when successful, performs the writing of a new role certificate to the database.
        Before adding new entries, extra care should be taken in order to guarantee the consistency in
        the ordering of the different timestamps stored in the database.

        In particular, the backend server performs the following checks:
        - The certificate must have a timestamp strictly greater than the last certificate for
          the same user in the same realm.
        - If the certificate corresponds to a role without write rights, its timestamp should
          be strictly greater than the timestamp of the last vlob update performed by the
          corresponding user in the corresponding realm.
        - If the certificate corresponds to a role without management rights, its timestamp should
          be strictly greater than the timestamp of the last role certificate uploaded by the
          corresponding user in the corresponding realm.

        If one of those constraints is not satisfied, an error is returned with the status
        `require_greater_timestamp` indicating to the client that it should craft a new certificate
        with a timestamp strictly greater than the timestamp provided with the error.

        The `api_vlob_create` and `api_vlob_update` calls also perform similar checks.
        """
        # An OUTSIDER is allowed to create a realm (given he needs to have one
        # to store it user manifest). However he cannot be MANAGER or OWNER in
        # a shared realm as well.
        # Hence the only way for him to be OWNER is to create a realm, and in
        # this case he cannot share this realm with anyone.
        # On top of that, we don't have to fetch the user profile from the
        # database before checking it given it cannot be updated.
        if client_ctx.profile == UserProfile.OUTSIDER:
            return {
                "status": "not_allowed",
                "reason": "Outsider user cannot share realm"
            }

        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 realm_update_roles_serializer.timestamp_out_of_ballpark_rep_dump(
                backend_timestamp=now, client_timestamp=data.timestamp)

        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": f"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 RealmRoleRequireGreaterTimestampError as exc:
            return realm_update_roles_serializer.require_greater_timestamp_rep_dump(
                exc.strictly_greater_than)

        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"})