Exemple #1
0
        def callback(_, pdu):
            with logcontext.PreserveLoggingContext(ctx):
                if not check_event_content_hash(pdu):
                    # let's try to distinguish between failures because the event was
                    # redacted (which are somewhat expected) vs actual ball-tampering
                    # incidents.
                    #
                    # This is just a heuristic, so we just assume that if the keys are
                    # about the same between the redacted and received events, then the
                    # received event was probably a redacted copy (but we then use our
                    # *actual* redacted copy to be on the safe side.)
                    redacted_event = prune_event(pdu)
                    if (set(redacted_event.keys()) == set(pdu.keys())
                            and set(six.iterkeys(redacted_event.content))
                            == set(six.iterkeys(pdu.content))):
                        logger.info(
                            "Event %s seems to have been redacted; using our redacted "
                            "copy",
                            pdu.event_id,
                        )
                    else:
                        logger.warning(
                            "Event %s content has been tampered, redacting",
                            pdu.event_id,
                            pdu.get_pdu_json(),
                        )
                    return redacted_event

                if self.spam_checker.check_event_for_spam(pdu):
                    logger.warn("Event contains spam, redacting %s: %s",
                                pdu.event_id, pdu.get_pdu_json())
                    return prune_event(pdu)

                return pdu
Exemple #2
0
    async def _check_sigs_and_hash(
        self, room_version: RoomVersion, pdu: EventBase
    ) -> EventBase:
        """Checks that event is correctly signed by the sending server.

        Also checks the content hash, and redacts the event if there is a mismatch.

        Also runs the event through the spam checker; if it fails, redacts the event
        and flags it as soft-failed.

        Args:
            room_version: The room version of the PDU
            pdu: the event to be checked

        Returns:
              * the original event if the checks pass
              * a redacted version of the event (if the signature
                matched but the hash did not). In this case a warning will be logged.

        Raises:
          InvalidEventSignatureError if the signature check failed. Nothing
             will be logged in this case.
        """
        await _check_sigs_on_pdu(self.keyring, room_version, pdu)

        if not check_event_content_hash(pdu):
            # let's try to distinguish between failures because the event was
            # redacted (which are somewhat expected) vs actual ball-tampering
            # incidents.
            #
            # This is just a heuristic, so we just assume that if the keys are
            # about the same between the redacted and received events, then the
            # received event was probably a redacted copy (but we then use our
            # *actual* redacted copy to be on the safe side.)
            redacted_event = prune_event(pdu)
            if set(redacted_event.keys()) == set(pdu.keys()) and set(
                redacted_event.content.keys()
            ) == set(pdu.content.keys()):
                logger.debug(
                    "Event %s seems to have been redacted; using our redacted copy",
                    pdu.event_id,
                )
            else:
                logger.warning(
                    "Event %s content has been tampered, redacting",
                    pdu.event_id,
                )
            return redacted_event

        spam_check = await self.spam_checker.check_event_for_spam(pdu)

        if spam_check != self.spam_checker.NOT_SPAM:
            logger.warning("Event contains spam, soft-failing %s", pdu.event_id)
            # we redact (to save disk space) as well as soft-failing (to stop
            # using the event in prev_events).
            redacted_event = prune_event(pdu)
            redacted_event.internal_metadata.soft_failed = True
            return redacted_event

        return pdu
Exemple #3
0
    async def _check_sigs_and_hash(self, room_version: RoomVersion,
                                   pdu: EventBase) -> EventBase:
        """Checks that event is correctly signed by the sending server.

        Args:
            room_version: The room version of the PDU
            pdu: the event to be checked

        Returns:
              * the original event if the checks pass
              * a redacted version of the event (if the signature
                matched but the hash did not)
              * throws a SynapseError if the signature check failed."""
        try:
            await _check_sigs_on_pdu(self.keyring, room_version, pdu)
        except SynapseError as e:
            logger.warning(
                "Signature check failed for %s: %s",
                pdu.event_id,
                e,
            )
            raise

        if not check_event_content_hash(pdu):
            # let's try to distinguish between failures because the event was
            # redacted (which are somewhat expected) vs actual ball-tampering
            # incidents.
            #
            # This is just a heuristic, so we just assume that if the keys are
            # about the same between the redacted and received events, then the
            # received event was probably a redacted copy (but we then use our
            # *actual* redacted copy to be on the safe side.)
            redacted_event = prune_event(pdu)
            if set(redacted_event.keys()) == set(pdu.keys()) and set(
                    redacted_event.content.keys()) == set(pdu.content.keys()):
                logger.info(
                    "Event %s seems to have been redacted; using our redacted copy",
                    pdu.event_id,
                )
            else:
                logger.warning(
                    "Event %s content has been tampered, redacting",
                    pdu.event_id,
                )
            return redacted_event

        result = await self.spam_checker.check_event_for_spam(pdu)

        if result:
            logger.warning("Event contains spam, soft-failing %s",
                           pdu.event_id)
            # we redact (to save disk space) as well as soft-failing (to stop
            # using the event in prev_events).
            redacted_event = prune_event(pdu)
            redacted_event.internal_metadata.soft_failed = True
            return redacted_event

        return pdu
        def callback(_, pdu):
            with logcontext.PreserveLoggingContext(ctx):
                if not check_event_content_hash(pdu):
                    logger.warn(
                        "Event content has been tampered, redacting %s: %s",
                        pdu.event_id, pdu.get_pdu_json())
                    return prune_event(pdu)

                if self.spam_checker.check_event_for_spam(pdu):
                    logger.warn("Event contains spam, redacting %s: %s",
                                pdu.event_id, pdu.get_pdu_json())
                    return prune_event(pdu)

                return pdu
Exemple #5
0
    def _get_event_from_row_txn(self,
                                txn,
                                internal_metadata,
                                js,
                                redacted,
                                check_redacted=True,
                                get_prev_content=False):
        d = json.loads(js)
        internal_metadata = json.loads(internal_metadata)

        ev = FrozenEvent(d, internal_metadata_dict=internal_metadata)

        if check_redacted and redacted:
            ev = prune_event(ev)

            ev.unsigned["redacted_by"] = redacted
            # Get the redaction event.

            because = self._get_event_txn(txn, redacted, check_redacted=False)

            if because:
                ev.unsigned["redacted_because"] = because

        if get_prev_content and "replaces_state" in ev.unsigned:
            prev = self._get_event_txn(
                txn,
                ev.unsigned["replaces_state"],
                get_prev_content=False,
            )
            if prev:
                ev.unsigned["prev_content"] = prev.get_dict()["content"]

        return ev
Exemple #6
0
        def redact_disallowed(event, state):
            if not state:
                return event

            history = state.get((EventTypes.RoomHistoryVisibility, ''), None)
            if history:
                visibility = history.content.get("history_visibility",
                                                 "shared")
                if visibility in ["invited", "joined"]:
                    # We now loop through all state events looking for
                    # membership states for the requesting server to determine
                    # if the server is either in the room or has been invited
                    # into the room.
                    for ev in state.values():
                        if ev.type != EventTypes.Member:
                            continue
                        try:
                            domain = UserID.from_string(ev.state_key).domain
                        except:
                            continue

                        if domain != server_name:
                            continue

                        memtype = ev.membership
                        if memtype == Membership.JOIN:
                            return event
                        elif memtype == Membership.INVITE:
                            if visibility == "invited":
                                return event
                    else:
                        return prune_event(event)

            return event
    def _check_sigs_and_hash(self, pdu):
        """Throws a SynapseError if the PDU does not have the correct
        signatures.

        Returns:
            FrozenEvent: Either the given event or it redacted if it failed the
            content hash check.
        """
        # Check signatures are correct.
        redacted_event = prune_event(pdu)
        redacted_pdu_json = redacted_event.get_pdu_json()

        try:
            yield self.keyring.verify_json_for_server(
                pdu.origin, redacted_pdu_json
            )
        except SynapseError:
            logger.warn(
                "Signature check failed for %s redacted to %s",
                encode_canonical_json(pdu.get_pdu_json()),
                encode_canonical_json(redacted_pdu_json),
            )
            raise

        if not check_event_content_hash(pdu):
            logger.warn(
                "Event content has been tampered, redacting %s, %s",
                pdu.event_id, encode_canonical_json(pdu.get_dict())
            )
            defer.returnValue(redacted_event)

        defer.returnValue(pdu)
Exemple #8
0
        def redact_disallowed(event, state):
            if not state:
                return event

            history = state.get((EventTypes.RoomHistoryVisibility, ''), None)
            if history:
                visibility = history.content.get("history_visibility", "shared")
                if visibility in ["invited", "joined"]:
                    # We now loop through all state events looking for
                    # membership states for the requesting server to determine
                    # if the server is either in the room or has been invited
                    # into the room.
                    for ev in state.values():
                        if ev.type != EventTypes.Member:
                            continue
                        try:
                            domain = UserID.from_string(ev.state_key).domain
                        except:
                            continue

                        if domain != server_name:
                            continue

                        memtype = ev.membership
                        if memtype == Membership.JOIN:
                            return event
                        elif memtype == Membership.INVITE:
                            if visibility == "invited":
                                return event
                    else:
                        return prune_event(event)

            return event
Exemple #9
0
    def _check_sigs_and_hash(self, pdu):
        """Throws a SynapseError if the PDU does not have the correct
        signatures.

        Returns:
            FrozenEvent: Either the given event or it redacted if it failed the
            content hash check.
        """
        # Check signatures are correct.
        redacted_event = prune_event(pdu)
        redacted_pdu_json = redacted_event.get_pdu_json()

        try:
            yield self.keyring.verify_json_for_server(pdu.origin,
                                                      redacted_pdu_json)
        except SynapseError:
            logger.warn(
                "Signature check failed for %s redacted to %s",
                encode_canonical_json(pdu.get_pdu_json()),
                encode_canonical_json(redacted_pdu_json),
            )
            raise

        if not check_event_content_hash(pdu):
            logger.warn("Event content has been tampered, redacting %s, %s",
                        pdu.event_id, encode_canonical_json(pdu.get_dict()))
            defer.returnValue(redacted_event)

        defer.returnValue(pdu)
Exemple #10
0
    def _get_event_from_row(self,
                            internal_metadata,
                            js,
                            redacted,
                            check_redacted=True,
                            get_prev_content=False,
                            rejected_reason=None):
        d = json.loads(js)
        internal_metadata = json.loads(internal_metadata)

        if rejected_reason:
            rejected_reason = yield self._simple_select_one_onecol(
                table="rejections",
                keyvalues={"event_id": rejected_reason},
                retcol="reason",
                desc="_get_event_from_row",
            )

        ev = FrozenEvent(
            d,
            internal_metadata_dict=internal_metadata,
            rejected_reason=rejected_reason,
        )

        if check_redacted and redacted:
            ev = prune_event(ev)

            redaction_id = yield self._simple_select_one_onecol(
                table="redactions",
                keyvalues={"redacts": ev.event_id},
                retcol="event_id",
                desc="_get_event_from_row",
            )

            ev.unsigned["redacted_by"] = redaction_id
            # Get the redaction event.

            because = yield self.get_event(
                redaction_id,
                check_redacted=False,
                allow_none=True,
            )

            if because:
                ev.unsigned["redacted_because"] = because

        if get_prev_content and "replaces_state" in ev.unsigned:
            prev = yield self.get_event(
                ev.unsigned["replaces_state"],
                get_prev_content=False,
                allow_none=True,
            )
            if prev:
                ev.unsigned["prev_content"] = prev.get_dict()["content"]

        self._get_event_cache.prefill(ev.event_id, check_redacted,
                                      get_prev_content, ev)

        defer.returnValue(ev)
