Beispiel #1
0
def check_redaction(event, auth_events):
    """Check whether the event sender is allowed to redact the target event.

    Returns:
        True if the the sender is allowed to redact the target event if the
        target event was created by them.
        False if the sender is allowed to redact the target event with no
        further checks.

    Raises:
        AuthError if the event sender is definitely not allowed to redact
        the target event.
    """
    user_level = get_user_power_level(event.user_id, auth_events)

    redact_level = _get_named_level(auth_events, "redact", 50)

    if user_level >= redact_level:
        return False

    redacter_domain = get_domain_from_id(event.event_id)
    redactee_domain = get_domain_from_id(event.redacts)
    if redacter_domain == redactee_domain:
        return True

    raise AuthError(
        403,
        "You don't have permission to redact events"
    )
Beispiel #2
0
        def _renew_attestation(group_id, user_id):
            try:
                if not self.is_mine_id(group_id):
                    destination = get_domain_from_id(group_id)
                elif not self.is_mine_id(user_id):
                    destination = get_domain_from_id(user_id)
                else:
                    logger.warn(
                        "Incorrectly trying to do attestations for user: %r in %r",
                        user_id, group_id,
                    )
                    yield self.store.remove_attestation_renewal(group_id, user_id)
                    return

                attestation = self.attestations.create_attestation(group_id, user_id)

                yield self.transport_client.renew_group_attestation(
                    destination, group_id, user_id,
                    content={"attestation": attestation},
                )

                yield self.store.update_attestation_renewal(
                    group_id, user_id, attestation
                )
            except Exception:
                logger.exception("Error renewing attestation of %r in %r",
                                 user_id, group_id)
Beispiel #3
0
    def verify_attestation(self, attestation, group_id, user_id, server_name=None):
        """Verifies that the given attestation matches the given parameters.

        An optional server_name can be supplied to explicitly set which server's
        signature is expected. Otherwise assumes that either the group_id or user_id
        is local and uses the other's server as the one to check.
        """

        if not server_name:
            if get_domain_from_id(group_id) == self.server_name:
                server_name = get_domain_from_id(user_id)
            elif get_domain_from_id(user_id) == self.server_name:
                server_name = get_domain_from_id(group_id)
            else:
                raise Exception("Expected either group_id or user_id to be local")

        if user_id != attestation["user_id"]:
            raise SynapseError(400, "Attestation has incorrect user_id")

        if group_id != attestation["group_id"]:
            raise SynapseError(400, "Attestation has incorrect group_id")
        valid_until_ms = attestation["valid_until_ms"]

        # TODO: We also want to check that *new* attestations that people give
        # us to store are valid for at least a little while.
        if valid_until_ms < self.clock.time_msec():
            raise SynapseError(400, "Attestation expired")

        yield self.keyring.verify_json_for_server(server_name, attestation)
Beispiel #4
0
    def get_users_in_group(self, group_id, requester_user_id):
        """Get users in a group
        """
        if self.is_mine_id(group_id):
            res = yield self.groups_server_handler.get_users_in_group(
                group_id, requester_user_id
            )
            defer.returnValue(res)

        group_server_name = get_domain_from_id(group_id)

        res = yield self.transport_client.get_users_in_group(
            get_domain_from_id(group_id), group_id, requester_user_id,
        )

        chunk = res["chunk"]
        valid_entries = []
        for entry in chunk:
            g_user_id = entry["user_id"]
            attestation = entry.pop("attestation", {})
            try:
                if get_domain_from_id(g_user_id) != group_server_name:
                    yield self.attestations.verify_attestation(
                        attestation,
                        group_id=group_id,
                        user_id=g_user_id,
                        server_name=get_domain_from_id(g_user_id),
                    )
                valid_entries.append(entry)
            except Exception as e:
                logger.info("Failed to verify user is in group: %s", e)

        res["chunk"] = valid_entries

        defer.returnValue(res)
Beispiel #5
0
        def _update_profile_in_user_dir_txn(txn):
            new_entry = self._simple_upsert_txn(
                txn,
                table="user_directory",
                keyvalues={"user_id": user_id},
                insertion_values={"room_id": room_id},
                values={"display_name": display_name, "avatar_url": avatar_url},
                lock=False,  # We're only inserter
            )

            if isinstance(self.database_engine, PostgresEngine):
                # We weight the localpart most highly, then display name and finally
                # server name
                if new_entry:
                    sql = """
                        INSERT INTO user_directory_search(user_id, vector)
                        VALUES (?,
                            setweight(to_tsvector('english', ?), 'A')
                            || setweight(to_tsvector('english', ?), 'D')
                            || setweight(to_tsvector('english', COALESCE(?, '')), 'B')
                        )
                    """
                    txn.execute(
                        sql,
                        (
                            user_id, get_localpart_from_id(user_id),
                            get_domain_from_id(user_id), display_name,
                        )
                    )
                else:
                    sql = """
                        UPDATE user_directory_search
                        SET vector = setweight(to_tsvector('english', ?), 'A')
                            || setweight(to_tsvector('english', ?), 'D')
                            || setweight(to_tsvector('english', COALESCE(?, '')), 'B')
                        WHERE user_id = ?
                    """
                    txn.execute(
                        sql,
                        (
                            get_localpart_from_id(user_id), get_domain_from_id(user_id),
                            display_name, user_id,
                        )
                    )
            elif isinstance(self.database_engine, Sqlite3Engine):
                value = "%s %s" % (user_id, display_name,) if display_name else user_id
                self._simple_upsert_txn(
                    txn,
                    table="user_directory_search",
                    keyvalues={"user_id": user_id},
                    values={"value": value},
                    lock=False,  # We're only inserter
                )
            else:
                # This should be unreachable.
                raise Exception("Unrecognized database engine")

            txn.call_after(self.get_user_in_directory.invalidate, (user_id,))
Beispiel #6
0
    def remove_user_from_group(self, group_id, user_id, requester_user_id, content):
        """Remove a user from a group
        """
        if user_id == requester_user_id:
            token = yield self.store.register_user_group_membership(
                group_id, user_id,
                membership="leave",
            )
            self.notifier.on_new_event(
                "groups_key", token, users=[user_id],
            )

            # TODO: Should probably remember that we tried to leave so that we can
            # retry if the group server is currently down.

        if self.is_mine_id(group_id):
            res = yield self.groups_server_handler.remove_user_from_group(
                group_id, user_id, requester_user_id, content,
            )
        else:
            content["requester_user_id"] = requester_user_id
            res = yield self.transport_client.remove_user_from_group(
                get_domain_from_id(group_id), group_id, requester_user_id,
                user_id, content,
            )

        defer.returnValue(res)
Beispiel #7
0
    def on_direct_to_device_edu(self, origin, content):
        local_messages = {}
        sender_user_id = content["sender"]
        if origin != get_domain_from_id(sender_user_id):
            logger.warn(
                "Dropping device message from %r with spoofed sender %r",
                origin, sender_user_id
            )
        message_type = content["type"]
        message_id = content["message_id"]
        for user_id, by_device in content["messages"].items():
            messages_by_device = {
                device_id: {
                    "content": message_content,
                    "type": message_type,
                    "sender": sender_user_id,
                }
                for device_id, message_content in by_device.items()
            }
            if messages_by_device:
                local_messages[user_id] = messages_by_device

        stream_id = yield self.store.add_messages_from_remote_to_device_inbox(
            origin, message_id, local_messages
        )

        self.notifier.on_new_event(
            "to_device_key", stream_id, users=local_messages.keys()
        )
Beispiel #8
0
    def _create_association(self, room_alias, room_id, servers=None, creator=None):
        # general association creation for both human users and app services

        for wchar in string.whitespace:
                if wchar in room_alias.localpart:
                    raise SynapseError(400, "Invalid characters in room alias")

        if not self.hs.is_mine(room_alias):
            raise SynapseError(400, "Room alias must be local")
            # TODO(erikj): Change this.

        # TODO(erikj): Add transactions.
        # TODO(erikj): Check if there is a current association.
        if not servers:
            users = yield self.state.get_current_user_in_room(room_id)
            servers = set(get_domain_from_id(u) for u in users)

        if not servers:
            raise SynapseError(400, "Failed to get server list")

        yield self.store.create_room_alias_association(
            room_alias,
            room_id,
            servers,
            creator=creator,
        )
Beispiel #9
0
    def on_direct_to_device_edu(self, origin, content):
        local_messages = {}
        sender_user_id = content["sender"]
        if origin != get_domain_from_id(sender_user_id):
            logger.warn(
                "Dropping device message from %r with spoofed sender %r",
                origin, sender_user_id
            )
        message_type = content["type"]
        message_id = content["message_id"]
        for user_id, by_device in content["messages"].items():
            # we use UserID.from_string to catch invalid user ids
            if not self.is_mine(UserID.from_string(user_id)):
                logger.warning("Request for keys for non-local user %s",
                               user_id)
                raise SynapseError(400, "Not a user here")

            messages_by_device = {
                device_id: {
                    "content": message_content,
                    "type": message_type,
                    "sender": sender_user_id,
                }
                for device_id, message_content in by_device.items()
            }
            if messages_by_device:
                local_messages[user_id] = messages_by_device

        stream_id = yield self.store.add_messages_from_remote_to_device_inbox(
            origin, message_id, local_messages
        )

        self.notifier.on_new_event(
            "to_device_key", stream_id, users=local_messages.keys()
        )
Beispiel #10
0
    def _add_new_user(self, room_id, user_id):
        """Add new user to the room by creating an event and poking the federation API.
        """

        hostname = get_domain_from_id(user_id)

        room_version = self.get_success(self.store.get_room_version(room_id))

        builder = EventBuilder(
            state=self.state,
            auth=self.auth,
            store=self.store,
            clock=self.clock,
            hostname=hostname,
            signing_key=self.random_signing_key,
            format_version=room_version_to_event_format(room_version),
            room_id=room_id,
            type=EventTypes.Member,
            sender=user_id,
            state_key=user_id,
            content={"membership": Membership.JOIN},
        )

        prev_event_ids = self.get_success(
            self.store.get_latest_event_ids_in_room(room_id)
        )

        event = self.get_success(builder.build(prev_event_ids))

        self.get_success(self.federation_handler.on_receive_pdu(hostname, event))

        # Check that it was successfully persisted.
        self.get_success(self.store.get_event(event.event_id))
        self.get_success(self.store.get_event(event.event_id))
Beispiel #11
0
    def remove_user_from_group(self, group_id, user_id, requester_user_id, content):
        """Remove a user from the group; either a user is leaving or an admin
        kicked them.
        """

        yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True)

        is_kick = False
        if requester_user_id != user_id:
            is_admin = yield self.store.is_user_admin_in_group(
                group_id, requester_user_id
            )
            if not is_admin:
                raise SynapseError(403, "User is not admin in group")

            is_kick = True

        yield self.store.remove_user_from_group(
            group_id, user_id,
        )

        if is_kick:
            if self.hs.is_mine_id(user_id):
                groups_local = self.hs.get_groups_local_handler()
                yield groups_local.user_removed_from_group(group_id, user_id, {})
            else:
                yield self.transport_client.remove_user_from_group_notification(
                    get_domain_from_id(user_id), group_id, user_id, {}
                )

        if not self.hs.is_mine_id(user_id):
            yield self.store.maybe_delete_remote_profile_cache(user_id)

        defer.returnValue({})
Beispiel #12
0
    def _recv_edu(self, origin, content):
        room_id = content["room_id"]
        user_id = content["user_id"]

        member = RoomMember(user_id=user_id, room_id=room_id)

        # Check that the string is a valid user id
        user = UserID.from_string(user_id)

        if user.domain != origin:
            logger.info(
                "Got typing update from %r with bad 'user_id': %r",
                origin, user_id,
            )
            return

        users = yield self.state.get_current_user_in_room(room_id)
        domains = set(get_domain_from_id(u) for u in users)

        if self.server_name in domains:
            logger.info("Got typing update from %s: %r", user_id, content)
            now = self.clock.time_msec()
            self._member_typing_until[member] = now + FEDERATION_TIMEOUT
            self.wheel_timer.insert(
                now=now,
                obj=member,
                then=now + FEDERATION_TIMEOUT,
            )
            self._push_update_local(
                member=member,
                typing=content["typing"]
            )
