示例#1
0
    async def edit_published_room_list(self, requester: Requester,
                                       room_id: str, visibility: str):
        """Edit the entry of the room in the published room list.

        requester
        room_id
        visibility: "public" or "private"
        """
        user_id = requester.user.to_string()

        if not await self.spam_checker.user_may_publish_room(user_id, room_id):
            raise AuthError(
                403,
                "This user is not permitted to publish rooms to the room list")

        if requester.is_guest:
            raise AuthError(403, "Guests cannot edit the published room list")

        if visibility not in ["public", "private"]:
            raise SynapseError(400, "Invalid visibility setting")

        if visibility == "public" and not self.enable_room_list_search:
            # The room list has been disabled.
            raise AuthError(
                403,
                "This user is not permitted to publish rooms to the room list")

        room = await self.store.get_room(room_id)
        if room is None:
            raise SynapseError(400, "Unknown room")

        can_change_room_list = await self.auth.check_can_change_room_list(
            room_id, requester.user)
        if not can_change_room_list:
            raise AuthError(
                403,
                "This server requires you to be a moderator in the room to"
                " edit its room list entry",
            )

        making_public = visibility == "public"
        if making_public:
            room_aliases = await self.store.get_aliases_for_room(room_id)
            canonical_alias = await self.store.get_canonical_alias_for_room(
                room_id)
            if canonical_alias:
                room_aliases.append(canonical_alias)

            if not self.config.is_publishing_room_allowed(
                    user_id, room_id, room_aliases):
                # Lets just return a generic message, as there may be all sorts of
                # reasons why we said no. TODO: Allow configurable error messages
                # per alias creation rule?
                raise SynapseError(403, "Not allowed to publish room")

            # Check if publishing is blocked by a third party module
            allowed_by_third_party_rules = await (
                self.third_party_event_rules.check_visibility_can_be_modified(
                    room_id, visibility))
            if not allowed_by_third_party_rules:
                raise SynapseError(403, "Not allowed to publish room")

        await self.store.set_room_is_public(room_id, making_public)
示例#2
0
    def get_user_by_req(self, request):
        """ Get a registered user's ID.

        Args:
            request - An HTTP request with an access_token query parameter.
        Returns:
            tuple : of UserID and device string:
                User ID object of the user making the request
                Client ID object of the client instance the user is using
        Raises:
            AuthError if no user by that token exists or the token is invalid.
        """
        # Can optionally look elsewhere in the request (e.g. headers)
        try:
            access_token = request.args["access_token"][0]

            # Check for application service tokens with a user_id override
            try:
                app_service = yield self.store.get_app_service_by_token(
                    access_token)
                if not app_service:
                    raise KeyError

                user_id = app_service.sender
                if "user_id" in request.args:
                    user_id = request.args["user_id"][0]
                    if not app_service.is_interested_in_user(user_id):
                        raise AuthError(
                            403,
                            "Application service cannot masquerade as this user."
                        )

                if not user_id:
                    raise KeyError

                defer.returnValue(
                    (UserID.from_string(user_id), ClientInfo("", "")))
                return
            except KeyError:
                pass  # normal users won't have this query parameter set

            user_info = yield self.get_user_by_token(access_token)
            user = user_info["user"]
            device_id = user_info["device_id"]
            token_id = user_info["token_id"]

            ip_addr = self.hs.get_ip_from_request(request)
            user_agent = request.requestHeaders.getRawHeaders("User-Agent",
                                                              default=[""])[0]
            if user and access_token and ip_addr:
                self.store.insert_client_ip(user=user,
                                            access_token=access_token,
                                            device_id=user_info["device_id"],
                                            ip=ip_addr,
                                            user_agent=user_agent)

            request.authenticated_entity = user.to_string()

            defer.returnValue((user, ClientInfo(device_id, token_id)))
        except KeyError:
            raise AuthError(self.TOKEN_NOT_FOUND_HTTP_STATUS,
                            "Missing access token.",
                            errcode=Codes.MISSING_TOKEN)
示例#3
0
    def send_membership_event(
        self,
        requester,
        event,
        context,
        remote_room_hosts=None,
        ratelimit=True,
    ):
        """
        Change the membership status of a user in a room.

        Args:
            requester (Requester): The local user who requested the membership
                event. If None, certain checks, like whether this homeserver can
                act as the sender, will be skipped.
            event (SynapseEvent): The membership event.
            context: The context of the event.
            is_guest (bool): Whether the sender is a guest.
            room_hosts ([str]): Homeservers which are likely to already be in
                the room, and could be danced with in order to join this
                homeserver for the first time.
            ratelimit (bool): Whether to rate limit this request.
        Raises:
            SynapseError if there was a problem changing the membership.
        """
        remote_room_hosts = remote_room_hosts or []

        target_user = UserID.from_string(event.state_key)
        room_id = event.room_id

        if requester is not None:
            sender = UserID.from_string(event.sender)
            assert sender == requester.user, (
                "Sender (%s) must be same as requester (%s)" %
                (sender, requester.user))
            assert self.hs.is_mine(
                sender), "Sender must be our own: %s" % (sender, )
        else:
            requester = synapse.types.create_requester(target_user)

        prev_event = yield self.event_creation_hander.deduplicate_state_event(
            event,
            context,
        )
        if prev_event is not None:
            return

        if event.membership == Membership.JOIN:
            if requester.is_guest:
                guest_can_join = yield self._can_guest_join(
                    context.prev_state_ids)
                if not guest_can_join:
                    # This should be an auth check, but guests are a local concept,
                    # so don't really fit into the general auth process.
                    raise AuthError(403, "Guest access not allowed")

        if event.membership not in (Membership.LEAVE, Membership.BAN):
            is_blocked = yield self.store.is_room_blocked(room_id)
            if is_blocked:
                raise SynapseError(
                    403, "This room has been blocked on this server")

        yield self.event_creation_hander.handle_new_client_event(
            requester,
            event,
            context,
            extra_users=[target_user],
            ratelimit=ratelimit,
        )

        prev_member_event_id = context.prev_state_ids.get(
            (EventTypes.Member, event.state_key), None)

        if event.membership == Membership.JOIN:
            # Only fire user_joined_room if the user has acutally joined the
            # room. Don't bother if the user is just changing their profile
            # info.
            newly_joined = True
            if prev_member_event_id:
                prev_member_event = yield self.store.get_event(
                    prev_member_event_id)
                newly_joined = prev_member_event.membership != Membership.JOIN
            if newly_joined:
                yield self._user_joined_room(target_user, room_id)
        elif event.membership == Membership.LEAVE:
            if prev_member_event_id:
                prev_member_event = yield self.store.get_event(
                    prev_member_event_id)
                if prev_member_event.membership == Membership.JOIN:
                    yield self._user_left_room(target_user, room_id)
示例#4
0
def check(
    room_version_obj: RoomVersion,
    event: EventBase,
    auth_events: StateMap[EventBase],
    do_sig_check: bool = True,
    do_size_check: bool = True,
) -> None:
    """ Checks if this event is correctly authed.

    Args:
        room_version_obj: the version of the room
        event: the event being checked.
        auth_events: the existing room state.

    Raises:
        AuthError if the checks fail

    Returns:
         if the auth checks pass.
    """
    assert isinstance(auth_events, dict)

    if do_size_check:
        _check_size_limits(event)

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

    room_id = event.room_id

    # 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.
    for auth_event in auth_events.values():
        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 do_sig_check:
        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")

    # 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

    # 3. If event does not have a m.room.create in its auth_events, reject.
    creation_event = auth_events.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_events):
            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

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

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

    _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 don't have permission to invite users")
        else:
            logger.debug("Allowing! %s", event)
            return

    _can_send_event(event, auth_events)

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

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

    logger.debug("Allowing! %s", event)
示例#5
0
 def _check_joined_room(self, member, user_id, room_id):
     if not member or member.membership != Membership.JOIN:
         raise AuthError(
             403, "User %s not in room %s (%s)" %
             (user_id, room_id, repr(member)))