Exemple #11
0
    def _get_event_from_row_txn(self,
                                txn,
                                internal_metadata,
                                js,
                                redacted,
                                check_redacted=True,
                                get_prev_content=False,
                                rejected_reason=None):
        d = json.loads(js)
        internal_metadata = json.loads(internal_metadata)

        if rejected_reason:
            rejected_reason = self._simple_select_one_onecol_txn(
                txn,
                table="rejections",
                keyvalues={"event_id": rejected_reason},
                retcol="reason",
            )

        ev = FrozenEvent(
            d,
            internal_metadata_dict=internal_metadata,
            rejected_reason=rejected_reason,
        )

        if check_redacted and redacted:
            ev = prune_event(ev)

            redaction_id = self._simple_select_one_onecol_txn(
                txn,
                table="redactions",
                keyvalues={"redacts": ev.event_id},
                retcol="event_id",
            )

            ev.unsigned["redacted_by"] = redaction_id
            # Get the redaction event.

            because = self._get_event_txn(txn,
                                          redaction_id,
                                          check_redacted=False)

            if because:
                ev.unsigned["redacted_because"] = because

        if get_prev_content and "replaces_state" in ev.unsigned:
            prev = self._get_event_txn(
                txn,
                ev.unsigned["replaces_state"],
                get_prev_content=False,
            )
            if prev:
                ev.unsigned["prev_content"] = prev.content
                ev.unsigned["prev_sender"] = prev.sender

        self._get_event_cache.prefill(
            (ev.event_id, check_redacted, get_prev_content), ev)

        return ev
Exemple #12
0
    def _get_event_from_row(self, internal_metadata, js, redacted,
                            check_redacted=True, get_prev_content=False,
                            rejected_reason=None):
        d = json.loads(js)
        internal_metadata = json.loads(internal_metadata)

        if rejected_reason:
            rejected_reason = yield self._simple_select_one_onecol(
                table="rejections",
                keyvalues={"event_id": rejected_reason},
                retcol="reason",
                desc="_get_event_from_row",
            )

        ev = FrozenEvent(
            d,
            internal_metadata_dict=internal_metadata,
            rejected_reason=rejected_reason,
        )

        if check_redacted and redacted:
            ev = prune_event(ev)

            redaction_id = yield self._simple_select_one_onecol(
                table="redactions",
                keyvalues={"redacts": ev.event_id},
                retcol="event_id",
                desc="_get_event_from_row",
            )

            ev.unsigned["redacted_by"] = redaction_id
            # Get the redaction event.

            because = yield self.get_event(
                redaction_id,
                check_redacted=False,
                allow_none=True,
            )

            if because:
                # It's fine to do add the event directly, since get_pdu_json
                # will serialise this field correctly
                ev.unsigned["redacted_because"] = because

        if get_prev_content and "replaces_state" in ev.unsigned:
            prev = yield self.get_event(
                ev.unsigned["replaces_state"],
                get_prev_content=False,
                allow_none=True,
            )
            if prev:
                ev.unsigned["prev_content"] = prev.content
                ev.unsigned["prev_sender"] = prev.sender

        self._get_event_cache.prefill(
            (ev.event_id, check_redacted, get_prev_content), ev
        )

        defer.returnValue(ev)
Exemple #13
0
    def redact_disallowed(event, state):
        # if the sender has been gdpr17ed, always return a redacted
        # copy of the event.
        if erased_senders[event.sender]:
            logger.info(
                "Sender of %s has been erased, redacting",
                event.event_id,
            )
            return prune_event(event)

        # state will be None if we decided we didn't need to filter by
        # room membership.
        if not state:
            return event

        history = state.get((EventTypes.RoomHistoryVisibility, ''), None)
        if history:
            visibility = history.content.get("history_visibility", "shared")
            if visibility in ["invited", "joined"]:
                # We now loop through all state events looking for
                # membership states for the requesting server to determine
                # if the server is either in the room or has been invited
                # into the room.
                for ev in itervalues(state):
                    if ev.type != EventTypes.Member:
                        continue
                    try:
                        domain = get_domain_from_id(ev.state_key)
                    except Exception:
                        continue

                    if domain != server_name:
                        continue

                    memtype = ev.membership
                    if memtype == Membership.JOIN:
                        return event
                    elif memtype == Membership.INVITE:
                        if visibility == "invited":
                            return event
                else:
                    # server has no users in the room: redact
                    return prune_event(event)

        return event
Exemple #14
0
    def redact_disallowed(event, state):
        # if the sender has been gdpr17ed, always return a redacted
        # copy of the event.
        if erased_senders[event.sender]:
            logger.info(
                "Sender of %s has been erased, redacting",
                event.event_id,
            )
            return prune_event(event)

        # state will be None if we decided we didn't need to filter by
        # room membership.
        if not state:
            return event

        history = state.get((EventTypes.RoomHistoryVisibility, ''), None)
        if history:
            visibility = history.content.get("history_visibility", "shared")
            if visibility in ["invited", "joined"]:
                # We now loop through all state events looking for
                # membership states for the requesting server to determine
                # if the server is either in the room or has been invited
                # into the room.
                for ev in itervalues(state):
                    if ev.type != EventTypes.Member:
                        continue
                    try:
                        domain = get_domain_from_id(ev.state_key)
                    except Exception:
                        continue

                    if domain != server_name:
                        continue

                    memtype = ev.membership
                    if memtype == Membership.JOIN:
                        return event
                    elif memtype == Membership.INVITE:
                        if visibility == "invited":
                            return event
                else:
                    # server has no users in the room: redact
                    return prune_event(event)

        return event
Exemple #15
0
def compute_event_signature(event, signature_name, signing_key):
    tmp_event = prune_event(event)
    redact_json = tmp_event.get_pdu_json()
    redact_json.pop("age_ts", None)
    redact_json.pop("unsigned", None)
    logger.debug("Signing event: %s", encode_canonical_json(redact_json))
    redact_json = sign_json(redact_json, signature_name, signing_key)
    logger.debug("Signed event: %s", encode_canonical_json(redact_json))
    return redact_json["signatures"]
Exemple #16
0
    def _get_event_from_row(self,
                            internal_metadata,
                            js,
                            redacted,
                            rejected_reason=None):
        with Measure(self._clock, "_get_event_from_row"):
            d = json.loads(js)
            internal_metadata = json.loads(internal_metadata)

            if rejected_reason:
                rejected_reason = yield self._simple_select_one_onecol(
                    table="rejections",
                    keyvalues={"event_id": rejected_reason},
                    retcol="reason",
                    desc="_get_event_from_row_rejected_reason",
                )

            original_ev = FrozenEvent(
                d,
                internal_metadata_dict=internal_metadata,
                rejected_reason=rejected_reason,
            )

            redacted_event = None
            if redacted:
                redacted_event = prune_event(original_ev)

                redaction_id = yield self._simple_select_one_onecol(
                    table="redactions",
                    keyvalues={"redacts": redacted_event.event_id},
                    retcol="event_id",
                    desc="_get_event_from_row_redactions",
                )

                redacted_event.unsigned["redacted_by"] = redaction_id
                # Get the redaction event.

                because = yield self.get_event(
                    redaction_id,
                    check_redacted=False,
                    allow_none=True,
                )

                if because:
                    # It's fine to do add the event directly, since get_pdu_json
                    # will serialise this field correctly
                    redacted_event.unsigned["redacted_because"] = because

            cache_entry = _EventCacheEntry(
                event=original_ev,
                redacted_event=redacted_event,
            )

            self._get_event_cache.prefill((original_ev.event_id, ),
                                          cache_entry)

        defer.returnValue(cache_entry)
Exemple #17
0
    def _get_event_from_row_txn(self, txn, internal_metadata, js, redacted,
                                check_redacted=True, get_prev_content=False,
                                rejected_reason=None):
        d = json.loads(js)
        internal_metadata = json.loads(internal_metadata)

        if rejected_reason:
            rejected_reason = self._simple_select_one_onecol_txn(
                txn,
                table="rejections",
                keyvalues={"event_id": rejected_reason},
                retcol="reason",
            )

        ev = FrozenEvent(
            d,
            internal_metadata_dict=internal_metadata,
            rejected_reason=rejected_reason,
        )

        if check_redacted and redacted:
            ev = prune_event(ev)

            redaction_id = self._simple_select_one_onecol_txn(
                txn,
                table="redactions",
                keyvalues={"redacts": ev.event_id},
                retcol="event_id",
            )

            ev.unsigned["redacted_by"] = redaction_id
            # Get the redaction event.

            because = self._get_event_txn(
                txn,
                redaction_id,
                check_redacted=False
            )

            if because:
                ev.unsigned["redacted_because"] = because

        if get_prev_content and "replaces_state" in ev.unsigned:
            prev = self._get_event_txn(
                txn,
                ev.unsigned["replaces_state"],
                get_prev_content=False,
            )
            if prev:
                ev.unsigned["prev_content"] = prev.content
                ev.unsigned["prev_sender"] = prev.sender

        self._get_event_cache.prefill(
            (ev.event_id, check_redacted, get_prev_content), ev
        )

        return ev
Exemple #18
0
def compute_event_reference_hash(event, hash_algorithm=hashlib.sha256):
    tmp_event = prune_event(event)
    event_json = tmp_event.get_pdu_json()
    event_json.pop("signatures", None)
    event_json.pop("age_ts", None)
    event_json.pop("unsigned", None)
    event_json_bytes = encode_canonical_json(event_json)
    hashed = hash_algorithm(event_json_bytes)
    return (hashed.name, hashed.digest())
Exemple #19
0
def compute_event_reference_hash(event, hash_algorithm=hashlib.sha256):
    tmp_event = prune_event(event)
    event_json = tmp_event.get_pdu_json()
    event_json.pop("signatures", None)
    event_json.pop("age_ts", None)
    event_json.pop("unsigned", None)
    event_json_bytes = encode_canonical_json(event_json)
    hashed = hash_algorithm(event_json_bytes)
    return (hashed.name, hashed.digest())