Beispiel #13
0
    def _push_update(self, room_id, user_id, typing):
        users = yield self.state.get_current_user_in_room(room_id)
        domains = set(get_domain_from_id(u) for u in users)

        deferreds = []
        for domain in domains:
            if domain == self.server_name:
                preserve_fn(self._push_update_local)(
                    room_id=room_id,
                    user_id=user_id,
                    typing=typing
                )
            else:
                deferreds.append(preserve_fn(self.federation.send_edu)(
                    destination=domain,
                    edu_type="m.typing",
                    content={
                        "room_id": room_id,
                        "user_id": user_id,
                        "typing": typing,
                    },
                    key=(room_id, user_id),
                ))

        yield preserve_context_over_deferred(
            defer.DeferredList(deferreds, consumeErrors=True)
        )
Beispiel #14
0
    def f(self, group_id, *args, **kwargs):
        if self.is_mine_id(group_id):
            return getattr(self.groups_server_handler, func_name)(
                group_id, *args, **kwargs
            )
        else:
            destination = get_domain_from_id(group_id)
            d = getattr(self.transport_client, func_name)(
                destination, group_id, *args, **kwargs
            )

            # Capture errors returned by the remote homeserver and
            # re-throw specific errors as SynapseErrors. This is so
            # when the remote end responds with things like 403 Not
            # In Group, we can communicate that to the client instead
            # of a 500.
            def http_response_errback(failure):
                failure.trap(HttpResponseException)
                e = failure.value
                if e.code == 403:
                    raise e.to_synapse_error()
                return failure

            def request_failed_errback(failure):
                failure.trap(RequestSendFailed)
                raise SynapseError(502, "Failed to contact group server")

            d.addErrback(http_response_errback)
            d.addErrback(request_failed_errback)
            return d
Beispiel #15
0
    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
Beispiel #16
0
def get_interested_remotes(store, states, state_handler):
    """Given a list of presence states figure out which remote servers
    should be sent which.

    All the presence states should be for local users only.

    Args:
        store (DataStore)
        states (list(UserPresenceState))

    Returns:
        Deferred list of ([destinations], [UserPresenceState]), where for
        each row the list of UserPresenceState should be sent to each
        destination
    """
    hosts_and_states = []

    # First we look up the rooms each user is in (as well as any explicit
    # subscriptions), then for each distinct room we look up the remote
    # hosts in those rooms.
    room_ids_to_states, users_to_states = yield get_interested_parties(store, states)

    for room_id, states in iteritems(room_ids_to_states):
        hosts = yield state_handler.get_current_hosts_in_room(room_id)
        hosts_and_states.append((hosts, states))

    for user_id, states in iteritems(users_to_states):
        host = get_domain_from_id(user_id)
        hosts_and_states.append(([host], states))

    defer.returnValue(hosts_and_states)
Beispiel #17
0
    def _push_remote(self, member, typing):
        try:
            users = yield self.state.get_current_user_in_room(member.room_id)
            self._member_last_federation_poke[member] = self.clock.time_msec()

            now = self.clock.time_msec()
            self.wheel_timer.insert(
                now=now,
                obj=member,
                then=now + FEDERATION_PING_INTERVAL,
            )

            for domain in set(get_domain_from_id(u) for u in users):
                if domain != self.server_name:
                    self.federation.send_edu(
                        destination=domain,
                        edu_type="m.typing",
                        content={
                            "room_id": member.room_id,
                            "user_id": member.user_id,
                            "typing": typing,
                        },
                        key=member,
                    )
        except Exception:
            logger.exception("Error pushing typing notif to remotes")
Beispiel #18
0
    def notify_device_update(self, user_id, device_ids):
        """Notify that a user's device(s) has changed. Pokes the notifier, and
        remote servers if the user is local.
        """
        users_who_share_room = yield self.store.get_users_who_share_room_with_user(
            user_id
        )

        hosts = set()
        if self.hs.is_mine_id(user_id):
            hosts.update(get_domain_from_id(u) for u in users_who_share_room)
            hosts.discard(self.server_name)

        position = yield self.store.add_device_change_to_streams(
            user_id, device_ids, list(hosts)
        )

        room_ids = yield self.store.get_rooms_for_user(user_id)

        yield self.notifier.on_new_event(
            "device_list_key", position, rooms=room_ids,
        )

        if hosts:
            logger.info("Sending device list update notif to: %r", hosts)
            for host in hosts:
                self.federation_sender.send_device_messages(host)
Beispiel #19
0
    def is_host_joined(self, room_id, host):
        if '%' in host or '_' in host:
            raise Exception("Invalid host name")

        sql = """
            SELECT state_key FROM current_state_events AS c
            INNER JOIN room_memberships USING (event_id)
            WHERE membership = 'join'
                AND type = 'm.room.member'
                AND c.room_id = ?
                AND state_key LIKE ?
            LIMIT 1
        """

        # We do need to be careful to ensure that host doesn't have any wild cards
        # in it, but we checked above for known ones and we'll check below that
        # the returned user actually has the correct domain.
        like_clause = "%:" + host

        rows = yield self._execute("is_host_joined", None, sql, room_id, like_clause)

        if not rows:
            defer.returnValue(False)

        user_id = rows[0][0]
        if get_domain_from_id(user_id) != host:
            # This can only happen if the host name has something funky in it
            raise Exception("Invalid host name")

        defer.returnValue(True)
Beispiel #20
0
    def incoming_device_list_update(self, origin, edu_content):
        """Called on incoming device list update from federation. Responsible
        for parsing the EDU and adding to pending updates list.
        """

        user_id = edu_content.pop("user_id")
        device_id = edu_content.pop("device_id")
        stream_id = str(edu_content.pop("stream_id"))  # They may come as ints
        prev_ids = edu_content.pop("prev_id", [])
        prev_ids = [str(p) for p in prev_ids]   # They may come as ints

        if get_domain_from_id(user_id) != origin:
            # TODO: Raise?
            logger.warning("Got device list update edu for %r from %r", user_id, origin)
            return

        room_ids = yield self.store.get_rooms_for_user(user_id)
        if not room_ids:
            # We don't share any rooms with this user. Ignore update, as we
            # probably won't get any further updates.
            return

        self._pending_updates.setdefault(user_id, []).append(
            (device_id, stream_id, prev_ids, edu_content)
        )

        yield self._handle_device_updates(user_id)
Beispiel #21
0
    def _push_remotes(self, receipts):
        """Given a list of receipts, works out which remote servers should be
        poked and pokes them.
        """
        # TODO: Some of this stuff should be coallesced.
        for receipt in receipts:
            room_id = receipt["room_id"]
            receipt_type = receipt["receipt_type"]
            user_id = receipt["user_id"]
            event_ids = receipt["event_ids"]
            data = receipt["data"]

            users = yield self.state.get_current_user_in_room(room_id)
            remotedomains = set(get_domain_from_id(u) for u in users)
            remotedomains = remotedomains.copy()
            remotedomains.discard(self.server_name)

            logger.debug("Sending receipt to: %r", remotedomains)

            for domain in remotedomains:
                self.federation.send_edu(
                    destination=domain,
                    edu_type="m.receipt",
                    content={
                        room_id: {
                            receipt_type: {
                                user_id: {
                                    "event_ids": event_ids,
                                    "data": data,
                                }
                            }
                        },
                    },
                    key=(room_id, receipt_type, user_id),
                )
Beispiel #22
0
    def get_group_summary(self, group_id, requester_user_id):
        """Get the group summary for a group.

        If the group is remote we check that the users have valid attestations.
        """
        if self.is_mine_id(group_id):
            res = yield self.groups_server_handler.get_group_summary(
                group_id, requester_user_id
            )
        else:
            res = yield self.transport_client.get_group_summary(
                get_domain_from_id(group_id), group_id, requester_user_id,
            )

            group_server_name = get_domain_from_id(group_id)

            # Loop through the users and validate the attestations.
            chunk = res["users_section"]["users"]
            valid_users = []
            for entry in chunk:
                g_user_id = entry["user_id"]
                attestation = entry.pop("attestation", {})
                try:
                    if get_domain_from_id(g_user_id) != group_server_name:
                        yield self.attestations.verify_attestation(
                            attestation,
                            group_id=group_id,
                            user_id=g_user_id,
                            server_name=get_domain_from_id(g_user_id),
                        )
                    valid_users.append(entry)
                except Exception as e:
                    logger.info("Failed to verify user is in group: %s", e)

            res["users_section"]["users"] = valid_users

            res["users_section"]["users"].sort(key=lambda e: e.get("order", 0))
            res["rooms_section"]["rooms"].sort(key=lambda e: e.get("order", 0))

        # Add `is_publicised` flag to indicate whether the user has publicised their
        # membership of the group on their profile
        result = yield self.store.get_publicised_groups_for_user(requester_user_id)
        is_publicised = group_id in result

        res.setdefault("user", {})["is_publicised"] = is_publicised

        defer.returnValue(res)
Beispiel #23
0
 def get_hosts_in_room(self, room_id, cache_context):
     """Returns the set of all hosts currently in the room
     """
     user_ids = yield self.get_users_in_room(
         room_id, on_invalidate=cache_context.invalidate
     )
     hosts = frozenset(get_domain_from_id(user_id) for user_id in user_ids)
     defer.returnValue(hosts)
Beispiel #24
0
    def claim_one_time_keys(self, query, timeout):
        local_query = []
        remote_queries = {}

        for user_id, device_keys in query.get("one_time_keys", {}).items():
            # we use UserID.from_string to catch invalid user ids
            if self.is_mine(UserID.from_string(user_id)):
                for device_id, algorithm in device_keys.items():
                    local_query.append((user_id, device_id, algorithm))
            else:
                domain = get_domain_from_id(user_id)
                remote_queries.setdefault(domain, {})[user_id] = device_keys

        results = yield self.store.claim_e2e_one_time_keys(local_query)

        json_result = {}
        failures = {}
        for user_id, device_keys in results.items():
            for device_id, keys in device_keys.items():
                for key_id, json_bytes in keys.items():
                    json_result.setdefault(user_id, {})[device_id] = {
                        key_id: json.loads(json_bytes)
                    }

        @defer.inlineCallbacks
        def claim_client_keys(destination):
            device_keys = remote_queries[destination]
            try:
                remote_result = yield self.federation.claim_client_keys(
                    destination,
                    {"one_time_keys": device_keys},
                    timeout=timeout
                )
                for user_id, keys in remote_result["one_time_keys"].items():
                    if user_id in device_keys:
                        json_result[user_id] = keys
            except Exception as e:
                failures[destination] = _exception_to_failure(e)

        yield make_deferred_yieldable(defer.gatherResults([
            run_in_background(claim_client_keys, destination)
            for destination in remote_queries
        ], consumeErrors=True))

        logger.info(
            "Claimed one-time-keys: %s",
            ",".join((
                "%s for %s:%s" % (key_id, user_id, device_id)
                for user_id, user_keys in iteritems(json_result)
                for device_id, device_keys in iteritems(user_keys)
                for key_id, _ in iteritems(device_keys)
            )),
        )

        defer.returnValue({
            "one_time_keys": json_result,
            "failures": failures
        })