示例#6
0
    async def create_association(
        self,
        requester: Requester,
        room_alias: RoomAlias,
        room_id: str,
        servers: Optional[List[str]] = None,
        check_membership: bool = True,
    ) -> None:
        """Attempt to create a new alias

        Args:
            requester
            room_alias
            room_id
            servers: Iterable of servers that others servers should try and join via
            check_membership: Whether to check if the user is in the room
                before the alias can be set (if the server's config requires it).
        """

        user_id = requester.user.to_string()

        if len(room_alias.to_string()) > MAX_ALIAS_LENGTH:
            raise SynapseError(
                400,
                "Can't create aliases longer than %s characters" %
                MAX_ALIAS_LENGTH,
                Codes.INVALID_PARAM,
            )

        service = requester.app_service
        if service:
            if not service.is_interested_in_alias(room_alias.to_string()):
                raise SynapseError(
                    400,
                    "This application service has not reserved this kind of alias.",
                    errcode=Codes.EXCLUSIVE,
                )
        else:
            # Server admins are not subject to the same constraints as normal
            # users when creating an alias (e.g. being in the room).
            is_admin = await self.auth.is_server_admin(requester.user)

            if (self.require_membership and check_membership) and not is_admin:
                rooms_for_user = await self.store.get_rooms_for_user(user_id)
                if room_id not in rooms_for_user:
                    raise AuthError(
                        403,
                        "You must be in the room to create an alias for it")

            if not self.spam_checker.user_may_create_room_alias(
                    user_id, room_alias):
                raise AuthError(
                    403, "This user is not permitted to create this alias")

            if not self.config.is_alias_creation_allowed(
                    user_id, room_id, room_alias.to_string()):
                # Lets just return a generic message, as there may be all sorts of
                # reasons why we said no. TODO: Allow configurable error messages
                # per alias creation rule?
                raise SynapseError(403, "Not allowed to create alias")

            can_create = self.can_modify_alias(room_alias, user_id=user_id)
            if not can_create:
                raise AuthError(
                    400,
                    "This alias is reserved by an application service.",
                    errcode=Codes.EXCLUSIVE,
                )

        await self._create_association(room_alias,
                                       room_id,
                                       servers,
                                       creator=user_id)
示例#7
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

    # 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

    if Membership.JOIN != membership:
        if (caller_invited 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:
            invite_level = _get_named_level(auth_events, "invite", 0)

            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 or restricted.
        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 or (
                room_version.msc3083_join_rules
                and join_rule == JoinRules.MSC3083_RESTRICTED):
            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)
示例#8
0
    async def validate_user_via_ui_auth(
        self,
        requester: Requester,
        request: SynapseRequest,
        request_body: Dict[str, Any],
        clientip: str,
        description: str,
    ) -> Tuple[dict, str]:
        """
        Checks that the user is who they claim to be, via a UI auth.

        This is used for things like device deletion and password reset where
        the user already has a valid access token, but we want to double-check
        that it isn't stolen by re-authenticating them.

        Args:
            requester: The user, as given by the access token

            request: The request sent by the client.

            request_body: The body of the request sent by the client

            clientip: The IP address of the client.

            description: A human readable string to be displayed to the user that
                         describes the operation happening on their account.

        Returns:
            A tuple of (params, session_id).

                'params' contains the parameters for this request (which may
                have been given only in a previous call).

                'session_id' is the ID of this session, either passed in by the
                client or assigned by this call

        Raises:
            InteractiveAuthIncompleteError if the client has not yet completed
                any of the permitted login flows

            AuthError if the client has completed a login flow, and it gives
                a different user to `requester`

            LimitExceededError if the ratelimiter's failed request count for this
                user is too high to proceed

        """

        user_id = requester.user.to_string()

        # Check if we should be ratelimited due to too many previous failed attempts
        self._failed_uia_attempts_ratelimiter.ratelimit(user_id, update=False)

        # build a list of supported flows
        flows = [[login_type] for login_type in self._supported_ui_auth_types]

        try:
            result, params, session_id = await self.check_ui_auth(
                flows, request, request_body, clientip, description)
        except LoginError:
            # Update the ratelimiter to say we failed (`can_do_action` doesn't raise).
            self._failed_uia_attempts_ratelimiter.can_do_action(user_id)
            raise

        # find the completed login type
        for login_type in self._supported_ui_auth_types:
            if login_type not in result:
                continue

            user_id = result[login_type]
            break
        else:
            # this can't happen
            raise Exception(
                "check_auth returned True but no successful login type")

        # check that the UI auth matched the access token
        if user_id != requester.user.to_string():
            raise AuthError(403, "Invalid auth")

        return params, session_id
示例#9
0
    def get_event_context(self, user, room_id, event_id, limit, event_filter):
        """Retrieves events, pagination tokens and state around a given event
        in a room.

        Args:
            user (UserID)
            room_id (str)
            event_id (str)
            limit (int): The maximum number of events to return in total
                (excluding state).
            event_filter (Filter|None): the filter to apply to the events returned
                (excluding the target event_id)

        Returns:
            dict, or None if the event isn't found
        """
        before_limit = math.floor(limit / 2.)
        after_limit = limit - before_limit

        users = yield self.store.get_users_in_room(room_id)
        is_peeking = user.to_string() not in users

        def filter_evts(events):
            return filter_events_for_client(
                self.store,
                user.to_string(),
                events,
                is_peeking=is_peeking
            )

        event = yield self.store.get_event(event_id, get_prev_content=True,
                                           allow_none=True)
        if not event:
            defer.returnValue(None)
            return

        filtered = yield(filter_evts([event]))
        if not filtered:
            raise AuthError(
                403,
                "You don't have permission to access that event."
            )

        results = yield self.store.get_events_around(
            room_id, event_id, before_limit, after_limit, event_filter
        )

        results["events_before"] = yield filter_evts(results["events_before"])
        results["events_after"] = yield filter_evts(results["events_after"])
        results["event"] = event

        if results["events_after"]:
            last_event_id = results["events_after"][-1].event_id
        else:
            last_event_id = event_id

        if event_filter and event_filter.lazy_load_members():
            state_filter = StateFilter.from_lazy_load_member_list(
                ev.sender
                for ev in itertools.chain(
                    results["events_before"],
                    (results["event"],),
                    results["events_after"],
                )
            )
        else:
            state_filter = StateFilter.all()

        # XXX: why do we return the state as of the last event rather than the
        # first? Shouldn't we be consistent with /sync?
        # https://github.com/matrix-org/matrix-doc/issues/687

        state = yield self.store.get_state_for_events(
            [last_event_id], state_filter=state_filter,
        )
        results["state"] = list(state[last_event_id].values())

        # We use a dummy token here as we only care about the room portion of
        # the token, which we replace.
        token = StreamToken.START

        results["start"] = token.copy_and_replace(
            "room_key", results["start"]
        ).to_string()

        results["end"] = token.copy_and_replace(
            "room_key", results["end"]
        ).to_string()

        defer.returnValue(results)
示例#10
0
    async def check_username(self,
                             localpart,
                             guest_access_token=None,
                             assigned_user_id=None):
        if types.contains_invalid_mxid_characters(localpart):
            raise SynapseError(
                400,
                "User ID can only contain characters a-z, 0-9, or '=_-./'",
                Codes.INVALID_USERNAME,
            )

        if not localpart:
            raise SynapseError(400, "User ID cannot be empty",
                               Codes.INVALID_USERNAME)

        if localpart[0] == "_":
            raise SynapseError(400, "User ID may not begin with _",
                               Codes.INVALID_USERNAME)

        user = UserID(localpart, self.hs.hostname)
        user_id = user.to_string()

        if assigned_user_id:
            if user_id == assigned_user_id:
                return
            else:
                raise SynapseError(
                    400,
                    "A different user ID has already been registered for this session",
                )

        self.check_user_id_not_appservice_exclusive(user_id)

        if len(user_id) > MAX_USERID_LENGTH:
            raise SynapseError(
                400,
                "User ID may not be longer than %s characters" %
                (MAX_USERID_LENGTH, ),
                Codes.INVALID_USERNAME,
            )

        users = await self.store.get_users_by_id_case_insensitive(user_id)
        if users:
            if not guest_access_token:
                raise SynapseError(400,
                                   "User ID already taken.",
                                   errcode=Codes.USER_IN_USE)
            user_data = await self.auth.get_user_by_access_token(
                guest_access_token)
            if (not user_data.is_guest or UserID.from_string(
                    user_data.user_id).localpart != localpart):
                raise AuthError(
                    403,
                    "Cannot register taken user ID without valid guest "
                    "credentials for that user.",
                    errcode=Codes.FORBIDDEN,
                )

        if guest_access_token is None:
            try:
                int(localpart)
                raise SynapseError(
                    400,
                    "Numeric user IDs are reserved for guest users.",
                    errcode=Codes.INVALID_USERNAME,
                )
            except ValueError:
                pass