Exemple #20
0
    def _check_sigs_and_hashes(self, pdus):
        """Checks that each of the received events is correctly signed by the
        sending server.

        Args:
            pdus (list[FrozenEvent]): the events to be checked

        Returns:
            list[Deferred]: for each input event, a deferred which:
              * returns the original event if the checks pass
              * returns a redacted version of the event (if the signature
                matched but the hash did not)
              * throws a SynapseError if the signature check failed.
            The deferreds run their callbacks in the sentinel logcontext.
        """

        redacted_pdus = [prune_event(pdu) for pdu in pdus]

        deferreds = self.keyring.verify_json_objects_for_server([
            (p.origin, p.get_pdu_json()) for p in redacted_pdus
        ])

        ctx = logcontext.LoggingContext.current_context()

        def callback(_, pdu, redacted):
            with logcontext.PreserveLoggingContext(ctx):
                if not check_event_content_hash(pdu):
                    logger.warn(
                        "Event content has been tampered, redacting %s: %s",
                        pdu.event_id, pdu.get_pdu_json())
                    return redacted

                if self.spam_checker.check_event_for_spam(pdu):
                    logger.warn("Event contains spam, redacting %s: %s",
                                pdu.event_id, pdu.get_pdu_json())
                    return redacted

                return pdu

        def errback(failure, pdu):
            failure.trap(SynapseError)
            with logcontext.PreserveLoggingContext(ctx):
                logger.warn(
                    "Signature check failed for %s",
                    pdu.event_id,
                )
            return failure

        for deferred, pdu, redacted in zip(deferreds, pdus, redacted_pdus):
            deferred.addCallbacks(
                callback,
                errback,
                callbackArgs=[pdu, redacted],
                errbackArgs=[pdu],
            )

        return deferreds
Exemple #21
0
def compute_event_signature(event, signature_name, signing_key):
    tmp_event = prune_event(event)
    redact_json = tmp_event.get_pdu_json()
    redact_json.pop("age_ts", None)
    redact_json.pop("unsigned", None)
    logger.debug("Signing event: %s", encode_canonical_json(redact_json))
    redact_json = sign_json(redact_json, signature_name, signing_key)
    logger.debug("Signed event: %s", encode_canonical_json(redact_json))
    return redact_json["signatures"]
Exemple #22
0
    def _get_event_from_row(self, internal_metadata, js, redacted,
                            check_redacted=True, get_prev_content=False,
                            rejected_reason=None):
        d = json.loads(js)
        internal_metadata = json.loads(internal_metadata)

        if rejected_reason:
            rejected_reason = yield self._simple_select_one_onecol(
                table="rejections",
                keyvalues={"event_id": rejected_reason},
                retcol="reason",
                desc="_get_event_from_row",
            )

        ev = FrozenEvent(
            d,
            internal_metadata_dict=internal_metadata,
            rejected_reason=rejected_reason,
        )

        if check_redacted and redacted:
            ev = prune_event(ev)

            redaction_id = yield self._simple_select_one_onecol(
                table="redactions",
                keyvalues={"redacts": ev.event_id},
                retcol="event_id",
                desc="_get_event_from_row",
            )

            ev.unsigned["redacted_by"] = redaction_id
            # Get the redaction event.

            because = yield self.get_event(
                redaction_id,
                check_redacted=False
            )

            if because:
                ev.unsigned["redacted_because"] = because

        if get_prev_content and "replaces_state" in ev.unsigned:
            prev = yield self.get_event(
                ev.unsigned["replaces_state"],
                get_prev_content=False,
            )
            if prev:
                ev.unsigned["prev_content"] = prev.get_dict()["content"]

        self._get_event_cache.prefill(
            ev.event_id, check_redacted, get_prev_content, ev
        )

        defer.returnValue(ev)
Exemple #23
0
    def _get_event_from_row(self, internal_metadata, js, redacted,
                            rejected_reason=None):
        with Measure(self._clock, "_get_event_from_row"):
            d = json.loads(js)
            internal_metadata = json.loads(internal_metadata)

            if rejected_reason:
                rejected_reason = yield self._simple_select_one_onecol(
                    table="rejections",
                    keyvalues={"event_id": rejected_reason},
                    retcol="reason",
                    desc="_get_event_from_row_rejected_reason",
                )

            original_ev = FrozenEvent(
                d,
                internal_metadata_dict=internal_metadata,
                rejected_reason=rejected_reason,
            )

            redacted_event = None
            if redacted:
                redacted_event = prune_event(original_ev)

                redaction_id = yield self._simple_select_one_onecol(
                    table="redactions",
                    keyvalues={"redacts": redacted_event.event_id},
                    retcol="event_id",
                    desc="_get_event_from_row_redactions",
                )

                redacted_event.unsigned["redacted_by"] = redaction_id
                # Get the redaction event.

                because = yield self.get_event(
                    redaction_id,
                    check_redacted=False,
                    allow_none=True,
                )

                if because:
                    # It's fine to do add the event directly, since get_pdu_json
                    # will serialise this field correctly
                    redacted_event.unsigned["redacted_because"] = because

            cache_entry = _EventCacheEntry(
                event=original_ev,
                redacted_event=redacted_event,
            )

            self._get_event_cache.prefill((original_ev.event_id,), cache_entry)

        defer.returnValue(cache_entry)
Exemple #24
0
    def run_test(self, evdict, matchdict, **kwargs):
        """
        Asserts that a new event constructed with `evdict` will look like
        `matchdict` when it is redacted.

        Args:
             evdict: The dictionary to build the event from.
             matchdict: The expected resulting dictionary.
             kwargs: Additional keyword arguments used to create the event.
        """
        self.assertEqual(
            prune_event(make_event_from_dict(evdict, **kwargs)).get_dict(),
            matchdict)
Exemple #25
0
    def _get_event_from_row_txn(self, txn, internal_metadata, js, redacted,
                                check_redacted=True, get_prev_content=False,
                                rejected_reason=None):

        start_time = time.time() * 1000

        def update_counter(desc, last_time):
            curr_time = self._get_event_counters.update(desc, last_time)
            sql_getevents_timer.inc_by(curr_time - last_time, desc)
            return curr_time

        d = json.loads(js)
        start_time = update_counter("decode_json", start_time)

        internal_metadata = json.loads(internal_metadata)
        start_time = update_counter("decode_internal", start_time)

        ev = FrozenEvent(
            d,
            internal_metadata_dict=internal_metadata,
            rejected_reason=rejected_reason,
        )
        start_time = update_counter("build_frozen_event", start_time)

        if check_redacted and redacted:
            ev = prune_event(ev)

            ev.unsigned["redacted_by"] = redacted
            # Get the redaction event.

            because = self._get_event_txn(
                txn,
                redacted,
                check_redacted=False
            )

            if because:
                ev.unsigned["redacted_because"] = because
            start_time = update_counter("redact_event", start_time)

        if get_prev_content and "replaces_state" in ev.unsigned:
            prev = self._get_event_txn(
                txn,
                ev.unsigned["replaces_state"],
                get_prev_content=False,
            )
            if prev:
                ev.unsigned["prev_content"] = prev.get_dict()["content"]
            start_time = update_counter("get_prev_content", start_time)

        return ev
Exemple #26
0
        def callback(_, pdu):
            with logcontext.PreserveLoggingContext(ctx):
                if not check_event_content_hash(pdu):
                    # let's try to distinguish between failures because the event was
                    # redacted (which are somewhat expected) vs actual ball-tampering
                    # incidents.
                    #
                    # This is just a heuristic, so we just assume that if the keys are
                    # about the same between the redacted and received events, then the
                    # received event was probably a redacted copy (but we then use our
                    # *actual* redacted copy to be on the safe side.)
                    redacted_event = prune_event(pdu)
                    if (
                        set(redacted_event.keys()) == set(pdu.keys()) and
                        set(six.iterkeys(redacted_event.content))
                            == set(six.iterkeys(pdu.content))
                    ):
                        logger.info(
                            "Event %s seems to have been redacted; using our redacted "
                            "copy",
                            pdu.event_id,
                        )
                    else:
                        logger.warning(
                            "Event %s content has been tampered, redacting",
                            pdu.event_id, pdu.get_pdu_json(),
                        )
                    return redacted_event

                if self.spam_checker.check_event_for_spam(pdu):
                    logger.warn(
                        "Event contains spam, redacting %s: %s",
                        pdu.event_id, pdu.get_pdu_json()
                    )
                    return prune_event(pdu)

                return pdu
Exemple #27
0
    def _get_event_txn(self, txn, event_id, check_redacted=True,
                       get_prev_content=True):
        sql = (
            "SELECT internal_metadata, json, r.event_id FROM event_json as e "
            "LEFT JOIN redactions as r ON e.event_id = r.redacts "
            "WHERE e.event_id = ? "
            "LIMIT 1 "
        )

        txn.execute(sql, (event_id,))

        res = txn.fetchone()

        if not res:
            return None

        internal_metadata, js, redacted = res

        d = json.loads(js)
        internal_metadata = json.loads(internal_metadata)

        ev = FrozenEvent(d, internal_metadata_dict=internal_metadata)

        if check_redacted and redacted:
            ev = prune_event(ev)

            ev.unsigned["redacted_by"] = redacted
            # Get the redaction event.

            because = self._get_event_txn(
                txn,
                redacted,
                check_redacted=False
            )

            if because:
                ev.unsigned["redacted_because"] = because

        if get_prev_content and "replaces_state" in ev.unsigned:
            prev = self._get_event_txn(
                txn,
                ev.unsigned["replaces_state"],
                get_prev_content=False,
            )
            if prev:
                ev.unsigned["prev_content"] = prev.get_dict()["content"]

        return ev
Exemple #28
0
    def _get_event_txn(self,
                       txn,
                       event_id,
                       check_redacted=True,
                       get_prev_content=True):
        sql = (
            "SELECT internal_metadata, json, r.event_id FROM event_json as e "
            "LEFT JOIN redactions as r ON e.event_id = r.redacts "
            "WHERE e.event_id = ? "
            "LIMIT 1 ")

        txn.execute(sql, (event_id, ))

        res = txn.fetchone()

        if not res:
            return None

        internal_metadata, js, redacted = res

        d = json.loads(js)
        internal_metadata = json.loads(internal_metadata)

        ev = FrozenEvent(d, internal_metadata_dict=internal_metadata)

        if check_redacted and redacted:
            ev = prune_event(ev)

            ev.unsigned["redacted_by"] = redacted
            # Get the redaction event.

            because = self._get_event_txn(txn, redacted, check_redacted=False)

            if because:
                ev.unsigned["redacted_because"] = because

        if get_prev_content and "replaces_state" in ev.unsigned:
            prev = self._get_event_txn(
                txn,
                ev.unsigned["replaces_state"],
                get_prev_content=False,
            )
            if prev:
                ev.unsigned["prev_content"] = prev.get_dict()["content"]

        return ev