Beispiel #25
0
    def get_destinations(self, state_entry):
        """Get set of destinations for a state entry

        Args:
            state_entry(synapse.state._StateCacheEntry)
        """
        if state_entry.state_group == self.state_group:
            defer.returnValue(frozenset(self.hosts_to_joined_users))

        with (yield self.linearizer.queue(())):
            if state_entry.state_group == self.state_group:
                pass
            elif state_entry.prev_group == self.state_group:
                for (typ, state_key), event_id in iteritems(state_entry.delta_ids):
                    if typ != EventTypes.Member:
                        continue

                    host = intern_string(get_domain_from_id(state_key))
                    user_id = state_key
                    known_joins = self.hosts_to_joined_users.setdefault(host, set())

                    event = yield self.store.get_event(event_id)
                    if event.membership == Membership.JOIN:
                        known_joins.add(user_id)
                    else:
                        known_joins.discard(user_id)

                        if not known_joins:
                            self.hosts_to_joined_users.pop(host, None)
            else:
                joined_users = yield self.store.get_joined_users_from_state(
                    self.room_id, state_entry
                )

                self.hosts_to_joined_users = {}
                for user_id in joined_users:
                    host = intern_string(get_domain_from_id(user_id))
                    self.hosts_to_joined_users.setdefault(host, set()).add(user_id)

            if state_entry.state_group:
                self.state_group = state_entry.state_group
            else:
                self.state_group = object()
            self._len = sum(len(v) for v in itervalues(self.hosts_to_joined_users))
        defer.returnValue(frozenset(self.hosts_to_joined_users))
Beispiel #26
0
    def get_association(self, room_alias):
        room_id = None
        if self.hs.is_mine(room_alias):
            result = yield self.get_association_from_room_alias(
                room_alias
            )

            if result:
                room_id = result.room_id
                servers = result.servers
        else:
            try:
                result = yield self.federation.make_query(
                    destination=room_alias.domain,
                    query_type="directory",
                    args={
                        "room_alias": room_alias.to_string(),
                    },
                    retry_on_dns_fail=False,
                    ignore_backoff=True,
                )
            except CodeMessageException as e:
                logging.warn("Error retrieving alias")
                if e.code == 404:
                    result = None
                else:
                    raise

            if result and "room_id" in result and "servers" in result:
                room_id = result["room_id"]
                servers = result["servers"]

        if not room_id:
            raise SynapseError(
                404,
                "Room alias %s not found" % (room_alias.to_string(),),
                Codes.NOT_FOUND
            )

        users = yield self.state.get_current_user_in_room(room_id)
        extra_servers = set(get_domain_from_id(u) for u in users)
        servers = set(extra_servers) | set(servers)

        # If this server is in the list of servers, return it first.
        if self.server_name in servers:
            servers = (
                [self.server_name] +
                [s for s in servers if s != self.server_name]
            )
        else:
            servers = list(servers)

        defer.returnValue({
            "room_id": room_id,
            "servers": servers,
        })
        return
Beispiel #27
0
    def claim_one_time_keys(self, query, timeout):
        local_query = []
        remote_queries = {}

        for user_id, device_keys in query.get("one_time_keys", {}).items():
            if self.is_mine_id(user_id):
                for device_id, algorithm in device_keys.items():
                    local_query.append((user_id, device_id, algorithm))
            else:
                domain = get_domain_from_id(user_id)
                remote_queries.setdefault(domain, {})[user_id] = device_keys

        results = yield self.store.claim_e2e_one_time_keys(local_query)

        json_result = {}
        failures = {}
        for user_id, device_keys in results.items():
            for device_id, keys in device_keys.items():
                for key_id, json_bytes in keys.items():
                    json_result.setdefault(user_id, {})[device_id] = {
                        key_id: json.loads(json_bytes)
                    }

        @defer.inlineCallbacks
        def claim_client_keys(destination):
            device_keys = remote_queries[destination]
            try:
                limiter = yield get_retry_limiter(
                    destination, self.clock, self.store
                )
                with limiter:
                    remote_result = yield self.federation.claim_client_keys(
                        destination,
                        {"one_time_keys": device_keys},
                        timeout=timeout
                    )
                    for user_id, keys in remote_result["one_time_keys"].items():
                        if user_id in device_keys:
                            json_result[user_id] = keys
            except CodeMessageException as e:
                failures[destination] = {
                    "status": e.code, "message": e.message
                }
            except NotRetryingDestination as e:
                failures[destination] = {
                    "status": 503, "message": "Not ready for retry",
                }

        yield preserve_context_over_deferred(defer.gatherResults([
            preserve_fn(claim_client_keys)(destination)
            for destination in remote_queries
        ]))

        defer.returnValue({
            "one_time_keys": json_result,
            "failures": failures
        })
Beispiel #28
0
 def _kick_user_from_group(user_id):
     if self.hs.is_mine_id(user_id):
         groups_local = self.hs.get_groups_local_handler()
         yield groups_local.user_removed_from_group(group_id, user_id, {})
     else:
         yield self.transport_client.remove_user_from_group_notification(
             get_domain_from_id(user_id), group_id, user_id, {}
         )
         yield self.store.maybe_delete_remote_profile_cache(user_id)
Beispiel #29
0
    def on_POST(self, origin, content, query, group_id, user_id):
        if get_domain_from_id(group_id) != origin:
            raise SynapseError(403, "group_id doesn't match origin")

        new_content = yield self.handler.on_invite(
            group_id, user_id, content,
        )

        defer.returnValue((200, new_content))
Beispiel #30
0
    def create_group(self, group_id, user_id, content):
        """Create a group
        """

        logger.info("Asking to create group with ID: %r", group_id)

        if self.is_mine_id(group_id):
            res = yield self.groups_server_handler.create_group(
                group_id, user_id, content
            )
            local_attestation = None
            remote_attestation = None
        else:
            local_attestation = self.attestations.create_attestation(group_id, user_id)
            content["attestation"] = local_attestation

            content["user_profile"] = yield self.profile_handler.get_profile(user_id)

            res = yield self.transport_client.create_group(
                get_domain_from_id(group_id), group_id, user_id, content,
            )

            remote_attestation = res["attestation"]
            yield self.attestations.verify_attestation(
                remote_attestation,
                group_id=group_id,
                user_id=user_id,
                server_name=get_domain_from_id(group_id),
            )

        is_publicised = content.get("publicise", False)
        token = yield self.store.register_user_group_membership(
            group_id, user_id,
            membership="join",
            is_admin=True,
            local_attestation=local_attestation,
            remote_attestation=remote_attestation,
            is_publicised=is_publicised,
        )
        self.notifier.on_new_event(
            "groups_key", token, users=[user_id],
        )

        defer.returnValue(res)
Beispiel #31
0
    async def f(self, group_id, *args, **kwargs):
        if not GroupID.is_valid(group_id):
            raise SynapseError(400,
                               "%s is not a legal group ID" % (group_id, ))

        if self.is_mine_id(group_id):
            return await getattr(self.groups_server_handler,
                                 func_name)(group_id, *args, **kwargs)
        else:
            destination = get_domain_from_id(group_id)

            try:
                return await getattr(self.transport_client,
                                     func_name)(destination, group_id, *args,
                                                **kwargs)
            except HttpResponseException as e:
                # Capture errors returned by the remote homeserver and
                # re-throw specific errors as SynapseErrors. This is so
                # when the remote end responds with things like 403 Not
                # In Group, we can communicate that to the client instead
                # of a 500.
                raise e.to_synapse_error()
            except RequestSendFailed:
                raise SynapseError(502, "Failed to contact group server")
Beispiel #32
0
    def invite(self, group_id, user_id, requester_user_id, config):
        """Invite a user to a group
        """
        content = {
            "requester_user_id": requester_user_id,
            "config": config,
        }
        if self.is_mine_id(group_id):
            res = yield self.groups_server_handler.invite_to_group(
                group_id,
                user_id,
                requester_user_id,
                content,
            )
        else:
            res = yield self.transport_client.invite_to_group(
                get_domain_from_id(group_id),
                group_id,
                user_id,
                requester_user_id,
                content,
            )

        defer.returnValue(res)
Beispiel #33
0
    async def _recv_edu(self, origin: str, content: JsonDict) -> None:
        room_id = content["room_id"]
        user_id = content["user_id"]

        member = RoomMember(user_id=user_id, room_id=room_id)

        # Check that the string is a valid user id
        user = UserID.from_string(user_id)

        if user.domain != origin:
            logger.info(
                "Got typing update from %r with bad 'user_id': %r", origin, user_id
            )
            return

        users = await self.store.get_users_in_room(room_id)
        domains = {get_domain_from_id(u) for u in users}

        if self.server_name in domains:
            logger.info("Got typing update from %s: %r", user_id, content)
            now = self.clock.time_msec()
            self._member_typing_until[member] = now + FEDERATION_TIMEOUT
            self.wheel_timer.insert(now=now, obj=member, then=now + FEDERATION_TIMEOUT)
            self._push_update_local(member=member, typing=content["typing"])
Beispiel #34
0
def check_database_before_upgrade(cur: Cursor,
                                  database_engine: BaseDatabaseEngine,
                                  config: HomeServerConfig) -> None:
    """Called before upgrading an existing database to check that it is broadly sane
    compared with the configuration.
    """
    logger.info("Checking database for consistency with configuration...")

    # if there are any users in the database, check that the username matches our
    # configured server name.

    cur.execute("SELECT name FROM users LIMIT 1")
    rows = cur.fetchall()
    if not rows:
        return

    user_domain = get_domain_from_id(rows[0][0])
    if user_domain == config.server.server_name:
        return

    raise Exception(
        "Found users in database not native to %s!\n"
        "You cannot change a synapse server_name after it's been configured" %
        (config.server.server_name, ))
Beispiel #35
0
    def user_joined_room(self, user, room_id):
        """Called (via the distributor) when a user joins a room. This funciton
        sends presence updates to servers, either:
            1. the joining user is a local user and we send their presence to
               all servers in the room.
            2. the joining user is a remote user and so we send presence for all
               local users in the room.
        """
        # We only need to send presence to servers that don't have it yet. We
        # don't need to send to local clients here, as that is done as part
        # of the event stream/sync.
        # TODO: Only send to servers not already in the room.
        user_ids = yield self.state.get_current_user_in_room(room_id)
        if self.is_mine(user):
            state = yield self.current_state_for_user(user.to_string())

            hosts = set(get_domain_from_id(u) for u in user_ids)
            self._push_to_remotes({host: (state, ) for host in hosts})
        else:
            user_ids = filter(self.is_mine_id, user_ids)

            states = yield self.current_state_for_users(user_ids)

            self._push_to_remotes({user.domain: states.values()})
Beispiel #36
0
    async def on_DELETE(
        self,
        origin: str,
        content: Literal[None],
        query: Dict[bytes, List[bytes]],
        group_id: str,
        role_id: str,
        user_id: str,
    ) -> Tuple[int, JsonDict]:
        requester_user_id = parse_string_from_args(query,
                                                   "requester_user_id",
                                                   required=True)
        if get_domain_from_id(requester_user_id) != origin:
            raise SynapseError(403, "requester_user_id doesn't match origin")

        if role_id == "":
            raise SynapseError(400, "role_id cannot be empty string")

        resp = await self.handler.delete_group_summary_user(group_id,
                                                            requester_user_id,
                                                            user_id=user_id,
                                                            role_id=role_id)

        return 200, resp
Beispiel #37
0
    def notify_device_update(self, user_id, device_ids):
        """Notify that a user's device(s) has changed. Pokes the notifier, and
        remote servers if the user is local.
        """
        users_who_share_room = yield self.store.get_users_who_share_room_with_user(
            user_id)

        hosts = set()
        if self.hs.is_mine_id(user_id):
            hosts.update(get_domain_from_id(u) for u in users_who_share_room)
            hosts.discard(self.server_name)

        position = yield self.store.add_device_change_to_streams(
            user_id, device_ids, list(hosts))

        for device_id in device_ids:
            logger.debug(
                "Notifying about update %r/%r, ID: %r",
                user_id,
                device_id,
                position,
            )

        room_ids = yield self.store.get_rooms_for_user(user_id)

        yield self.notifier.on_new_event(
            "device_list_key",
            position,
            rooms=room_ids,
        )

        if hosts:
            logger.info("Sending device list update notif for %r to: %r",
                        user_id, hosts)
            for host in hosts:
                self.federation_sender.send_device_messages(host)