示例#11
0
    def _update_membership(
        self,
        requester,
        target,
        room_id,
        action,
        txn_id=None,
        remote_room_hosts=None,
        third_party_signed=None,
        ratelimit=True,
        content=None,
        require_consent=True,
    ):
        content_specified = bool(content)
        if content is None:
            content = {}
        else:
            # We do a copy here as we potentially change some keys
            # later on.
            content = dict(content)

        if not self.allow_per_room_profiles:
            # Strip profile data, knowing that new profile data will be added to the
            # event's content in event_creation_handler.create_event() using the target's
            # global profile.
            content.pop("displayname", None)
            content.pop("avatar_url", None)

        effective_membership_state = action
        if action in ["kick", "unban"]:
            effective_membership_state = "leave"

        # if this is a join with a 3pid signature, we may need to turn a 3pid
        # invite into a normal invite before we can handle the join.
        if third_party_signed is not None:
            yield self.federation_handler.exchange_third_party_invite(
                third_party_signed["sender"],
                target.to_string(),
                room_id,
                third_party_signed,
            )

        if not remote_room_hosts:
            remote_room_hosts = []

        if effective_membership_state not in ("leave", "ban"):
            is_blocked = yield self.store.is_room_blocked(room_id)
            if is_blocked:
                raise SynapseError(
                    403, "This room has been blocked on this server")

        if effective_membership_state == Membership.INVITE:
            # block any attempts to invite the server notices mxid
            if target.to_string() == self._server_notices_mxid:
                raise SynapseError(http_client.FORBIDDEN,
                                   "Cannot invite this user")

            block_invite = False

            if (self._server_notices_mxid is not None and
                    requester.user.to_string() == self._server_notices_mxid):
                # allow the server notices mxid to send invites
                is_requester_admin = True

            else:
                is_requester_admin = yield self.auth.is_server_admin(
                    requester.user)

            if not is_requester_admin:
                if self.config.block_non_admin_invites:
                    logger.info(
                        "Blocking invite: user is not admin and non-admin "
                        "invites disabled")
                    block_invite = True

                if not self.spam_checker.user_may_invite(
                        requester.user.to_string(), target.to_string(),
                        room_id):
                    logger.info("Blocking invite due to spam checker")
                    block_invite = True

            if block_invite:
                raise SynapseError(
                    403, "Invites have been disabled on this server")

        prev_events_and_hashes = yield self.store.get_prev_events_for_room(
            room_id)
        latest_event_ids = (event_id
                            for (event_id, _, _) in prev_events_and_hashes)

        current_state_ids = yield self.state_handler.get_current_state_ids(
            room_id, latest_event_ids=latest_event_ids)

        # TODO: Refactor into dictionary of explicitly allowed transitions
        # between old and new state, with specific error messages for some
        # transitions and generic otherwise
        old_state_id = current_state_ids.get(
            (EventTypes.Member, target.to_string()))
        if old_state_id:
            old_state = yield self.store.get_event(old_state_id,
                                                   allow_none=True)
            old_membership = old_state.content.get(
                "membership") if old_state else None
            if action == "unban" and old_membership != "ban":
                raise SynapseError(
                    403,
                    "Cannot unban user who was not banned"
                    " (membership=%s)" % old_membership,
                    errcode=Codes.BAD_STATE,
                )
            if old_membership == "ban" and action != "unban":
                raise SynapseError(
                    403,
                    "Cannot %s user who was banned" % (action, ),
                    errcode=Codes.BAD_STATE,
                )

            if old_state:
                same_content = content == old_state.content
                same_membership = old_membership == effective_membership_state
                same_sender = requester.user.to_string() == old_state.sender
                if same_sender and same_membership and same_content:
                    return old_state

            if old_membership in ["ban", "leave"] and action == "kick":
                raise AuthError(403, "The target user is not in the room")

            # we don't allow people to reject invites to the server notice
            # room, but they can leave it once they are joined.
            if (old_membership == Membership.INVITE
                    and effective_membership_state == Membership.LEAVE):
                is_blocked = yield self._is_server_notice_room(room_id)
                if is_blocked:
                    raise SynapseError(
                        http_client.FORBIDDEN,
                        "You cannot reject this invite",
                        errcode=Codes.CANNOT_LEAVE_SERVER_NOTICE_ROOM,
                    )
        else:
            if action == "kick":
                raise AuthError(403, "The target user is not in the room")

        is_host_in_room = yield self._is_host_in_room(current_state_ids)

        if effective_membership_state == Membership.JOIN:
            if requester.is_guest:
                guest_can_join = yield self._can_guest_join(current_state_ids)
                if not guest_can_join:
                    # This should be an auth check, but guests are a local concept,
                    # so don't really fit into the general auth process.
                    raise AuthError(403, "Guest access not allowed")

            if not is_host_in_room:
                inviter = yield self._get_inviter(target.to_string(), room_id)
                if inviter and not self.hs.is_mine(inviter):
                    remote_room_hosts.append(inviter.domain)

                content["membership"] = Membership.JOIN

                profile = self.profile_handler
                if not content_specified:
                    content["displayname"] = yield profile.get_displayname(
                        target)
                    content["avatar_url"] = yield profile.get_avatar_url(
                        target)

                if requester.is_guest:
                    content["kind"] = "guest"

                remote_join_response = yield self._remote_join(
                    requester, remote_room_hosts, room_id, target, content)

                return remote_join_response

        elif effective_membership_state == Membership.LEAVE:
            if not is_host_in_room:
                # perhaps we've been invited
                inviter = yield self._get_inviter(target.to_string(), room_id)
                if not inviter:
                    raise SynapseError(404, "Not a known room")

                if self.hs.is_mine(inviter):
                    # the inviter was on our server, but has now left. Carry on
                    # with the normal rejection codepath.
                    #
                    # This is a bit of a hack, because the room might still be
                    # active on other servers.
                    pass
                else:
                    # send the rejection to the inviter's HS.
                    remote_room_hosts = remote_room_hosts + [inviter.domain]
                    res = yield self._remote_reject_invite(
                        requester,
                        remote_room_hosts,
                        room_id,
                        target,
                        content,
                    )
                    return res

        res = yield self._local_membership_update(
            requester=requester,
            target=target,
            room_id=room_id,
            membership=effective_membership_state,
            txn_id=txn_id,
            ratelimit=ratelimit,
            prev_events_and_hashes=prev_events_and_hashes,
            content=content,
            require_consent=require_consent,
        )
        return res
示例#12
0
    def on_POST(self, request, room_id):
        requester = yield self.auth.get_user_by_req(request)
        is_admin = yield self.auth.is_server_admin(requester.user)
        if not is_admin:
            raise AuthError(403, "You are not a server admin")

        content = parse_json_object_from_request(request)

        new_room_user_id = content.get("new_room_user_id")
        if not new_room_user_id:
            raise SynapseError(400, "Please provide field `new_room_user_id`")

        room_creator_requester = create_requester(new_room_user_id)

        message = content.get("message", self.DEFAULT_MESSAGE)
        room_name = content.get("room_name", "Content Violation Notification")

        info = yield self.handlers.room_creation_handler.create_room(
            room_creator_requester,
            config={
                "preset": "public_chat",
                "name": room_name,
                "power_level_content_override": {
                    "users_default": -10,
                },
            },
            ratelimit=False,
        )
        new_room_id = info["room_id"]

        msg_handler = self.handlers.message_handler
        yield msg_handler.create_and_send_nonmember_event(
            room_creator_requester,
            {
                "type": "m.room.message",
                "content": {
                    "body": message,
                    "msgtype": "m.text"
                },
                "room_id": new_room_id,
                "sender": new_room_user_id,
            },
            ratelimit=False,
        )

        requester_user_id = requester.user.to_string()

        logger.info("Shutting down room %r", room_id)

        yield self.store.block_room(room_id, requester_user_id)

        users = yield self.state.get_current_user_in_room(room_id)
        kicked_users = []
        for user_id in users:
            if not self.hs.is_mine_id(user_id):
                continue

            logger.info("Kicking %r from %r...", user_id, room_id)

            target_requester = create_requester(user_id)
            yield self.handlers.room_member_handler.update_membership(
                requester=target_requester,
                target=target_requester.user,
                room_id=room_id,
                action=Membership.LEAVE,
                content={},
                ratelimit=False)

            yield self.handlers.room_member_handler.forget(
                target_requester.user, room_id)

            yield self.handlers.room_member_handler.update_membership(
                requester=target_requester,
                target=target_requester.user,
                room_id=new_room_id,
                action=Membership.JOIN,
                content={},
                ratelimit=False)

            kicked_users.append(user_id)

        aliases_for_room = yield self.store.get_aliases_for_room(room_id)

        yield self.store.update_aliases_for_room(room_id, new_room_id,
                                                 requester_user_id)

        defer.returnValue((200, {
            "kicked_users": kicked_users,
            "local_aliases": aliases_for_room,
            "new_room_id": new_room_id,
        }))
