mirror of https://github.com/watcha-fr/synapse
commit
0b0b24cb82
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1 +0,0 @@ |
||||
Enforce the specified API for report_event |
@ -1 +0,0 @@ |
||||
Include CPU time from database threads in request/block metrics. |
@ -1 +0,0 @@ |
||||
Add CPU metrics for _fetch_event_list |
@ -1 +0,0 @@ |
||||
Reduce database consumption when processing large numbers of receipts |
@ -0,0 +1 @@ |
||||
Correctly announce deleted devices over federation |
@ -1 +0,0 @@ |
||||
Cache optimisation for /sync requests |
@ -1 +0,0 @@ |
||||
Fix queued federation requests being processed in the wrong order |
@ -1 +0,0 @@ |
||||
refactor: use parse_{string,integer} and assert's from http.servlet for deduplication |
@ -1 +0,0 @@ |
||||
check isort for each PR |
@ -1 +0,0 @@ |
||||
Optimisation to make handling incoming federation requests more efficient. |
@ -1 +0,0 @@ |
||||
Ensure that erasure requests are correctly honoured for publicly accessible rooms when accessed over federation. |
@ -0,0 +1 @@ |
||||
Catch failures saving metrics captured by Measure, and instead log the faulty metrics information for further analysis. |
@ -0,0 +1 @@ |
||||
Release notes are now in the Markdown format. |
@ -0,0 +1 @@ |
||||
Add metrics to track resource usage by background processes |
@ -0,0 +1 @@ |
||||
Add `code` label to `synapse_http_server_response_time_seconds` prometheus metric |
@ -0,0 +1 @@ |
||||
Add metrics to track resource usage by background processes |
@ -0,0 +1 @@ |
||||
add config for pep8 |
@ -0,0 +1 @@ |
||||
Fix potential stack overflow and deadlock under heavy load |
@ -0,0 +1 @@ |
||||
Merge Linearizer and Limiter |
@ -0,0 +1 @@ |
||||
Merge Linearizer and Limiter |
@ -0,0 +1,63 @@ |
||||
Shared-Secret Registration |
||||
========================== |
||||
|
||||
This API allows for the creation of users in an administrative and |
||||
non-interactive way. This is generally used for bootstrapping a Synapse |
||||
instance with administrator accounts. |
||||
|
||||
To authenticate yourself to the server, you will need both the shared secret |
||||
(``registration_shared_secret`` in the homeserver configuration), and a |
||||
one-time nonce. If the registration shared secret is not configured, this API |
||||
is not enabled. |
||||
|
||||
To fetch the nonce, you need to request one from the API:: |
||||
|
||||
> GET /_matrix/client/r0/admin/register |
||||
|
||||
< {"nonce": "thisisanonce"} |
||||
|
||||
Once you have the nonce, you can make a ``POST`` to the same URL with a JSON |
||||
body containing the nonce, username, password, whether they are an admin |
||||
(optional, False by default), and a HMAC digest of the content. |
||||
|
||||
As an example:: |
||||
|
||||
> POST /_matrix/client/r0/admin/register |
||||
> { |
||||
"nonce": "thisisanonce", |
||||
"username": "pepper_roni", |
||||
"password": "pizza", |
||||
"admin": true, |
||||
"mac": "mac_digest_here" |
||||
} |
||||
|
||||
< { |
||||
"access_token": "token_here", |
||||
"user_id": "@pepper_roni@test", |
||||
"home_server": "test", |
||||
"device_id": "device_id_here" |
||||
} |
||||
|
||||
The MAC is the hex digest output of the HMAC-SHA1 algorithm, with the key being |
||||
the shared secret and the content being the nonce, user, password, and either |
||||
the string "admin" or "notadmin", each separated by NULs. For an example of |
||||
generation in Python:: |
||||
|
||||
import hmac, hashlib |
||||
|
||||
def generate_mac(nonce, user, password, admin=False): |
||||
|
||||
mac = hmac.new( |
||||
key=shared_secret, |
||||
digestmod=hashlib.sha1, |
||||
) |
||||
|
||||
mac.update(nonce.encode('utf8')) |
||||
mac.update(b"\x00") |
||||
mac.update(user.encode('utf8')) |
||||
mac.update(b"\x00") |
||||
mac.update(password.encode('utf8')) |
||||
mac.update(b"\x00") |
||||
mac.update(b"admin" if admin else b"notadmin") |
||||
|
||||
return mac.hexdigest() |
@ -1,5 +1,30 @@ |
||||
[tool.towncrier] |
||||
package = "synapse" |
||||
filename = "CHANGES.rst" |
||||
filename = "CHANGES.md" |
||||
directory = "changelog.d" |
||||
issue_format = "`#{issue} <https://github.com/matrix-org/synapse/issues/{issue}>`_" |
||||
issue_format = "[\\#{issue}](https://github.com/matrix-org/synapse/issues/{issue}>)" |
||||
|
||||
[[tool.towncrier.type]] |
||||
directory = "feature" |
||||
name = "Features" |
||||
showcontent = true |
||||
|
||||
[[tool.towncrier.type]] |
||||
directory = "bugfix" |
||||
name = "Bugfixes" |
||||
showcontent = true |
||||
|
||||
[[tool.towncrier.type]] |
||||
directory = "doc" |
||||
name = "Improved Documentation" |
||||
showcontent = true |
||||
|
||||
[[tool.towncrier.type]] |
||||
directory = "removal" |
||||
name = "Deprecations and Removals" |
||||
showcontent = true |
||||
|
||||
[[tool.towncrier.type]] |
||||
directory = "misc" |
||||
name = "Internal Changes" |
||||
showcontent = true |
||||
|
@ -0,0 +1,179 @@ |
||||
# -*- coding: utf-8 -*- |
||||
# Copyright 2018 New Vector 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 six |
||||
|
||||
from prometheus_client.core import REGISTRY, Counter, GaugeMetricFamily |
||||
|
||||
from twisted.internet import defer |
||||
|
||||
from synapse.util.logcontext import LoggingContext, PreserveLoggingContext |
||||
|
||||
_background_process_start_count = Counter( |
||||
"synapse_background_process_start_count", |
||||
"Number of background processes started", |
||||
["name"], |
||||
) |
||||
|
||||
# we set registry=None in all of these to stop them getting registered with |
||||
# the default registry. Instead we collect them all via the CustomCollector, |
||||
# which ensures that we can update them before they are collected. |
||||
# |
||||
_background_process_ru_utime = Counter( |
||||
"synapse_background_process_ru_utime_seconds", |
||||
"User CPU time used by background processes, in seconds", |
||||
["name"], |
||||
registry=None, |
||||
) |
||||
|
||||
_background_process_ru_stime = Counter( |
||||
"synapse_background_process_ru_stime_seconds", |
||||
"System CPU time used by background processes, in seconds", |
||||
["name"], |
||||
registry=None, |
||||
) |
||||
|
||||
_background_process_db_txn_count = Counter( |
||||
"synapse_background_process_db_txn_count", |
||||
"Number of database transactions done by background processes", |
||||
["name"], |
||||
registry=None, |
||||
) |
||||
|
||||
_background_process_db_txn_duration = Counter( |
||||
"synapse_background_process_db_txn_duration_seconds", |
||||
("Seconds spent by background processes waiting for database " |
||||
"transactions, excluding scheduling time"), |
||||
["name"], |
||||
registry=None, |
||||
) |
||||
|
||||
_background_process_db_sched_duration = Counter( |
||||
"synapse_background_process_db_sched_duration_seconds", |
||||
"Seconds spent by background processes waiting for database connections", |
||||
["name"], |
||||
registry=None, |
||||
) |
||||
|
||||
# map from description to a counter, so that we can name our logcontexts |
||||
# incrementally. (It actually duplicates _background_process_start_count, but |
||||
# it's much simpler to do so than to try to combine them.) |
||||
_background_process_counts = dict() # type: dict[str, int] |
||||
|
||||
# map from description to the currently running background processes. |
||||
# |
||||
# it's kept as a dict of sets rather than a big set so that we can keep track |
||||
# of process descriptions that no longer have any active processes. |
||||
_background_processes = dict() # type: dict[str, set[_BackgroundProcess]] |
||||
|
||||
|
||||
class _Collector(object): |
||||
"""A custom metrics collector for the background process metrics. |
||||
|
||||
Ensures that all of the metrics are up-to-date with any in-flight processes |
||||
before they are returned. |
||||
""" |
||||
def collect(self): |
||||
background_process_in_flight_count = GaugeMetricFamily( |
||||
"synapse_background_process_in_flight_count", |
||||
"Number of background processes in flight", |
||||
labels=["name"], |
||||
) |
||||
|
||||
for desc, processes in six.iteritems(_background_processes): |
||||
background_process_in_flight_count.add_metric( |
||||
(desc,), len(processes), |
||||
) |
||||
for process in processes: |
||||
process.update_metrics() |
||||
|
||||
yield background_process_in_flight_count |
||||
|
||||
# now we need to run collect() over each of the static Counters, and |
||||
# yield each metric they return. |
||||
for m in ( |
||||
_background_process_ru_utime, |
||||
_background_process_ru_stime, |
||||
_background_process_db_txn_count, |
||||
_background_process_db_txn_duration, |
||||
_background_process_db_sched_duration, |
||||
): |
||||
for r in m.collect(): |
||||
yield r |
||||
|
||||
|
||||
REGISTRY.register(_Collector()) |
||||
|
||||
|
||||
class _BackgroundProcess(object): |
||||
def __init__(self, desc, ctx): |
||||
self.desc = desc |
||||
self._context = ctx |
||||
self._reported_stats = None |
||||
|
||||
def update_metrics(self): |
||||
"""Updates the metrics with values from this process.""" |
||||
new_stats = self._context.get_resource_usage() |
||||
if self._reported_stats is None: |
||||
diff = new_stats |
||||
else: |
||||
diff = new_stats - self._reported_stats |
||||
self._reported_stats = new_stats |
||||
|
||||
_background_process_ru_utime.labels(self.desc).inc(diff.ru_utime) |
||||
_background_process_ru_stime.labels(self.desc).inc(diff.ru_stime) |
||||
_background_process_db_txn_count.labels(self.desc).inc( |
||||
diff.db_txn_count, |
||||
) |
||||
_background_process_db_txn_duration.labels(self.desc).inc( |
||||
diff.db_txn_duration_sec, |
||||
) |
||||
_background_process_db_sched_duration.labels(self.desc).inc( |
||||
diff.db_sched_duration_sec, |
||||
) |
||||
|
||||
|
||||
def run_as_background_process(desc, func, *args, **kwargs): |
||||
"""Run the given function in its own logcontext, with resource metrics |
||||
|
||||
This should be used to wrap processes which are fired off to run in the |
||||
background, instead of being associated with a particular request. |
||||
|
||||
Args: |
||||
desc (str): a description for this background process type |
||||
func: a function, which may return a Deferred |
||||
args: positional args for func |
||||
kwargs: keyword args for func |
||||
|
||||
Returns: None |
||||
""" |
||||
@defer.inlineCallbacks |
||||
def run(): |
||||
count = _background_process_counts.get(desc, 0) |
||||
_background_process_counts[desc] = count + 1 |
||||
_background_process_start_count.labels(desc).inc() |
||||
|
||||
with LoggingContext(desc) as context: |
||||
context.request = "%s-%i" % (desc, count) |
||||
proc = _BackgroundProcess(desc, context) |
||||
_background_processes.setdefault(desc, set()).add(proc) |
||||
try: |
||||
yield func(*args, **kwargs) |
||||
finally: |
||||
proc.update_metrics() |
||||
_background_processes[desc].remove(proc) |
||||
|
||||
with PreserveLoggingContext(): |
||||
run() |
@ -0,0 +1,3 @@ |
||||
""" |
||||
REST APIs that are only used in v1 (the legacy API). |
||||
""" |
@ -0,0 +1,39 @@ |
||||
# -*- coding: utf-8 -*- |
||||
# Copyright 2014-2016 OpenMarket Ltd |
||||
# Copyright 2018 New Vector 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. |
||||
|
||||
"""This module contains base REST classes for constructing client v1 servlets. |
||||
""" |
||||
|
||||
import re |
||||
|
||||
from synapse.api.urls import CLIENT_PREFIX |
||||
|
||||
|
||||
def v1_only_client_path_patterns(path_regex, include_in_unstable=True): |
||||
"""Creates a regex compiled client path with the correct client path |
||||
prefix. |
||||
|
||||
Args: |
||||
path_regex (str): The regex string to match. This should NOT have a ^ |
||||
as this will be prefixed. |
||||
Returns: |
||||
list of SRE_Pattern |
||||
""" |
||||
patterns = [re.compile("^" + CLIENT_PREFIX + path_regex)] |
||||
if include_in_unstable: |
||||
unstable_prefix = CLIENT_PREFIX.replace("/api/v1", "/unstable") |
||||
patterns.append(re.compile("^" + unstable_prefix + path_regex)) |
||||
return patterns |
@ -0,0 +1,42 @@ |
||||
# -*- coding: utf-8 -*- |
||||
# Copyright 2018 New Vector 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. |
||||
|
||||
""" |
||||
Injectable secrets module for Synapse. |
||||
|
||||
See https://docs.python.org/3/library/secrets.html#module-secrets for the API |
||||
used in Python 3.6, and the API emulated in Python 2.7. |
||||
""" |
||||
|
||||
import six |
||||
|
||||
if six.PY3: |
||||
import secrets |
||||
|
||||
def Secrets(): |
||||
return secrets |
||||
|
||||
|
||||
else: |
||||
|
||||
import os |
||||
import binascii |
||||
|
||||
class Secrets(object): |
||||
def token_bytes(self, nbytes=32): |
||||
return os.urandom(nbytes) |
||||
|
||||
def token_hex(self, nbytes=32): |
||||
return binascii.hexlify(self.token_bytes(nbytes)) |
@ -0,0 +1,305 @@ |
||||
# -*- coding: utf-8 -*- |
||||
# Copyright 2018 New Vector 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 hashlib |
||||
import hmac |
||||
import json |
||||
|
||||
from mock import Mock |
||||
|
||||
from synapse.http.server import JsonResource |
||||
from synapse.rest.client.v1.admin import register_servlets |
||||
from synapse.util import Clock |
||||
|
||||
from tests import unittest |
||||
from tests.server import ( |
||||
ThreadedMemoryReactorClock, |
||||
make_request, |
||||
render, |
||||
setup_test_homeserver, |
||||
) |
||||
|
||||
|
||||
class UserRegisterTestCase(unittest.TestCase): |
||||
def setUp(self): |
||||
|
||||
self.clock = ThreadedMemoryReactorClock() |
||||
self.hs_clock = Clock(self.clock) |
||||
self.url = "/_matrix/client/r0/admin/register" |
||||
|
||||
self.registration_handler = Mock() |
||||
self.identity_handler = Mock() |
||||
self.login_handler = Mock() |
||||
self.device_handler = Mock() |
||||
self.device_handler.check_device_registered = Mock(return_value="FAKE") |
||||
|
||||
self.datastore = Mock(return_value=Mock()) |
||||
self.datastore.get_current_state_deltas = Mock(return_value=[]) |
||||
|
||||
self.secrets = Mock() |
||||
|
||||
self.hs = setup_test_homeserver( |
||||
http_client=None, clock=self.hs_clock, reactor=self.clock |
||||
) |
||||
|
||||
self.hs.config.registration_shared_secret = u"shared" |
||||
|
||||
self.hs.get_media_repository = Mock() |
||||
self.hs.get_deactivate_account_handler = Mock() |
||||
|
||||
self.resource = JsonResource(self.hs) |
||||
register_servlets(self.hs, self.resource) |
||||
|
||||
def test_disabled(self): |
||||
""" |
||||
If there is no shared secret, registration through this method will be |
||||
prevented. |
||||
""" |
||||
self.hs.config.registration_shared_secret = None |
||||
|
||||
request, channel = make_request("POST", self.url, b'{}') |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual( |
||||
'Shared secret registration is not enabled', channel.json_body["error"] |
||||
) |
||||
|
||||
def test_get_nonce(self): |
||||
""" |
||||
Calling GET on the endpoint will return a randomised nonce, using the |
||||
homeserver's secrets provider. |
||||
""" |
||||
secrets = Mock() |
||||
secrets.token_hex = Mock(return_value="abcd") |
||||
|
||||
self.hs.get_secrets = Mock(return_value=secrets) |
||||
|
||||
request, channel = make_request("GET", self.url) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(channel.json_body, {"nonce": "abcd"}) |
||||
|
||||
def test_expired_nonce(self): |
||||
""" |
||||
Calling GET on the endpoint will return a randomised nonce, which will |
||||
only last for SALT_TIMEOUT (60s). |
||||
""" |
||||
request, channel = make_request("GET", self.url) |
||||
render(request, self.resource, self.clock) |
||||
nonce = channel.json_body["nonce"] |
||||
|
||||
# 59 seconds |
||||
self.clock.advance(59) |
||||
|
||||
body = json.dumps({"nonce": nonce}) |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('username must be specified', channel.json_body["error"]) |
||||
|
||||
# 61 seconds |
||||
self.clock.advance(2) |
||||
|
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('unrecognised nonce', channel.json_body["error"]) |
||||
|
||||
def test_register_incorrect_nonce(self): |
||||
""" |
||||
Only the provided nonce can be used, as it's checked in the MAC. |
||||
""" |
||||
request, channel = make_request("GET", self.url) |
||||
render(request, self.resource, self.clock) |
||||
nonce = channel.json_body["nonce"] |
||||
|
||||
want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1) |
||||
want_mac.update(b"notthenonce\x00bob\x00abc123\x00admin") |
||||
want_mac = want_mac.hexdigest() |
||||
|
||||
body = json.dumps( |
||||
{ |
||||
"nonce": nonce, |
||||
"username": "bob", |
||||
"password": "abc123", |
||||
"admin": True, |
||||
"mac": want_mac, |
||||
} |
||||
).encode('utf8') |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(403, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual("HMAC incorrect", channel.json_body["error"]) |
||||
|
||||
def test_register_correct_nonce(self): |
||||
""" |
||||
When the correct nonce is provided, and the right key is provided, the |
||||
user is registered. |
||||
""" |
||||
request, channel = make_request("GET", self.url) |
||||
render(request, self.resource, self.clock) |
||||
nonce = channel.json_body["nonce"] |
||||
|
||||
want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1) |
||||
want_mac.update(nonce.encode('ascii') + b"\x00bob\x00abc123\x00admin") |
||||
want_mac = want_mac.hexdigest() |
||||
|
||||
body = json.dumps( |
||||
{ |
||||
"nonce": nonce, |
||||
"username": "bob", |
||||
"password": "abc123", |
||||
"admin": True, |
||||
"mac": want_mac, |
||||
} |
||||
).encode('utf8') |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual("@bob:test", channel.json_body["user_id"]) |
||||
|
||||
def test_nonce_reuse(self): |
||||
""" |
||||
A valid unrecognised nonce. |
||||
""" |
||||
request, channel = make_request("GET", self.url) |
||||
render(request, self.resource, self.clock) |
||||
nonce = channel.json_body["nonce"] |
||||
|
||||
want_mac = hmac.new(key=b"shared", digestmod=hashlib.sha1) |
||||
want_mac.update(nonce.encode('ascii') + b"\x00bob\x00abc123\x00admin") |
||||
want_mac = want_mac.hexdigest() |
||||
|
||||
body = json.dumps( |
||||
{ |
||||
"nonce": nonce, |
||||
"username": "bob", |
||||
"password": "abc123", |
||||
"admin": True, |
||||
"mac": want_mac, |
||||
} |
||||
).encode('utf8') |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(200, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual("@bob:test", channel.json_body["user_id"]) |
||||
|
||||
# Now, try and reuse it |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('unrecognised nonce', channel.json_body["error"]) |
||||
|
||||
def test_missing_parts(self): |
||||
""" |
||||
Synapse will complain if you don't give nonce, username, password, and |
||||
mac. Admin is optional. Additional checks are done for length and |
||||
type. |
||||
""" |
||||
def nonce(): |
||||
request, channel = make_request("GET", self.url) |
||||
render(request, self.resource, self.clock) |
||||
return channel.json_body["nonce"] |
||||
|
||||
# |
||||
# Nonce check |
||||
# |
||||
|
||||
# Must be present |
||||
body = json.dumps({}) |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('nonce must be specified', channel.json_body["error"]) |
||||
|
||||
# |
||||
# Username checks |
||||
# |
||||
|
||||
# Must be present |
||||
body = json.dumps({"nonce": nonce()}) |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('username must be specified', channel.json_body["error"]) |
||||
|
||||
# Must be a string |
||||
body = json.dumps({"nonce": nonce(), "username": 1234}) |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('Invalid username', channel.json_body["error"]) |
||||
|
||||
# Must not have null bytes |
||||
body = json.dumps({"nonce": nonce(), "username": b"abcd\x00"}) |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('Invalid username', channel.json_body["error"]) |
||||
|
||||
# Must not have null bytes |
||||
body = json.dumps({"nonce": nonce(), "username": "a" * 1000}) |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('Invalid username', channel.json_body["error"]) |
||||
|
||||
# |
||||
# Username checks |
||||
# |
||||
|
||||
# Must be present |
||||
body = json.dumps({"nonce": nonce(), "username": "a"}) |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('password must be specified', channel.json_body["error"]) |
||||
|
||||
# Must be a string |
||||
body = json.dumps({"nonce": nonce(), "username": "a", "password": 1234}) |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('Invalid password', channel.json_body["error"]) |
||||
|
||||
# Must not have null bytes |
||||
body = json.dumps({"nonce": nonce(), "username": "a", "password": b"abcd\x00"}) |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('Invalid password', channel.json_body["error"]) |
||||
|
||||
# Super long |
||||
body = json.dumps({"nonce": nonce(), "username": "a", "password": "A" * 1000}) |
||||
request, channel = make_request("POST", self.url, body.encode('utf8')) |
||||
render(request, self.resource, self.clock) |
||||
|
||||
self.assertEqual(400, int(channel.result["code"]), msg=channel.result["body"]) |
||||
self.assertEqual('Invalid password', channel.json_body["error"]) |
@ -1,70 +0,0 @@ |
||||
# -*- coding: utf-8 -*- |
||||
# Copyright 2016 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 twisted.internet import defer |
||||
|
||||
from synapse.util.async import Limiter |
||||
|
||||
from tests import unittest |
||||
|
||||
|
||||
class LimiterTestCase(unittest.TestCase): |
||||
|
||||
@defer.inlineCallbacks |
||||
def test_limiter(self): |
||||
limiter = Limiter(3) |
||||
|
||||
key = object() |
||||
|
||||
d1 = limiter.queue(key) |
||||
cm1 = yield d1 |
||||
|
||||
d2 = limiter.queue(key) |
||||
cm2 = yield d2 |
||||
|
||||
d3 = limiter.queue(key) |
||||
cm3 = yield d3 |
||||
|
||||
d4 = limiter.queue(key) |
||||
self.assertFalse(d4.called) |
||||
|
||||
d5 = limiter.queue(key) |
||||
self.assertFalse(d5.called) |
||||
|
||||
with cm1: |
||||
self.assertFalse(d4.called) |
||||
self.assertFalse(d5.called) |
||||
|
||||
self.assertTrue(d4.called) |
||||
self.assertFalse(d5.called) |
||||
|
||||
with cm3: |
||||
self.assertFalse(d5.called) |
||||
|
||||
self.assertTrue(d5.called) |
||||
|
||||
with cm2: |
||||
pass |
||||
|
||||
with (yield d4): |
||||
pass |
||||
|
||||
with (yield d5): |
||||
pass |
||||
|
||||
d6 = limiter.queue(key) |
||||
with (yield d6): |
||||
pass |
Loading…
Reference in new issue