mirror of
https://mau.dev/maunium/synapse.git
synced 2024-11-04 21:58:54 +01:00
Merge branch 'develop' into key_distribution
This commit is contained in:
commit
eede182df7
12 changed files with 310 additions and 68 deletions
34
UPGRADE.rst
34
UPGRADE.rst
|
@ -1,3 +1,37 @@
|
||||||
|
Upgrading to v0.x.x
|
||||||
|
===================
|
||||||
|
|
||||||
|
Application services have had a breaking API change in this version.
|
||||||
|
|
||||||
|
They can no longer register themselves with a home server using the AS HTTP API. This
|
||||||
|
decision was made because a compromised application service with free reign to register
|
||||||
|
any regex in effect grants full read/write access to the home server if a regex of ``.*``
|
||||||
|
is used. An attack where a compromised AS re-registers itself with ``.*`` was deemed too
|
||||||
|
big of a security risk to ignore, and so the ability to register with the HS remotely has
|
||||||
|
been removed.
|
||||||
|
|
||||||
|
It has been replaced by specifying a list of application service registrations in
|
||||||
|
``homeserver.yaml``::
|
||||||
|
|
||||||
|
app_service_config_files: ["registration-01.yaml", "registration-02.yaml"]
|
||||||
|
|
||||||
|
Where ``registration-01.yaml`` looks like::
|
||||||
|
|
||||||
|
url: <String> # e.g. "https://my.application.service.com"
|
||||||
|
as_token: <String>
|
||||||
|
hs_token: <String>
|
||||||
|
sender_localpart: <String> # This is a new field which denotes the user_id localpart when using the AS token
|
||||||
|
namespaces:
|
||||||
|
users:
|
||||||
|
- exclusive: <Boolean>
|
||||||
|
regex: <String> # e.g. "@prefix_.*"
|
||||||
|
aliases:
|
||||||
|
- exclusive: <Boolean>
|
||||||
|
regex: <String>
|
||||||
|
rooms:
|
||||||
|
- exclusive: <Boolean>
|
||||||
|
regex: <String>
|
||||||
|
|
||||||
Upgrading to v0.8.0
|
Upgrading to v0.8.0
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
|
50
docs/metrics-howto.rst
Normal file
50
docs/metrics-howto.rst
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
How to monitor Synapse metrics using Prometheus
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
1: Install prometheus:
|
||||||
|
Follow instructions at http://prometheus.io/docs/introduction/install/
|
||||||
|
|
||||||
|
2: Enable synapse metrics:
|
||||||
|
Simply setting a (local) port number will enable it. Pick a port.
|
||||||
|
prometheus itself defaults to 9090, so starting just above that for
|
||||||
|
locally monitored services seems reasonable. E.g. 9092:
|
||||||
|
|
||||||
|
Add to homeserver.yaml
|
||||||
|
|
||||||
|
metrics_port: 9092
|
||||||
|
|
||||||
|
Restart synapse
|
||||||
|
|
||||||
|
3: Check out synapse-prometheus-config
|
||||||
|
https://github.com/matrix-org/synapse-prometheus-config
|
||||||
|
|
||||||
|
4: Add ``synapse.html`` and ``synapse.rules``
|
||||||
|
The ``.html`` file needs to appear in prometheus's ``consoles`` directory,
|
||||||
|
and the ``.rules`` file needs to be invoked somewhere in the main config
|
||||||
|
file. A symlink to each from the git checkout into the prometheus directory
|
||||||
|
might be easiest to ensure ``git pull`` keeps it updated.
|
||||||
|
|
||||||
|
5: Add a prometheus target for synapse
|
||||||
|
This is easiest if prometheus runs on the same machine as synapse, as it can
|
||||||
|
then just use localhost::
|
||||||
|
|
||||||
|
global: {
|
||||||
|
rule_file: "synapse.rules"
|
||||||
|
}
|
||||||
|
|
||||||
|
job: {
|
||||||
|
name: "synapse"
|
||||||
|
|
||||||
|
target_group: {
|
||||||
|
target: "http://localhost:9092/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
6: Start prometheus::
|
||||||
|
|
||||||
|
./prometheus -config.file=prometheus.conf
|
||||||
|
|
||||||
|
7: Wait a few seconds for it to start and perform the first scrape,
|
||||||
|
then visit the console:
|
||||||
|
|
||||||
|
http://server-where-prometheus-runs:9090/consoles/synapse.html
|
|
@ -183,18 +183,10 @@ class Auth(object):
|
||||||
else:
|
else:
|
||||||
join_rule = JoinRules.INVITE
|
join_rule = JoinRules.INVITE
|
||||||
|
|
||||||
user_level = self._get_power_level_from_event_state(
|
user_level = self._get_user_power_level(event.user_id, auth_events)
|
||||||
event,
|
|
||||||
event.user_id,
|
|
||||||
auth_events,
|
|
||||||
)
|
|
||||||
|
|
||||||
ban_level, kick_level, redact_level = (
|
# FIXME (erikj): What should we do here as the default?
|
||||||
self._get_ops_level_from_event_state(
|
ban_level = self._get_named_level(auth_events, "ban", 50)
|
||||||
event,
|
|
||||||
auth_events,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"is_membership_change_allowed: %s",
|
"is_membership_change_allowed: %s",
|
||||||
|
@ -210,11 +202,6 @@ class Auth(object):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if ban_level:
|
|
||||||
ban_level = int(ban_level)
|
|
||||||
else:
|
|
||||||
ban_level = 50 # FIXME (erikj): What should we do here?
|
|
||||||
|
|
||||||
if Membership.JOIN != membership:
|
if Membership.JOIN != membership:
|
||||||
# JOIN is the only action you can perform if you're not in the room
|
# JOIN is the only action you can perform if you're not in the room
|
||||||
if not caller_in_room: # caller isn't joined
|
if not caller_in_room: # caller isn't joined
|
||||||
|
@ -259,10 +246,7 @@ class Auth(object):
|
||||||
403, "You cannot unban user &s." % (target_user_id,)
|
403, "You cannot unban user &s." % (target_user_id,)
|
||||||
)
|
)
|
||||||
elif target_user_id != event.user_id:
|
elif target_user_id != event.user_id:
|
||||||
if kick_level:
|
kick_level = self._get_named_level(auth_events, "kick", 50)
|
||||||
kick_level = int(kick_level)
|
|
||||||
else:
|
|
||||||
kick_level = 50 # FIXME (erikj): What should we do here?
|
|
||||||
|
|
||||||
if user_level < kick_level:
|
if user_level < kick_level:
|
||||||
raise AuthError(
|
raise AuthError(
|
||||||
|
@ -276,34 +260,42 @@ class Auth(object):
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _get_power_level_from_event_state(self, event, user_id, auth_events):
|
def _get_power_level_event(self, auth_events):
|
||||||
key = (EventTypes.PowerLevels, "", )
|
key = (EventTypes.PowerLevels, "", )
|
||||||
power_level_event = auth_events.get(key)
|
return auth_events.get(key)
|
||||||
level = None
|
|
||||||
|
def _get_user_power_level(self, user_id, auth_events):
|
||||||
|
power_level_event = self._get_power_level_event(auth_events)
|
||||||
|
|
||||||
if power_level_event:
|
if power_level_event:
|
||||||
level = power_level_event.content.get("users", {}).get(user_id)
|
level = power_level_event.content.get("users", {}).get(user_id)
|
||||||
if not level:
|
if not level:
|
||||||
level = power_level_event.content.get("users_default", 0)
|
level = power_level_event.content.get("users_default", 0)
|
||||||
|
|
||||||
|
if level is None:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return int(level)
|
||||||
else:
|
else:
|
||||||
key = (EventTypes.Create, "", )
|
key = (EventTypes.Create, "", )
|
||||||
create_event = auth_events.get(key)
|
create_event = auth_events.get(key)
|
||||||
if (create_event is not None and
|
if (create_event is not None and
|
||||||
create_event.content["creator"] == user_id):
|
create_event.content["creator"] == user_id):
|
||||||
return 100
|
return 100
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
return level
|
def _get_named_level(self, auth_events, name, default):
|
||||||
|
power_level_event = self._get_power_level_event(auth_events)
|
||||||
|
|
||||||
def _get_ops_level_from_event_state(self, event, auth_events):
|
if not power_level_event:
|
||||||
key = (EventTypes.PowerLevels, "", )
|
return default
|
||||||
power_level_event = auth_events.get(key)
|
|
||||||
|
|
||||||
if power_level_event:
|
level = power_level_event.content.get(name, None)
|
||||||
return (
|
if level is not None:
|
||||||
power_level_event.content.get("ban", 50),
|
return int(level)
|
||||||
power_level_event.content.get("kick", 50),
|
else:
|
||||||
power_level_event.content.get("redact", 50),
|
return default
|
||||||
)
|
|
||||||
return None, None, None,
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_user_by_req(self, request):
|
def get_user_by_req(self, request):
|
||||||
|
@ -498,16 +490,7 @@ class Auth(object):
|
||||||
else:
|
else:
|
||||||
send_level = 0
|
send_level = 0
|
||||||
|
|
||||||
user_level = self._get_power_level_from_event_state(
|
user_level = self._get_user_power_level(event.user_id, auth_events)
|
||||||
event,
|
|
||||||
event.user_id,
|
|
||||||
auth_events,
|
|
||||||
)
|
|
||||||
|
|
||||||
if user_level:
|
|
||||||
user_level = int(user_level)
|
|
||||||
else:
|
|
||||||
user_level = 0
|
|
||||||
|
|
||||||
if user_level < send_level:
|
if user_level < send_level:
|
||||||
raise AuthError(
|
raise AuthError(
|
||||||
|
@ -539,16 +522,9 @@ class Auth(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _check_redaction(self, event, auth_events):
|
def _check_redaction(self, event, auth_events):
|
||||||
user_level = self._get_power_level_from_event_state(
|
user_level = self._get_user_power_level(event.user_id, auth_events)
|
||||||
event,
|
|
||||||
event.user_id,
|
|
||||||
auth_events,
|
|
||||||
)
|
|
||||||
|
|
||||||
_, _, redact_level = self._get_ops_level_from_event_state(
|
redact_level = self._get_named_level(auth_events, "redact", 50)
|
||||||
event,
|
|
||||||
auth_events,
|
|
||||||
)
|
|
||||||
|
|
||||||
if user_level < redact_level:
|
if user_level < redact_level:
|
||||||
raise AuthError(
|
raise AuthError(
|
||||||
|
@ -576,11 +552,7 @@ class Auth(object):
|
||||||
if not current_state:
|
if not current_state:
|
||||||
return
|
return
|
||||||
|
|
||||||
user_level = self._get_power_level_from_event_state(
|
user_level = self._get_user_power_level(event.user_id, auth_events)
|
||||||
event,
|
|
||||||
event.user_id,
|
|
||||||
auth_events,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check other levels:
|
# Check other levels:
|
||||||
levels_to_check = [
|
levels_to_check = [
|
||||||
|
|
|
@ -35,8 +35,8 @@ class Codes(object):
|
||||||
LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
|
||||||
CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
|
CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
|
||||||
CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
|
CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
|
||||||
MISSING_PARAM = "M_MISSING_PARAM",
|
MISSING_PARAM = "M_MISSING_PARAM"
|
||||||
TOO_LARGE = "M_TOO_LARGE",
|
TOO_LARGE = "M_TOO_LARGE"
|
||||||
EXCLUSIVE = "M_EXCLUSIVE"
|
EXCLUSIVE = "M_EXCLUSIVE"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,9 @@ metrics = synapse.metrics.get_metrics_for(__name__)
|
||||||
# Don't bother bumping "last active" time if it differs by less than 60 seconds
|
# Don't bother bumping "last active" time if it differs by less than 60 seconds
|
||||||
LAST_ACTIVE_GRANULARITY = 60*1000
|
LAST_ACTIVE_GRANULARITY = 60*1000
|
||||||
|
|
||||||
|
# Keep no more than this number of offline serial revisions
|
||||||
|
MAX_OFFLINE_SERIALS = 1000
|
||||||
|
|
||||||
|
|
||||||
# TODO(paul): Maybe there's one of these I can steal from somewhere
|
# TODO(paul): Maybe there's one of these I can steal from somewhere
|
||||||
def partition(l, func):
|
def partition(l, func):
|
||||||
|
@ -135,6 +138,9 @@ class PresenceHandler(BaseHandler):
|
||||||
self._remote_sendmap = {}
|
self._remote_sendmap = {}
|
||||||
# map remote users to sets of local users who're interested in them
|
# map remote users to sets of local users who're interested in them
|
||||||
self._remote_recvmap = {}
|
self._remote_recvmap = {}
|
||||||
|
# list of (serial, set of(userids)) tuples, ordered by serial, latest
|
||||||
|
# first
|
||||||
|
self._remote_offline_serials = []
|
||||||
|
|
||||||
# map any user to a UserPresenceCache
|
# map any user to a UserPresenceCache
|
||||||
self._user_cachemap = {}
|
self._user_cachemap = {}
|
||||||
|
@ -714,8 +720,24 @@ class PresenceHandler(BaseHandler):
|
||||||
statuscache=statuscache,
|
statuscache=statuscache,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
user_id = user.to_string()
|
||||||
|
|
||||||
if state["presence"] == PresenceState.OFFLINE:
|
if state["presence"] == PresenceState.OFFLINE:
|
||||||
|
self._remote_offline_serials.insert(
|
||||||
|
0,
|
||||||
|
(self._user_cachemap_latest_serial, set([user_id]))
|
||||||
|
)
|
||||||
|
while len(self._remote_offline_serials) > MAX_OFFLINE_SERIALS:
|
||||||
|
self._remote_offline_serials.pop() # remove the oldest
|
||||||
del self._user_cachemap[user]
|
del self._user_cachemap[user]
|
||||||
|
else:
|
||||||
|
# Remove the user from remote_offline_serials now that they're
|
||||||
|
# no longer offline
|
||||||
|
for idx, elem in enumerate(self._remote_offline_serials):
|
||||||
|
(_, user_ids) = elem
|
||||||
|
user_ids.discard(user_id)
|
||||||
|
if not user_ids:
|
||||||
|
self._remote_offline_serials.pop(idx)
|
||||||
|
|
||||||
for poll in content.get("poll", []):
|
for poll in content.get("poll", []):
|
||||||
user = UserID.from_string(poll)
|
user = UserID.from_string(poll)
|
||||||
|
@ -836,6 +858,8 @@ class PresenceEventSource(object):
|
||||||
|
|
||||||
presence = self.hs.get_handlers().presence_handler
|
presence = self.hs.get_handlers().presence_handler
|
||||||
cachemap = presence._user_cachemap
|
cachemap = presence._user_cachemap
|
||||||
|
clock = self.clock
|
||||||
|
latest_serial = None
|
||||||
|
|
||||||
updates = []
|
updates = []
|
||||||
# TODO(paul): use a DeferredList ? How to limit concurrency.
|
# TODO(paul): use a DeferredList ? How to limit concurrency.
|
||||||
|
@ -845,18 +869,31 @@ class PresenceEventSource(object):
|
||||||
if cached.serial <= from_key:
|
if cached.serial <= from_key:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (yield self.is_visible(observer_user, observed_user)):
|
if not (yield self.is_visible(observer_user, observed_user)):
|
||||||
updates.append((observed_user, cached))
|
continue
|
||||||
|
|
||||||
|
if latest_serial is None or cached.serial > latest_serial:
|
||||||
|
latest_serial = cached.serial
|
||||||
|
updates.append(cached.make_event(user=observed_user, clock=clock))
|
||||||
|
|
||||||
# TODO(paul): limit
|
# TODO(paul): limit
|
||||||
|
|
||||||
|
for serial, user_ids in presence._remote_offline_serials:
|
||||||
|
if serial < from_key:
|
||||||
|
break
|
||||||
|
|
||||||
|
for u in user_ids:
|
||||||
|
updates.append({
|
||||||
|
"type": "m.presence",
|
||||||
|
"content": {"user_id": u, "presence": PresenceState.OFFLINE},
|
||||||
|
})
|
||||||
|
# TODO(paul): For the v2 API we want to tell the client their from_key
|
||||||
|
# is too old if we fell off the end of the _remote_offline_serials
|
||||||
|
# list, and get them to invalidate+resync. In v1 we have no such
|
||||||
|
# concept so this is a best-effort result.
|
||||||
|
|
||||||
if updates:
|
if updates:
|
||||||
clock = self.clock
|
defer.returnValue((updates, latest_serial))
|
||||||
|
|
||||||
latest_serial = max([x[1].serial for x in updates])
|
|
||||||
data = [x[1].make_event(user=x[0], clock=clock) for x in updates]
|
|
||||||
|
|
||||||
defer.returnValue((data, latest_serial))
|
|
||||||
else:
|
else:
|
||||||
defer.returnValue(([], presence._user_cachemap_latest_serial))
|
defer.returnValue(([], presence._user_cachemap_latest_serial))
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
# Copyright 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
from synapse.push.rulekinds import PRIORITY_CLASS_MAP, PRIORITY_CLASS_INVERSE_MAP
|
from synapse.push.rulekinds import PRIORITY_CLASS_MAP, PRIORITY_CLASS_INVERSE_MAP
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
# Copyright 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
PRIORITY_CLASS_MAP = {
|
PRIORITY_CLASS_MAP = {
|
||||||
'underride': 1,
|
'underride': 1,
|
||||||
'sender': 2,
|
'sender': 2,
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
# Copyright 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from distutils.version import LooseVersion
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
# Copyright 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
from pydenticon import Generator
|
from pydenticon import Generator
|
||||||
from twisted.web.resource import Resource
|
from twisted.web.resource import Resource
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
# Copyright 2015 OpenMarket Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
/* Copyright 2015 OpenMarket Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
CREATE TABLE IF NOT EXISTS push_rules_enable (
|
CREATE TABLE IF NOT EXISTS push_rules_enable (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
user_name TEXT NOT NULL,
|
user_name TEXT NOT NULL,
|
||||||
|
|
|
@ -878,6 +878,71 @@ class PresencePushTestCase(MockedDatastorePresenceTestCase):
|
||||||
state
|
state
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_recv_remote_offline(self):
|
||||||
|
""" Various tests relating to SYN-261 """
|
||||||
|
potato_set = self.handler._remote_recvmap.setdefault(self.u_potato,
|
||||||
|
set())
|
||||||
|
potato_set.add(self.u_apple)
|
||||||
|
|
||||||
|
self.room_members = [self.u_banana, self.u_potato]
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 0)
|
||||||
|
|
||||||
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
|
"/_matrix/federation/v1/send/1000000/",
|
||||||
|
_make_edu_json("elsewhere", "m.presence",
|
||||||
|
content={
|
||||||
|
"push": [
|
||||||
|
{"user_id": "@potato:remote",
|
||||||
|
"presence": "offline"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 1)
|
||||||
|
|
||||||
|
(events, _) = yield self.event_source.get_new_events_for_user(
|
||||||
|
self.u_apple, 0, None
|
||||||
|
)
|
||||||
|
self.assertEquals(events,
|
||||||
|
[
|
||||||
|
{"type": "m.presence",
|
||||||
|
"content": {
|
||||||
|
"user_id": "@potato:remote",
|
||||||
|
"presence": OFFLINE,
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
|
"/_matrix/federation/v1/send/1000001/",
|
||||||
|
_make_edu_json("elsewhere", "m.presence",
|
||||||
|
content={
|
||||||
|
"push": [
|
||||||
|
{"user_id": "@potato:remote",
|
||||||
|
"presence": "online"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEquals(self.event_source.get_current_key(), 2)
|
||||||
|
|
||||||
|
(events, _) = yield self.event_source.get_new_events_for_user(
|
||||||
|
self.u_apple, 0, None
|
||||||
|
)
|
||||||
|
self.assertEquals(events,
|
||||||
|
[
|
||||||
|
{"type": "m.presence",
|
||||||
|
"content": {
|
||||||
|
"user_id": "@potato:remote",
|
||||||
|
"presence": ONLINE,
|
||||||
|
}}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_join_room_local(self):
|
def test_join_room_local(self):
|
||||||
self.room_members = [self.u_apple, self.u_banana]
|
self.room_members = [self.u_apple, self.u_banana]
|
||||||
|
|
Loading…
Reference in a new issue