示例#13
0
文件: message.py 项目: xorob0/synapse
    async def persist_and_notify_client_event(self,
                                              requester,
                                              event,
                                              context,
                                              ratelimit=True,
                                              extra_users=[]):
        """Called when we have fully built the event, have already
        calculated the push actions for the event, and checked auth.

        This should only be run on master.
        """
        assert not self.config.worker_app

        if ratelimit:
            # We check if this is a room admin redacting an event so that we
            # can apply different ratelimiting. We do this by simply checking
            # it's not a self-redaction (to avoid having to look up whether the
            # user is actually admin or not).
            is_admin_redaction = False
            if event.type == EventTypes.Redaction:
                original_event = await self.store.get_event(
                    event.redacts,
                    redact_behaviour=EventRedactBehaviour.AS_IS,
                    get_prev_content=False,
                    allow_rejected=False,
                    allow_none=True,
                )

                is_admin_redaction = (original_event and
                                      event.sender != original_event.sender)

            await self.base_handler.ratelimit(
                requester, is_admin_redaction=is_admin_redaction)

        await self.base_handler.maybe_kick_guest_users(event, context)

        if event.type == EventTypes.CanonicalAlias:
            # Validate a newly added alias or newly added alt_aliases.

            original_alias = None
            original_alt_aliases = set()

            original_event_id = event.unsigned.get("replaces_state")
            if original_event_id:
                original_event = await self.store.get_event(original_event_id)

                if original_event:
                    original_alias = original_event.content.get("alias", None)
                    original_alt_aliases = original_event.content.get(
                        "alt_aliases", [])

            # Check the alias is currently valid (if it has changed).
            room_alias_str = event.content.get("alias", None)
            directory_handler = self.hs.get_handlers().directory_handler
            if room_alias_str and room_alias_str != original_alias:
                await self._validate_canonical_alias(directory_handler,
                                                     room_alias_str,
                                                     event.room_id)

            # Check that alt_aliases is the proper form.
            alt_aliases = event.content.get("alt_aliases", [])
            if not isinstance(alt_aliases, (list, tuple)):
                raise SynapseError(400,
                                   "The alt_aliases property must be a list.",
                                   Codes.INVALID_PARAM)

            # If the old version of alt_aliases is of an unknown form,
            # completely replace it.
            if not isinstance(original_alt_aliases, (list, tuple)):
                original_alt_aliases = []

            # Check that each alias is currently valid.
            new_alt_aliases = set(alt_aliases) - set(original_alt_aliases)
            if new_alt_aliases:
                for alias_str in new_alt_aliases:
                    await self._validate_canonical_alias(
                        directory_handler, alias_str, event.room_id)

        federation_handler = self.hs.get_handlers().federation_handler

        if event.type == EventTypes.Member:
            if event.content["membership"] == Membership.INVITE:

                def is_inviter_member_event(e):
                    return e.type == EventTypes.Member and e.sender == event.sender

                current_state_ids = await context.get_current_state_ids()

                state_to_include_ids = [
                    e_id for k, e_id in iteritems(current_state_ids)
                    if k[0] in self.room_invite_state_types or k == (
                        EventTypes.Member, event.sender)
                ]

                state_to_include = await self.store.get_events(
                    state_to_include_ids)

                event.unsigned["invite_room_state"] = [{
                    "type": e.type,
                    "state_key": e.state_key,
                    "content": e.content,
                    "sender": e.sender,
                } for e in itervalues(state_to_include)]

                invitee = UserID.from_string(event.state_key)
                if not self.hs.is_mine(invitee):
                    # TODO: Can we add signature from remote server in a nicer
                    # way? If we have been invited by a remote server, we need
                    # to get them to sign the event.

                    returned_invite = await federation_handler.send_invite(
                        invitee.domain, event)
                    event.unsigned.pop("room_state", None)

                    # TODO: Make sure the signatures actually are correct.
                    event.signatures.update(returned_invite.signatures)

        if event.type == EventTypes.Redaction:
            original_event = await self.store.get_event(
                event.redacts,
                redact_behaviour=EventRedactBehaviour.AS_IS,
                get_prev_content=False,
                allow_rejected=False,
                allow_none=True,
            )

            # we can make some additional checks now if we have the original event.
            if original_event:
                if original_event.type == EventTypes.Create:
                    raise AuthError(
                        403, "Redacting create events is not permitted")

                if original_event.room_id != event.room_id:
                    raise SynapseError(
                        400, "Cannot redact event from a different room")

            prev_state_ids = await context.get_prev_state_ids()
            auth_events_ids = await self.auth.compute_auth_events(
                event, prev_state_ids, for_verification=True)
            auth_events = await self.store.get_events(auth_events_ids)
            auth_events = {(e.type, e.state_key): e
                           for e in auth_events.values()}

            room_version = await self.store.get_room_version_id(event.room_id)
            room_version_obj = KNOWN_ROOM_VERSIONS[room_version]

            if event_auth.check_redaction(room_version_obj,
                                          event,
                                          auth_events=auth_events):
                # this user doesn't have 'redact' rights, so we need to do some more
                # checks on the original event. Let's start by checking the original
                # event exists.
                if not original_event:
                    raise NotFoundError("Could not find event %s" %
                                        (event.redacts, ))

                if event.user_id != original_event.user_id:
                    raise AuthError(
                        403, "You don't have permission to redact events")

                # all the checks are done.
                event.internal_metadata.recheck_redaction = False

        if event.type == EventTypes.Create:
            prev_state_ids = await context.get_prev_state_ids()
            if prev_state_ids:
                raise AuthError(403,
                                "Changing the room create event is forbidden")

        event_stream_id, max_stream_id = await self.storage.persistence.persist_event(
            event, context=context)

        if self._ephemeral_events_enabled:
            # If there's an expiry timestamp on the event, schedule its expiry.
            self._message_handler.maybe_schedule_expiry(event)

        await self.pusher_pool.on_new_notifications(event_stream_id,
                                                    max_stream_id)

        def _notify():
            try:
                self.notifier.on_new_room_event(event,
                                                event_stream_id,
                                                max_stream_id,
                                                extra_users=extra_users)
            except Exception:
                logger.exception("Error notifying about new room event")

        run_in_background(_notify)

        if event.type == EventTypes.Message:
            # We don't want to block sending messages on any presence code. This
            # matters as sometimes presence code can take a while.
            run_in_background(self._bump_active_time, requester.user)
示例#14
0
文件: message.py 项目: xorob0/synapse
    def create_event(
        self,
        requester,
        event_dict,
        token_id=None,
        txn_id=None,
        prev_event_ids: Optional[Collection[str]] = None,
        require_consent=True,
    ):
        """
        Given a dict from a client, create a new event.

        Creates an FrozenEvent object, filling out auth_events, prev_events,
        etc.

        Adds display names to Join membership events.

        Args:
            requester
            event_dict (dict): An entire event
            token_id (str)
            txn_id (str)

            prev_event_ids:
                the forward extremities to use as the prev_events for the
                new event.

                If None, they will be requested from the database.

            require_consent (bool): Whether to check if the requester has
                consented to privacy policy.
        Raises:
            ResourceLimitError if server is blocked to some resource being
            exceeded
        Returns:
            Tuple of created event (FrozenEvent), Context
        """
        yield self.auth.check_auth_blocking(requester.user.to_string())

        if event_dict["type"] == EventTypes.Create and event_dict[
                "state_key"] == "":
            room_version = event_dict["content"]["room_version"]
        else:
            try:
                room_version = yield self.store.get_room_version_id(
                    event_dict["room_id"])
            except NotFoundError:
                raise AuthError(403, "Unknown room")

        builder = self.event_builder_factory.new(room_version, event_dict)

        self.validator.validate_builder(builder)

        if builder.type == EventTypes.Member:
            membership = builder.content.get("membership", None)
            target = UserID.from_string(builder.state_key)

            if membership in {Membership.JOIN, Membership.INVITE}:
                # If event doesn't include a display name, add one.
                profile = self.profile_handler
                content = builder.content

                try:
                    if "displayname" not in content:
                        content["displayname"] = yield profile.get_displayname(
                            target)
                    if "avatar_url" not in content:
                        content["avatar_url"] = yield profile.get_avatar_url(
                            target)
                except Exception as e:
                    logger.info("Failed to get profile information for %r: %s",
                                target, e)

        is_exempt = yield self._is_exempt_from_privacy_policy(
            builder, requester)
        if require_consent and not is_exempt:
            yield self.assert_accepted_privacy_policy(requester)

        if token_id is not None:
            builder.internal_metadata.token_id = token_id

        if txn_id is not None:
            builder.internal_metadata.txn_id = txn_id

        event, context = yield self.create_new_client_event(
            builder=builder,
            requester=requester,
            prev_event_ids=prev_event_ids,
        )

        # In an ideal world we wouldn't need the second part of this condition. However,
        # this behaviour isn't spec'd yet, meaning we should be able to deactivate this
        # behaviour. Another reason is that this code is also evaluated each time a new
        # m.room.aliases event is created, which includes hitting a /directory route.
        # Therefore not including this condition here would render the similar one in
        # synapse.handlers.directory pointless.
        if builder.type == EventTypes.Aliases and self.require_membership_for_aliases:
            # Ideally we'd do the membership check in event_auth.check(), which
            # describes a spec'd algorithm for authenticating events received over
            # federation as well as those created locally. As of room v3, aliases events
            # can be created by users that are not in the room, therefore we have to
            # tolerate them in event_auth.check().
            prev_state_ids = yield context.get_prev_state_ids()
            prev_event_id = prev_state_ids.get(
                (EventTypes.Member, event.sender))
            prev_event = (yield self.store.get_event(
                prev_event_id, allow_none=True) if prev_event_id else None)
            if not prev_event or prev_event.membership != Membership.JOIN:
                logger.warning(
                    ("Attempt to send `m.room.aliases` in room %s by user %s but"
                     " membership is %s"),
                    event.room_id,
                    event.sender,
                    prev_event.membership if prev_event else None,
                )

                raise AuthError(
                    403, "You must be in the room to create an alias for it")

        self.validator.validate_new(event, self.config)

        return (event, context)