Exemple #29
0
    def _check_sigs_and_hashes(self, pdus):
        """Throws a SynapseError if a PDU does not have the correct
        signatures.

        Returns:
            FrozenEvent: Either the given event or it redacted if it failed the
            content hash check.
        """

        redacted_pdus = [
            prune_event(pdu)
            for pdu in pdus
        ]

        deferreds = self.keyring.verify_json_objects_for_server([
            (p.origin, p.get_pdu_json())
            for p in redacted_pdus
        ])

        def callback(_, pdu, redacted):
            if not check_event_content_hash(pdu):
                logger.warn(
                    "Event content has been tampered, redacting %s: %s",
                    pdu.event_id, pdu.get_pdu_json()
                )
                return redacted
            return pdu

        def errback(failure, pdu):
            failure.trap(SynapseError)
            logger.warn(
                "Signature check failed for %s",
                pdu.event_id,
            )
            return failure

        for deferred, pdu, redacted in zip(deferreds, pdus, redacted_pdus):
            deferred.addCallbacks(
                callback, errback,
                callbackArgs=[pdu, redacted],
                errbackArgs=[pdu],
            )

        return deferreds
Exemple #30
0
def compute_event_reference_hash(event, hash_algorithm=hashlib.sha256):
    """Computes the event reference hash. This is the hash of the redacted
    event.

    Args:
        event (FrozenEvent)
        hash_algorithm: A hasher from `hashlib`, e.g. hashlib.sha256, to use
            to hash the event

    Returns:
        tuple[str, bytes]: A tuple of the name of hash and the hash as raw
        bytes.
    """
    tmp_event = prune_event(event)
    event_dict = tmp_event.get_pdu_json()
    event_dict.pop("signatures", None)
    event_dict.pop("age_ts", None)
    event_dict.pop("unsigned", None)
    event_json_bytes = encode_canonical_json(event_dict)
    hashed = hash_algorithm(event_json_bytes)
    return (hashed.name, hashed.digest())
    def _check_sigs_and_hashes(self, pdus):
        """Throws a SynapseError if a PDU does not have the correct
        signatures.

        Returns:
            FrozenEvent: Either the given event or it redacted if it failed the
            content hash check.
        """

        redacted_pdus = [prune_event(pdu) for pdu in pdus]

        deferreds = preserve_fn(self.keyring.verify_json_objects_for_server)([
            (p.origin, p.get_pdu_json()) for p in redacted_pdus
        ])

        def callback(_, pdu, redacted):
            if not check_event_content_hash(pdu):
                logger.warn(
                    "Event content has been tampered, redacting %s: %s",
                    pdu.event_id, pdu.get_pdu_json())
                return redacted
            return pdu

        def errback(failure, pdu):
            failure.trap(SynapseError)
            logger.warn(
                "Signature check failed for %s",
                pdu.event_id,
            )
            return failure

        for deferred, pdu, redacted in zip(deferreds, pdus, redacted_pdus):
            deferred.addCallbacks(
                callback,
                errback,
                callbackArgs=[pdu, redacted],
                errbackArgs=[pdu],
            )

        return deferreds
Exemple #32
0
 def run_test(self, evdict, matchdict):
     self.assertEquals(
         prune_event(FrozenEvent(evdict)).get_dict(),
         matchdict
     )
def reinsert_events(cursor, server_name, signing_key):
    print "Running delta: v10"

    cursor.executescript(delta_sql)

    cursor.execute(
        "SELECT * FROM events ORDER BY rowid ASC"
    )

    print "Getting events..."

    rows = store.cursor_to_dict(cursor)

    events = store._generate_event_json(cursor, rows)

    print "Got events from DB."

    algorithms = {
        "sha256": hashlib.sha256,
    }

    key_id = "%s:%s" % (signing_key.alg, signing_key.version)
    verify_key = signing_key.verify_key
    verify_key.alg = signing_key.alg
    verify_key.version = signing_key.version

    server_keys = {
        server_name: {
            key_id: verify_key
        }
    }

    i = 0
    N = len(events)

    for event in events:
        if i % 100 == 0:
            print "Processed: %d/%d events" % (i,N,)
        i += 1

        # for alg_name in event.hashes:
        #     if check_event_content_hash(event, algorithms[alg_name]):
        #         pass
        #     else:
        #         pass
        #         print "FAIL content hash %s %s" % (alg_name, event.event_id, )

        have_own_correctly_signed = False
        for host, sigs in event.signatures.items():
            pruned = prune_event(event)

            for key_id in sigs:
                if host not in server_keys:
                    server_keys[host] = {}  # get_key(host)
                if key_id in server_keys[host]:
                    try:
                        verify_signed_json(
                            pruned.get_pdu_json(),
                            host,
                            server_keys[host][key_id]
                        )

                        if host == server_name:
                            have_own_correctly_signed = True
                    except SignatureVerifyException:
                        print "FAIL signature check %s %s" % (
                            key_id, event.event_id
                        )

        # TODO: Re sign with our own server key
        if not have_own_correctly_signed:
            sigs = compute_event_signature(event, server_name, signing_key)
            event.signatures.update(sigs)

            pruned = prune_event(event)

            for key_id in event.signatures[server_name]:
                verify_signed_json(
                    pruned.get_pdu_json(),
                    server_name,
                    server_keys[server_name][key_id]
                )

        event_json = encode_canonical_json(
            event.get_dict()
        ).decode("UTF-8")

        metadata_json = encode_canonical_json(
            event.internal_metadata.get_dict()
        ).decode("UTF-8")

        store._simple_insert_txn(
            cursor,
            table="event_json",
            values={
                "event_id": event.event_id,
                "room_id": event.room_id,
                "internal_metadata": metadata_json,
                "json": event_json,
            },
            or_replace=True,
        )
Exemple #34
0
    def _check_sigs_and_hashes(self, pdus):
        """Checks that each of the received events is correctly signed by the
        sending server.

        Args:
            pdus (list[FrozenEvent]): the events to be checked

        Returns:
            list[Deferred]: for each input event, a deferred which:
              * returns the original event if the checks pass
              * returns a redacted version of the event (if the signature
                matched but the hash did not)
              * throws a SynapseError if the signature check failed.
            The deferreds run their callbacks in the sentinel logcontext.
        """

        redacted_pdus = [
            prune_event(pdu)
            for pdu in pdus
        ]

        deferreds = self.keyring.verify_json_objects_for_server([
            (p.origin, p.get_pdu_json())
            for p in redacted_pdus
        ])

        ctx = logcontext.LoggingContext.current_context()

        def callback(_, pdu, redacted):
            with logcontext.PreserveLoggingContext(ctx):
                if not check_event_content_hash(pdu):
                    logger.warn(
                        "Event content has been tampered, redacting %s: %s",
                        pdu.event_id, pdu.get_pdu_json()
                    )
                    return redacted

                if self.spam_checker.check_event_for_spam(pdu):
                    logger.warn(
                        "Event contains spam, redacting %s: %s",
                        pdu.event_id, pdu.get_pdu_json()
                    )
                    return redacted

                return pdu

        def errback(failure, pdu):
            failure.trap(SynapseError)
            with logcontext.PreserveLoggingContext(ctx):
                logger.warn(
                    "Signature check failed for %s",
                    pdu.event_id,
                )
            return failure

        for deferred, pdu, redacted in zip(deferreds, pdus, redacted_pdus):
            deferred.addCallbacks(
                callback, errback,
                callbackArgs=[pdu, redacted],
                errbackArgs=[pdu],
            )

        return deferreds
Exemple #35
0
async def filter_events_for_server(
    storage: Storage,
    server_name,
    events,
    redact=True,
    check_history_visibility_only=False,
):
    """Filter a list of events based on whether given server is allowed to
    see them.

    Args:
        storage
        server_name (str)
        events (iterable[FrozenEvent])
        redact (bool): Whether to return a redacted version of the event, or
            to filter them out entirely.
        check_history_visibility_only (bool): Whether to only check the
            history visibility, rather than things like if the sender has been
            erased. This is used e.g. during pagination to decide whether to
            backfill or not.

    Returns
        list[FrozenEvent]
    """

    def is_sender_erased(event, erased_senders):
        if erased_senders and erased_senders[event.sender]:
            logger.info("Sender of %s has been erased, redacting", event.event_id)
            return True
        return False

    def check_event_is_visible(event, state):
        history = state.get((EventTypes.RoomHistoryVisibility, ""), None)
        if history:
            visibility = history.content.get(
                "history_visibility", HistoryVisibility.SHARED
            )
            if visibility in [HistoryVisibility.INVITED, HistoryVisibility.JOINED]:
                # We now loop through all state events looking for
                # membership states for the requesting server to determine
                # if the server is either in the room or has been invited
                # into the room.
                for ev in state.values():
                    if ev.type != EventTypes.Member:
                        continue
                    try:
                        domain = get_domain_from_id(ev.state_key)
                    except Exception:
                        continue

                    if domain != server_name:
                        continue

                    memtype = ev.membership
                    if memtype == Membership.JOIN:
                        return True
                    elif memtype == Membership.INVITE:
                        if visibility == HistoryVisibility.INVITED:
                            return True
                else:
                    # server has no users in the room: redact
                    return False

        return True

    # Lets check to see if all the events have a history visibility
    # of "shared" or "world_readable". If that's the case then we don't
    # need to check membership (as we know the server is in the room).
    event_to_state_ids = await storage.state.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(
            types=((EventTypes.RoomHistoryVisibility, ""),)
        ),
    )

    visibility_ids = set()
    for sids in event_to_state_ids.values():
        hist = sids.get((EventTypes.RoomHistoryVisibility, ""))
        if hist:
            visibility_ids.add(hist)

    # If we failed to find any history visibility events then the default
    # is "shared" visibility.
    if not visibility_ids:
        all_open = True
    else:
        event_map = await storage.main.get_events(visibility_ids)
        all_open = all(
            e.content.get("history_visibility")
            in (None, HistoryVisibility.SHARED, HistoryVisibility.WORLD_READABLE)
            for e in event_map.values()
        )

    if not check_history_visibility_only:
        erased_senders = await storage.main.are_users_erased((e.sender for e in events))
    else:
        # We don't want to check whether users are erased, which is equivalent
        # to no users having been erased.
        erased_senders = {}

    if all_open:
        # all the history_visibility state affecting these events is open, so
        # we don't need to filter by membership state. We *do* need to check
        # for user erasure, though.
        if erased_senders:
            to_return = []
            for e in events:
                if not is_sender_erased(e, erased_senders):
                    to_return.append(e)
                elif redact:
                    to_return.append(prune_event(e))

            return to_return

        # If there are no erased users then we can just return the given list
        # of events without having to copy it.
        return events

    # Ok, so we're dealing with events that have non-trivial visibility
    # rules, so we need to also get the memberships of the room.

    # first, for each event we're wanting to return, get the event_ids
    # of the history vis and membership state at those events.
    event_to_state_ids = await storage.state.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(
            types=((EventTypes.RoomHistoryVisibility, ""), (EventTypes.Member, None))
        ),
    )

    # We only want to pull out member events that correspond to the
    # server's domain.
    #
    # event_to_state_ids contains lots of duplicates, so it turns out to be
    # cheaper to build a complete event_id => (type, state_key) dict, and then
    # filter out the ones we don't want
    #
    event_id_to_state_key = {
        event_id: key
        for key_to_eid in event_to_state_ids.values()
        for key, event_id in key_to_eid.items()
    }

    def include(typ, state_key):
        if typ != EventTypes.Member:
            return True

        # we avoid using get_domain_from_id here for efficiency.
        idx = state_key.find(":")
        if idx == -1:
            return False
        return state_key[idx + 1 :] == server_name

    event_map = await storage.main.get_events(
        [e_id for e_id, key in event_id_to_state_key.items() if include(key[0], key[1])]
    )

    event_to_state = {
        e_id: {
            key: event_map[inner_e_id]
            for key, inner_e_id in key_to_eid.items()
            if inner_e_id in event_map
        }
        for e_id, key_to_eid in event_to_state_ids.items()
    }

    to_return = []
    for e in events:
        erased = is_sender_erased(e, erased_senders)
        visible = check_event_is_visible(e, event_to_state[e.event_id])
        if visible and not erased:
            to_return.append(e)
        elif redact:
            to_return.append(prune_event(e))

    return to_return