Beispiel #38
0
    def notify_device_update(self, user_id, device_ids):
        """Notify that a user's device(s) has changed. Pokes the notifier, and
        remote servers if the user is local.
        """
        users_who_share_room = yield self.store.get_users_who_share_room_with_user(
            user_id)

        hosts = set()
        if self.hs.is_mine_id(user_id):
            hosts.update(get_domain_from_id(u) for u in users_who_share_room)
            hosts.discard(self.server_name)

        set_tag("target_hosts", hosts)

        position = yield self.store.add_device_change_to_streams(
            user_id, device_ids, list(hosts))

        for device_id in device_ids:
            logger.debug("Notifying about update %r/%r, ID: %r", user_id,
                         device_id, position)

        room_ids = yield self.store.get_rooms_for_user(user_id)

        # specify the user ID too since the user should always get their own device list
        # updates, even if they aren't in any rooms.
        yield self.notifier.on_new_event("device_list_key",
                                         position,
                                         users=[user_id],
                                         rooms=room_ids)

        if hosts:
            logger.info("Sending device list update notif for %r to: %r",
                        user_id, hosts)
            for host in hosts:
                self.federation_sender.send_device_messages(host)
                log_kv({"message": "sent device update to host", "host": host})
Beispiel #39
0
    def _push_update(self, room_id, user_id, typing):
        users = yield self.state.get_current_user_in_room(room_id)
        domains = set(get_domain_from_id(u) for u in users)

        deferreds = []
        for domain in domains:
            if domain == self.server_name:
                preserve_fn(self._push_update_local)(room_id=room_id,
                                                     user_id=user_id,
                                                     typing=typing)
            else:
                deferreds.append(
                    preserve_fn(self.federation.send_edu)(
                        destination=domain,
                        edu_type="m.typing",
                        content={
                            "room_id": room_id,
                            "user_id": user_id,
                            "typing": typing,
                        },
                    ))

        yield preserve_context_over_deferred(
            defer.DeferredList(deferreds, consumeErrors=True))
Beispiel #40
0
    async def _update_remote_profile_cache(self) -> None:
        """Called periodically to check profiles of remote users we haven't
        checked in a while.
        """
        entries = await self.store.get_remote_profile_cache_entries_that_expire(
            last_checked=self.clock.time_msec() - self.PROFILE_UPDATE_EVERY_MS
        )

        for user_id, displayname, avatar_url in entries:
            is_subscribed = await self.store.is_subscribed_remote_profile_for_user(
                user_id
            )
            if not is_subscribed:
                await self.store.maybe_delete_remote_profile_cache(user_id)
                continue

            try:
                profile = await self.federation.make_query(
                    destination=get_domain_from_id(user_id),
                    query_type="profile",
                    args={"user_id": user_id},
                    ignore_backoff=True,
                )
            except Exception:
                logger.exception("Failed to get avatar_url")

                await self.store.update_remote_profile_cache(
                    user_id, displayname, avatar_url
                )
                continue

            new_name = profile.get("displayname")
            new_avatar = profile.get("avatar_url")

            # We always hit update to update the last_check timestamp
            await self.store.update_remote_profile_cache(user_id, new_name, new_avatar)
Beispiel #41
0
    def _invalidate_state_caches(self, room_id: str,
                                 members_changed: Collection[str]) -> None:
        """Invalidates caches that are based on the current state, but does
        not stream invalidations down replication.

        Args:
            room_id: Room where state changed
            members_changed: The user_ids of members that have changed
        """
        # If there were any membership changes, purge the appropriate caches.
        for host in {get_domain_from_id(u) for u in members_changed}:
            self._attempt_to_invalidate_cache("is_host_joined",
                                              (room_id, host))
        if members_changed:
            self._attempt_to_invalidate_cache("get_users_in_room", (room_id, ))
            self._attempt_to_invalidate_cache("get_current_hosts_in_room",
                                              (room_id, ))
            self._attempt_to_invalidate_cache(
                "get_users_in_room_with_profiles", (room_id, ))

        # Purge other caches based on room state.
        self._attempt_to_invalidate_cache("get_room_summary", (room_id, ))
        self._attempt_to_invalidate_cache("get_partial_current_state_ids",
                                          (room_id, ))
Beispiel #42
0
    async def on_POST(
        self,
        origin: str,
        content: JsonDict,
        query: Dict[bytes, List[bytes]],
        group_id: str,
        category_id: str,
        room_id: str,
    ) -> Tuple[int, JsonDict]:
        requester_user_id = parse_string_from_args(query,
                                                   "requester_user_id",
                                                   required=True)
        if get_domain_from_id(requester_user_id) != origin:
            raise SynapseError(403, "requester_user_id doesn't match origin")

        if category_id == "":
            raise SynapseError(400, "category_id cannot be empty string",
                               Codes.INVALID_PARAM)

        if len(category_id) > MAX_GROUP_CATEGORYID_LENGTH:
            raise SynapseError(
                400,
                "category_id may not be longer than %s characters" %
                (MAX_GROUP_CATEGORYID_LENGTH, ),
                Codes.INVALID_PARAM,
            )

        resp = await self.handler.update_group_summary_room(
            group_id,
            requester_user_id,
            room_id=room_id,
            category_id=category_id,
            content=content,
        )

        return 200, resp
Beispiel #43
0
    async def user_device_resync(
            self,
            user_id: str,
            mark_failed_as_stale: bool = True) -> Optional[JsonDict]:
        """Fetches all devices for a user and updates the device cache with them.

        Args:
            user_id: The user's id whose device_list will be updated.
            mark_failed_as_stale: Whether to mark the user's device list as stale
                if the attempt to resync failed.
        Returns:
            A dict with device info as under the "devices" in the result of this
            request:
            https://matrix.org/docs/spec/server_server/r0.1.2#get-matrix-federation-v1-user-devices-userid
        """
        logger.debug("Attempting to resync the device list for %s", user_id)
        log_kv({"message": "Doing resync to update device list."})
        # Fetch all devices for the user.
        origin = get_domain_from_id(user_id)
        try:
            result = await self.federation.query_user_devices(origin, user_id)
        except NotRetryingDestination:
            if mark_failed_as_stale:
                # Mark the remote user's device list as stale so we know we need to retry
                # it later.
                await self.store.mark_remote_user_device_cache_as_stale(user_id
                                                                        )

            return None
        except (RequestSendFailed, HttpResponseException) as e:
            logger.warning(
                "Failed to handle device list update for %s: %s",
                user_id,
                e,
            )

            if mark_failed_as_stale:
                # Mark the remote user's device list as stale so we know we need to retry
                # it later.
                await self.store.mark_remote_user_device_cache_as_stale(user_id
                                                                        )

            # We abort on exceptions rather than accepting the update
            # as otherwise synapse will 'forget' that its device list
            # is out of date. If we bail then we will retry the resync
            # next time we get a device list update for this user_id.
            # This makes it more likely that the device lists will
            # eventually become consistent.
            return None
        except FederationDeniedError as e:
            set_tag("error", True)
            log_kv({"reason": "FederationDeniedError"})
            logger.info(e)
            return None
        except Exception as e:
            set_tag("error", True)
            log_kv({
                "message": "Exception raised by federation request",
                "exception": e
            })
            logger.exception("Failed to handle device list update for %s",
                             user_id)

            if mark_failed_as_stale:
                # Mark the remote user's device list as stale so we know we need to retry
                # it later.
                await self.store.mark_remote_user_device_cache_as_stale(user_id
                                                                        )

            return None
        log_kv({"result": result})
        stream_id = result["stream_id"]
        devices = result["devices"]

        # Get the master key and the self-signing key for this user if provided in the
        # response (None if not in the response).
        # The response will not contain the user signing key, as this key is only used by
        # its owner, thus it doesn't make sense to send it over federation.
        master_key = result.get("master_key")
        self_signing_key = result.get("self_signing_key")

        ignore_devices = False
        # If the remote server has more than ~1000 devices for this user
        # we assume that something is going horribly wrong (e.g. a bot
        # that logs in and creates a new device every time it tries to
        # send a message).  Maintaining lots of devices per user in the
        # cache can cause serious performance issues as if this request
        # takes more than 60s to complete, internal replication from the
        # inbound federation worker to the synapse master may time out
        # causing the inbound federation to fail and causing the remote
        # server to retry, causing a DoS.  So in this scenario we give
        # up on storing the total list of devices and only handle the
        # delta instead.
        if len(devices) > 1000:
            logger.warning(
                "Ignoring device list snapshot for %s as it has >1K devs (%d)",
                user_id,
                len(devices),
            )
            devices = []
            ignore_devices = True
        else:
            cached_devices = await self.store.get_cached_devices_for_user(
                user_id)
            if cached_devices == {d["device_id"]: d for d in devices}:
                logging.info(
                    "Skipping device list resync for %s, as our cache matches already",
                    user_id,
                )
                devices = []
                ignore_devices = True

        for device in devices:
            logger.debug(
                "Handling resync update %r/%r, ID: %r",
                user_id,
                device["device_id"],
                stream_id,
            )

        if not ignore_devices:
            await self.store.update_remote_device_list_cache(
                user_id, devices, stream_id)
        # mark the cache as valid, whether or not we actually processed any device
        # list updates.
        await self.store.mark_remote_user_device_cache_as_valid(user_id)
        device_ids = [device["device_id"] for device in devices]

        # Handle cross-signing keys.
        cross_signing_device_ids = await self.process_cross_signing_key_update(
            user_id,
            master_key,
            self_signing_key,
        )
        device_ids = device_ids + cross_signing_device_ids

        if device_ids:
            await self.device_handler.notify_device_update(user_id, device_ids)

        # We clobber the seen updates since we've re-synced from a given
        # point.
        self._seen_updates[user_id] = {stream_id}

        return result
Beispiel #44
0
    def get_events_as_list(
        self,
        event_ids,
        check_redacted=True,
        get_prev_content=False,
        allow_rejected=False,
    ):
        """Get events from the database and return in a list in the same order
        as given by `event_ids` arg.

        Args:
            event_ids (list): The event_ids of the events to fetch
            check_redacted (bool): If True, check if event has been redacted
                and redact it.
            get_prev_content (bool): If True and event is a state event,
                include the previous states content in the unsigned field.
            allow_rejected (bool): If True return rejected events.

        Returns:
            Deferred[list[EventBase]]: List of events fetched from the database. The
            events are in the same order as `event_ids` arg.

            Note that the returned list may be smaller than the list of event
            IDs if not all events could be fetched.
        """

        if not event_ids:
            return []

        # there may be duplicates so we cast the list to a set
        event_entry_map = yield self._get_events_from_cache_or_db(
            set(event_ids), allow_rejected=allow_rejected)

        events = []
        for event_id in event_ids:
            entry = event_entry_map.get(event_id, None)
            if not entry:
                continue

            if not allow_rejected:
                assert not entry.event.rejected_reason, (
                    "rejected event returned from _get_events_from_cache_or_db despite "
                    "allow_rejected=False")

            # We may not have had the original event when we received a redaction, so
            # we have to recheck auth now.

            if not allow_rejected and entry.event.type == EventTypes.Redaction:
                if not hasattr(entry.event, "redacts"):
                    # A redacted redaction doesn't have a `redacts` key, in
                    # which case lets just withhold the event.
                    #
                    # Note: Most of the time if the redactions has been
                    # redacted we still have the un-redacted event in the DB
                    # and so we'll still see the `redacts` key. However, this
                    # isn't always true e.g. if we have censored the event.
                    logger.debug(
                        "Withholding redaction event %s as we don't have redacts key",
                        event_id,
                    )
                    continue

                redacted_event_id = entry.event.redacts
                event_map = yield self._get_events_from_cache_or_db(
                    [redacted_event_id])
                original_event_entry = event_map.get(redacted_event_id)
                if not original_event_entry:
                    # we don't have the redacted event (or it was rejected).
                    #
                    # We assume that the redaction isn't authorized for now; if the
                    # redacted event later turns up, the redaction will be re-checked,
                    # and if it is found valid, the original will get redacted before it
                    # is served to the client.
                    logger.debug(
                        "Withholding redaction event %s since we don't (yet) have the "
                        "original %s",
                        event_id,
                        redacted_event_id,
                    )
                    continue

                original_event = original_event_entry.event
                if original_event.type == EventTypes.Create:
                    # we never serve redactions of Creates to clients.
                    logger.info(
                        "Withholding redaction %s of create event %s",
                        event_id,
                        redacted_event_id,
                    )
                    continue

                if original_event.room_id != entry.event.room_id:
                    logger.info(
                        "Withholding redaction %s of event %s from a different room",
                        event_id,
                        redacted_event_id,
                    )
                    continue

                if entry.event.internal_metadata.need_to_check_redaction():
                    original_domain = get_domain_from_id(original_event.sender)
                    redaction_domain = get_domain_from_id(entry.event.sender)
                    if original_domain != redaction_domain:
                        # the senders don't match, so this is forbidden
                        logger.info(
                            "Withholding redaction %s whose sender domain %s doesn't "
                            "match that of redacted event %s %s",
                            event_id,
                            redaction_domain,
                            redacted_event_id,
                            original_domain,
                        )
                        continue

                    # Update the cache to save doing the checks again.
                    entry.event.internal_metadata.recheck_redaction = False

            if check_redacted and entry.redacted_event:
                event = entry.redacted_event
            else:
                event = entry.event

            events.append(event)

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

        return events