示例#15
0
    def handle_new_client_event(self,
                                requester,
                                event,
                                context,
                                ratelimit=True,
                                extra_users=[]):
        # We now need to go and hit out to wherever we need to hit out to.

        if ratelimit:
            self.ratelimit(requester)

        try:
            yield self.auth.check_from_context(event, context)
        except AuthError as err:
            logger.warn("Denying new event %r because %s", event, err)
            raise err

        yield self.maybe_kick_guest_users(event, context)

        if event.type == EventTypes.CanonicalAlias:
            # Check the alias is acually valid (at this time at least)
            room_alias_str = event.content.get("alias", None)
            if room_alias_str:
                room_alias = RoomAlias.from_string(room_alias_str)
                directory_handler = self.hs.get_handlers().directory_handler
                mapping = yield directory_handler.get_association(room_alias)

                if mapping["room_id"] != event.room_id:
                    raise SynapseError(
                        400, "Room alias %s does not point to the room" %
                        (room_alias_str, ))

        federation_handler = self.hs.get_handlers().federation_handler

        if event.type == EventTypes.Member:
            if event.content["membership"] == Membership.INVITE:

                def is_inviter_member_event(e):
                    return (e.type == EventTypes.Member
                            and e.sender == event.sender)

                state_to_include_ids = [
                    e_id for k, e_id in context.current_state_ids.items()
                    if k[0] in self.hs.config.room_invite_state_types
                    or k[0] == EventTypes.Member and k[1] == event.sender
                ]

                state_to_include = yield self.store.get_events(
                    state_to_include_ids)

                event.unsigned["invite_room_state"] = [{
                    "type": e.type,
                    "state_key": e.state_key,
                    "content": e.content,
                    "sender": e.sender,
                } for e in state_to_include.values()]

                invitee = UserID.from_string(event.state_key)
                if not self.hs.is_mine(invitee):
                    # TODO: Can we add signature from remote server in a nicer
                    # way? If we have been invited by a remote server, we need
                    # to get them to sign the event.

                    returned_invite = yield federation_handler.send_invite(
                        invitee.domain,
                        event,
                    )

                    event.unsigned.pop("room_state", None)

                    # TODO: Make sure the signatures actually are correct.
                    event.signatures.update(returned_invite.signatures)

        if event.type == EventTypes.Redaction:
            auth_events_ids = yield self.auth.compute_auth_events(
                event,
                context.prev_state_ids,
                for_verification=True,
            )
            auth_events = yield self.store.get_events(auth_events_ids)
            auth_events = {(e.type, e.state_key): e
                           for e in auth_events.values()}
            if self.auth.check_redaction(event, auth_events=auth_events):
                original_event = yield self.store.get_event(
                    event.redacts,
                    check_redacted=False,
                    get_prev_content=False,
                    allow_rejected=False,
                    allow_none=False)
                if event.user_id != original_event.user_id:
                    raise AuthError(
                        403, "You don't have permission to redact events")

        if event.type == EventTypes.Create and context.prev_state_ids:
            raise AuthError(
                403,
                "Changing the room create event is forbidden",
            )

        action_generator = ActionGenerator(self.hs)
        yield action_generator.handle_push_actions_for_event(event, context)

        (event_stream_id,
         max_stream_id) = yield self.store.persist_event(event,
                                                         context=context)

        # this intentionally does not yield: we don't care about the result
        # and don't need to wait for it.
        preserve_fn(self.hs.get_pusherpool().on_new_notifications)(
            event_stream_id, max_stream_id)

        users_in_room = yield self.store.get_joined_users_from_context(
            event, context)

        destinations = [
            get_domain_from_id(user_id) for user_id in users_in_room
            if not self.hs.is_mine_id(user_id)
        ]

        @defer.inlineCallbacks
        def _notify():
            yield run_on_reactor()
            yield self.notifier.on_new_room_event(event,
                                                  event_stream_id,
                                                  max_stream_id,
                                                  extra_users=extra_users)

        preserve_fn(_notify)()

        # If invite, remove room_state from unsigned before sending.
        event.unsigned.pop("invite_room_state", None)

        preserve_fn(federation_handler.handle_new_event)(
            event,
            destinations=destinations,
        )
示例#16
0
    async def on_POST(self, request, room_id, membership_action, txn_id=None):
        requester = await self.auth.get_user_by_req(request, allow_guest=True)

        if requester.is_guest and membership_action not in {
            Membership.JOIN,
            Membership.LEAVE,
        }:
            raise AuthError(403, "Guest access not allowed")

        try:
            content = parse_json_object_from_request(request)
        except Exception:
            # Turns out we used to ignore the body entirely, and some clients
            # cheekily send invalid bodies.
            content = {}

        if membership_action == "invite" and self._has_3pid_invite_keys(content):
            try:
                await self.room_member_handler.do_3pid_invite(
                    room_id,
                    requester.user,
                    content["medium"],
                    content["address"],
                    content["id_server"],
                    requester,
                    txn_id,
                    content.get("id_access_token"),
                )
            except ShadowBanError:
                # Pretend the request succeeded.
                pass
            return 200, {}

        target = requester.user
        if membership_action in ["invite", "ban", "unban", "kick"]:
            assert_params_in_dict(content, ["user_id"])
            target = UserID.from_string(content["user_id"])

        event_content = None
        if "reason" in content:
            event_content = {"reason": content["reason"]}

        try:
            await self.room_member_handler.update_membership(
                requester=requester,
                target=target,
                room_id=room_id,
                action=membership_action,
                txn_id=txn_id,
                third_party_signed=content.get("third_party_signed", None),
                content=event_content,
            )
        except ShadowBanError:
            # Pretend the request succeeded.
            pass

        return_value = {}

        if membership_action == "join":
            return_value["room_id"] = room_id

        return 200, return_value
示例#17
0
    async def set_displayname(
        self,
        target_user: UserID,
        requester: Requester,
        new_displayname: str,
        by_admin: bool = False,
    ) -> None:
        """Set the displayname of a user

        Args:
            target_user: the user whose displayname is to be changed.
            requester: The user attempting to make this change.
            new_displayname: The displayname to give this user.
            by_admin: Whether this change was made by an administrator.
        """
        if not self.hs.is_mine(target_user):
            raise SynapseError(400, "User is not hosted on this homeserver")

        if not by_admin and target_user != requester.user:
            raise AuthError(400, "Cannot set another user's displayname")

        if not by_admin and not self.hs.config.registration.enable_set_displayname:
            profile = await self.store.get_profileinfo(target_user.localpart)
            if profile.display_name:
                raise SynapseError(
                    400,
                    "Changing display name is disabled on this server",
                    Codes.FORBIDDEN,
                )

        if not isinstance(new_displayname, str):
            raise SynapseError(
                400, "'displayname' must be a string", errcode=Codes.INVALID_PARAM
            )

        if len(new_displayname) > MAX_DISPLAYNAME_LEN:
            raise SynapseError(
                400, "Displayname is too long (max %i)" % (MAX_DISPLAYNAME_LEN,)
            )

        displayname_to_set: Optional[str] = new_displayname
        if new_displayname == "":
            displayname_to_set = None

        # If the admin changes the display name of a user, the requesting user cannot send
        # the join event to update the displayname in the rooms.
        # This must be done by the target user himself.
        if by_admin:
            requester = create_requester(
                target_user,
                authenticated_entity=requester.authenticated_entity,
            )

        await self.store.set_profile_displayname(
            target_user.localpart, displayname_to_set
        )

        profile = await self.store.get_profileinfo(target_user.localpart)
        await self.user_directory_handler.handle_local_profile_change(
            target_user.to_string(), profile
        )

        await self._update_join_states(requester, target_user)