Exemple #36
0
def _check_sigs_on_pdus(keyring, pdus):
    """Check that the given events are correctly signed

    Args:
        keyring (synapse.crypto.Keyring): keyring object to do the checks
        pdus (Collection[EventBase]): the events to be checked

    Returns:
        List[Deferred]: a Deferred for each event in pdus, which will either succeed if
           the signatures are valid, or fail (with a SynapseError) if not.
    """

    # (currently this is written assuming the v1 room structure; we'll probably want a
    # separate function for checking v2 rooms)

    # we want to check that the event is signed by:
    #
    # (a) the server which created the event_id
    #
    # (b) the sender's server.
    #
    #     - except in the case of invites created from a 3pid invite, which are exempt
    #     from this check, because the sender has to match that of the original 3pid
    #     invite, but the event may come from a different HS, for reasons that I don't
    #     entirely grok (why do the senders have to match? and if they do, why doesn't the
    #     joining server ask the inviting server to do the switcheroo with
    #     exchange_third_party_invite?).
    #
    #     That's pretty awful, since redacting such an invite will render it invalid
    #     (because it will then look like a regular invite without a valid signature),
    #     and signatures are *supposed* to be valid whether or not an event has been
    #     redacted. But this isn't the worst of the ways that 3pid invites are broken.
    #
    # let's start by getting the domain for each pdu, and flattening the event back
    # to JSON.
    pdus_to_check = [
        PduToCheckSig(
            pdu=p,
            redacted_pdu_json=prune_event(p).get_pdu_json(),
            event_id_domain=get_domain_from_id(p.event_id),
            sender_domain=get_domain_from_id(p.sender),
            deferreds=[],
        )
        for p in pdus
    ]

    # first make sure that the event is signed by the event_id's domain
    deferreds = keyring.verify_json_objects_for_server([
        (p.event_id_domain, p.redacted_pdu_json)
        for p in pdus_to_check
    ])

    for p, d in zip(pdus_to_check, deferreds):
        p.deferreds.append(d)

    # now let's look for events where the sender's domain is different to the
    # event id's domain (normally only the case for joins/leaves), and add additional
    # checks.
    pdus_to_check_sender = [
        p for p in pdus_to_check
        if p.sender_domain != p.event_id_domain and not _is_invite_via_3pid(p.pdu)
    ]

    more_deferreds = keyring.verify_json_objects_for_server([
        (p.sender_domain, p.redacted_pdu_json)
        for p in pdus_to_check_sender
    ])

    for p, d in zip(pdus_to_check_sender, more_deferreds):
        p.deferreds.append(d)

    # replace lists of deferreds with single Deferreds
    return [_flatten_deferred_list(p.deferreds) for p in pdus_to_check]
Exemple #37
0
async def filter_events_for_server(
    storage: StorageControllers,
    server_name: str,
    events: List[EventBase],
    redact: bool = True,
    check_history_visibility_only: bool = False,
) -> List[EventBase]:
    """Filter a list of events based on whether given server is allowed to
    see them.

    Args:
        storage
        server_name
        events
        redact: Whether to return a redacted version of the event, or
            to filter them out entirely.
        check_history_visibility_only: Whether to only check the
            history visibility, rather than things like if the sender has been
            erased. This is used e.g. during pagination to decide whether to
            backfill or not.

    Returns
        The filtered events.
    """

    def is_sender_erased(event: EventBase, erased_senders: Dict[str, bool]) -> bool:
        if erased_senders and erased_senders[event.sender]:
            logger.info("Sender of %s has been erased, redacting", event.event_id)
            return True
        return False

    def check_event_is_visible(
        visibility: str, memberships: StateMap[EventBase]
    ) -> bool:
        if visibility not in (HistoryVisibility.INVITED, HistoryVisibility.JOINED):
            return True

        # We now loop through all membership events looking for
        # membership states for the requesting server to determine
        # if the server is either in the room or has been invited
        # into the room.
        for ev in memberships.values():
            assert get_domain_from_id(ev.state_key) == server_name

            memtype = ev.membership
            if memtype == Membership.JOIN:
                return True
            elif memtype == Membership.INVITE:
                if visibility == HistoryVisibility.INVITED:
                    return True

        # server has no users in the room: redact
        return False

    if not check_history_visibility_only:
        erased_senders = await storage.main.are_users_erased(e.sender for e in events)
    else:
        # We don't want to check whether users are erased, which is equivalent
        # to no users having been erased.
        erased_senders = {}

    # Let's check to see if all the events have a history visibility
    # of "shared" or "world_readable". If that's the case then we don't
    # need to check membership (as we know the server is in the room).
    event_to_history_vis = await _event_to_history_vis(storage, events)

    # for any with restricted vis, we also need the memberships
    event_to_memberships = await _event_to_memberships(
        storage,
        [
            e
            for e in events
            if event_to_history_vis[e.event_id]
            not in (HistoryVisibility.SHARED, HistoryVisibility.WORLD_READABLE)
        ],
        server_name,
    )

    to_return = []
    for e in events:
        erased = is_sender_erased(e, erased_senders)
        visible = check_event_is_visible(
            event_to_history_vis[e.event_id], event_to_memberships.get(e.event_id, {})
        )
        if visible and not erased:
            to_return.append(e)
        elif redact:
            to_return.append(prune_event(e))

    return to_return
def _check_sigs_on_pdus(keyring, pdus):
    """Check that the given events are correctly signed

    Args:
        keyring (synapse.crypto.Keyring): keyring object to do the checks
        pdus (Collection[EventBase]): the events to be checked

    Returns:
        List[Deferred]: a Deferred for each event in pdus, which will either succeed if
           the signatures are valid, or fail (with a SynapseError) if not.
    """

    # (currently this is written assuming the v1 room structure; we'll probably want a
    # separate function for checking v2 rooms)

    # we want to check that the event is signed by:
    #
    # (a) the server which created the event_id
    #
    # (b) the sender's server.
    #
    #     - except in the case of invites created from a 3pid invite, which are exempt
    #     from this check, because the sender has to match that of the original 3pid
    #     invite, but the event may come from a different HS, for reasons that I don't
    #     entirely grok (why do the senders have to match? and if they do, why doesn't the
    #     joining server ask the inviting server to do the switcheroo with
    #     exchange_third_party_invite?).
    #
    #     That's pretty awful, since redacting such an invite will render it invalid
    #     (because it will then look like a regular invite without a valid signature),
    #     and signatures are *supposed* to be valid whether or not an event has been
    #     redacted. But this isn't the worst of the ways that 3pid invites are broken.
    #
    # let's start by getting the domain for each pdu, and flattening the event back
    # to JSON.
    pdus_to_check = [
        PduToCheckSig(
            pdu=p,
            redacted_pdu_json=prune_event(p).get_pdu_json(),
            event_id_domain=get_domain_from_id(p.event_id),
            sender_domain=get_domain_from_id(p.sender),
            deferreds=[],
        ) for p in pdus
    ]

    # first make sure that the event is signed by the event_id's domain
    deferreds = keyring.verify_json_objects_for_server([
        (p.event_id_domain, p.redacted_pdu_json) for p in pdus_to_check
    ])

    for p, d in zip(pdus_to_check, deferreds):
        p.deferreds.append(d)

    # now let's look for events where the sender's domain is different to the
    # event id's domain (normally only the case for joins/leaves), and add additional
    # checks.
    pdus_to_check_sender = [
        p for p in pdus_to_check if p.sender_domain != p.event_id_domain
        and not _is_invite_via_3pid(p.pdu)
    ]

    more_deferreds = keyring.verify_json_objects_for_server([
        (p.sender_domain, p.redacted_pdu_json) for p in pdus_to_check_sender
    ])

    for p, d in zip(pdus_to_check_sender, more_deferreds):
        p.deferreds.append(d)

    # replace lists of deferreds with single Deferreds
    return [_flatten_deferred_list(p.deferreds) for p in pdus_to_check]
Exemple #39
0
    def allowed(event):
        """
        Args:
            event (synapse.events.EventBase): event to check

        Returns:
            None|EventBase:
               None if the user cannot see this event at all

               a redacted copy of the event if they can only see a redacted
               version

               the original event if they can see it as normal.
        """
        if not event.is_state() and event.sender in ignore_list:
            return None

        if event.event_id in always_include_ids:
            return event

        state = event_id_to_state[event.event_id]

        # get the room_visibility at the time of the event.
        visibility_event = state.get((EventTypes.RoomHistoryVisibility, ""), None)
        if visibility_event:
            visibility = visibility_event.content.get("history_visibility", "shared")
        else:
            visibility = "shared"

        if visibility not in VISIBILITY_PRIORITY:
            visibility = "shared"

        # Always allow history visibility events on boundaries. This is done
        # by setting the effective visibility to the least restrictive
        # of the old vs new.
        if event.type == EventTypes.RoomHistoryVisibility:
            prev_content = event.unsigned.get("prev_content", {})
            prev_visibility = prev_content.get("history_visibility", None)

            if prev_visibility not in VISIBILITY_PRIORITY:
                prev_visibility = "shared"

            new_priority = VISIBILITY_PRIORITY.index(visibility)
            old_priority = VISIBILITY_PRIORITY.index(prev_visibility)
            if old_priority < new_priority:
                visibility = prev_visibility

        # likewise, if the event is the user's own membership event, use
        # the 'most joined' membership
        membership = None
        if event.type == EventTypes.Member and event.state_key == user_id:
            membership = event.content.get("membership", None)
            if membership not in MEMBERSHIP_PRIORITY:
                membership = "leave"

            prev_content = event.unsigned.get("prev_content", {})
            prev_membership = prev_content.get("membership", None)
            if prev_membership not in MEMBERSHIP_PRIORITY:
                prev_membership = "leave"

            # Always allow the user to see their own leave events, otherwise
            # they won't see the room disappear if they reject the invite
            if membership == "leave" and (
                prev_membership == "join" or prev_membership == "invite"
            ):
                return event

            new_priority = MEMBERSHIP_PRIORITY.index(membership)
            old_priority = MEMBERSHIP_PRIORITY.index(prev_membership)
            if old_priority < new_priority:
                membership = prev_membership

        # otherwise, get the user's membership at the time of the event.
        if membership is None:
            membership_event = state.get((EventTypes.Member, user_id), None)
            if membership_event:
                membership = membership_event.membership

        # if the user was a member of the room at the time of the event,
        # they can see it.
        if membership == Membership.JOIN:
            return event

        # otherwise, it depends on the room visibility.

        if visibility == "joined":
            # we weren't a member at the time of the event, so we can't
            # see this event.
            return None

        elif visibility == "invited":
            # user can also see the event if they were *invited* at the time
            # of the event.
            return (
                event if membership == Membership.INVITE else None
            )

        elif visibility == "shared" and is_peeking:
            # if the visibility is shared, users cannot see the event unless
            # they have *subequently* joined the room (or were members at the
            # time, of course)
            #
            # XXX: if the user has subsequently joined and then left again,
            # ideally we would share history up to the point they left. But
            # we don't know when they left. We just treat it as though they
            # never joined, and restrict access.
            return None

        # the visibility is either shared or world_readable, and the user was
        # not a member at the time. We allow it, provided the original sender
        # has not requested their data to be erased, in which case, we return
        # a redacted version.
        if erased_senders[event.sender]:
            return prune_event(event)

        return event