Beispiel #45
0
    def _handle_device_updates(self, user_id):
        "Actually handle pending updates."

        with (yield self._remote_edu_linearizer.queue(user_id)):
            pending_updates = self._pending_updates.pop(user_id, [])
            if not pending_updates:
                # This can happen since we batch updates
                return

            # Given a list of updates we check if we need to resync. This
            # happens if we've missed updates.
            resync = yield self._need_to_do_resync(user_id, pending_updates)

            if resync:
                # Fetch all devices for the user.
                origin = get_domain_from_id(user_id)
                try:
                    result = yield self.federation.query_user_devices(
                        origin, user_id)
                except NotRetryingDestination:
                    # TODO: Remember that we are now out of sync and try again
                    # later
                    logger.warn(
                        "Failed to handle device list update for %s,"
                        " we're not retrying the remote",
                        user_id,
                    )
                    # We abort on exceptions rather than accepting the update
                    # as otherwise synapse will 'forget' that its device list
                    # is out of date. If we bail then we will retry the resync
                    # next time we get a device list update for this user_id.
                    # This makes it more likely that the device lists will
                    # eventually become consistent.
                    return
                except FederationDeniedError as e:
                    logger.info(e)
                    return
                except Exception:
                    # TODO: Remember that we are now out of sync and try again
                    # later
                    logger.exception(
                        "Failed to handle device list update for %s", user_id)
                    return

                stream_id = result["stream_id"]
                devices = result["devices"]
                yield self.store.update_remote_device_list_cache(
                    user_id,
                    devices,
                    stream_id,
                )
                device_ids = [device["device_id"] for device in devices]
                yield self.device_handler.notify_device_update(
                    user_id, device_ids)
            else:
                # Simply update the single device, since we know that is the only
                # change (becuase of the single prev_id matching the current cache)
                for device_id, stream_id, prev_ids, content in pending_updates:
                    yield self.store.update_remote_device_list_cache_entry(
                        user_id,
                        device_id,
                        content,
                        stream_id,
                    )

                yield self.device_handler.notify_device_update(
                    user_id,
                    [device_id for device_id, _, _, _ in pending_updates])

            self._seen_updates.setdefault(user_id, set()).update(
                stream_id for _, stream_id, _, _ in pending_updates)
Beispiel #46
0
def validate_event_for_room_version(room_version_obj: RoomVersion,
                                    event: "EventBase") -> None:
    """Ensure that the event complies with the limits, and has the right signatures

    NB: does not *validate* the signatures - it assumes that any signatures present
    have already been checked.

    NB: it does not check that the event satisfies the auth rules (that is done in
    check_auth_rules_for_event) - these tests are independent of the rest of the state
    in the room.

    NB: This is used to check events that have been received over federation. As such,
    it can only enforce the checks specified in the relevant room version, to avoid
    a split-brain situation where some servers accept such events, and others reject
    them.

    TODO: consider moving this into EventValidator

    Args:
        room_version_obj: the version of the room which contains this event
        event: the event to be checked

    Raises:
        SynapseError if there is a problem with the event
    """
    _check_size_limits(event)

    if not hasattr(event, "room_id"):
        raise AuthError(500, "Event has no room_id: %s" % event)

    # check that the event has the correct signatures
    sender_domain = get_domain_from_id(event.sender)

    is_invite_via_3pid = (event.type == EventTypes.Member
                          and event.membership == Membership.INVITE
                          and "third_party_invite" in event.content)

    # Check the sender's domain has signed the event
    if not event.signatures.get(sender_domain):
        # We allow invites via 3pid to have a sender from a different
        # HS, as the sender must match the sender of the original
        # 3pid invite. This is checked further down with the
        # other dedicated membership checks.
        if not is_invite_via_3pid:
            raise AuthError(403, "Event not signed by sender's server")

    if event.format_version in (EventFormatVersions.V1, ):
        # Only older room versions have event IDs to check.
        event_id_domain = get_domain_from_id(event.event_id)

        # Check the origin domain has signed the event
        if not event.signatures.get(event_id_domain):
            raise AuthError(403, "Event not signed by sending server")

    is_invite_via_allow_rule = (room_version_obj.msc3083_join_rules
                                and event.type == EventTypes.Member
                                and event.membership == Membership.JOIN
                                and EventContentFields.AUTHORISING_USER
                                in event.content)
    if is_invite_via_allow_rule:
        authoriser_domain = get_domain_from_id(
            event.content[EventContentFields.AUTHORISING_USER])
        if not event.signatures.get(authoriser_domain):
            raise AuthError(403, "Event not signed by authorising server")
Beispiel #47
0
    async def _handle_received_pdu(self, origin: str, pdu: EventBase) -> None:
        """ Process a PDU received in a federation /send/ transaction.

        If the event is invalid, then this method throws a FederationError.
        (The error will then be logged and sent back to the sender (which
        probably won't do anything with it), and other events in the
        transaction will be processed as normal).

        It is likely that we'll then receive other events which refer to
        this rejected_event in their prev_events, etc.  When that happens,
        we'll attempt to fetch the rejected event again, which will presumably
        fail, so those second-generation events will also get rejected.

        Eventually, we get to the point where there are more than 10 events
        between any new events and the original rejected event. Since we
        only try to backfill 10 events deep on received pdu, we then accept the
        new event, possibly introducing a discontinuity in the DAG, with new
        forward extremities, so normal service is approximately returned,
        until we try to backfill across the discontinuity.

        Args:
            origin: server which sent the pdu
            pdu: received pdu

        Raises: FederationError if the signatures / hash do not match, or
            if the event was unacceptable for any other reason (eg, too large,
            too many prev_events, couldn't find the prev_events)
        """
        # check that it's actually being sent from a valid destination to
        # workaround bug #1753 in 0.18.5 and 0.18.6
        if origin != get_domain_from_id(pdu.sender):
            # We continue to accept join events from any server; this is
            # necessary for the federation join dance to work correctly.
            # (When we join over federation, the "helper" server is
            # responsible for sending out the join event, rather than the
            # origin. See bug #1893. This is also true for some third party
            # invites).
            if not (pdu.type == "m.room.member" and pdu.content
                    and pdu.content.get("membership", None)
                    in (Membership.JOIN, Membership.INVITE)):
                logger.info("Discarding PDU %s from invalid origin %s",
                            pdu.event_id, origin)
                return
            else:
                logger.info("Accepting join PDU %s from %s", pdu.event_id,
                            origin)

        # We've already checked that we know the room version by this point
        room_version = await self.store.get_room_version(pdu.room_id)

        # Check signature.
        try:
            pdu = await self._check_sigs_and_hash(room_version, pdu)
        except SynapseError as e:
            raise FederationError("ERROR",
                                  e.code,
                                  e.msg,
                                  affected=pdu.event_id)

        await self.handler.on_receive_pdu(origin,
                                          pdu,
                                          sent_to_us_directly=True)
Beispiel #48
0
    async def _get_joined_hosts(
        self,
        room_id: str,
        state_group: int,
        current_state_ids: StateMap[str],
        state_entry: "_StateCacheEntry",
    ) -> FrozenSet[str]:
        # We don't use `state_group`, its there so that we can cache based on
        # it. However, its important that its never None, since two
        # current_state's with a state_group of None are likely to be different.
        #
        # The `state_group` must match the `state_entry.state_group` (if not None).
        assert state_group is not None
        assert state_entry.state_group is None or state_entry.state_group == state_group

        # We use a secondary cache of previous work to allow us to build up the
        # joined hosts for the given state group based on previous state groups.
        #
        # We cache one object per room containing the results of the last state
        # group we got joined hosts for. The idea is that generally
        # `get_joined_hosts` is called with the "current" state group for the
        # room, and so consecutive calls will be for consecutive state groups
        # which point to the previous state group.
        cache = await self._get_joined_hosts_cache(room_id)

        # If the state group in the cache matches, we already have the data we need.
        if state_entry.state_group == cache.state_group:
            return frozenset(cache.hosts_to_joined_users)

        # Since we'll mutate the cache we need to lock.
        with (await self._joined_host_linearizer.queue(room_id)):
            if state_entry.state_group == cache.state_group:
                # Same state group, so nothing to do. We've already checked for
                # this above, but the cache may have changed while waiting on
                # the lock.
                pass
            elif state_entry.prev_group == cache.state_group:
                # The cached work is for the previous state group, so we work out
                # the delta.
                for (typ,
                     state_key), event_id in state_entry.delta_ids.items():
                    if typ != EventTypes.Member:
                        continue

                    host = intern_string(get_domain_from_id(state_key))
                    user_id = state_key
                    known_joins = cache.hosts_to_joined_users.setdefault(
                        host, set())

                    event = await self.get_event(event_id)
                    if event.membership == Membership.JOIN:
                        known_joins.add(user_id)
                    else:
                        known_joins.discard(user_id)

                        if not known_joins:
                            cache.hosts_to_joined_users.pop(host, None)
            else:
                # The cache doesn't match the state group or prev state group,
                # so we calculate the result from first principles.
                joined_users = await self.get_joined_users_from_state(
                    room_id, state_entry)

                cache.hosts_to_joined_users = {}
                for user_id in joined_users:
                    host = intern_string(get_domain_from_id(user_id))
                    cache.hosts_to_joined_users.setdefault(host,
                                                           set()).add(user_id)

            if state_entry.state_group:
                cache.state_group = state_entry.state_group
            else:
                cache.state_group = object()

        return frozenset(cache.hosts_to_joined_users)