示例#18
0
文件: auth.py 项目: samuelyi/synapse
    async def _wrapped_get_user_by_req(
        self,
        request: SynapseRequest,
        allow_guest: bool,
        allow_expired: bool,
    ) -> Requester:
        """Helper for get_user_by_req

        Once get_user_by_req has set up the opentracing span, this does the actual work.
        """
        try:
            ip_addr = request.getClientAddress().host
            user_agent = get_request_user_agent(request)

            access_token = self.get_access_token_from_request(request)

            (
                user_id,
                device_id,
                app_service,
            ) = await self._get_appservice_user_id_and_device_id(request)
            if user_id and app_service:
                if ip_addr and self._track_appservice_user_ips:
                    await self.store.insert_client_ip(
                        user_id=user_id,
                        access_token=access_token,
                        ip=ip_addr,
                        user_agent=user_agent,
                        device_id="dummy-device"
                        if device_id is None else device_id,  # stubbed
                    )

                requester = create_requester(user_id,
                                             app_service=app_service,
                                             device_id=device_id)

                request.requester = user_id
                return requester

            user_info = await self.get_user_by_access_token(
                access_token, allow_expired=allow_expired)
            token_id = user_info.token_id
            is_guest = user_info.is_guest
            shadow_banned = user_info.shadow_banned

            # Deny the request if the user account has expired.
            if not allow_expired:
                if await self._account_validity_handler.is_user_expired(
                        user_info.user_id):
                    # Raise the error if either an account validity module has determined
                    # the account has expired, or the legacy account validity
                    # implementation is enabled and determined the account has expired
                    raise AuthError(
                        403,
                        "User account has expired",
                        errcode=Codes.EXPIRED_ACCOUNT,
                    )

            device_id = user_info.device_id

            if access_token and ip_addr:
                await self.store.insert_client_ip(
                    user_id=user_info.token_owner,
                    access_token=access_token,
                    ip=ip_addr,
                    user_agent=user_agent,
                    device_id=device_id,
                )
                # Track also the puppeted user client IP if enabled and the user is puppeting
                if (user_info.user_id != user_info.token_owner
                        and self._track_puppeted_user_ips):
                    await self.store.insert_client_ip(
                        user_id=user_info.user_id,
                        access_token=access_token,
                        ip=ip_addr,
                        user_agent=user_agent,
                        device_id=device_id,
                    )

            if is_guest and not allow_guest:
                raise AuthError(
                    403,
                    "Guest access not allowed",
                    errcode=Codes.GUEST_ACCESS_FORBIDDEN,
                )

            # Mark the token as used. This is used to invalidate old refresh
            # tokens after some time.
            if not user_info.token_used and token_id is not None:
                await self.store.mark_access_token_as_used(token_id)

            requester = create_requester(
                user_info.user_id,
                token_id,
                is_guest,
                shadow_banned,
                device_id,
                app_service=app_service,
                authenticated_entity=user_info.token_owner,
            )

            request.requester = requester
            return requester
        except KeyError:
            raise MissingClientTokenError()
示例#19
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.

    Raises:
        AuthError if the checks fail

    Returns:
         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

    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
        logger.debug("Allowing! %s", event)
        return

    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"
            )
        logger.debug("Allowing! %s", event)
        return

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

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

    _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:
            logger.debug("Allowing! %s", event)
            return

    _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)
示例#20
0
文件: auth.py 项目: samuelyi/synapse
    async def _get_appservice_user_id_and_device_id(
        self, request: Request
    ) -> Tuple[Optional[str], Optional[str], Optional[ApplicationService]]:
        """
        Given a request, reads the request parameters to determine:
        - whether it's an application service that's making this request
        - what user the application service should be treated as controlling
          (the user_id URI parameter allows an application service to masquerade
          any applicable user in its namespace)
        - what device the application service should be treated as controlling
          (the device_id[^1] URI parameter allows an application service to masquerade
          as any device that exists for the relevant user)

        [^1] Unstable and provided by MSC3202.
             Must use `org.matrix.msc3202.device_id` in place of `device_id` for now.

        Returns:
            3-tuple of
            (user ID?, device ID?, application service?)

        Postconditions:
        - If an application service is returned, so is a user ID
        - A user ID is never returned without an application service
        - A device ID is never returned without a user ID or an application service
        - The returned application service, if present, is permitted to control the
          returned user ID.
        - The returned device ID, if present, has been checked to be a valid device ID
          for the returned user ID.
        """
        DEVICE_ID_ARG_NAME = b"org.matrix.msc3202.device_id"

        app_service = self.store.get_app_service_by_token(
            self.get_access_token_from_request(request))
        if app_service is None:
            return None, None, None

        if app_service.ip_range_whitelist:
            ip_address = IPAddress(request.getClientAddress().host)
            if ip_address not in app_service.ip_range_whitelist:
                return None, None, None

        # This will always be set by the time Twisted calls us.
        assert request.args is not None

        if b"user_id" in request.args:
            effective_user_id = request.args[b"user_id"][0].decode("utf8")
            await self.validate_appservice_can_control_user_id(
                app_service, effective_user_id)
        else:
            effective_user_id = app_service.sender

        effective_device_id: Optional[str] = None

        if (self.hs.config.experimental.msc3202_device_masquerading_enabled
                and DEVICE_ID_ARG_NAME in request.args):
            effective_device_id = request.args[DEVICE_ID_ARG_NAME][0].decode(
                "utf8")
            # We only just set this so it can't be None!
            assert effective_device_id is not None
            device_opt = await self.store.get_device(effective_user_id,
                                                     effective_device_id)
            if device_opt is None:
                # For now, use 400 M_EXCLUSIVE if the device doesn't exist.
                # This is an open thread of discussion on MSC3202 as of 2021-12-09.
                raise AuthError(
                    400,
                    f"Application service trying to use a device that doesn't exist ('{effective_device_id}' for {effective_user_id})",
                    Codes.EXCLUSIVE,
                )

        return effective_user_id, effective_device_id, app_service
示例#21
0
def _check_joined_room(member: Optional[EventBase], user_id: str,
                       room_id: str) -> None:
    if not member or member.membership != Membership.JOIN:
        raise AuthError(
            403,
            "User %s not in room %s (%s)" % (user_id, room_id, repr(member)))
示例#22
0
    def create_event(self,
                     requester,
                     event_dict,
                     token_id=None,
                     txn_id=None,
                     prev_events_and_hashes=None):
        """
        Given a dict from a client, create a new event.

        Creates an FrozenEvent object, filling out auth_events, prev_events,
        etc.

        Adds display names to Join membership events.

        Args:
            requester
            event_dict (dict): An entire event
            token_id (str)
            txn_id (str)

            prev_events_and_hashes (list[(str, dict[str, str], int)]|None):
                the forward extremities to use as the prev_events for the
                new event. For each event, a tuple of (event_id, hashes, depth)
                where *hashes* is a map from algorithm to hash.

                If None, they will be requested from the database.
        Raises:
            ResourceLimitError if server is blocked to some resource being
            exceeded
        Returns:
            Tuple of created event (FrozenEvent), Context
        """
        yield self.auth.check_auth_blocking(requester.user.to_string())

        if event_dict["type"] == EventTypes.Create and event_dict[
                "state_key"] == "":
            room_version = event_dict["content"]["room_version"]
        else:
            try:
                room_version = yield self.store.get_room_version(
                    event_dict["room_id"])
            except NotFoundError:
                raise AuthError(403, "Unknown room")

        builder = self.event_builder_factory.new(room_version, event_dict)

        self.validator.validate_builder(builder)

        if builder.type == EventTypes.Member:
            membership = builder.content.get("membership", None)
            target = UserID.from_string(builder.state_key)

            if membership in {Membership.JOIN, Membership.INVITE}:
                # If event doesn't include a display name, add one.
                profile = self.profile_handler
                content = builder.content

                try:
                    if "displayname" not in content:
                        content["displayname"] = yield profile.get_displayname(
                            target)
                    if "avatar_url" not in content:
                        content["avatar_url"] = yield profile.get_avatar_url(
                            target)
                except Exception as e:
                    logger.info("Failed to get profile information for %r: %s",
                                target, e)

        is_exempt = yield self._is_exempt_from_privacy_policy(
            builder, requester)
        if not is_exempt:
            yield self.assert_accepted_privacy_policy(requester)

        if token_id is not None:
            builder.internal_metadata.token_id = token_id

        if txn_id is not None:
            builder.internal_metadata.txn_id = txn_id

        event, context = yield self.create_new_client_event(
            builder=builder,
            requester=requester,
            prev_events_and_hashes=prev_events_and_hashes,
        )

        self.validator.validate_new(event)

        defer.returnValue((event, context))
