Merge remote-tracking branch 'origin/develop' into matrix-org-hotfixes
commit
9dbe34f0d0
|
@ -0,0 +1 @@
|
||||||
|
Clean up and update docs on setting up federation.
|
|
@ -0,0 +1 @@
|
||||||
|
Implement `GET /_matrix/client/r0/rooms/{roomId}/aliases` endpoint as per [MSC2432](https://github.com/matrix-org/matrix-doc/pull/2432).
|
|
@ -0,0 +1 @@
|
||||||
|
Implement `GET /_matrix/client/r0/rooms/{roomId}/aliases` endpoint as per [MSC2432](https://github.com/matrix-org/matrix-doc/pull/2432).
|
|
@ -0,0 +1 @@
|
||||||
|
Tiny optimisation for incoming HTTP request dispatch.
|
|
@ -0,0 +1 @@
|
||||||
|
Revert #6937.
|
|
@ -0,0 +1,94 @@
|
||||||
|
# Delegation
|
||||||
|
|
||||||
|
By default, other homeservers will expect to be able to reach yours via
|
||||||
|
your `server_name`, on port 8448. For example, if you set your `server_name`
|
||||||
|
to `example.com` (so that your user names look like `@user:example.com`),
|
||||||
|
other servers will try to connect to yours at `https://example.com:8448/`.
|
||||||
|
|
||||||
|
Delegation is a Matrix feature allowing a homeserver admin to retain a
|
||||||
|
`server_name` of `example.com` so that user IDs, room aliases, etc continue
|
||||||
|
to look like `*:example.com`, whilst having federation traffic routed
|
||||||
|
to a different server and/or port (e.g. `synapse.example.com:443`).
|
||||||
|
|
||||||
|
## .well-known delegation
|
||||||
|
|
||||||
|
To use this method, you need to be able to alter the
|
||||||
|
`server_name` 's https server to serve the `/.well-known/matrix/server`
|
||||||
|
URL. Having an active server (with a valid TLS certificate) serving your
|
||||||
|
`server_name` domain is out of the scope of this documentation.
|
||||||
|
|
||||||
|
The URL `https://<server_name>/.well-known/matrix/server` should
|
||||||
|
return a JSON structure containing the key `m.server` like so:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"m.server": "<synapse.server.name>[:<yourport>]"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In our example, this would mean that URL `https://example.com/.well-known/matrix/server`
|
||||||
|
should return:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"m.server": "synapse.example.com:443"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note, specifying a port is optional. If no port is specified, then it defaults
|
||||||
|
to 8448.
|
||||||
|
|
||||||
|
With .well-known delegation, federating servers will check for a valid TLS
|
||||||
|
certificate for the delegated hostname (in our example: `synapse.example.com`).
|
||||||
|
|
||||||
|
## SRV DNS record delegation
|
||||||
|
|
||||||
|
It is also possible to do delegation using a SRV DNS record. However, that is
|
||||||
|
considered an advanced topic since it's a bit complex to set up, and `.well-known`
|
||||||
|
delegation is already enough in most cases.
|
||||||
|
|
||||||
|
However, if you really need it, you can find some documentation on how such a
|
||||||
|
record should look like and how Synapse will use it in [the Matrix
|
||||||
|
specification](https://matrix.org/docs/spec/server_server/latest#resolving-server-names).
|
||||||
|
|
||||||
|
## Delegation FAQ
|
||||||
|
|
||||||
|
### When do I need delegation?
|
||||||
|
|
||||||
|
If your homeserver's APIs are accessible on the default federation port (8448)
|
||||||
|
and the domain your `server_name` points to, you do not need any delegation.
|
||||||
|
|
||||||
|
For instance, if you registered `example.com` and pointed its DNS A record at a
|
||||||
|
fresh server, you could install Synapse on that host, giving it a `server_name`
|
||||||
|
of `example.com`, and once a reverse proxy has been set up to proxy all requests
|
||||||
|
sent to the port `8448` and serve TLS certificates for `example.com`, you
|
||||||
|
wouldn't need any delegation set up.
|
||||||
|
|
||||||
|
**However**, if your homeserver's APIs aren't accessible on port 8448 and on the
|
||||||
|
domain `server_name` points to, you will need to let other servers know how to
|
||||||
|
find it using delegation.
|
||||||
|
|
||||||
|
### Do you still recommend against using a reverse proxy on the federation port?
|
||||||
|
|
||||||
|
We no longer actively recommend against using a reverse proxy. Many admins will
|
||||||
|
find it easier to direct federation traffic to a reverse proxy and manage their
|
||||||
|
own TLS certificates, and this is a supported configuration.
|
||||||
|
|
||||||
|
See [reverse_proxy.md](reverse_proxy.md) for information on setting up a
|
||||||
|
reverse proxy.
|
||||||
|
|
||||||
|
### Do I still need to give my TLS certificates to Synapse if I am using a reverse proxy?
|
||||||
|
|
||||||
|
This is no longer necessary. If you are using a reverse proxy for all of your
|
||||||
|
TLS traffic, then you can set `no_tls: True` in the Synapse config.
|
||||||
|
|
||||||
|
In that case, the only reason Synapse needs the certificate is to populate a legacy
|
||||||
|
`tls_fingerprints` field in the federation API. This is ignored by Synapse 0.99.0
|
||||||
|
and later, and the only time pre-0.99 Synapses will check it is when attempting to
|
||||||
|
fetch the server keys - and generally this is delegated via `matrix.org`, which
|
||||||
|
is running a modern version of Synapse.
|
||||||
|
|
||||||
|
### Do I need the same certificate for the client and federation port?
|
||||||
|
|
||||||
|
No. There is nothing stopping you from using different certificates,
|
||||||
|
particularly if you are using a reverse proxy.
|
178
docs/federate.md
178
docs/federate.md
|
@ -1,163 +1,41 @@
|
||||||
Setting up Federation
|
Setting up federation
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Federation is the process by which users on different servers can participate
|
Federation is the process by which users on different servers can participate
|
||||||
in the same room. For this to work, those other servers must be able to contact
|
in the same room. For this to work, those other servers must be able to contact
|
||||||
yours to send messages.
|
yours to send messages.
|
||||||
|
|
||||||
The ``server_name`` configured in the Synapse configuration file (often
|
The `server_name` configured in the Synapse configuration file (often
|
||||||
``homeserver.yaml``) defines how resources (users, rooms, etc.) will be
|
`homeserver.yaml`) defines how resources (users, rooms, etc.) will be
|
||||||
identified (eg: ``@user:example.com``, ``#room:example.com``). By
|
identified (eg: `@user:example.com`, `#room:example.com`). By default,
|
||||||
default, it is also the domain that other servers will use to
|
it is also the domain that other servers will use to try to reach your
|
||||||
try to reach your server (via port 8448). This is easy to set
|
server (via port 8448). This is easy to set up and will work provided
|
||||||
up and will work provided you set the ``server_name`` to match your
|
you set the `server_name` to match your machine's public DNS hostname.
|
||||||
machine's public DNS hostname, and provide Synapse with a TLS certificate
|
|
||||||
which is valid for your ``server_name``.
|
For this default configuration to work, you will need to listen for TLS
|
||||||
|
connections on port 8448. The preferred way to do that is by using a
|
||||||
|
reverse proxy: see [reverse_proxy.md](<reverse_proxy.md>) for instructions
|
||||||
|
on how to correctly set one up.
|
||||||
|
|
||||||
|
In some cases you might not want to run Synapse on the machine that has
|
||||||
|
the `server_name` as its public DNS hostname, or you might want federation
|
||||||
|
traffic to use a different port than 8448. For example, you might want to
|
||||||
|
have your user names look like `@user:example.com`, but you want to run
|
||||||
|
Synapse on `synapse.example.com` on port 443. This can be done using
|
||||||
|
delegation, which allows an admin to control where federation traffic should
|
||||||
|
be sent. See [delegate.md](delegate.md) for instructions on how to set this up.
|
||||||
|
|
||||||
Once federation has been configured, you should be able to join a room over
|
Once federation has been configured, you should be able to join a room over
|
||||||
federation. A good place to start is ``#synapse:matrix.org`` - a room for
|
federation. A good place to start is `#synapse:matrix.org` - a room for
|
||||||
Synapse admins.
|
Synapse admins.
|
||||||
|
|
||||||
|
|
||||||
## Delegation
|
|
||||||
|
|
||||||
For a more flexible configuration, you can have ``server_name``
|
|
||||||
resources (eg: ``@user:example.com``) served by a different host and
|
|
||||||
port (eg: ``synapse.example.com:443``). There are two ways to do this:
|
|
||||||
|
|
||||||
- adding a ``/.well-known/matrix/server`` URL served on ``https://example.com``.
|
|
||||||
- adding a DNS ``SRV`` record in the DNS zone of domain
|
|
||||||
``example.com``.
|
|
||||||
|
|
||||||
Without configuring delegation, the matrix federation will
|
|
||||||
expect to find your server via ``example.com:8448``. The following methods
|
|
||||||
allow you retain a `server_name` of `example.com` so that your user IDs, room
|
|
||||||
aliases, etc continue to look like `*:example.com`, whilst having your
|
|
||||||
federation traffic routed to a different server.
|
|
||||||
|
|
||||||
### .well-known delegation
|
|
||||||
|
|
||||||
To use this method, you need to be able to alter the
|
|
||||||
``server_name`` 's https server to serve the ``/.well-known/matrix/server``
|
|
||||||
URL. Having an active server (with a valid TLS certificate) serving your
|
|
||||||
``server_name`` domain is out of the scope of this documentation.
|
|
||||||
|
|
||||||
The URL ``https://<server_name>/.well-known/matrix/server`` should
|
|
||||||
return a JSON structure containing the key ``m.server`` like so:
|
|
||||||
|
|
||||||
{
|
|
||||||
"m.server": "<synapse.server.name>[:<yourport>]"
|
|
||||||
}
|
|
||||||
|
|
||||||
In our example, this would mean that URL ``https://example.com/.well-known/matrix/server``
|
|
||||||
should return:
|
|
||||||
|
|
||||||
{
|
|
||||||
"m.server": "synapse.example.com:443"
|
|
||||||
}
|
|
||||||
|
|
||||||
Note, specifying a port is optional. If a port is not specified an SRV lookup
|
|
||||||
is performed, as described below. If the target of the
|
|
||||||
delegation does not have an SRV record, then the port defaults to 8448.
|
|
||||||
|
|
||||||
Most installations will not need to configure .well-known. However, it can be
|
|
||||||
useful in cases where the admin is hosting on behalf of someone else and
|
|
||||||
therefore cannot gain access to the necessary certificate. With .well-known,
|
|
||||||
federation servers will check for a valid TLS certificate for the delegated
|
|
||||||
hostname (in our example: ``synapse.example.com``).
|
|
||||||
|
|
||||||
### DNS SRV delegation
|
|
||||||
|
|
||||||
To use this delegation method, you need to have write access to your
|
|
||||||
``server_name`` 's domain zone DNS records (in our example it would be
|
|
||||||
``example.com`` DNS zone).
|
|
||||||
|
|
||||||
This method requires the target server to provide a
|
|
||||||
valid TLS certificate for the original ``server_name``.
|
|
||||||
|
|
||||||
You need to add a SRV record in your ``server_name`` 's DNS zone with
|
|
||||||
this format:
|
|
||||||
|
|
||||||
_matrix._tcp.<yourdomain.com> <ttl> IN SRV <priority> <weight> <port> <synapse.server.name>
|
|
||||||
|
|
||||||
In our example, we would need to add this SRV record in the
|
|
||||||
``example.com`` DNS zone:
|
|
||||||
|
|
||||||
_matrix._tcp.example.com. 3600 IN SRV 10 5 443 synapse.example.com.
|
|
||||||
|
|
||||||
Once done and set up, you can check the DNS record with ``dig -t srv
|
|
||||||
_matrix._tcp.<server_name>``. In our example, we would expect this:
|
|
||||||
|
|
||||||
$ dig -t srv _matrix._tcp.example.com
|
|
||||||
_matrix._tcp.example.com. 3600 IN SRV 10 0 443 synapse.example.com.
|
|
||||||
|
|
||||||
Note that the target of a SRV record cannot be an alias (CNAME record): it has to point
|
|
||||||
directly to the server hosting the synapse instance.
|
|
||||||
|
|
||||||
### Delegation FAQ
|
|
||||||
#### When do I need a SRV record or .well-known URI?
|
|
||||||
|
|
||||||
If your homeserver listens on the default federation port (8448), and your
|
|
||||||
`server_name` points to the host that your homeserver runs on, you do not need an SRV
|
|
||||||
record or `.well-known/matrix/server` URI.
|
|
||||||
|
|
||||||
For instance, if you registered `example.com` and pointed its DNS A record at a
|
|
||||||
fresh server, you could install Synapse on that host,
|
|
||||||
giving it a `server_name` of `example.com`, and once [ACME](acme.md) support is enabled,
|
|
||||||
it would automatically generate a valid TLS certificate for you via Let's Encrypt
|
|
||||||
and no SRV record or .well-known URI would be needed.
|
|
||||||
|
|
||||||
**However**, if your server does not listen on port 8448, or if your `server_name`
|
|
||||||
does not point to the host that your homeserver runs on, you will need to let
|
|
||||||
other servers know how to find it. The way to do this is via .well-known or an
|
|
||||||
SRV record.
|
|
||||||
|
|
||||||
#### I have created a .well-known URI. Do I also need an SRV record?
|
|
||||||
|
|
||||||
No. You can use either `.well-known` delegation or use an SRV record for delegation. You
|
|
||||||
do not need to use both to delegate to the same location.
|
|
||||||
|
|
||||||
#### Can I manage my own certificates rather than having Synapse renew certificates itself?
|
|
||||||
|
|
||||||
Yes, you are welcome to manage your certificates yourself. Synapse will only
|
|
||||||
attempt to obtain certificates from Let's Encrypt if you configure it to do
|
|
||||||
so.The only requirement is that there is a valid TLS cert present for
|
|
||||||
federation end points.
|
|
||||||
|
|
||||||
#### Do you still recommend against using a reverse proxy on the federation port?
|
|
||||||
|
|
||||||
We no longer actively recommend against using a reverse proxy. Many admins will
|
|
||||||
find it easier to direct federation traffic to a reverse proxy and manage their
|
|
||||||
own TLS certificates, and this is a supported configuration.
|
|
||||||
|
|
||||||
See [reverse_proxy.md](reverse_proxy.md) for information on setting up a
|
|
||||||
reverse proxy.
|
|
||||||
|
|
||||||
#### Do I still need to give my TLS certificates to Synapse if I am using a reverse proxy?
|
|
||||||
|
|
||||||
Practically speaking, this is no longer necessary.
|
|
||||||
|
|
||||||
If you are using a reverse proxy for all of your TLS traffic, then you can set
|
|
||||||
`no_tls: True` in the Synapse config. In that case, the only reason Synapse
|
|
||||||
needs the certificate is to populate a legacy `tls_fingerprints` field in the
|
|
||||||
federation API. This is ignored by Synapse 0.99.0 and later, and the only time
|
|
||||||
pre-0.99 Synapses will check it is when attempting to fetch the server keys -
|
|
||||||
and generally this is delegated via `matrix.org`, which will be running a modern
|
|
||||||
version of Synapse.
|
|
||||||
|
|
||||||
#### Do I need the same certificate for the client and federation port?
|
|
||||||
|
|
||||||
No. There is nothing stopping you from using different certificates,
|
|
||||||
particularly if you are using a reverse proxy. However, Synapse will use the
|
|
||||||
same certificate on any ports where TLS is configured.
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
You can use the [federation tester](
|
You can use the [federation tester](https://matrix.org/federationtester)
|
||||||
<https://matrix.org/federationtester>) to check if your homeserver is
|
to check if your homeserver is configured correctly. Alternatively try the
|
||||||
configured correctly. Alternatively try the [JSON API used by the federation tester](https://matrix.org/federationtester/api/report?server_name=DOMAIN).
|
[JSON API used by the federation tester](https://matrix.org/federationtester/api/report?server_name=DOMAIN).
|
||||||
Note that you'll have to modify this URL to replace ``DOMAIN`` with your
|
Note that you'll have to modify this URL to replace `DOMAIN` with your
|
||||||
``server_name``. Hitting the API directly provides extra detail.
|
`server_name`. Hitting the API directly provides extra detail.
|
||||||
|
|
||||||
The typical failure mode for federation is that when the server tries to join
|
The typical failure mode for federation is that when the server tries to join
|
||||||
a room, it is rejected with "401: Unauthorized". Generally this means that other
|
a room, it is rejected with "401: Unauthorized". Generally this means that other
|
||||||
|
@ -169,8 +47,8 @@ you invite them to. This can be caused by an incorrectly-configured reverse
|
||||||
proxy: see [reverse_proxy.md](<reverse_proxy.md>) for instructions on how to correctly
|
proxy: see [reverse_proxy.md](<reverse_proxy.md>) for instructions on how to correctly
|
||||||
configure a reverse proxy.
|
configure a reverse proxy.
|
||||||
|
|
||||||
## Running a Demo Federation of Synapses
|
## Running a demo federation of Synapses
|
||||||
|
|
||||||
If you want to get up and running quickly with a trio of homeservers in a
|
If you want to get up and running quickly with a trio of homeservers in a
|
||||||
private federation, there is a script in the ``demo`` directory. This is mainly
|
private federation, there is a script in the `demo` directory. This is mainly
|
||||||
useful just for development purposes. See [demo/README](<../demo/README>).
|
useful just for development purposes. See [demo/README](<../demo/README>).
|
||||||
|
|
|
@ -18,9 +18,10 @@ When setting up a reverse proxy, remember that Matrix clients and other
|
||||||
Matrix servers do not necessarily need to connect to your server via the
|
Matrix servers do not necessarily need to connect to your server via the
|
||||||
same server name or port. Indeed, clients will use port 443 by default,
|
same server name or port. Indeed, clients will use port 443 by default,
|
||||||
whereas servers default to port 8448. Where these are different, we
|
whereas servers default to port 8448. Where these are different, we
|
||||||
refer to the 'client port' and the \'federation port\'. See [Setting
|
refer to the 'client port' and the \'federation port\'. See [the Matrix
|
||||||
up federation](federate.md) for more details of the algorithm used for
|
specification](https://matrix.org/docs/spec/server_server/latest#resolving-server-names)
|
||||||
federation connections.
|
for more details of the algorithm used for federation connections, and
|
||||||
|
[delegate.md](<delegate.md>) for instructions on setting up delegation.
|
||||||
|
|
||||||
Let's assume that we expect clients to connect to our server at
|
Let's assume that we expect clients to connect to our server at
|
||||||
`https://matrix.example.com`, and other servers to connect at
|
`https://matrix.example.com`, and other servers to connect at
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from six import itervalues
|
from six import itervalues
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ from synapse.api.errors import (
|
||||||
)
|
)
|
||||||
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
|
from synapse.api.room_versions import KNOWN_ROOM_VERSIONS
|
||||||
from synapse.config.server import is_threepid_reserved
|
from synapse.config.server import is_threepid_reserved
|
||||||
|
from synapse.events import EventBase
|
||||||
from synapse.types import StateMap, UserID
|
from synapse.types import StateMap, UserID
|
||||||
from synapse.util.caches import CACHE_SIZE_FACTOR, register_cache
|
from synapse.util.caches import CACHE_SIZE_FACTOR, register_cache
|
||||||
from synapse.util.caches.lrucache import LruCache
|
from synapse.util.caches.lrucache import LruCache
|
||||||
|
@ -92,20 +94,34 @@ class Auth(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def check_joined_room(self, room_id, user_id, current_state=None):
|
def check_user_in_room(
|
||||||
"""Check if the user is currently joined in the room
|
self,
|
||||||
|
room_id: str,
|
||||||
|
user_id: str,
|
||||||
|
current_state: Optional[StateMap[EventBase]] = None,
|
||||||
|
allow_departed_users: bool = False,
|
||||||
|
):
|
||||||
|
"""Check if the user is in the room, or was at some point.
|
||||||
Args:
|
Args:
|
||||||
room_id(str): The room to check.
|
room_id: The room to check.
|
||||||
user_id(str): The user to check.
|
|
||||||
current_state(dict): Optional map of the current state of the room.
|
user_id: The user to check.
|
||||||
|
|
||||||
|
current_state: Optional map of the current state of the room.
|
||||||
If provided then that map is used to check whether they are a
|
If provided then that map is used to check whether they are a
|
||||||
member of the room. Otherwise the current membership is
|
member of the room. Otherwise the current membership is
|
||||||
loaded from the database.
|
loaded from the database.
|
||||||
|
|
||||||
|
allow_departed_users: if True, accept users that were previously
|
||||||
|
members but have now departed.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
AuthError if the user is not in the room.
|
AuthError if the user is/was not in the room.
|
||||||
Returns:
|
Returns:
|
||||||
A deferred membership event for the user if the user is in
|
Deferred[Optional[EventBase]]:
|
||||||
the room.
|
Membership event for the user if the user was in the
|
||||||
|
room. This will be the join event if they are currently joined to
|
||||||
|
the room. This will be the leave event if they have left the room.
|
||||||
"""
|
"""
|
||||||
if current_state:
|
if current_state:
|
||||||
member = current_state.get((EventTypes.Member, user_id), None)
|
member = current_state.get((EventTypes.Member, user_id), None)
|
||||||
|
@ -113,37 +129,19 @@ class Auth(object):
|
||||||
member = yield self.state.get_current_state(
|
member = yield self.state.get_current_state(
|
||||||
room_id=room_id, event_type=EventTypes.Member, state_key=user_id
|
room_id=room_id, event_type=EventTypes.Member, state_key=user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
self._check_joined_room(member, user_id, room_id)
|
|
||||||
return member
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def check_user_was_in_room(self, room_id, user_id):
|
|
||||||
"""Check if the user was in the room at some point.
|
|
||||||
Args:
|
|
||||||
room_id(str): The room to check.
|
|
||||||
user_id(str): The user to check.
|
|
||||||
Raises:
|
|
||||||
AuthError if the user was never in the room.
|
|
||||||
Returns:
|
|
||||||
A deferred membership event for the user if the user was in the
|
|
||||||
room. This will be the join event if they are currently joined to
|
|
||||||
the room. This will be the leave event if they have left the room.
|
|
||||||
"""
|
|
||||||
member = yield self.state.get_current_state(
|
|
||||||
room_id=room_id, event_type=EventTypes.Member, state_key=user_id
|
|
||||||
)
|
|
||||||
membership = member.membership if member else None
|
membership = member.membership if member else None
|
||||||
|
|
||||||
if membership not in (Membership.JOIN, Membership.LEAVE):
|
if membership == Membership.JOIN:
|
||||||
raise AuthError(403, "User %s not in room %s" % (user_id, room_id))
|
return member
|
||||||
|
|
||||||
if membership == Membership.LEAVE:
|
# XXX this looks totally bogus. Why do we not allow users who have been banned,
|
||||||
|
# or those who were members previously and have been re-invited?
|
||||||
|
if allow_departed_users and membership == Membership.LEAVE:
|
||||||
forgot = yield self.store.did_forget(user_id, room_id)
|
forgot = yield self.store.did_forget(user_id, room_id)
|
||||||
if forgot:
|
if not forgot:
|
||||||
raise AuthError(403, "User %s not in room %s" % (user_id, room_id))
|
return member
|
||||||
|
|
||||||
return member
|
raise AuthError(403, "User %s not in room %s" % (user_id, room_id))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def check_host_in_room(self, room_id, host):
|
def check_host_in_room(self, room_id, host):
|
||||||
|
@ -151,12 +149,6 @@ class Auth(object):
|
||||||
latest_event_ids = yield self.store.is_host_joined(room_id, host)
|
latest_event_ids = yield self.store.is_host_joined(room_id, host)
|
||||||
return latest_event_ids
|
return latest_event_ids
|
||||||
|
|
||||||
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))
|
|
||||||
)
|
|
||||||
|
|
||||||
def can_federate(self, event, auth_events):
|
def can_federate(self, event, auth_events):
|
||||||
creation_event = auth_events.get((EventTypes.Create, ""))
|
creation_event = auth_events.get((EventTypes.Create, ""))
|
||||||
|
|
||||||
|
@ -560,7 +552,7 @@ class Auth(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
user_id = user.to_string()
|
user_id = user.to_string()
|
||||||
yield self.check_joined_room(room_id, user_id)
|
yield self.check_user_in_room(room_id, user_id)
|
||||||
|
|
||||||
# We currently require the user is a "moderator" in the room. We do this
|
# We currently require the user is a "moderator" in the room. We do this
|
||||||
# by checking if they would (theoretically) be able to change the
|
# by checking if they would (theoretically) be able to change the
|
||||||
|
@ -633,10 +625,18 @@ class Auth(object):
|
||||||
return query_params[0].decode("ascii")
|
return query_params[0].decode("ascii")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def check_in_room_or_world_readable(self, room_id, user_id):
|
def check_user_in_room_or_world_readable(
|
||||||
|
self, room_id: str, user_id: str, allow_departed_users: bool = False
|
||||||
|
):
|
||||||
"""Checks that the user is or was in the room or the room is world
|
"""Checks that the user is or was in the room or the room is world
|
||||||
readable. If it isn't then an exception is raised.
|
readable. If it isn't then an exception is raised.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
room_id: room to check
|
||||||
|
user_id: user to check
|
||||||
|
allow_departed_users: if True, accept users that were previously
|
||||||
|
members but have now departed
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Deferred[tuple[str, str|None]]: Resolves to the current membership of
|
Deferred[tuple[str, str|None]]: Resolves to the current membership of
|
||||||
the user in the room and the membership event ID of the user. If
|
the user in the room and the membership event ID of the user. If
|
||||||
|
@ -645,12 +645,14 @@ class Auth(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# check_user_was_in_room will return the most recent membership
|
# check_user_in_room will return the most recent membership
|
||||||
# event for the user if:
|
# event for the user if:
|
||||||
# * The user is a non-guest user, and was ever in the room
|
# * The user is a non-guest user, and was ever in the room
|
||||||
# * The user is a guest user, and has joined the room
|
# * The user is a guest user, and has joined the room
|
||||||
# else it will throw.
|
# else it will throw.
|
||||||
member_event = yield self.check_user_was_in_room(room_id, user_id)
|
member_event = yield self.check_user_in_room(
|
||||||
|
room_id, user_id, allow_departed_users=allow_departed_users
|
||||||
|
)
|
||||||
return member_event.membership, member_event.event_id
|
return member_event.membership, member_event.event_id
|
||||||
except AuthError:
|
except AuthError:
|
||||||
visibility = yield self.state.get_current_state(
|
visibility = yield self.state.get_current_state(
|
||||||
|
@ -662,7 +664,9 @@ class Auth(object):
|
||||||
):
|
):
|
||||||
return Membership.JOIN, None
|
return Membership.JOIN, None
|
||||||
raise AuthError(
|
raise AuthError(
|
||||||
403, "Guest access not allowed", errcode=Codes.GUEST_ACCESS_FORBIDDEN
|
403,
|
||||||
|
"User %s not in room %s, and room previews are disabled"
|
||||||
|
% (user_id, room_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
|
|
|
@ -463,7 +463,9 @@ class DirectoryHandler(BaseHandler):
|
||||||
# allow access to server admins and current members of the room
|
# allow access to server admins and current members of the room
|
||||||
is_admin = await self.auth.is_server_admin(requester.user)
|
is_admin = await self.auth.is_server_admin(requester.user)
|
||||||
if not is_admin:
|
if not is_admin:
|
||||||
await self.auth.check_joined_room(room_id, requester.user.to_string())
|
await self.auth.check_user_in_room_or_world_readable(
|
||||||
|
room_id, requester.user.to_string()
|
||||||
|
)
|
||||||
|
|
||||||
aliases = await self.store.get_aliases_for_room(room_id)
|
aliases = await self.store.get_aliases_for_room(room_id)
|
||||||
return aliases
|
return aliases
|
||||||
|
|
|
@ -18,7 +18,7 @@ import logging
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
from synapse.api.constants import EventTypes, Membership
|
from synapse.api.constants import EventTypes, Membership
|
||||||
from synapse.api.errors import AuthError, Codes, SynapseError
|
from synapse.api.errors import SynapseError
|
||||||
from synapse.events.validator import EventValidator
|
from synapse.events.validator import EventValidator
|
||||||
from synapse.handlers.presence import format_user_presence_state
|
from synapse.handlers.presence import format_user_presence_state
|
||||||
from synapse.logging.context import make_deferred_yieldable, run_in_background
|
from synapse.logging.context import make_deferred_yieldable, run_in_background
|
||||||
|
@ -274,8 +274,11 @@ class InitialSyncHandler(BaseHandler):
|
||||||
|
|
||||||
user_id = requester.user.to_string()
|
user_id = requester.user.to_string()
|
||||||
|
|
||||||
membership, member_event_id = await self._check_in_room_or_world_readable(
|
(
|
||||||
room_id, user_id
|
membership,
|
||||||
|
member_event_id,
|
||||||
|
) = await self.auth.check_user_in_room_or_world_readable(
|
||||||
|
room_id, user_id, allow_departed_users=True,
|
||||||
)
|
)
|
||||||
is_peeking = member_event_id is None
|
is_peeking = member_event_id is None
|
||||||
|
|
||||||
|
@ -433,25 +436,3 @@ class InitialSyncHandler(BaseHandler):
|
||||||
ret["membership"] = membership
|
ret["membership"] = membership
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
async def _check_in_room_or_world_readable(self, room_id, user_id):
|
|
||||||
try:
|
|
||||||
# check_user_was_in_room will return the most recent membership
|
|
||||||
# event for the user if:
|
|
||||||
# * The user is a non-guest user, and was ever in the room
|
|
||||||
# * The user is a guest user, and has joined the room
|
|
||||||
# else it will throw.
|
|
||||||
member_event = await self.auth.check_user_was_in_room(room_id, user_id)
|
|
||||||
return member_event.membership, member_event.event_id
|
|
||||||
except AuthError:
|
|
||||||
visibility = await self.state_handler.get_current_state(
|
|
||||||
room_id, EventTypes.RoomHistoryVisibility, ""
|
|
||||||
)
|
|
||||||
if (
|
|
||||||
visibility
|
|
||||||
and visibility.content["history_visibility"] == "world_readable"
|
|
||||||
):
|
|
||||||
return Membership.JOIN, None
|
|
||||||
raise AuthError(
|
|
||||||
403, "Guest access not allowed", errcode=Codes.GUEST_ACCESS_FORBIDDEN
|
|
||||||
)
|
|
||||||
|
|
|
@ -99,7 +99,9 @@ class MessageHandler(object):
|
||||||
(
|
(
|
||||||
membership,
|
membership,
|
||||||
membership_event_id,
|
membership_event_id,
|
||||||
) = yield self.auth.check_in_room_or_world_readable(room_id, user_id)
|
) = yield self.auth.check_user_in_room_or_world_readable(
|
||||||
|
room_id, user_id, allow_departed_users=True
|
||||||
|
)
|
||||||
|
|
||||||
if membership == Membership.JOIN:
|
if membership == Membership.JOIN:
|
||||||
data = yield self.state.get_current_state(room_id, event_type, state_key)
|
data = yield self.state.get_current_state(room_id, event_type, state_key)
|
||||||
|
@ -177,7 +179,9 @@ class MessageHandler(object):
|
||||||
(
|
(
|
||||||
membership,
|
membership,
|
||||||
membership_event_id,
|
membership_event_id,
|
||||||
) = yield self.auth.check_in_room_or_world_readable(room_id, user_id)
|
) = yield self.auth.check_user_in_room_or_world_readable(
|
||||||
|
room_id, user_id, allow_departed_users=True
|
||||||
|
)
|
||||||
|
|
||||||
if membership == Membership.JOIN:
|
if membership == Membership.JOIN:
|
||||||
state_ids = yield self.store.get_filtered_current_state_ids(
|
state_ids = yield self.store.get_filtered_current_state_ids(
|
||||||
|
@ -216,8 +220,8 @@ class MessageHandler(object):
|
||||||
if not requester.app_service:
|
if not requester.app_service:
|
||||||
# We check AS auth after fetching the room membership, as it
|
# We check AS auth after fetching the room membership, as it
|
||||||
# requires us to pull out all joined members anyway.
|
# requires us to pull out all joined members anyway.
|
||||||
membership, _ = yield self.auth.check_in_room_or_world_readable(
|
membership, _ = yield self.auth.check_user_in_room_or_world_readable(
|
||||||
room_id, user_id
|
room_id, user_id, allow_departed_users=True
|
||||||
)
|
)
|
||||||
if membership != Membership.JOIN:
|
if membership != Membership.JOIN:
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
|
|
|
@ -335,7 +335,9 @@ class PaginationHandler(object):
|
||||||
(
|
(
|
||||||
membership,
|
membership,
|
||||||
member_event_id,
|
member_event_id,
|
||||||
) = await self.auth.check_in_room_or_world_readable(room_id, user_id)
|
) = await self.auth.check_user_in_room_or_world_readable(
|
||||||
|
room_id, user_id, allow_departed_users=True
|
||||||
|
)
|
||||||
|
|
||||||
if source_config.direction == "b":
|
if source_config.direction == "b":
|
||||||
# if we're going backwards, we might need to backfill. This
|
# if we're going backwards, we might need to backfill. This
|
||||||
|
|
|
@ -125,7 +125,7 @@ class TypingHandler(object):
|
||||||
if target_user_id != auth_user_id:
|
if target_user_id != auth_user_id:
|
||||||
raise AuthError(400, "Cannot set another user's typing state")
|
raise AuthError(400, "Cannot set another user's typing state")
|
||||||
|
|
||||||
yield self.auth.check_joined_room(room_id, target_user_id)
|
yield self.auth.check_user_in_room(room_id, target_user_id)
|
||||||
|
|
||||||
logger.debug("%s has started typing in %s", target_user_id, room_id)
|
logger.debug("%s has started typing in %s", target_user_id, room_id)
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ class TypingHandler(object):
|
||||||
if target_user_id != auth_user_id:
|
if target_user_id != auth_user_id:
|
||||||
raise AuthError(400, "Cannot set another user's typing state")
|
raise AuthError(400, "Cannot set another user's typing state")
|
||||||
|
|
||||||
yield self.auth.check_joined_room(room_id, target_user_id)
|
yield self.auth.check_user_in_room(room_id, target_user_id)
|
||||||
|
|
||||||
logger.debug("%s has stopped typing in %s", target_user_id, room_id)
|
logger.debug("%s has stopped typing in %s", target_user_id, room_id)
|
||||||
|
|
||||||
|
|
|
@ -353,10 +353,12 @@ class JsonResource(HttpServer, resource.Resource):
|
||||||
if request.method == b"OPTIONS":
|
if request.method == b"OPTIONS":
|
||||||
return _options_handler, "options_request_handler", {}
|
return _options_handler, "options_request_handler", {}
|
||||||
|
|
||||||
|
request_path = request.path.decode("ascii")
|
||||||
|
|
||||||
# Loop through all the registered callbacks to check if the method
|
# Loop through all the registered callbacks to check if the method
|
||||||
# and path regex match
|
# and path regex match
|
||||||
for path_entry in self.path_regexs.get(request.method, []):
|
for path_entry in self.path_regexs.get(request.method, []):
|
||||||
m = path_entry.pattern.match(request.path.decode("ascii"))
|
m = path_entry.pattern.match(request_path)
|
||||||
if m:
|
if m:
|
||||||
# We found a match!
|
# We found a match!
|
||||||
return path_entry.callback, path_entry.servlet_classname, m.groupdict()
|
return path_entry.callback, path_entry.servlet_classname, m.groupdict()
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
""" This module contains REST servlets to do with rooms: /rooms/<paths> """
|
""" This module contains REST servlets to do with rooms: /rooms/<paths> """
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from six.moves.urllib import parse as urlparse
|
from six.moves.urllib import parse as urlparse
|
||||||
|
@ -848,7 +849,12 @@ class RoomTypingRestServlet(RestServlet):
|
||||||
|
|
||||||
|
|
||||||
class RoomAliasListServlet(RestServlet):
|
class RoomAliasListServlet(RestServlet):
|
||||||
PATTERNS = client_patterns("/rooms/(?P<room_id>[^/]*)/aliases", unstable=False)
|
PATTERNS = [
|
||||||
|
re.compile(
|
||||||
|
r"^/_matrix/client/unstable/org\.matrix\.msc2432"
|
||||||
|
r"/rooms/(?P<room_id>[^/]*)/aliases"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, hs: "synapse.server.HomeServer"):
|
def __init__(self, hs: "synapse.server.HomeServer"):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
|
@ -142,8 +142,8 @@ class RelationPaginationServlet(RestServlet):
|
||||||
):
|
):
|
||||||
requester = await self.auth.get_user_by_req(request, allow_guest=True)
|
requester = await self.auth.get_user_by_req(request, allow_guest=True)
|
||||||
|
|
||||||
await self.auth.check_in_room_or_world_readable(
|
await self.auth.check_user_in_room_or_world_readable(
|
||||||
room_id, requester.user.to_string()
|
room_id, requester.user.to_string(), allow_departed_users=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# This gets the original event and checks that a) the event exists and
|
# This gets the original event and checks that a) the event exists and
|
||||||
|
@ -235,8 +235,8 @@ class RelationAggregationPaginationServlet(RestServlet):
|
||||||
):
|
):
|
||||||
requester = await self.auth.get_user_by_req(request, allow_guest=True)
|
requester = await self.auth.get_user_by_req(request, allow_guest=True)
|
||||||
|
|
||||||
await self.auth.check_in_room_or_world_readable(
|
await self.auth.check_user_in_room_or_world_readable(
|
||||||
room_id, requester.user.to_string()
|
room_id, requester.user.to_string(), allow_departed_users=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# This checks that a) the event exists and b) the user is allowed to
|
# This checks that a) the event exists and b) the user is allowed to
|
||||||
|
@ -313,8 +313,8 @@ class RelationAggregationGroupPaginationServlet(RestServlet):
|
||||||
async def on_GET(self, request, room_id, parent_id, relation_type, event_type, key):
|
async def on_GET(self, request, room_id, parent_id, relation_type, event_type, key):
|
||||||
requester = await self.auth.get_user_by_req(request, allow_guest=True)
|
requester = await self.auth.get_user_by_req(request, allow_guest=True)
|
||||||
|
|
||||||
await self.auth.check_in_room_or_world_readable(
|
await self.auth.check_user_in_room_or_world_readable(
|
||||||
room_id, requester.user.to_string()
|
room_id, requester.user.to_string(), allow_departed_users=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# This checks that a) the event exists and b) the user is allowed to
|
# This checks that a) the event exists and b) the user is allowed to
|
||||||
|
|
|
@ -72,6 +72,8 @@ class VersionsRestServlet(RestServlet):
|
||||||
"org.matrix.label_based_filtering": True,
|
"org.matrix.label_based_filtering": True,
|
||||||
# Implements support for cross signing as described in MSC1756
|
# Implements support for cross signing as described in MSC1756
|
||||||
"org.matrix.e2e_cross_signing": True,
|
"org.matrix.e2e_cross_signing": True,
|
||||||
|
# Implements additional endpoints as described in MSC2432
|
||||||
|
"org.matrix.msc2432": True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -26,7 +26,6 @@ from synapse.storage._base import SQLBaseStore, make_in_list_sql_clause
|
||||||
from synapse.storage.data_stores.main.events_worker import EventsWorkerStore
|
from synapse.storage.data_stores.main.events_worker import EventsWorkerStore
|
||||||
from synapse.storage.data_stores.main.signatures import SignatureWorkerStore
|
from synapse.storage.data_stores.main.signatures import SignatureWorkerStore
|
||||||
from synapse.storage.database import Database
|
from synapse.storage.database import Database
|
||||||
from synapse.storage.engines import PostgresEngine
|
|
||||||
from synapse.util.caches.descriptors import cached
|
from synapse.util.caches.descriptors import cached
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -67,33 +66,6 @@ class EventFederationWorkerStore(EventsWorkerStore, SignatureWorkerStore, SQLBas
|
||||||
else:
|
else:
|
||||||
results = set()
|
results = set()
|
||||||
|
|
||||||
if isinstance(self.database_engine, PostgresEngine):
|
|
||||||
# For efficiency we make the database do this if we can.
|
|
||||||
|
|
||||||
# We need to be a little careful with querying large amounts at
|
|
||||||
# once, for some reason postgres really doesn't like it. We do this
|
|
||||||
# by only asking for auth chain of 500 events at a time.
|
|
||||||
event_ids = list(event_ids)
|
|
||||||
chunks = [event_ids[x : x + 500] for x in range(0, len(event_ids), 500)]
|
|
||||||
for chunk in chunks:
|
|
||||||
sql = """
|
|
||||||
WITH RECURSIVE auth_chain(event_id) AS (
|
|
||||||
SELECT auth_id FROM event_auth WHERE event_id = ANY(?)
|
|
||||||
UNION
|
|
||||||
SELECT auth_id FROM event_auth
|
|
||||||
INNER JOIN auth_chain USING (event_id)
|
|
||||||
)
|
|
||||||
SELECT event_id FROM auth_chain
|
|
||||||
"""
|
|
||||||
txn.execute(sql, (chunk,))
|
|
||||||
|
|
||||||
results.update(event_id for event_id, in txn)
|
|
||||||
|
|
||||||
return list(results)
|
|
||||||
|
|
||||||
# Database doesn't necessarily support recursive CTE, so we fall
|
|
||||||
# back to do doing it manually.
|
|
||||||
|
|
||||||
base_sql = "SELECT auth_id FROM event_auth WHERE "
|
base_sql = "SELECT auth_id FROM event_auth WHERE "
|
||||||
|
|
||||||
front = set(event_ids)
|
front = set(event_ids)
|
||||||
|
|
|
@ -122,11 +122,11 @@ class TypingNotificationsTestCase(unittest.HomeserverTestCase):
|
||||||
|
|
||||||
self.room_members = []
|
self.room_members = []
|
||||||
|
|
||||||
def check_joined_room(room_id, user_id):
|
def check_user_in_room(room_id, user_id):
|
||||||
if user_id not in [u.to_string() for u in self.room_members]:
|
if user_id not in [u.to_string() for u in self.room_members]:
|
||||||
raise AuthError(401, "User is not in the room")
|
raise AuthError(401, "User is not in the room")
|
||||||
|
|
||||||
hs.get_auth().check_joined_room = check_joined_room
|
hs.get_auth().check_user_in_room = check_user_in_room
|
||||||
|
|
||||||
def get_joined_hosts_for_room(room_id):
|
def get_joined_hosts_for_room(room_id):
|
||||||
return set(member.domain for member in self.room_members)
|
return set(member.domain for member in self.room_members)
|
||||||
|
|
|
@ -1729,8 +1729,7 @@ class ContextTestCase(unittest.HomeserverTestCase):
|
||||||
self.assertEqual(events_after[1].get("content"), {}, events_after[1])
|
self.assertEqual(events_after[1].get("content"), {}, events_after[1])
|
||||||
|
|
||||||
|
|
||||||
class DirectoryTestCase(unittest.HomeserverTestCase):
|
class RoomAliasListTestCase(unittest.HomeserverTestCase):
|
||||||
|
|
||||||
servlets = [
|
servlets = [
|
||||||
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
||||||
directory.register_servlets,
|
directory.register_servlets,
|
||||||
|
@ -1756,6 +1755,16 @@ class DirectoryTestCase(unittest.HomeserverTestCase):
|
||||||
res = self._get_aliases(user_tok, expected_code=403)
|
res = self._get_aliases(user_tok, expected_code=403)
|
||||||
self.assertEqual(res["errcode"], "M_FORBIDDEN")
|
self.assertEqual(res["errcode"], "M_FORBIDDEN")
|
||||||
|
|
||||||
|
def test_admin_user(self):
|
||||||
|
alias1 = self._random_alias()
|
||||||
|
self._set_alias_via_directory(alias1)
|
||||||
|
|
||||||
|
self.register_user("user", "test", admin=True)
|
||||||
|
user_tok = self.login("user", "test")
|
||||||
|
|
||||||
|
res = self._get_aliases(user_tok)
|
||||||
|
self.assertEqual(res["aliases"], [alias1])
|
||||||
|
|
||||||
def test_with_aliases(self):
|
def test_with_aliases(self):
|
||||||
alias1 = self._random_alias()
|
alias1 = self._random_alias()
|
||||||
alias2 = self._random_alias()
|
alias2 = self._random_alias()
|
||||||
|
@ -1766,11 +1775,29 @@ class DirectoryTestCase(unittest.HomeserverTestCase):
|
||||||
res = self._get_aliases(self.room_owner_tok)
|
res = self._get_aliases(self.room_owner_tok)
|
||||||
self.assertEqual(set(res["aliases"]), {alias1, alias2})
|
self.assertEqual(set(res["aliases"]), {alias1, alias2})
|
||||||
|
|
||||||
|
def test_peekable_room(self):
|
||||||
|
alias1 = self._random_alias()
|
||||||
|
self._set_alias_via_directory(alias1)
|
||||||
|
|
||||||
|
self.helper.send_state(
|
||||||
|
self.room_id,
|
||||||
|
EventTypes.RoomHistoryVisibility,
|
||||||
|
body={"history_visibility": "world_readable"},
|
||||||
|
tok=self.room_owner_tok,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.register_user("user", "test")
|
||||||
|
user_tok = self.login("user", "test")
|
||||||
|
|
||||||
|
res = self._get_aliases(user_tok)
|
||||||
|
self.assertEqual(res["aliases"], [alias1])
|
||||||
|
|
||||||
def _get_aliases(self, access_token: str, expected_code: int = 200) -> JsonDict:
|
def _get_aliases(self, access_token: str, expected_code: int = 200) -> JsonDict:
|
||||||
"""Calls the endpoint under test. returns the json response object."""
|
"""Calls the endpoint under test. returns the json response object."""
|
||||||
request, channel = self.make_request(
|
request, channel = self.make_request(
|
||||||
"GET",
|
"GET",
|
||||||
"/_matrix/client/r0/rooms/%s/aliases" % (self.room_id,),
|
"/_matrix/client/unstable/org.matrix.msc2432/rooms/%s/aliases"
|
||||||
|
% (self.room_id,),
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
)
|
)
|
||||||
self.render(request)
|
self.render(request)
|
||||||
|
|
Loading…
Reference in New Issue