Exemple #40
0
    def on_receive_pdu(self, origin, pdu, backfilled, state=None,
                       auth_chain=None):
        """ Called by the ReplicationLayer when we have a new pdu. We need to
        do auth checks and put it through the StateHandler.
        """
        event = pdu

        logger.debug("Got event: %s", event.event_id)

        # If we are currently in the process of joining this room, then we
        # queue up events for later processing.
        if event.room_id in self.room_queues:
            self.room_queues[event.room_id].append((pdu, origin))
            return

        logger.debug("Processing event: %s", event.event_id)

        redacted_event = prune_event(event)

        redacted_pdu_json = redacted_event.get_pdu_json()
        try:
            yield self.keyring.verify_json_for_server(
                event.origin, redacted_pdu_json
            )
        except SynapseError as e:
            logger.warn(
                "Signature check failed for %s redacted to %s",
                encode_canonical_json(pdu.get_pdu_json()),
                encode_canonical_json(redacted_pdu_json),
            )
            raise FederationError(
                "ERROR",
                e.code,
                e.msg,
                affected=event.event_id,
            )

        if not check_event_content_hash(event):
            logger.warn(
                "Event content has been tampered, redacting %s, %s",
                event.event_id, encode_canonical_json(event.get_dict())
            )
            event = redacted_event

        logger.debug("Event: %s", event)

        # FIXME (erikj): Awful hack to make the case where we are not currently
        # in the room work
        current_state = None
        is_in_room = yield self.auth.check_host_in_room(
            event.room_id,
            self.server_name
        )
        if not is_in_room and not event.internal_metadata.outlier:
            logger.debug("Got event for room we're not in.")

            replication = self.replication_layer

            if not state:
                state, auth_chain = yield replication.get_state_for_context(
                    origin, context=event.room_id, event_id=event.event_id,
                )

            if not auth_chain:
                auth_chain = yield replication.get_event_auth(
                    origin,
                    context=event.room_id,
                    event_id=event.event_id,
                )

            for e in auth_chain:
                e.internal_metadata.outlier = True
                try:
                    yield self._handle_new_event(e, fetch_auth_from=origin)
                except:
                    logger.exception(
                        "Failed to handle auth event %s",
                        e.event_id,
                    )

            current_state = state

        if state:
            for e in state:
                logging.info("A :) %r", e)
                e.internal_metadata.outlier = True
                try:
                    yield self._handle_new_event(e)
                except:
                    logger.exception(
                        "Failed to handle state event %s",
                        e.event_id,
                    )

        try:
            yield self._handle_new_event(
                event,
                state=state,
                backfilled=backfilled,
                current_state=current_state,
            )
        except AuthError as e:
            raise FederationError(
                "ERROR",
                e.code,
                e.msg,
                affected=event.event_id,
            )

        # if we're receiving valid events from an origin,
        # it's probably a good idea to mark it as not in retry-state
        # for sending (although this is a bit of a leap)
        retry_timings = yield self.store.get_destination_retry_timings(origin)
        if (retry_timings and retry_timings.retry_last_ts):
            self.store.set_destination_retry_timings(origin, 0, 0)

        room = yield self.store.get_room(event.room_id)

        if not room:
            try:
                yield self.store.store_room(
                    room_id=event.room_id,
                    room_creator_user_id="",
                    is_public=False,
                )
            except StoreError:
                logger.exception("Failed to store room.")

        if not backfilled:
            extra_users = []
            if event.type == EventTypes.Member:
                target_user_id = event.state_key
                target_user = self.hs.parse_userid(target_user_id)
                extra_users.append(target_user)

            yield self.notifier.on_new_room_event(
                event, extra_users=extra_users
            )

        if event.type == EventTypes.Member:
            if event.membership == Membership.JOIN:
                user = self.hs.parse_userid(event.state_key)
                yield self.distributor.fire(
                    "user_joined_room", user=user, room_id=event.room_id
                )
Exemple #41
0
    def on_receive_pdu(self,
                       origin,
                       pdu,
                       backfilled,
                       state=None,
                       auth_chain=None):
        """ Called by the ReplicationLayer when we have a new pdu. We need to
        do auth checks and put it through the StateHandler.
        """
        event = pdu

        logger.debug("Got event: %s", event.event_id)

        # If we are currently in the process of joining this room, then we
        # queue up events for later processing.
        if event.room_id in self.room_queues:
            self.room_queues[event.room_id].append((pdu, origin))
            return

        logger.debug("Processing event: %s", event.event_id)

        redacted_event = prune_event(event)

        redacted_pdu_json = redacted_event.get_pdu_json()
        try:
            yield self.keyring.verify_json_for_server(event.origin,
                                                      redacted_pdu_json)
        except SynapseError as e:
            logger.warn(
                "Signature check failed for %s redacted to %s",
                encode_canonical_json(pdu.get_pdu_json()),
                encode_canonical_json(redacted_pdu_json),
            )
            raise FederationError(
                "ERROR",
                e.code,
                e.msg,
                affected=event.event_id,
            )

        if not check_event_content_hash(event):
            logger.warn("Event content has been tampered, redacting %s, %s",
                        event.event_id,
                        encode_canonical_json(event.get_dict()))
            event = redacted_event

        logger.debug("Event: %s", event)

        # FIXME (erikj): Awful hack to make the case where we are not currently
        # in the room work
        current_state = None
        is_in_room = yield self.auth.check_host_in_room(
            event.room_id, self.server_name)
        if not is_in_room and not event.internal_metadata.outlier:
            logger.debug("Got event for room we're not in.")

            replication = self.replication_layer

            if not state:
                state, auth_chain = yield replication.get_state_for_context(
                    origin,
                    context=event.room_id,
                    event_id=event.event_id,
                )

            if not auth_chain:
                auth_chain = yield replication.get_event_auth(
                    origin,
                    context=event.room_id,
                    event_id=event.event_id,
                )

            for e in auth_chain:
                e.internal_metadata.outlier = True
                try:
                    yield self._handle_new_event(e, fetch_auth_from=origin)
                except:
                    logger.exception(
                        "Failed to handle auth event %s",
                        e.event_id,
                    )

            current_state = state

        if state:
            for e in state:
                logging.info("A :) %r", e)
                e.internal_metadata.outlier = True
                try:
                    yield self._handle_new_event(e)
                except:
                    logger.exception(
                        "Failed to handle state event %s",
                        e.event_id,
                    )

        try:
            yield self._handle_new_event(
                event,
                state=state,
                backfilled=backfilled,
                current_state=current_state,
            )
        except AuthError as e:
            raise FederationError(
                "ERROR",
                e.code,
                e.msg,
                affected=event.event_id,
            )

        # if we're receiving valid events from an origin,
        # it's probably a good idea to mark it as not in retry-state
        # for sending (although this is a bit of a leap)
        retry_timings = yield self.store.get_destination_retry_timings(origin)
        if (retry_timings and retry_timings.retry_last_ts):
            self.store.set_destination_retry_timings(origin, 0, 0)

        room = yield self.store.get_room(event.room_id)

        if not room:
            try:
                yield self.store.store_room(
                    room_id=event.room_id,
                    room_creator_user_id="",
                    is_public=False,
                )
            except StoreError:
                logger.exception("Failed to store room.")

        if not backfilled:
            extra_users = []
            if event.type == EventTypes.Member:
                target_user_id = event.state_key
                target_user = self.hs.parse_userid(target_user_id)
                extra_users.append(target_user)

            yield self.notifier.on_new_room_event(event,
                                                  extra_users=extra_users)

        if event.type == EventTypes.Member:
            if event.membership == Membership.JOIN:
                user = self.hs.parse_userid(event.state_key)
                yield self.distributor.fire("user_joined_room",
                                            user=user,
                                            room_id=event.room_id)
Exemple #42
0
    def _maybe_redact_event_row(self, original_ev, redactions, event_map):
        """Given an event object and a list of possible redacting event ids,
        determine whether to honour any of those redactions and if so return a redacted
        event.

        Args:
             original_ev (EventBase):
             redactions (iterable[str]): list of event ids of potential redaction events
             event_map (dict[str, EventBase]): other events which have been fetched, in
                 which we can look up the redaaction events. Map from event id to event.

        Returns:
            Deferred[EventBase|None]: if the event should be redacted, a pruned
                event object. Otherwise, None.
        """
        if original_ev.type == "m.room.create":
            # we choose to ignore redactions of m.room.create events.
            return None

        for redaction_id in redactions:
            redaction_event = event_map.get(redaction_id)
            if not redaction_event or redaction_event.rejected_reason:
                # we don't have the redaction event, or the redaction event was not
                # authorized.
                logger.debug(
                    "%s was redacted by %s but redaction not found/authed",
                    original_ev.event_id,
                    redaction_id,
                )
                continue

            if redaction_event.room_id != original_ev.room_id:
                logger.debug(
                    "%s was redacted by %s but redaction was in a different room!",
                    original_ev.event_id,
                    redaction_id,
                )
                continue

            # Starting in room version v3, some redactions need to be
            # rechecked if we didn't have the redacted event at the
            # time, so we recheck on read instead.
            if redaction_event.internal_metadata.need_to_check_redaction():
                expected_domain = get_domain_from_id(original_ev.sender)
                if get_domain_from_id(
                        redaction_event.sender) == expected_domain:
                    # This redaction event is allowed. Mark as not needing a recheck.
                    redaction_event.internal_metadata.recheck_redaction = False
                else:
                    # Senders don't match, so the event isn't actually redacted
                    logger.debug(
                        "%s was redacted by %s but the senders don't match",
                        original_ev.event_id,
                        redaction_id,
                    )
                    continue

            logger.debug("Redacting %s due to %s", original_ev.event_id,
                         redaction_id)

            # we found a good redaction event. Redact!
            redacted_event = prune_event(original_ev)
            redacted_event.unsigned["redacted_by"] = redaction_id

            # It's fine to add the event directly, since get_pdu_json
            # will serialise this field correctly
            redacted_event.unsigned["redacted_because"] = redaction_event

            return redacted_event

        # no valid redaction found for this event
        return None