示例#23
0
def _check_power_levels(
    room_version_obj: RoomVersion,
    event: EventBase,
    auth_events: StateMap[EventBase],
) -> None:
    user_list = event.content.get("users", {})
    # Validate users
    for k, v in user_list.items():
        try:
            UserID.from_string(k)
        except Exception:
            raise SynapseError(400, "Not a valid user_id: %s" % (k, ))

        try:
            int(v)
        except Exception:
            raise SynapseError(400, "Not a valid power level: %s" % (v, ))

    key = (event.type, event.state_key)
    current_state = auth_events.get(key)

    if not current_state:
        return

    user_level = get_user_power_level(event.user_id, auth_events)

    # Check other levels:
    levels_to_check = [
        ("users_default", None),
        ("events_default", None),
        ("state_default", None),
        ("ban", None),
        ("redact", None),
        ("kick", None),
        ("invite", None),
    ]  # type: List[Tuple[str, Optional[str]]]

    old_list = current_state.content.get("users", {})
    for user in set(list(old_list) + list(user_list)):
        levels_to_check.append((user, "users"))

    old_list = current_state.content.get("events", {})
    new_list = event.content.get("events", {})
    for ev_id in set(list(old_list) + list(new_list)):
        levels_to_check.append((ev_id, "events"))

    # MSC2209 specifies these checks should also be done for the "notifications"
    # key.
    if room_version_obj.limit_notifications_power_levels:
        old_list = current_state.content.get("notifications", {})
        new_list = event.content.get("notifications", {})
        for ev_id in set(list(old_list) + list(new_list)):
            levels_to_check.append((ev_id, "notifications"))

    old_state = current_state.content
    new_state = event.content

    for level_to_check, dir in levels_to_check:
        old_loc = old_state
        new_loc = new_state
        if dir:
            old_loc = old_loc.get(dir, {})
            new_loc = new_loc.get(dir, {})

        if level_to_check in old_loc:
            old_level = int(old_loc[level_to_check])  # type: Optional[int]
        else:
            old_level = None

        if level_to_check in new_loc:
            new_level = int(new_loc[level_to_check])  # type: Optional[int]
        else:
            new_level = None

        if new_level is not None and old_level is not None:
            if new_level == old_level:
                continue

        if dir == "users" and level_to_check != event.user_id:
            if old_level == user_level:
                raise AuthError(
                    403,
                    "You don't have permission to remove ops level equal "
                    "to your own",
                )

        # Check if the old and new levels are greater than the user level
        # (if defined)
        old_level_too_big = old_level is not None and old_level > user_level
        new_level_too_big = new_level is not None and new_level > user_level
        if old_level_too_big or new_level_too_big:
            raise AuthError(
                403,
                "You don't have permission to add ops level greater than your own"
            )
示例#24
0
    def persist_and_notify_client_event(
        self,
        requester,
        event,
        context,
        ratelimit=True,
        extra_users=[],
    ):
        """Called when we have fully built the event, have already
        calculated the push actions for the event, and checked auth.

        This should only be run on master.
        """
        assert not self.config.worker_app

        if ratelimit:
            yield self.base_handler.ratelimit(requester)

        yield self.base_handler.maybe_kick_guest_users(event, context)

        if event.type == EventTypes.CanonicalAlias:
            # Check the alias is acually valid (at this time at least)
            room_alias_str = event.content.get("alias", None)
            if room_alias_str:
                room_alias = RoomAlias.from_string(room_alias_str)
                directory_handler = self.hs.get_handlers().directory_handler
                mapping = yield directory_handler.get_association(room_alias)

                if mapping["room_id"] != event.room_id:
                    raise SynapseError(
                        400, "Room alias %s does not point to the room" %
                        (room_alias_str, ))

        federation_handler = self.hs.get_handlers().federation_handler

        if event.type == EventTypes.Member:
            if event.content["membership"] == Membership.INVITE:

                def is_inviter_member_event(e):
                    return (e.type == EventTypes.Member
                            and e.sender == event.sender)

                current_state_ids = yield context.get_current_state_ids(
                    self.store)

                state_to_include_ids = [
                    e_id for k, e_id in iteritems(current_state_ids)
                    if k[0] in self.hs.config.room_invite_state_types or k == (
                        EventTypes.Member, event.sender)
                ]

                state_to_include = yield self.store.get_events(
                    state_to_include_ids)

                event.unsigned["invite_room_state"] = [{
                    "type": e.type,
                    "state_key": e.state_key,
                    "content": e.content,
                    "sender": e.sender,
                } for e in itervalues(state_to_include)]

                invitee = UserID.from_string(event.state_key)
                if not self.hs.is_mine(invitee):
                    # TODO: Can we add signature from remote server in a nicer
                    # way? If we have been invited by a remote server, we need
                    # to get them to sign the event.

                    returned_invite = yield federation_handler.send_invite(
                        invitee.domain,
                        event,
                    )

                    event.unsigned.pop("room_state", None)

                    # TODO: Make sure the signatures actually are correct.
                    event.signatures.update(returned_invite.signatures)

        if event.type == EventTypes.Redaction:
            prev_state_ids = yield context.get_prev_state_ids(self.store)
            auth_events_ids = yield self.auth.compute_auth_events(
                event,
                prev_state_ids,
                for_verification=True,
            )
            auth_events = yield self.store.get_events(auth_events_ids)
            auth_events = {(e.type, e.state_key): e
                           for e in auth_events.values()}
            room_version = yield self.store.get_room_version(event.room_id)
            if self.auth.check_redaction(room_version,
                                         event,
                                         auth_events=auth_events):
                original_event = yield self.store.get_event(
                    event.redacts,
                    check_redacted=False,
                    get_prev_content=False,
                    allow_rejected=False,
                    allow_none=False)
                if event.user_id != original_event.user_id:
                    raise AuthError(
                        403, "You don't have permission to redact events")

                # We've already checked.
                event.internal_metadata.recheck_redaction = False

        if event.type == EventTypes.Create:
            prev_state_ids = yield context.get_prev_state_ids(self.store)
            if prev_state_ids:
                raise AuthError(
                    403,
                    "Changing the room create event is forbidden",
                )

        (event_stream_id,
         max_stream_id) = yield self.store.persist_event(event,
                                                         context=context)

        yield self.pusher_pool.on_new_notifications(
            event_stream_id,
            max_stream_id,
        )

        def _notify():
            try:
                self.notifier.on_new_room_event(event,
                                                event_stream_id,
                                                max_stream_id,
                                                extra_users=extra_users)
            except Exception:
                logger.exception("Error notifying about new room event")

        run_in_background(_notify)

        if event.type == EventTypes.Message:
            # We don't want to block sending messages on any presence code. This
            # matters as sometimes presence code can take a while.
            run_in_background(self._bump_active_time, requester.user)