Beispiel #49
0
def _is_membership_change_allowed(room_version: RoomVersion,
                                  event: "EventBase",
                                  auth_events: StateMap["EventBase"]) -> None:
    """
    Confirms that the event which changes membership is an allowed change.

    Args:
        room_version: The version of the room.
        event: The event to check.
        auth_events: The current auth events of the room.

    Raises:
        AuthError if the event is not allowed.
    """
    membership = event.content["membership"]

    # Check if this is the room creator joining:
    if len(event.prev_event_ids()) == 1 and Membership.JOIN == membership:
        # Get room creation event:
        key = (EventTypes.Create, "")
        create = auth_events.get(key)
        if create and event.prev_event_ids()[0] == create.event_id:
            if create.content["creator"] == event.state_key:
                return

    target_user_id = event.state_key

    creating_domain = get_domain_from_id(event.room_id)
    target_domain = get_domain_from_id(target_user_id)
    if creating_domain != target_domain:
        if not _can_federate(event, auth_events):
            raise AuthError(403, "This room has been marked as unfederatable.")

    # get info about the caller
    key = (EventTypes.Member, event.user_id)
    caller = auth_events.get(key)

    caller_in_room = caller and caller.membership == Membership.JOIN
    caller_invited = caller and caller.membership == Membership.INVITE
    caller_knocked = (caller and room_version.msc2403_knocking
                      and caller.membership == Membership.KNOCK)

    # get info about the target
    key = (EventTypes.Member, target_user_id)
    target = auth_events.get(key)

    target_in_room = target and target.membership == Membership.JOIN
    target_banned = target and target.membership == Membership.BAN

    key = (EventTypes.JoinRules, "")
    join_rule_event = auth_events.get(key)
    if join_rule_event:
        join_rule = join_rule_event.content.get("join_rule", JoinRules.INVITE)
    else:
        join_rule = JoinRules.INVITE

    user_level = get_user_power_level(event.user_id, auth_events)
    target_level = get_user_power_level(target_user_id, auth_events)

    invite_level = get_named_level(auth_events, "invite", 0)
    ban_level = get_named_level(auth_events, "ban", 50)

    logger.debug(
        "_is_membership_change_allowed: %s",
        {
            "caller_in_room": caller_in_room,
            "caller_invited": caller_invited,
            "caller_knocked": caller_knocked,
            "target_banned": target_banned,
            "target_in_room": target_in_room,
            "membership": membership,
            "join_rule": join_rule,
            "target_user_id": target_user_id,
            "event.user_id": event.user_id,
        },
    )

    if Membership.INVITE == membership and "third_party_invite" in event.content:
        if not _verify_third_party_invite(event, auth_events):
            raise AuthError(403, "You are not invited to this room.")
        if target_banned:
            raise AuthError(403,
                            "%s is banned from the room" % (target_user_id, ))
        return

    # Require the user to be in the room for membership changes other than join/knock.
    # Note that the room version check for knocking is done implicitly by `caller_knocked`
    # and the ability to set a membership of `knock` in the first place.
    if Membership.JOIN != membership and Membership.KNOCK != membership:
        # If the user has been invited or has knocked, they are allowed to change their
        # membership event to leave
        if ((caller_invited or caller_knocked)
                and Membership.LEAVE == membership
                and target_user_id == event.user_id):
            return

        if not caller_in_room:  # caller isn't joined
            raise AuthError(
                403, "%s not in room %s." % (event.user_id, event.room_id))

    if Membership.INVITE == membership:
        # TODO (erikj): We should probably handle this more intelligently
        # PRIVATE join rules.

        # Invites are valid iff caller is in the room and target isn't.
        if target_banned:
            raise AuthError(403,
                            "%s is banned from the room" % (target_user_id, ))
        elif target_in_room:  # the target is already in the room.
            raise AuthError(403, "%s is already in the room." % target_user_id)
        else:
            if user_level < invite_level:
                raise AuthError(403,
                                "You don't have permission to invite users")
    elif Membership.JOIN == membership:
        # Joins are valid iff caller == target and:
        # * They are not banned.
        # * They are accepting a previously sent invitation.
        # * They are already joined (it's a NOOP).
        # * The room is public.
        # * The room is restricted and the user meets the allows rules.
        if event.user_id != target_user_id:
            raise AuthError(403, "Cannot force another user to join.")
        elif target_banned:
            raise AuthError(403, "You are banned from this room")
        elif join_rule == JoinRules.PUBLIC:
            pass
        elif (room_version.msc3083_join_rules
              and join_rule == JoinRules.RESTRICTED) or (
                  room_version.msc3787_knock_restricted_join_rule
                  and join_rule == JoinRules.KNOCK_RESTRICTED):
            # This is the same as public, but the event must contain a reference
            # to the server who authorised the join. If the event does not contain
            # the proper content it is rejected.
            #
            # Note that if the caller is in the room or invited, then they do
            # not need to meet the allow rules.
            if not caller_in_room and not caller_invited:
                authorising_user = event.content.get(
                    EventContentFields.AUTHORISING_USER)

                if authorising_user is None:
                    raise AuthError(403,
                                    "Join event is missing authorising user.")

                # The authorising user must be in the room.
                key = (EventTypes.Member, authorising_user)
                member_event = auth_events.get(key)
                _check_joined_room(member_event, authorising_user,
                                   event.room_id)

                authorising_user_level = get_user_power_level(
                    authorising_user, auth_events)
                if authorising_user_level < invite_level:
                    raise AuthError(
                        403, "Join event authorised by invalid server.")

        elif (join_rule == JoinRules.INVITE or
              (room_version.msc2403_knocking and join_rule == JoinRules.KNOCK)
              or (room_version.msc3787_knock_restricted_join_rule
                  and join_rule == JoinRules.KNOCK_RESTRICTED)):
            if not caller_in_room and not caller_invited:
                raise AuthError(403, "You are not invited to this room.")
        else:
            # TODO (erikj): may_join list
            # TODO (erikj): private rooms
            raise AuthError(403, "You are not allowed to join this room")
    elif Membership.LEAVE == membership:
        # TODO (erikj): Implement kicks.
        if target_banned and user_level < ban_level:
            raise AuthError(403,
                            "You cannot unban user %s." % (target_user_id, ))
        elif target_user_id != event.user_id:
            kick_level = get_named_level(auth_events, "kick", 50)

            if user_level < kick_level or user_level <= target_level:
                raise AuthError(403,
                                "You cannot kick user %s." % target_user_id)
    elif Membership.BAN == membership:
        if user_level < ban_level or user_level <= target_level:
            raise AuthError(403, "You don't have permission to ban")
    elif room_version.msc2403_knocking and Membership.KNOCK == membership:
        if join_rule != JoinRules.KNOCK and (
                not room_version.msc3787_knock_restricted_join_rule
                or join_rule != JoinRules.KNOCK_RESTRICTED):
            raise AuthError(403, "You don't have permission to knock")
        elif target_user_id != event.user_id:
            raise AuthError(403, "You cannot knock for other users")
        elif target_in_room:
            raise AuthError(403,
                            "You cannot knock on a room you are already in")
        elif caller_invited:
            raise AuthError(403, "You are already invited to this room")
        elif target_banned:
            raise AuthError(403, "You are banned from this room")
    else:
        raise AuthError(500, "Unknown membership %s" % membership)
Beispiel #50
0
    def query_devices(self, query_body, timeout):
        """ Handle a device key query from a client

        {
            "device_keys": {
                "<user_id>": ["<device_id>"]
            }
        }
        ->
        {
            "device_keys": {
                "<user_id>": {
                    "<device_id>": {
                        ...
                    }
                }
            }
        }
        """
        device_keys_query = query_body.get("device_keys", {})

        # separate users by domain.
        # make a map from domain to user_id to device_ids
        local_query = {}
        remote_queries = {}

        for user_id, device_ids in device_keys_query.items():
            if self.is_mine_id(user_id):
                local_query[user_id] = device_ids
            else:
                remote_queries[user_id] = device_ids

        # Firt get local devices.
        failures = {}
        results = {}
        if local_query:
            local_result = yield self.query_local_devices(local_query)
            for user_id, keys in local_result.items():
                if user_id in local_query:
                    results[user_id] = keys

        # Now attempt to get any remote devices from our local cache.
        remote_queries_not_in_cache = {}
        if remote_queries:
            query_list = []
            for user_id, device_ids in remote_queries.iteritems():
                if device_ids:
                    query_list.extend((user_id, device_id) for device_id in device_ids)
                else:
                    query_list.append((user_id, None))

            user_ids_not_in_cache, remote_results = (
                yield self.store.get_user_devices_from_cache(
                    query_list
                )
            )
            for user_id, devices in remote_results.iteritems():
                user_devices = results.setdefault(user_id, {})
                for device_id, device in devices.iteritems():
                    keys = device.get("keys", None)
                    device_display_name = device.get("device_display_name", None)
                    if keys:
                        result = dict(keys)
                        unsigned = result.setdefault("unsigned", {})
                        if device_display_name:
                            unsigned["device_display_name"] = device_display_name
                        user_devices[device_id] = result

            for user_id in user_ids_not_in_cache:
                domain = get_domain_from_id(user_id)
                r = remote_queries_not_in_cache.setdefault(domain, {})
                r[user_id] = remote_queries[user_id]

        # Now fetch any devices that we don't have in our cache
        @defer.inlineCallbacks
        def do_remote_query(destination):
            destination_query = remote_queries_not_in_cache[destination]
            try:
                limiter = yield get_retry_limiter(
                    destination, self.clock, self.store
                )
                with limiter:
                    remote_result = yield self.federation.query_client_keys(
                        destination,
                        {"device_keys": destination_query},
                        timeout=timeout
                    )

                for user_id, keys in remote_result["device_keys"].items():
                    if user_id in destination_query:
                        results[user_id] = keys

            except CodeMessageException as e:
                failures[destination] = {
                    "status": e.code, "message": e.message
                }
            except NotRetryingDestination as e:
                failures[destination] = {
                    "status": 503, "message": "Not ready for retry",
                }
            except Exception as e:
                # include ConnectionRefused and other errors
                failures[destination] = {
                    "status": 503, "message": e.message
                }

        yield preserve_context_over_deferred(defer.gatherResults([
            preserve_fn(do_remote_query)(destination)
            for destination in remote_queries_not_in_cache
        ]))

        defer.returnValue({
            "device_keys": results, "failures": failures,
        })
Beispiel #51
0
    async def _on_user_joined_room(self, room_id, user_id):
        """Called when we detect a user joining the room via the current state
        delta stream.

        Args:
            room_id (str)
            user_id (str)

        Returns:
            Deferred
        """

        if self.is_mine_id(user_id):
            # If this is a local user then we need to send their presence
            # out to hosts in the room (who don't already have it)

            # TODO: We should be able to filter the hosts down to those that
            # haven't previously seen the user

            state = await self.current_state_for_user(user_id)
            hosts = await self.state.get_current_hosts_in_room(room_id)

            # Filter out ourselves.
            hosts = {host for host in hosts if host != self.server_name}

            self.federation.send_presence_to_destinations(
                states=[state], destinations=hosts
            )
        else:
            # A remote user has joined the room, so we need to:
            #   1. Check if this is a new server in the room
            #   2. If so send any presence they don't already have for
            #      local users in the room.

            # TODO: We should be able to filter the users down to those that
            # the server hasn't previously seen

            # TODO: Check that this is actually a new server joining the
            # room.

            user_ids = await self.state.get_current_users_in_room(room_id)
            user_ids = list(filter(self.is_mine_id, user_ids))

            states_d = await self.current_state_for_users(user_ids)

            # Filter out old presence, i.e. offline presence states where
            # the user hasn't been active for a week. We can change this
            # depending on what we want the UX to be, but at the least we
            # should filter out offline presence where the state is just the
            # default state.
            now = self.clock.time_msec()
            states = [
                state
                for state in states_d.values()
                if state.state != PresenceState.OFFLINE
                or now - state.last_active_ts < 7 * 24 * 60 * 60 * 1000
                or state.status_msg is not None
            ]

            if states:
                self.federation.send_presence_to_destinations(
                    states=states, destinations=[get_domain_from_id(user_id)]
                )