Exemple #43
0
def reinsert_events(cursor, server_name, signing_key):
    print "Running delta: v10"

    cursor.executescript(delta_sql)

    cursor.execute("SELECT * FROM events ORDER BY rowid ASC")

    print "Getting events..."

    rows = store.cursor_to_dict(cursor)

    events = store._generate_event_json(cursor, rows)

    print "Got events from DB."

    algorithms = {
        "sha256": hashlib.sha256,
    }

    key_id = "%s:%s" % (signing_key.alg, signing_key.version)
    verify_key = signing_key.verify_key
    verify_key.alg = signing_key.alg
    verify_key.version = signing_key.version

    server_keys = {server_name: {key_id: verify_key}}

    i = 0
    N = len(events)

    for event in events:
        if i % 100 == 0:
            print "Processed: %d/%d events" % (
                i,
                N,
            )
        i += 1

        # for alg_name in event.hashes:
        #     if check_event_content_hash(event, algorithms[alg_name]):
        #         pass
        #     else:
        #         pass
        #         print "FAIL content hash %s %s" % (alg_name, event.event_id, )

        have_own_correctly_signed = False
        for host, sigs in event.signatures.items():
            pruned = prune_event(event)

            for key_id in sigs:
                if host not in server_keys:
                    server_keys[host] = {}  # get_key(host)
                if key_id in server_keys[host]:
                    try:
                        verify_signed_json(pruned.get_pdu_json(), host,
                                           server_keys[host][key_id])

                        if host == server_name:
                            have_own_correctly_signed = True
                    except SignatureVerifyException:
                        print "FAIL signature check %s %s" % (key_id,
                                                              event.event_id)

        # TODO: Re sign with our own server key
        if not have_own_correctly_signed:
            sigs = compute_event_signature(event, server_name, signing_key)
            event.signatures.update(sigs)

            pruned = prune_event(event)

            for key_id in event.signatures[server_name]:
                verify_signed_json(pruned.get_pdu_json(), server_name,
                                   server_keys[server_name][key_id])

        event_json = encode_canonical_json(event.get_dict()).decode("UTF-8")

        metadata_json = encode_canonical_json(
            event.internal_metadata.get_dict()).decode("UTF-8")

        store._simple_insert_txn(
            cursor,
            table="event_json",
            values={
                "event_id": event.event_id,
                "room_id": event.room_id,
                "internal_metadata": metadata_json,
                "json": event_json,
            },
            or_replace=True,
        )
Exemple #44
0
 def run_test(self, evdict, matchdict):
     self.assertEquals(
         prune_event(FrozenEvent(evdict)).get_dict(),
         matchdict
     )
Exemple #45
0
    def _get_event_from_row(self,
                            internal_metadata,
                            js,
                            redacted,
                            format_version,
                            rejected_reason=None):
        with Measure(self._clock, "_get_event_from_row"):
            d = json.loads(js)
            internal_metadata = json.loads(internal_metadata)

            if rejected_reason:
                rejected_reason = yield self._simple_select_one_onecol(
                    table="rejections",
                    keyvalues={"event_id": rejected_reason},
                    retcol="reason",
                    desc="_get_event_from_row_rejected_reason",
                )

            if format_version is None:
                # This means that we stored the event before we had the concept
                # of a event format version, so it must be a V1 event.
                format_version = EventFormatVersions.V1

            original_ev = event_type_from_format_version(format_version)(
                event_dict=d,
                internal_metadata_dict=internal_metadata,
                rejected_reason=rejected_reason,
            )

            redacted_event = None
            if redacted:
                redacted_event = prune_event(original_ev)

                redaction_id = yield self._simple_select_one_onecol(
                    table="redactions",
                    keyvalues={"redacts": redacted_event.event_id},
                    retcol="event_id",
                    desc="_get_event_from_row_redactions",
                )

                redacted_event.unsigned["redacted_by"] = redaction_id
                # Get the redaction event.

                because = yield self.get_event(
                    redaction_id,
                    check_redacted=False,
                    allow_none=True,
                )

                if because:
                    # It's fine to do add the event directly, since get_pdu_json
                    # will serialise this field correctly
                    redacted_event.unsigned["redacted_because"] = because

                    # Starting in room version v3, some redactions need to be
                    # rechecked if we didn't have the redacted event at the
                    # time, so we recheck on read instead.
                    if because.internal_metadata.need_to_check_redaction():
                        expected_domain = get_domain_from_id(
                            original_ev.sender)
                        if get_domain_from_id(
                                because.sender) == expected_domain:
                            # This redaction event is allowed. Mark as not needing a
                            # recheck.
                            because.internal_metadata.recheck_redaction = False
                        else:
                            # Senders don't match, so the event isn't actually redacted
                            redacted_event = None

            cache_entry = _EventCacheEntry(
                event=original_ev,
                redacted_event=redacted_event,
            )

            self._get_event_cache.prefill((original_ev.event_id, ),
                                          cache_entry)

        defer.returnValue(cache_entry)
Exemple #46
0
    def allowed(event):
        """
        Args:
            event (synapse.events.EventBase): event to check

        Returns:
            None|EventBase:
               None if the user cannot see this event at all

               a redacted copy of the event if they can only see a redacted
               version

               the original event if they can see it as normal.
        """
        # Only run some checks if these events aren't about to be sent to clients. This is
        # because, if this is not the case, we're probably only checking if the users can
        # see events in the room at that point in the DAG, and that shouldn't be decided
        # on those checks.
        if filter_send_to_client:
            if event.type == EventTypes.Dummy:
                return None

            if not event.is_state() and event.sender in ignore_list:
                return None

            # Until MSC2261 has landed we can't redact malicious alias events, so for
            # now we temporarily filter out m.room.aliases entirely to mitigate
            # abuse, while we spec a better solution to advertising aliases
            # on rooms.
            if event.type == EventTypes.Aliases:
                return None

            # Don't try to apply the room's retention policy if the event is a state
            # event, as MSC1763 states that retention is only considered for non-state
            # events.
            if not event.is_state():
                retention_policy = retention_policies[event.room_id]
                max_lifetime = retention_policy.get("max_lifetime")

                if max_lifetime is not None:
                    oldest_allowed_ts = storage.main.clock.time_msec() - max_lifetime

                    if event.origin_server_ts < oldest_allowed_ts:
                        return None

        if event.event_id in always_include_ids:
            return event

        state = event_id_to_state[event.event_id]

        # get the room_visibility at the time of the event.
        visibility_event = state.get((EventTypes.RoomHistoryVisibility, ""), None)
        if visibility_event:
            visibility = visibility_event.content.get(
                "history_visibility", HistoryVisibility.SHARED
            )
        else:
            visibility = HistoryVisibility.SHARED

        if visibility not in VISIBILITY_PRIORITY:
            visibility = HistoryVisibility.SHARED

        # Always allow history visibility events on boundaries. This is done
        # by setting the effective visibility to the least restrictive
        # of the old vs new.
        if event.type == EventTypes.RoomHistoryVisibility:
            prev_content = event.unsigned.get("prev_content", {})
            prev_visibility = prev_content.get("history_visibility", None)

            if prev_visibility not in VISIBILITY_PRIORITY:
                prev_visibility = HistoryVisibility.SHARED

            new_priority = VISIBILITY_PRIORITY.index(visibility)
            old_priority = VISIBILITY_PRIORITY.index(prev_visibility)
            if old_priority < new_priority:
                visibility = prev_visibility

        # likewise, if the event is the user's own membership event, use
        # the 'most joined' membership
        membership = None
        if event.type == EventTypes.Member and event.state_key == user_id:
            membership = event.content.get("membership", None)
            if membership not in MEMBERSHIP_PRIORITY:
                membership = "leave"

            prev_content = event.unsigned.get("prev_content", {})
            prev_membership = prev_content.get("membership", None)
            if prev_membership not in MEMBERSHIP_PRIORITY:
                prev_membership = "leave"

            # Always allow the user to see their own leave events, otherwise
            # they won't see the room disappear if they reject the invite
            if membership == "leave" and (
                prev_membership == "join" or prev_membership == "invite"
            ):
                return event

            new_priority = MEMBERSHIP_PRIORITY.index(membership)
            old_priority = MEMBERSHIP_PRIORITY.index(prev_membership)
            if old_priority < new_priority:
                membership = prev_membership

        # otherwise, get the user's membership at the time of the event.
        if membership is None:
            membership_event = state.get((EventTypes.Member, user_id), None)
            if membership_event:
                membership = membership_event.membership

        # if the user was a member of the room at the time of the event,
        # they can see it.
        if membership == Membership.JOIN:
            return event

        # otherwise, it depends on the room visibility.

        if visibility == HistoryVisibility.JOINED:
            # we weren't a member at the time of the event, so we can't
            # see this event.
            return None

        elif visibility == HistoryVisibility.INVITED:
            # user can also see the event if they were *invited* at the time
            # of the event.
            return event if membership == Membership.INVITE else None

        elif visibility == HistoryVisibility.SHARED and is_peeking:
            # if the visibility is shared, users cannot see the event unless
            # they have *subequently* joined the room (or were members at the
            # time, of course)
            #
            # XXX: if the user has subsequently joined and then left again,
            # ideally we would share history up to the point they left. But
            # we don't know when they left. We just treat it as though they
            # never joined, and restrict access.
            return None

        # the visibility is either shared or world_readable, and the user was
        # not a member at the time. We allow it, provided the original sender
        # has not requested their data to be erased, in which case, we return
        # a redacted version.
        if erased_senders[event.sender]:
            return prune_event(event)

        return event