示例#25
0
    def is_membership_change_allowed(self, 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

        # 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 = self._get_user_power_level(event.user_id, auth_events)

        # FIXME (erikj): What should we do here as the default?
        ban_level = self._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.JOIN != membership:
            # JOIN is the only action you can perform if you're not in the room
            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 = self._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 = self._get_named_level(auth_events, "kick", 50)

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

        return True
示例#26
0
    def get_state_events(
            self,
            user_id,
            room_id,
            state_filter=StateFilter.all(),
            at_token=None,
            is_guest=False,
    ):
        """Retrieve all state events for a given room. If the user is
        joined to the room then return the current state. If the user has
        left the room return the state events from when they left. If an explicit
        'at' parameter is passed, return the state events as of that event, if
        visible.

        Args:
            user_id(str): The user requesting state events.
            room_id(str): The room ID to get all state events from.
            state_filter (StateFilter): The state filter used to fetch state
                from the database.
            at_token(StreamToken|None): the stream token of the at which we are requesting
                the stats. If the user is not allowed to view the state as of that
                stream token, we raise a 403 SynapseError. If None, returns the current
                state based on the current_state_events table.
            is_guest(bool): whether this user is a guest
        Returns:
            A list of dicts representing state events. [{}, {}, {}]
        Raises:
            NotFoundError (404) if the at token does not yield an event

            AuthError (403) if the user doesn't have permission to view
            members of this room.
        """
        if at_token:
            # FIXME this claims to get the state at a stream position, but
            # get_recent_events_for_room operates by topo ordering. This therefore
            # does not reliably give you the state at the given stream position.
            # (https://github.com/matrix-org/synapse/issues/3305)
            last_events, _ = yield self.store.get_recent_events_for_room(
                room_id,
                end_token=at_token.room_key,
                limit=1,
            )

            if not last_events:
                raise NotFoundError("Can't find event for token %s" %
                                    (at_token, ))

            visible_events = yield filter_events_for_client(
                self.store,
                user_id,
                last_events,
            )

            event = last_events[0]
            if visible_events:
                room_state = yield self.store.get_state_for_events(
                    [event.event_id],
                    state_filter=state_filter,
                )
                room_state = room_state[event.event_id]
            else:
                raise AuthError(
                    403,
                    "User %s not allowed to view events in room %s at token %s"
                    % (
                        user_id,
                        room_id,
                        at_token,
                    ))
        else:
            membership, membership_event_id = (
                yield self.auth.check_in_room_or_world_readable(
                    room_id,
                    user_id,
                ))

            if membership == Membership.JOIN:
                state_ids = yield self.store.get_filtered_current_state_ids(
                    room_id,
                    state_filter=state_filter,
                )
                room_state = yield self.store.get_events(state_ids.values())
            elif membership == Membership.LEAVE:
                room_state = yield self.store.get_state_for_events(
                    [membership_event_id],
                    state_filter=state_filter,
                )
                room_state = room_state[membership_event_id]

        now = self.clock.time_msec()
        defer.returnValue(
            [serialize_event(c, now) for c in room_state.values()])
示例#27
0
    def _update_membership(
        self,
        requester,
        target,
        room_id,
        action,
        txn_id=None,
        remote_room_hosts=None,
        third_party_signed=None,
        ratelimit=True,
        content=None,
    ):
        content_specified = bool(content)
        if content is None:
            content = {}
        else:
            # We do a copy here as we potentially change some keys
            # later on.
            content = dict(content)

        effective_membership_state = action
        if action in ["kick", "unban"]:
            effective_membership_state = "leave"

        # if this is a join with a 3pid signature, we may need to turn a 3pid
        # invite into a normal invite before we can handle the join.
        if third_party_signed is not None:
            yield self.federation_handler.exchange_third_party_invite(
                third_party_signed["sender"],
                target.to_string(),
                room_id,
                third_party_signed,
            )

        if not remote_room_hosts:
            remote_room_hosts = []

        if effective_membership_state not in (
                "leave",
                "ban",
        ):
            is_blocked = yield self.store.is_room_blocked(room_id)
            if is_blocked:
                raise SynapseError(
                    403, "This room has been blocked on this server")

        if effective_membership_state == "invite":
            block_invite = False
            is_requester_admin = yield self.auth.is_server_admin(
                requester.user, )
            if not is_requester_admin:
                if self.config.block_non_admin_invites:
                    logger.info(
                        "Blocking invite: user is not admin and non-admin "
                        "invites disabled")
                    block_invite = True

                if not self.spam_checker.user_may_invite(
                        requester.user.to_string(),
                        target.to_string(),
                        room_id,
                ):
                    logger.info("Blocking invite due to spam checker")
                    block_invite = True

            if block_invite:
                raise SynapseError(
                    403,
                    "Invites have been disabled on this server",
                )

        prev_events_and_hashes = yield self.store.get_prev_events_for_room(
            room_id, )
        latest_event_ids = (event_id
                            for (event_id, _, _) in prev_events_and_hashes)
        current_state_ids = yield self.state_handler.get_current_state_ids(
            room_id,
            latest_event_ids=latest_event_ids,
        )

        old_state_id = current_state_ids.get(
            (EventTypes.Member, target.to_string()))
        if old_state_id:
            old_state = yield self.store.get_event(old_state_id,
                                                   allow_none=True)
            old_membership = old_state.content.get(
                "membership") if old_state else None
            if action == "unban" and old_membership != "ban":
                raise SynapseError(403,
                                   "Cannot unban user who was not banned"
                                   " (membership=%s)" % old_membership,
                                   errcode=Codes.BAD_STATE)
            if old_membership == "ban" and action != "unban":
                raise SynapseError(403,
                                   "Cannot %s user who was banned" %
                                   (action, ),
                                   errcode=Codes.BAD_STATE)

            if old_state:
                same_content = content == old_state.content
                same_membership = old_membership == effective_membership_state
                same_sender = requester.user.to_string() == old_state.sender
                if same_sender and same_membership and same_content:
                    defer.returnValue(old_state)

        is_host_in_room = yield self._is_host_in_room(current_state_ids)

        if effective_membership_state == Membership.JOIN:
            if requester.is_guest:
                guest_can_join = yield self._can_guest_join(current_state_ids)
                if not guest_can_join:
                    # This should be an auth check, but guests are a local concept,
                    # so don't really fit into the general auth process.
                    raise AuthError(403, "Guest access not allowed")

            if not is_host_in_room:
                inviter = yield self._get_inviter(target.to_string(), room_id)
                if inviter and not self.hs.is_mine(inviter):
                    remote_room_hosts.append(inviter.domain)

                content["membership"] = Membership.JOIN

                profile = self.profile_handler
                if not content_specified:
                    content["displayname"] = yield profile.get_displayname(
                        target)
                    content["avatar_url"] = yield profile.get_avatar_url(
                        target)

                if requester.is_guest:
                    content["kind"] = "guest"

                ret = yield self._remote_join(requester, remote_room_hosts,
                                              room_id, target, content)
                defer.returnValue(ret)

        elif effective_membership_state == Membership.LEAVE:
            if not is_host_in_room:
                # perhaps we've been invited
                inviter = yield self._get_inviter(target.to_string(), room_id)
                if not inviter:
                    raise SynapseError(404, "Not a known room")

                if self.hs.is_mine(inviter):
                    # the inviter was on our server, but has now left. Carry on
                    # with the normal rejection codepath.
                    #
                    # This is a bit of a hack, because the room might still be
                    # active on other servers.
                    pass
                else:
                    # send the rejection to the inviter's HS.
                    remote_room_hosts = remote_room_hosts + [inviter.domain]
                    res = yield self._remote_reject_invite(
                        requester,
                        remote_room_hosts,
                        room_id,
                        target,
                    )
                    defer.returnValue(res)

        res = yield self._local_membership_update(
            requester=requester,
            target=target,
            room_id=room_id,
            membership=effective_membership_state,
            txn_id=txn_id,
            ratelimit=ratelimit,
            prev_events_and_hashes=prev_events_and_hashes,
            content=content,
        )
        defer.returnValue(res)
示例#28
0
 async def on_POST(self, request):
     raise AuthError(403, "tokenrefresh is no longer supported.")
示例#29
0
文件: auth.py 项目: n3h3m/synapse
    def _check_power_levels(self, event, auth_events):
        user_list = event.content.get("users", {})
        # Validate users
        for k, v in user_list.items():
            try:
                UserID.from_string(k)
            except:
                raise SynapseError(400, "Not a valid user_id: %s" % (k, ))

            try:
                int(v)
            except:
                raise SynapseError(400, "Not a valid power level: %s" % (v, ))

        key = (
            event.type,
            event.state_key,
        )
        current_state = auth_events.get(key)

        if not current_state:
            return

        user_level = self._get_user_power_level(event.user_id, auth_events)

        # Check other levels:
        levels_to_check = [
            ("users_default", None),
            ("events_default", None),
            ("state_default", None),
            ("ban", None),
            ("redact", None),
            ("kick", None),
            ("invite", None),
        ]

        old_list = current_state.content.get("users")
        for user in set(old_list.keys() + user_list.keys()):
            levels_to_check.append((user, "users"))

        old_list = current_state.content.get("events")
        new_list = event.content.get("events")
        for ev_id in set(old_list.keys() + new_list.keys()):
            levels_to_check.append((ev_id, "events"))

        old_state = current_state.content
        new_state = event.content

        for level_to_check, dir in levels_to_check:
            old_loc = old_state
            new_loc = new_state
            if dir:
                old_loc = old_loc.get(dir, {})
                new_loc = new_loc.get(dir, {})

            if level_to_check in old_loc:
                old_level = int(old_loc[level_to_check])
            else:
                old_level = None

            if level_to_check in new_loc:
                new_level = int(new_loc[level_to_check])
            else:
                new_level = None

            if new_level is not None and old_level is not None:
                if new_level == old_level:
                    continue

            if dir == "users" and level_to_check != event.user_id:
                if old_level == user_level:
                    raise AuthError(
                        403,
                        "You don't have permission to remove ops level equal "
                        "to your own")

            if old_level > user_level or new_level > user_level:
                raise AuthError(
                    403, "You don't have permission to add ops level greater "
                    "than your own")
示例#30
0
 def check_joined_room(room_id, user_id):
     if user_id not in [u.to_string() for u in self.room_members]:
         raise AuthError(401, "User is not in the room")