Beispiel #52
0
    def notify_new_events(self, current_id):
        """This gets called when we have some new events we might want to
        send out to other servers.
        """
        self._last_poked_id = max(current_id, self._last_poked_id)

        if self._is_processing:
            return

        try:
            self._is_processing = True
            while True:
                last_token = yield self.store.get_federation_out_pos("events")
                next_token, events = yield self.store.get_all_new_events_stream(
                    last_token,
                    self._last_poked_id,
                    limit=20,
                )

                logger.debug("Handling %s -> %s", last_token, next_token)

                if not events and next_token >= self._last_poked_id:
                    break

                for event in events:
                    # Only send events for this server.
                    send_on_behalf_of = event.internal_metadata.get_send_on_behalf_of(
                    )
                    is_mine = self.is_mine_id(event.event_id)
                    if not is_mine and send_on_behalf_of is None:
                        continue

                    # Get the state from before the event.
                    # We need to make sure that this is the state from before
                    # the event and not from after it.
                    # Otherwise if the last member on a server in a room is
                    # banned then it won't receive the event because it won't
                    # be in the room after the ban.
                    users_in_room = yield self.state.get_current_user_in_room(
                        event.room_id,
                        latest_event_ids=[
                            prev_id for prev_id, _ in event.prev_events
                        ],
                    )

                    destinations = set(
                        get_domain_from_id(user_id)
                        for user_id in users_in_room)
                    if send_on_behalf_of is not None:
                        # If we are sending the event on behalf of another server
                        # then it already has the event and there is no reason to
                        # send the event to it.
                        destinations.discard(send_on_behalf_of)

                    logger.debug("Sending %s to %r", event, destinations)

                    self._send_pdu(event, destinations)

                yield self.store.update_federation_out_pos(
                    "events", next_token)

        finally:
            self._is_processing = False
Beispiel #53
0
    def claim_one_time_keys(self, query, timeout):
        local_query = []
        remote_queries = {}

        for user_id, device_keys in query.get("one_time_keys", {}).items():
            if self.is_mine_id(user_id):
                for device_id, algorithm in device_keys.items():
                    local_query.append((user_id, device_id, algorithm))
            else:
                domain = get_domain_from_id(user_id)
                remote_queries.setdefault(domain, {})[user_id] = device_keys

        results = yield self.store.claim_e2e_one_time_keys(local_query)

        json_result = {}
        failures = {}
        for user_id, device_keys in results.items():
            for device_id, keys in device_keys.items():
                for key_id, json_bytes in keys.items():
                    json_result.setdefault(user_id, {})[device_id] = {
                        key_id: json.loads(json_bytes)
                    }

        @defer.inlineCallbacks
        def claim_client_keys(destination):
            device_keys = remote_queries[destination]
            try:
                limiter = yield get_retry_limiter(
                    destination, self.clock, self.store
                )
                with limiter:
                    remote_result = yield self.federation.claim_client_keys(
                        destination,
                        {"one_time_keys": device_keys},
                        timeout=timeout
                    )
                    for user_id, keys in remote_result["one_time_keys"].items():
                        if user_id in device_keys:
                            json_result[user_id] = keys
            except CodeMessageException as e:
                failures[destination] = {
                    "status": e.code, "message": e.message
                }
            except NotRetryingDestination as e:
                failures[destination] = {
                    "status": 503, "message": "Not ready for retry",
                }
            except Exception as e:
                # include ConnectionRefused and other errors
                failures[destination] = {
                    "status": 503, "message": e.message
                }

        yield preserve_context_over_deferred(defer.gatherResults([
            preserve_fn(claim_client_keys)(destination)
            for destination in remote_queries
        ]))

        defer.returnValue({
            "one_time_keys": json_result,
            "failures": failures
        })
Beispiel #54
0
def check(event, auth_events, do_sig_check=True, do_size_check=True):
    """ Checks if this event is correctly authed.

    Args:
        event: the event being checked.
        auth_events (dict: event-key -> event): the existing room state.


    Returns:
        True if the auth checks pass.
    """
    if do_size_check:
        _check_size_limits(event)

    if not hasattr(event, "room_id"):
        raise AuthError(500, "Event has no room_id: %s" % event)

    if do_sig_check:
        sender_domain = get_domain_from_id(event.sender)
        event_id_domain = get_domain_from_id(event.event_id)

        is_invite_via_3pid = (event.type == EventTypes.Member
                              and event.membership == Membership.INVITE
                              and "third_party_invite" in event.content)

        # Check the sender's domain has signed the event
        if not event.signatures.get(sender_domain):
            # We allow invites via 3pid to have a sender from a different
            # HS, as the sender must match the sender of the original
            # 3pid invite. This is checked further down with the
            # other dedicated membership checks.
            if not is_invite_via_3pid:
                raise AuthError(403, "Event not signed by sender's server")

        # Check the event_id's domain has signed the event
        if not event.signatures.get(event_id_domain):
            raise AuthError(403, "Event not signed by sending server")

    if auth_events is None:
        # Oh, we don't know what the state of the room was, so we
        # are trusting that this is allowed (at least for now)
        logger.warn("Trusting event: %s", event.event_id)
        return True

    if event.type == EventTypes.Create:
        room_id_domain = get_domain_from_id(event.room_id)
        if room_id_domain != sender_domain:
            raise AuthError(
                403, "Creation event's room_id domain does not match sender's")
        # FIXME
        return True

    creation_event = auth_events.get((EventTypes.Create, ""), None)

    if not creation_event:
        raise SynapseError(403, "Room %r does not exist" % (event.room_id, ))

    creating_domain = get_domain_from_id(event.room_id)
    originating_domain = get_domain_from_id(event.sender)
    if creating_domain != originating_domain:
        if not _can_federate(event, auth_events):
            raise AuthError(403, "This room has been marked as unfederatable.")

    # FIXME: Temp hack
    if event.type == EventTypes.Aliases:
        if not event.is_state():
            raise AuthError(
                403,
                "Alias event must be a state event",
            )
        if not event.state_key:
            raise AuthError(403, "Alias event must have non-empty state_key")
        sender_domain = get_domain_from_id(event.sender)
        if event.state_key != sender_domain:
            raise AuthError(
                403, "Alias event's state_key does not match sender's domain")
        return True

    if logger.isEnabledFor(logging.DEBUG):
        logger.debug("Auth events: %s",
                     [a.event_id for a in auth_events.values()])

    if event.type == EventTypes.Member:
        allowed = _is_membership_change_allowed(event, auth_events)
        if allowed:
            logger.debug("Allowing! %s", event)
        else:
            logger.debug("Denying! %s", event)
        return allowed

    _check_event_sender_in_room(event, auth_events)

    # Special case to allow m.room.third_party_invite events wherever
    # a user is allowed to issue invites.  Fixes
    # https://github.com/vector-im/vector-web/issues/1208 hopefully
    if event.type == EventTypes.ThirdPartyInvite:
        user_level = get_user_power_level(event.user_id, auth_events)
        invite_level = _get_named_level(auth_events, "invite", 0)

        if user_level < invite_level:
            raise AuthError(403,
                            ("You cannot issue a third party invite for %s." %
                             (event.content.display_name, )))
        else:
            return True

    _can_send_event(event, auth_events)

    if event.type == EventTypes.PowerLevels:
        _check_power_levels(event, auth_events)

    if event.type == EventTypes.Redaction:
        check_redaction(event, auth_events)

    logger.debug("Allowing! %s", event)
Beispiel #55
0
def _is_membership_change_allowed(event, auth_events):
    membership = event.content["membership"]

    # Check if this is the room creator joining:
    if len(event.prev_events) == 1 and Membership.JOIN == membership:
        # Get room creation event:
        key = (
            EventTypes.Create,
            "",
        )
        create = auth_events.get(key)
        if create and event.prev_events[0][0] == create.event_id:
            if create.content["creator"] == event.state_key:
                return True

    target_user_id = event.state_key

    creating_domain = get_domain_from_id(event.room_id)
    target_domain = get_domain_from_id(target_user_id)
    if creating_domain != target_domain:
        if not _can_federate(event, auth_events):
            raise AuthError(403, "This room has been marked as unfederatable.")

    # get info about the caller
    key = (
        EventTypes.Member,
        event.user_id,
    )
    caller = auth_events.get(key)

    caller_in_room = caller and caller.membership == Membership.JOIN
    caller_invited = caller and caller.membership == Membership.INVITE

    # get info about the target
    key = (
        EventTypes.Member,
        target_user_id,
    )
    target = auth_events.get(key)

    target_in_room = target and target.membership == Membership.JOIN
    target_banned = target and target.membership == Membership.BAN

    key = (
        EventTypes.JoinRules,
        "",
    )
    join_rule_event = auth_events.get(key)
    if join_rule_event:
        join_rule = join_rule_event.content.get("join_rule", JoinRules.INVITE)
    else:
        join_rule = JoinRules.INVITE

    user_level = get_user_power_level(event.user_id, auth_events)
    target_level = get_user_power_level(target_user_id, auth_events)

    # FIXME (erikj): What should we do here as the default?
    ban_level = _get_named_level(auth_events, "ban", 50)

    logger.debug(
        "_is_membership_change_allowed: %s", {
            "caller_in_room": caller_in_room,
            "caller_invited": caller_invited,
            "target_banned": target_banned,
            "target_in_room": target_in_room,
            "membership": membership,
            "join_rule": join_rule,
            "target_user_id": target_user_id,
            "event.user_id": event.user_id,
        })

    if Membership.INVITE == membership and "third_party_invite" in event.content:
        if not _verify_third_party_invite(event, auth_events):
            raise AuthError(403, "You are not invited to this room.")
        if target_banned:
            raise AuthError(403,
                            "%s is banned from the room" % (target_user_id, ))
        return True

    if Membership.JOIN != membership:
        if (caller_invited and Membership.LEAVE == membership
                and target_user_id == event.user_id):
            return True

        if not caller_in_room:  # caller isn't joined
            raise AuthError(
                403, "%s not in room %s." % (
                    event.user_id,
                    event.room_id,
                ))

    if Membership.INVITE == membership:
        # TODO (erikj): We should probably handle this more intelligently
        # PRIVATE join rules.

        # Invites are valid iff caller is in the room and target isn't.
        if target_banned:
            raise AuthError(403,
                            "%s is banned from the room" % (target_user_id, ))
        elif target_in_room:  # the target is already in the room.
            raise AuthError(403, "%s is already in the room." % target_user_id)
        else:
            invite_level = _get_named_level(auth_events, "invite", 0)

            if user_level < invite_level:
                raise AuthError(403,
                                "You cannot invite user %s." % target_user_id)
    elif Membership.JOIN == membership:
        # Joins are valid iff caller == target and they were:
        # invited: They are accepting the invitation
        # joined: It's a NOOP
        if event.user_id != target_user_id:
            raise AuthError(403, "Cannot force another user to join.")
        elif target_banned:
            raise AuthError(403, "You are banned from this room")
        elif join_rule == JoinRules.PUBLIC:
            pass
        elif join_rule == JoinRules.INVITE:
            if not caller_in_room and not caller_invited:
                raise AuthError(403, "You are not invited to this room.")
        else:
            # TODO (erikj): may_join list
            # TODO (erikj): private rooms
            raise AuthError(403, "You are not allowed to join this room")
    elif Membership.LEAVE == membership:
        # TODO (erikj): Implement kicks.
        if target_banned and user_level < ban_level:
            raise AuthError(403,
                            "You cannot unban user &s." % (target_user_id, ))
        elif target_user_id != event.user_id:
            kick_level = _get_named_level(auth_events, "kick", 50)

            if user_level < kick_level or user_level <= target_level:
                raise AuthError(403,
                                "You cannot kick user %s." % target_user_id)
    elif Membership.BAN == membership:
        if user_level < ban_level or user_level <= target_level:
            raise AuthError(403, "You don't have permission to ban")
    else:
        raise AuthError(500, "Unknown membership %s" % membership)

    return True