Exemple #47
0
def _check_sigs_on_pdus(keyring, room_version, pdus):
    """Check that the given events are correctly signed

    Args:
        keyring (synapse.crypto.Keyring): keyring object to do the checks
        room_version (str): the room version of the PDUs
        pdus (Collection[EventBase]): the events to be checked

    Returns:
        List[Deferred]: a Deferred for each event in pdus, which will either succeed if
           the signatures are valid, or fail (with a SynapseError) if not.
    """

    # we want to check that the event is signed by:
    #
    # (a) the sender's server
    #
    #     - except in the case of invites created from a 3pid invite, which are exempt
    #     from this check, because the sender has to match that of the original 3pid
    #     invite, but the event may come from a different HS, for reasons that I don't
    #     entirely grok (why do the senders have to match? and if they do, why doesn't the
    #     joining server ask the inviting server to do the switcheroo with
    #     exchange_third_party_invite?).
    #
    #     That's pretty awful, since redacting such an invite will render it invalid
    #     (because it will then look like a regular invite without a valid signature),
    #     and signatures are *supposed* to be valid whether or not an event has been
    #     redacted. But this isn't the worst of the ways that 3pid invites are broken.
    #
    # (b) for V1 and V2 rooms, the server which created the event_id
    #
    # let's start by getting the domain for each pdu, and flattening the event back
    # to JSON.

    pdus_to_check = [
        PduToCheckSig(
            pdu=p,
            redacted_pdu_json=prune_event(p).get_pdu_json(),
            sender_domain=get_domain_from_id(p.sender),
            deferreds=[],
        )
        for p in pdus
    ]

    v = KNOWN_ROOM_VERSIONS.get(room_version)
    if not v:
        raise RuntimeError("Unrecognized room version %s" % (room_version,))

    # First we check that the sender event is signed by the sender's domain
    # (except if its a 3pid invite, in which case it may be sent by any server)
    pdus_to_check_sender = [p for p in pdus_to_check if not _is_invite_via_3pid(p.pdu)]

    more_deferreds = keyring.verify_json_objects_for_server(
        [
            (
                p.sender_domain,
                p.redacted_pdu_json,
                p.pdu.origin_server_ts if v.enforce_key_validity else 0,
                p.pdu.event_id,
            )
            for p in pdus_to_check_sender
        ]
    )

    def sender_err(e, pdu_to_check):
        errmsg = "event id %s: unable to verify signature for sender %s: %s" % (
            pdu_to_check.pdu.event_id,
            pdu_to_check.sender_domain,
            e.getErrorMessage(),
        )
        raise SynapseError(403, errmsg, Codes.FORBIDDEN)

    for p, d in zip(pdus_to_check_sender, more_deferreds):
        d.addErrback(sender_err, p)
        p.deferreds.append(d)

    # now let's look for events where the sender's domain is different to the
    # event id's domain (normally only the case for joins/leaves), and add additional
    # checks. Only do this if the room version has a concept of event ID domain
    # (ie, the room version uses old-style non-hash event IDs).
    if v.event_format == EventFormatVersions.V1:
        pdus_to_check_event_id = [
            p
            for p in pdus_to_check
            if p.sender_domain != get_domain_from_id(p.pdu.event_id)
        ]

        more_deferreds = keyring.verify_json_objects_for_server(
            [
                (
                    get_domain_from_id(p.pdu.event_id),
                    p.redacted_pdu_json,
                    p.pdu.origin_server_ts if v.enforce_key_validity else 0,
                    p.pdu.event_id,
                )
                for p in pdus_to_check_event_id
            ]
        )

        def event_err(e, pdu_to_check):
            errmsg = (
                "event id %s: unable to verify signature for event id domain: %s"
                % (pdu_to_check.pdu.event_id, e.getErrorMessage())
            )
            raise SynapseError(403, errmsg, Codes.FORBIDDEN)

        for p, d in zip(pdus_to_check_event_id, more_deferreds):
            d.addErrback(event_err, p)
            p.deferreds.append(d)

    # replace lists of deferreds with single Deferreds
    return [_flatten_deferred_list(p.deferreds) for p in pdus_to_check]
Exemple #48
0
    def allowed(event):
        """
        Args:
            event (synapse.events.EventBase): event to check

        Returns:
            None|EventBase:
               None if the user cannot see this event at all

               a redacted copy of the event if they can only see a redacted
               version

               the original event if they can see it as normal.
        """
        if not event.is_state() and event.sender in ignore_list:
            return None

        if event.event_id in always_include_ids:
            return event

        state = event_id_to_state[event.event_id]

        # get the room_visibility at the time of the event.
        visibility_event = state.get((EventTypes.RoomHistoryVisibility, ""),
                                     None)
        if visibility_event:
            visibility = visibility_event.content.get("history_visibility",
                                                      "shared")
        else:
            visibility = "shared"

        if visibility not in VISIBILITY_PRIORITY:
            visibility = "shared"

        # Always allow history visibility events on boundaries. This is done
        # by setting the effective visibility to the least restrictive
        # of the old vs new.
        if event.type == EventTypes.RoomHistoryVisibility:
            prev_content = event.unsigned.get("prev_content", {})
            prev_visibility = prev_content.get("history_visibility", None)

            if prev_visibility not in VISIBILITY_PRIORITY:
                prev_visibility = "shared"

            new_priority = VISIBILITY_PRIORITY.index(visibility)
            old_priority = VISIBILITY_PRIORITY.index(prev_visibility)
            if old_priority < new_priority:
                visibility = prev_visibility

        # likewise, if the event is the user's own membership event, use
        # the 'most joined' membership
        membership = None
        if event.type == EventTypes.Member and event.state_key == user_id:
            membership = event.content.get("membership", None)
            if membership not in MEMBERSHIP_PRIORITY:
                membership = "leave"

            prev_content = event.unsigned.get("prev_content", {})
            prev_membership = prev_content.get("membership", None)
            if prev_membership not in MEMBERSHIP_PRIORITY:
                prev_membership = "leave"

            # Always allow the user to see their own leave events, otherwise
            # they won't see the room disappear if they reject the invite
            if membership == "leave" and (prev_membership == "join"
                                          or prev_membership == "invite"):
                return event

            new_priority = MEMBERSHIP_PRIORITY.index(membership)
            old_priority = MEMBERSHIP_PRIORITY.index(prev_membership)
            if old_priority < new_priority:
                membership = prev_membership

        # otherwise, get the user's membership at the time of the event.
        if membership is None:
            membership_event = state.get((EventTypes.Member, user_id), None)
            if membership_event:
                membership = membership_event.membership

        # if the user was a member of the room at the time of the event,
        # they can see it.
        if membership == Membership.JOIN:
            return event

        # otherwise, it depends on the room visibility.

        if visibility == "joined":
            # we weren't a member at the time of the event, so we can't
            # see this event.
            return None

        elif visibility == "invited":
            # user can also see the event if they were *invited* at the time
            # of the event.
            return event if membership == Membership.INVITE else None

        elif visibility == "shared" and is_peeking:
            # if the visibility is shared, users cannot see the event unless
            # they have *subequently* joined the room (or were members at the
            # time, of course)
            #
            # XXX: if the user has subsequently joined and then left again,
            # ideally we would share history up to the point they left. But
            # we don't know when they left. We just treat it as though they
            # never joined, and restrict access.
            return None

        # the visibility is either shared or world_readable, and the user was
        # not a member at the time. We allow it, provided the original sender
        # has not requested their data to be erased, in which case, we return
        # a redacted version.
        if erased_senders[event.sender]:
            return prune_event(event)

        return event
Exemple #49
0
def filter_events_for_server(store, server_name, events, redact=True,
                             check_history_visibility_only=False):
    """Filter a list of events based on whether given server is allowed to
    see them.

    Args:
        store (DataStore)
        server_name (str)
        events (iterable[FrozenEvent])
        redact (bool): Whether to return a redacted version of the event, or
            to filter them out entirely.
        check_history_visibility_only (bool): Whether to only check the
            history visibility, rather than things like if the sender has been
            erased. This is used e.g. during pagination to decide whether to
            backfill or not.

    Returns
        Deferred[list[FrozenEvent]]
    """

    def is_sender_erased(event, erased_senders):
        if erased_senders and erased_senders[event.sender]:
            logger.info(
                "Sender of %s has been erased, redacting",
                event.event_id,
            )
            return True
        return False

    def check_event_is_visible(event, state):
        history = state.get((EventTypes.RoomHistoryVisibility, ''), None)
        if history:
            visibility = history.content.get("history_visibility", "shared")
            if visibility in ["invited", "joined"]:
                # We now loop through all state events looking for
                # membership states for the requesting server to determine
                # if the server is either in the room or has been invited
                # into the room.
                for ev in itervalues(state):
                    if ev.type != EventTypes.Member:
                        continue
                    try:
                        domain = get_domain_from_id(ev.state_key)
                    except Exception:
                        continue

                    if domain != server_name:
                        continue

                    memtype = ev.membership
                    if memtype == Membership.JOIN:
                        return True
                    elif memtype == Membership.INVITE:
                        if visibility == "invited":
                            return True
                else:
                    # server has no users in the room: redact
                    return False

        return True

    # Lets check to see if all the events have a history visibility
    # of "shared" or "world_readable". If thats the case then we don't
    # need to check membership (as we know the server is in the room).
    event_to_state_ids = yield store.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(
            types=((EventTypes.RoomHistoryVisibility, ""),),
        )
    )

    visibility_ids = set()
    for sids in itervalues(event_to_state_ids):
        hist = sids.get((EventTypes.RoomHistoryVisibility, ""))
        if hist:
            visibility_ids.add(hist)

    # If we failed to find any history visibility events then the default
    # is "shared" visiblity.
    if not visibility_ids:
        all_open = True
    else:
        event_map = yield store.get_events(visibility_ids)
        all_open = all(
            e.content.get("history_visibility") in (None, "shared", "world_readable")
            for e in itervalues(event_map)
        )

    if not check_history_visibility_only:
        erased_senders = yield store.are_users_erased(
            (e.sender for e in events),
        )
    else:
        # We don't want to check whether users are erased, which is equivalent
        # to no users having been erased.
        erased_senders = {}

    if all_open:
        # all the history_visibility state affecting these events is open, so
        # we don't need to filter by membership state. We *do* need to check
        # for user erasure, though.
        if erased_senders:
            to_return = []
            for e in events:
                if not is_sender_erased(e, erased_senders):
                    to_return.append(e)
                elif redact:
                    to_return.append(prune_event(e))

            defer.returnValue(to_return)

        # If there are no erased users then we can just return the given list
        # of events without having to copy it.
        defer.returnValue(events)

    # Ok, so we're dealing with events that have non-trivial visibility
    # rules, so we need to also get the memberships of the room.

    # first, for each event we're wanting to return, get the event_ids
    # of the history vis and membership state at those events.
    event_to_state_ids = yield store.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(
            types=(
                (EventTypes.RoomHistoryVisibility, ""),
                (EventTypes.Member, None),
            ),
        )
    )

    # We only want to pull out member events that correspond to the
    # server's domain.
    #
    # event_to_state_ids contains lots of duplicates, so it turns out to be
    # cheaper to build a complete event_id => (type, state_key) dict, and then
    # filter out the ones we don't want
    #
    event_id_to_state_key = {
        event_id: key
        for key_to_eid in itervalues(event_to_state_ids)
        for key, event_id in iteritems(key_to_eid)
    }

    def include(typ, state_key):
        if typ != EventTypes.Member:
            return True

        # we avoid using get_domain_from_id here for efficiency.
        idx = state_key.find(":")
        if idx == -1:
            return False
        return state_key[idx + 1:] == server_name

    event_map = yield store.get_events([
        e_id
        for e_id, key in iteritems(event_id_to_state_key)
        if include(key[0], key[1])
    ])

    event_to_state = {
        e_id: {
            key: event_map[inner_e_id]
            for key, inner_e_id in iteritems(key_to_eid)
            if inner_e_id in event_map
        }
        for e_id, key_to_eid in iteritems(event_to_state_ids)
    }

    to_return = []
    for e in events:
        erased = is_sender_erased(e, erased_senders)
        visible = check_event_is_visible(e, event_to_state[e.event_id])
        if visible and not erased:
            to_return.append(e)
        elif redact:
            to_return.append(prune_event(e))

    defer.returnValue(to_return)