Beispiel #56
0
async def _check_sigs_on_pdu(keyring: Keyring, room_version: RoomVersion,
                             pdu: EventBase) -> None:
    """Check that the given events are correctly signed

    Raise a SynapseError if the event wasn't correctly signed.

    Args:
        keyring: keyring object to do the checks
        room_version: the room version of the PDUs
        pdus: the events to be checked
    """

    # 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.

    # 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)
    if not _is_invite_via_3pid(pdu):
        try:
            await keyring.verify_event_for_server(
                get_domain_from_id(pdu.sender),
                pdu,
                pdu.origin_server_ts
                if room_version.enforce_key_validity else 0,
            )
        except Exception as e:
            errmsg = "event id %s: unable to verify signature for sender %s: %s" % (
                pdu.event_id,
                get_domain_from_id(pdu.sender),
                e,
            )
            raise SynapseError(403, errmsg, Codes.FORBIDDEN)

    # 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 room_version.event_format == EventFormatVersions.V1 and get_domain_from_id(
            pdu.event_id) != get_domain_from_id(pdu.sender):
        try:
            await keyring.verify_event_for_server(
                get_domain_from_id(pdu.event_id),
                pdu,
                pdu.origin_server_ts
                if room_version.enforce_key_validity else 0,
            )
        except Exception as e:
            errmsg = (
                "event id %s: unable to verify signature for event id domain %s: %s"
                % (
                    pdu.event_id,
                    get_domain_from_id(pdu.event_id),
                    e,
                ))
            raise SynapseError(403, errmsg, Codes.FORBIDDEN)

    # If this is a join event for a restricted room it may have been authorised
    # via a different server from the sending server. Check those signatures.
    if (room_version.msc3083_join_rules and pdu.type == EventTypes.Member
            and pdu.membership == Membership.JOIN
            and EventContentFields.AUTHORISING_USER in pdu.content):
        authorising_server = get_domain_from_id(
            pdu.content[EventContentFields.AUTHORISING_USER])
        try:
            await keyring.verify_event_for_server(
                authorising_server,
                pdu,
                pdu.origin_server_ts
                if room_version.enforce_key_validity else 0,
            )
        except Exception as e:
            errmsg = (
                "event id %s: unable to verify signature for authorising server %s: %s"
                % (
                    pdu.event_id,
                    authorising_server,
                    e,
                ))
            raise SynapseError(403, errmsg, Codes.FORBIDDEN)
Beispiel #57
0
        def _update_profile_in_user_dir_txn(txn):
            new_entry = self._simple_upsert_txn(
                txn,
                table="user_directory",
                keyvalues={"user_id": user_id},
                insertion_values={"room_id": room_id},
                values={
                    "display_name": display_name,
                    "avatar_url": avatar_url
                },
                lock=False,  # We're only inserter
            )

            if isinstance(self.database_engine, PostgresEngine):
                # We weight the localpart most highly, then display name and finally
                # server name
                if new_entry:
                    sql = """
                        INSERT INTO user_directory_search(user_id, vector)
                        VALUES (?,
                            setweight(to_tsvector('english', ?), 'A')
                            || setweight(to_tsvector('english', ?), 'D')
                            || setweight(to_tsvector('english', COALESCE(?, '')), 'B')
                        )
                    """
                    txn.execute(sql, (
                        user_id,
                        get_localpart_from_id(user_id),
                        get_domain_from_id(user_id),
                        display_name,
                    ))
                else:
                    sql = """
                        UPDATE user_directory_search
                        SET vector = setweight(to_tsvector('english', ?), 'A')
                            || setweight(to_tsvector('english', ?), 'D')
                            || setweight(to_tsvector('english', COALESCE(?, '')), 'B')
                        WHERE user_id = ?
                    """
                    txn.execute(sql, (
                        get_localpart_from_id(user_id),
                        get_domain_from_id(user_id),
                        display_name,
                        user_id,
                    ))
            elif isinstance(self.database_engine, Sqlite3Engine):
                value = "%s %s" % (
                    user_id,
                    display_name,
                ) if display_name else user_id
                self._simple_upsert_txn(
                    txn,
                    table="user_directory_search",
                    keyvalues={"user_id": user_id},
                    values={"value": value},
                    lock=False,  # We're only inserter
                )
            else:
                # This should be unreachable.
                raise Exception("Unrecognized database engine")

            txn.call_after(self.get_user_in_directory.invalidate, (user_id, ))
Beispiel #58
0
def check_auth_rules_for_event(
    room_version_obj: RoomVersion,
    event: "EventBase",
    auth_events: Iterable["EventBase"],
) -> None:
    """Check that an event complies with the auth rules

    Checks whether an event passes the auth rules with a given set of state events

    Assumes that we have already checked that the event is the right shape (it has
    enough signatures, has a room ID, etc). In other words:

     - it's fine for use in state resolution, when we have already decided whether to
       accept the event or not, and are now trying to decide whether it should make it
       into the room state

     - when we're doing the initial event auth, it is only suitable in combination with
       a bunch of other tests.

    Args:
        room_version_obj: the version of the room
        event: the event being checked.
        auth_events: the room state to check the events against.

    Raises:
        AuthError if the checks fail
    """
    # We need to ensure that the auth events are actually for the same room, to
    # stop people from using powers they've been granted in other rooms for
    # example.
    #
    # Arguably we don't need to do this when we're just doing state res, as presumably
    # the state res algorithm isn't silly enough to give us events from different rooms.
    # Still, it's easier to do it anyway.
    room_id = event.room_id
    for auth_event in auth_events:
        if auth_event.room_id != room_id:
            raise AuthError(
                403,
                "During auth for event %s in room %s, found event %s in the state "
                "which is in room %s" %
                (event.event_id, room_id, auth_event.event_id,
                 auth_event.room_id),
            )
        if auth_event.rejected_reason:
            raise AuthError(
                403,
                "During auth for event %s: found rejected event %s in the state"
                % (event.event_id, auth_event.event_id),
            )

    # Implementation of https://matrix.org/docs/spec/rooms/v1#authorization-rules
    #
    # 1. If type is m.room.create:
    if event.type == EventTypes.Create:
        # 1b. If the domain of the room_id does not match the domain of the sender,
        # reject.
        sender_domain = get_domain_from_id(event.sender)
        room_id_domain = get_domain_from_id(event.room_id)
        if room_id_domain != sender_domain:
            raise AuthError(
                403, "Creation event's room_id domain does not match sender's")

        # 1c. If content.room_version is present and is not a recognised version, reject
        room_version_prop = event.content.get("room_version", "1")
        if room_version_prop not in KNOWN_ROOM_VERSIONS:
            raise AuthError(
                403,
                "room appears to have unsupported version %s" %
                (room_version_prop, ),
            )

        logger.debug("Allowing! %s", event)
        return

    auth_dict = {(e.type, e.state_key): e for e in auth_events}

    # 3. If event does not have a m.room.create in its auth_events, reject.
    creation_event = auth_dict.get((EventTypes.Create, ""), None)
    if not creation_event:
        raise AuthError(403, "No create event in auth events")

    # additional check for m.federate
    creating_domain = get_domain_from_id(event.room_id)
    originating_domain = get_domain_from_id(event.sender)
    if creating_domain != originating_domain:
        if not _can_federate(event, auth_dict):
            raise AuthError(403, "This room has been marked as unfederatable.")

    # 4. If type is m.room.aliases
    if event.type == EventTypes.Aliases and room_version_obj.special_case_aliases_auth:
        # 4a. If event has no state_key, reject
        if not event.is_state():
            raise AuthError(403, "Alias event must be a state event")
        if not event.state_key:
            raise AuthError(403, "Alias event must have non-empty state_key")

        # 4b. If sender's domain doesn't matches [sic] state_key, reject
        sender_domain = get_domain_from_id(event.sender)
        if event.state_key != sender_domain:
            raise AuthError(
                403, "Alias event's state_key does not match sender's domain")

        # 4c. Otherwise, allow.
        logger.debug("Allowing! %s", event)
        return

    # 5. If type is m.room.membership
    if event.type == EventTypes.Member:
        _is_membership_change_allowed(room_version_obj, event, auth_dict)
        logger.debug("Allowing! %s", event)
        return

    _check_event_sender_in_room(event, auth_dict)

    # Special case to allow m.room.third_party_invite events wherever
    # a user is allowed to issue invites.  Fixes
    # https://github.com/vector-im/vector-web/issues/1208 hopefully
    if event.type == EventTypes.ThirdPartyInvite:
        user_level = get_user_power_level(event.user_id, auth_dict)
        invite_level = get_named_level(auth_dict, "invite", 0)

        if user_level < invite_level:
            raise AuthError(403, "You don't have permission to invite users")
        else:
            logger.debug("Allowing! %s", event)
            return

    _can_send_event(event, auth_dict)

    if event.type == EventTypes.PowerLevels:
        _check_power_levels(room_version_obj, event, auth_dict)

    if event.type == EventTypes.Redaction:
        check_redaction(room_version_obj, event, auth_dict)

    if (event.type == EventTypes.MSC2716_INSERTION
            or event.type == EventTypes.MSC2716_BATCH
            or event.type == EventTypes.MSC2716_MARKER):
        check_historical(room_version_obj, event, auth_dict)

    logger.debug("Allowing! %s", event)
Beispiel #59
0
        def _update_profile_in_user_dir_txn(txn):
            new_entry = self.db_pool.simple_upsert_txn(
                txn,
                table="user_directory",
                keyvalues={"user_id": user_id},
                values={
                    "display_name": display_name,
                    "avatar_url": avatar_url
                },
                lock=False,  # We're only inserter
            )

            if isinstance(self.database_engine, PostgresEngine):
                # We weight the localpart most highly, then display name and finally
                # server name
                if self.database_engine.can_native_upsert:
                    sql = """
                        INSERT INTO user_directory_search(user_id, vector)
                        VALUES (?,
                            setweight(to_tsvector('english', ?), 'A')
                            || setweight(to_tsvector('english', ?), 'D')
                            || setweight(to_tsvector('english', COALESCE(?, '')), 'B')
                        ) ON CONFLICT (user_id) DO UPDATE SET vector=EXCLUDED.vector
                    """
                    txn.execute(
                        sql,
                        (
                            user_id,
                            get_localpart_from_id(user_id),
                            get_domain_from_id(user_id),
                            display_name,
                        ),
                    )
                else:
                    # TODO: Remove this code after we've bumped the minimum version
                    # of postgres to always support upserts, so we can get rid of
                    # `new_entry` usage
                    if new_entry is True:
                        sql = """
                            INSERT INTO user_directory_search(user_id, vector)
                            VALUES (?,
                                setweight(to_tsvector('english', ?), 'A')
                                || setweight(to_tsvector('english', ?), 'D')
                                || setweight(to_tsvector('english', COALESCE(?, '')), 'B')
                            )
                        """
                        txn.execute(
                            sql,
                            (
                                user_id,
                                get_localpart_from_id(user_id),
                                get_domain_from_id(user_id),
                                display_name,
                            ),
                        )
                    elif new_entry is False:
                        sql = """
                            UPDATE user_directory_search
                            SET vector = setweight(to_tsvector('english', ?), 'A')
                                || setweight(to_tsvector('english', ?), 'D')
                                || setweight(to_tsvector('english', COALESCE(?, '')), 'B')
                            WHERE user_id = ?
                        """
                        txn.execute(
                            sql,
                            (
                                get_localpart_from_id(user_id),
                                get_domain_from_id(user_id),
                                display_name,
                                user_id,
                            ),
                        )
                    else:
                        raise RuntimeError(
                            "upsert returned None when 'can_native_upsert' is False"
                        )
            elif isinstance(self.database_engine, Sqlite3Engine):
                value = "%s %s" % (user_id,
                                   display_name) if display_name else user_id
                self.db_pool.simple_upsert_txn(
                    txn,
                    table="user_directory_search",
                    keyvalues={"user_id": user_id},
                    values={"value": value},
                    lock=False,  # We're only inserter
                )
            else:
                # This should be unreachable.
                raise Exception("Unrecognized database engine")

            txn.call_after(self.get_user_in_directory.invalidate, (user_id, ))
Beispiel #60
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