mirror of
https://git.anonymousland.org/anonymousland/synapse-product.git
synced 2024-10-01 08:25:44 -04:00
Merge branch 'master' of github.com:matrix-org/synapse into develop
This commit is contained in:
commit
7e68691ce9
21
CHANGES.md
21
CHANGES.md
@ -1,3 +1,24 @@
|
||||
Synapse 1.0.0 (2019-06-11)
|
||||
==========================
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Fix bug where attempting to send transactions with large number of EDUs can fail. ([\#5418](https://github.com/matrix-org/synapse/issues/5418))
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Expand the federation guide to include relevant content from the MSC1711 FAQ ([\#5419](https://github.com/matrix-org/synapse/issues/5419))
|
||||
|
||||
|
||||
Internal Changes
|
||||
----------------
|
||||
|
||||
- Move password reset links to /_matrix/client/unstable namespace. ([\#5424](https://github.com/matrix-org/synapse/issues/5424))
|
||||
|
||||
|
||||
Synapse 1.0.0rc3 (2019-06-10)
|
||||
=============================
|
||||
|
||||
|
27
INSTALL.md
27
INSTALL.md
@ -1,14 +1,14 @@
|
||||
* [Installing Synapse](#installing-synapse)
|
||||
* [Installing from source](#installing-from-source)
|
||||
* [Platform-Specific Instructions](#platform-specific-instructions)
|
||||
* [Troubleshooting Installation](#troubleshooting-installation)
|
||||
* [Prebuilt packages](#prebuilt-packages)
|
||||
* [Setting up Synapse](#setting-up-synapse)
|
||||
* [TLS certificates](#tls-certificates)
|
||||
* [Email](#email)
|
||||
* [Registering a user](#registering-a-user)
|
||||
* [Setting up a TURN server](#setting-up-a-turn-server)
|
||||
* [URL previews](#url-previews)
|
||||
- [Installing Synapse](#installing-synapse)
|
||||
- [Installing from source](#installing-from-source)
|
||||
- [Platform-Specific Instructions](#platform-specific-instructions)
|
||||
- [Troubleshooting Installation](#troubleshooting-installation)
|
||||
- [Prebuilt packages](#prebuilt-packages)
|
||||
- [Setting up Synapse](#setting-up-synapse)
|
||||
- [TLS certificates](#tls-certificates)
|
||||
- [Email](#email)
|
||||
- [Registering a user](#registering-a-user)
|
||||
- [Setting up a TURN server](#setting-up-a-turn-server)
|
||||
- [URL previews](#url-previews)
|
||||
|
||||
# Installing Synapse
|
||||
|
||||
@ -395,8 +395,9 @@ To configure Synapse to expose an HTTPS port, you will need to edit
|
||||
instance, if using certbot, use `fullchain.pem` as your certificate, not
|
||||
`cert.pem`).
|
||||
|
||||
For those of you upgrading your TLS certificate for Synapse 1.0 compliance,
|
||||
please take a look at [our guide](docs/MSC1711_certificates_FAQ.md#configuring-certificates-for-compatibility-with-synapse-100).
|
||||
For a more detailed guide to configuring your server for federation, see
|
||||
[federate.md](docs/federate.md)
|
||||
|
||||
|
||||
## Email
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
Fix bug where attempting to send transactions with large number of EDUs can fail.
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -1,3 +1,9 @@
|
||||
matrix-synapse-py3 (1.0.0) stable; urgency=medium
|
||||
|
||||
* New synapse release 1.0.0.
|
||||
|
||||
-- Synapse Packaging team <packages@matrix.org> Tue, 11 Jun 2019 17:09:53 +0100
|
||||
|
||||
matrix-synapse-py3 (0.99.5.2) stable; urgency=medium
|
||||
|
||||
* New synapse release 0.99.5.2.
|
||||
|
@ -1,5 +1,22 @@
|
||||
# MSC1711 Certificates FAQ
|
||||
|
||||
## Historical Note
|
||||
This document was originally written to guide server admins through the upgrade
|
||||
path towards Synapse 1.0. Specifically,
|
||||
[MSC1711](https://github.com/matrix-org/matrix-doc/blob/master/proposals/1711-x509-for-federation.md)
|
||||
required that all servers present valid TLS certificates on their federation
|
||||
API. Admins were encouraged to achieve compliance from version 0.99.0 (released
|
||||
in February 2019) ahead of version 1.0 (released June 2019) enforcing the
|
||||
certificate checks.
|
||||
|
||||
Much of what follows is now outdated since most admins will have already
|
||||
upgraded, however it may be of use to those with old installs returning to the
|
||||
project.
|
||||
|
||||
If you are setting up a server from scratch you almost certainly should look at
|
||||
the [installation guide](INSTALL.md) instead.
|
||||
|
||||
## Introduction
|
||||
The goal of Synapse 0.99.0 is to act as a stepping stone to Synapse 1.0.0. It
|
||||
supports the r0.1 release of the server to server specification, but is
|
||||
compatible with both the legacy Matrix federation behaviour (pre-r0.1) as well
|
||||
|
@ -14,9 +14,9 @@ up and will work provided you set the ``server_name`` to match your
|
||||
machine's public DNS hostname, and provide Synapse with a TLS certificate
|
||||
which is valid for your ``server_name``.
|
||||
|
||||
Once you have completed the steps necessary to federate, you should be able to
|
||||
join a room via federation. (A good place to start is ``#synapse:matrix.org`` - a
|
||||
room for Synapse admins.)
|
||||
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
|
||||
Synapse admins.
|
||||
|
||||
|
||||
## Delegation
|
||||
@ -98,6 +98,77 @@ _matrix._tcp.<server_name>``. In our example, we would expect this:
|
||||
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.
|
||||
|
||||
This is the common case, although you can add an SRV record or
|
||||
`.well-known/matrix/server` URI for completeness if you wish.
|
||||
|
||||
**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 still need an SRV record?
|
||||
|
||||
As of Synapse 0.99, Synapse will first check for the existence of a .well-known
|
||||
URI and follow any delegation it suggests. It will only then check for the
|
||||
existence of an SRV record.
|
||||
|
||||
That means that the SRV record will often be redundant. However, you should
|
||||
remember that there may still be older versions of Synapse in the federation
|
||||
which do not understand .well-known URIs, so if you removed your SRV record
|
||||
you would no longer be able to federate with them.
|
||||
|
||||
It is therefore best to leave the SRV record in place for now. Synapse 0.34 and
|
||||
earlier will follow the SRV record (and not care about the invalid
|
||||
certificate). Synapse 0.99 and later will follow the .well-known URI, with the
|
||||
correct certificate chain.
|
||||
|
||||
#### 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.rst](reverse_proxy.rst) 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
|
||||
|
||||
You can use the [federation tester](
|
||||
|
@ -27,4 +27,4 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
__version__ = "1.0.0rc3"
|
||||
__version__ = "1.0.0"
|
||||
|
@ -176,7 +176,6 @@ class SynapseHomeServer(HomeServer):
|
||||
|
||||
resources.update({
|
||||
"/_matrix/client/api/v1": client_resource,
|
||||
"/_synapse/password_reset": client_resource,
|
||||
"/_matrix/client/r0": client_resource,
|
||||
"/_matrix/client/unstable": client_resource,
|
||||
"/_matrix/client/v2_alpha": client_resource,
|
||||
|
@ -117,7 +117,7 @@ class Mailer(object):
|
||||
|
||||
link = (
|
||||
self.hs.config.public_baseurl +
|
||||
"_synapse/password_reset/email/submit_token"
|
||||
"_matrix/client/unstable/password_reset/email/submit_token"
|
||||
"?token=%s&client_secret=%s&sid=%s" %
|
||||
(token, client_secret, sid)
|
||||
)
|
||||
|
@ -80,7 +80,7 @@ REQUIREMENTS = [
|
||||
]
|
||||
|
||||
CONDITIONAL_REQUIREMENTS = {
|
||||
"email": ["Jinja2>=2.9", "bleach>=1.4.2"],
|
||||
"email": ["Jinja2>=2.9", "bleach>=1.4.3"],
|
||||
"matrix-synapse-ldap3": ["matrix-synapse-ldap3>=0.1"],
|
||||
|
||||
# we use execute_batch, which arrived in psycopg 2.7.
|
||||
|
@ -1,6 +1,6 @@
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<p>Your password was successfully reset. You may now close this window.</p>
|
||||
<p>Your email has now been validated, please return to your client to reset your password. You may now close this window.</p>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -15,7 +15,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import logging
|
||||
import re
|
||||
|
||||
from six.moves import http_client
|
||||
|
||||
@ -231,9 +230,11 @@ class MsisdnPasswordRequestTokenRestServlet(RestServlet):
|
||||
|
||||
class PasswordResetSubmitTokenServlet(RestServlet):
|
||||
"""Handles 3PID validation token submission"""
|
||||
PATTERNS = [
|
||||
re.compile("^/_synapse/password_reset/(?P<medium>[^/]*)/submit_token/*$"),
|
||||
]
|
||||
PATTERNS = client_patterns(
|
||||
"/password_reset/(?P<medium>[^/]*)/submit_token/*$",
|
||||
releases=(),
|
||||
unstable=True,
|
||||
)
|
||||
|
||||
def __init__(self, hs):
|
||||
"""
|
||||
|
@ -24,15 +24,9 @@ from synapse.rest.client.v1 import login, room
|
||||
|
||||
from tests.unittest import HomeserverTestCase
|
||||
|
||||
try:
|
||||
from synapse.push.mailer import load_jinja2_templates
|
||||
except Exception:
|
||||
load_jinja2_templates = None
|
||||
|
||||
|
||||
class EmailPusherTests(HomeserverTestCase):
|
||||
|
||||
skip = "No Jinja installed" if not load_jinja2_templates else None
|
||||
servlets = [
|
||||
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
||||
room.register_servlets,
|
||||
|
@ -23,15 +23,9 @@ from synapse.util.logcontext import make_deferred_yieldable
|
||||
|
||||
from tests.unittest import HomeserverTestCase
|
||||
|
||||
try:
|
||||
from synapse.push.mailer import load_jinja2_templates
|
||||
except Exception:
|
||||
load_jinja2_templates = None
|
||||
|
||||
|
||||
class HTTPPusherTests(HomeserverTestCase):
|
||||
|
||||
skip = "No Jinja installed" if not load_jinja2_templates else None
|
||||
servlets = [
|
||||
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
||||
room.register_servlets,
|
||||
|
@ -23,14 +23,8 @@ from synapse.rest.consent import consent_resource
|
||||
from tests import unittest
|
||||
from tests.server import render
|
||||
|
||||
try:
|
||||
from synapse.push.mailer import load_jinja2_templates
|
||||
except Exception:
|
||||
load_jinja2_templates = None
|
||||
|
||||
|
||||
class ConsentResourceTestCase(unittest.HomeserverTestCase):
|
||||
skip = "No Jinja installed" if not load_jinja2_templates else None
|
||||
servlets = [
|
||||
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
||||
room.register_servlets,
|
||||
|
241
tests/rest/client/v2_alpha/test_account.py
Normal file
241
tests/rest/client/v2_alpha/test_account.py
Normal file
@ -0,0 +1,241 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2015-2016 OpenMarket Ltd
|
||||
# Copyright 2017-2018 New Vector Ltd
|
||||
# Copyright 2019 The Matrix.org Foundation C.I.C.
|
||||
#
|
||||
# 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 os
|
||||
import re
|
||||
from email.parser import Parser
|
||||
|
||||
import pkg_resources
|
||||
|
||||
import synapse.rest.admin
|
||||
from synapse.api.constants import LoginType
|
||||
from synapse.rest.client.v1 import login
|
||||
from synapse.rest.client.v2_alpha import account, register
|
||||
|
||||
from tests import unittest
|
||||
|
||||
|
||||
class PasswordResetTestCase(unittest.HomeserverTestCase):
|
||||
|
||||
servlets = [
|
||||
account.register_servlets,
|
||||
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
||||
register.register_servlets,
|
||||
login.register_servlets,
|
||||
]
|
||||
|
||||
def make_homeserver(self, reactor, clock):
|
||||
config = self.default_config()
|
||||
|
||||
# Email config.
|
||||
self.email_attempts = []
|
||||
|
||||
def sendmail(smtphost, from_addr, to_addrs, msg, **kwargs):
|
||||
self.email_attempts.append(msg)
|
||||
return
|
||||
|
||||
config["email"] = {
|
||||
"enable_notifs": False,
|
||||
"template_dir": os.path.abspath(
|
||||
pkg_resources.resource_filename("synapse", "res/templates")
|
||||
),
|
||||
"smtp_host": "127.0.0.1",
|
||||
"smtp_port": 20,
|
||||
"require_transport_security": False,
|
||||
"smtp_user": None,
|
||||
"smtp_pass": None,
|
||||
"notif_from": "test@example.com",
|
||||
}
|
||||
config["public_baseurl"] = "https://example.com"
|
||||
|
||||
hs = self.setup_test_homeserver(config=config, sendmail=sendmail)
|
||||
return hs
|
||||
|
||||
def prepare(self, reactor, clock, hs):
|
||||
self.store = hs.get_datastore()
|
||||
|
||||
def test_basic_password_reset(self):
|
||||
"""Test basic password reset flow
|
||||
"""
|
||||
old_password = "monkey"
|
||||
new_password = "kangeroo"
|
||||
|
||||
user_id = self.register_user("kermit", old_password)
|
||||
self.login("kermit", old_password)
|
||||
|
||||
email = "test@example.com"
|
||||
|
||||
# Add a threepid
|
||||
self.get_success(
|
||||
self.store.user_add_threepid(
|
||||
user_id=user_id,
|
||||
medium="email",
|
||||
address=email,
|
||||
validated_at=0,
|
||||
added_at=0,
|
||||
)
|
||||
)
|
||||
|
||||
client_secret = "foobar"
|
||||
session_id = self._request_token(email, client_secret)
|
||||
|
||||
self.assertEquals(len(self.email_attempts), 1)
|
||||
link = self._get_link_from_email()
|
||||
|
||||
self._validate_token(link)
|
||||
|
||||
self._reset_password(new_password, session_id, client_secret)
|
||||
|
||||
# Assert we can log in with the new password
|
||||
self.login("kermit", new_password)
|
||||
|
||||
# Assert we can't log in with the old password
|
||||
self.attempt_wrong_password_login("kermit", old_password)
|
||||
|
||||
def test_cant_reset_password_without_clicking_link(self):
|
||||
"""Test that we do actually need to click the link in the email
|
||||
"""
|
||||
old_password = "monkey"
|
||||
new_password = "kangeroo"
|
||||
|
||||
user_id = self.register_user("kermit", old_password)
|
||||
self.login("kermit", old_password)
|
||||
|
||||
email = "test@example.com"
|
||||
|
||||
# Add a threepid
|
||||
self.get_success(
|
||||
self.store.user_add_threepid(
|
||||
user_id=user_id,
|
||||
medium="email",
|
||||
address=email,
|
||||
validated_at=0,
|
||||
added_at=0,
|
||||
)
|
||||
)
|
||||
|
||||
client_secret = "foobar"
|
||||
session_id = self._request_token(email, client_secret)
|
||||
|
||||
self.assertEquals(len(self.email_attempts), 1)
|
||||
|
||||
# Attempt to reset password without clicking the link
|
||||
self._reset_password(
|
||||
new_password, session_id, client_secret, expected_code=401,
|
||||
)
|
||||
|
||||
# Assert we can log in with the old password
|
||||
self.login("kermit", old_password)
|
||||
|
||||
# Assert we can't log in with the new password
|
||||
self.attempt_wrong_password_login("kermit", new_password)
|
||||
|
||||
def test_no_valid_token(self):
|
||||
"""Test that we do actually need to request a token and can't just
|
||||
make a session up.
|
||||
"""
|
||||
old_password = "monkey"
|
||||
new_password = "kangeroo"
|
||||
|
||||
user_id = self.register_user("kermit", old_password)
|
||||
self.login("kermit", old_password)
|
||||
|
||||
email = "test@example.com"
|
||||
|
||||
# Add a threepid
|
||||
self.get_success(
|
||||
self.store.user_add_threepid(
|
||||
user_id=user_id,
|
||||
medium="email",
|
||||
address=email,
|
||||
validated_at=0,
|
||||
added_at=0,
|
||||
)
|
||||
)
|
||||
|
||||
client_secret = "foobar"
|
||||
session_id = "weasle"
|
||||
|
||||
# Attempt to reset password without even requesting an email
|
||||
self._reset_password(
|
||||
new_password, session_id, client_secret, expected_code=401,
|
||||
)
|
||||
|
||||
# Assert we can log in with the old password
|
||||
self.login("kermit", old_password)
|
||||
|
||||
# Assert we can't log in with the new password
|
||||
self.attempt_wrong_password_login("kermit", new_password)
|
||||
|
||||
def _request_token(self, email, client_secret):
|
||||
request, channel = self.make_request(
|
||||
"POST",
|
||||
b"account/password/email/requestToken",
|
||||
{"client_secret": client_secret, "email": email, "send_attempt": 1},
|
||||
)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, channel.result)
|
||||
|
||||
return channel.json_body["sid"]
|
||||
|
||||
def _validate_token(self, link):
|
||||
# Remove the host
|
||||
path = link.replace("https://example.com", "")
|
||||
|
||||
request, channel = self.make_request("GET", path, shorthand=False)
|
||||
self.render(request)
|
||||
self.assertEquals(200, channel.code, channel.result)
|
||||
|
||||
def _get_link_from_email(self):
|
||||
assert self.email_attempts, "No emails have been sent"
|
||||
|
||||
raw_msg = self.email_attempts[-1].decode("UTF-8")
|
||||
mail = Parser().parsestr(raw_msg)
|
||||
|
||||
text = None
|
||||
for part in mail.walk():
|
||||
if part.get_content_type() == "text/plain":
|
||||
text = part.get_payload(decode=True).decode("UTF-8")
|
||||
break
|
||||
|
||||
if not text:
|
||||
self.fail("Could not find text portion of email to parse")
|
||||
|
||||
match = re.search(r"https://example.com\S+", text)
|
||||
assert match, "Could not find link in email"
|
||||
|
||||
return match.group(0)
|
||||
|
||||
def _reset_password(
|
||||
self, new_password, session_id, client_secret, expected_code=200
|
||||
):
|
||||
request, channel = self.make_request(
|
||||
"POST",
|
||||
b"account/password",
|
||||
{
|
||||
"new_password": new_password,
|
||||
"auth": {
|
||||
"type": LoginType.EMAIL_IDENTITY,
|
||||
"threepid_creds": {
|
||||
"client_secret": client_secret,
|
||||
"sid": session_id,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
self.render(request)
|
||||
self.assertEquals(expected_code, channel.code, channel.result)
|
@ -30,11 +30,6 @@ from synapse.rest.client.v2_alpha import account_validity, register, sync
|
||||
|
||||
from tests import unittest
|
||||
|
||||
try:
|
||||
from synapse.push.mailer import load_jinja2_templates
|
||||
except ImportError:
|
||||
load_jinja2_templates = None
|
||||
|
||||
|
||||
class RegisterRestServletTestCase(unittest.HomeserverTestCase):
|
||||
|
||||
@ -307,7 +302,6 @@ class AccountValidityTestCase(unittest.HomeserverTestCase):
|
||||
|
||||
class AccountValidityRenewalByEmailTestCase(unittest.HomeserverTestCase):
|
||||
|
||||
skip = "No Jinja installed" if not load_jinja2_templates else None
|
||||
servlets = [
|
||||
register.register_servlets,
|
||||
synapse.rest.admin.register_servlets_for_client_rest_resource,
|
||||
|
@ -441,3 +441,15 @@ class HomeserverTestCase(TestCase):
|
||||
|
||||
access_token = channel.json_body["access_token"]
|
||||
return access_token
|
||||
|
||||
def attempt_wrong_password_login(self, username, password):
|
||||
"""Attempts to login as the user with the given password, asserting
|
||||
that the attempt *fails*.
|
||||
"""
|
||||
body = {"type": "m.login.password", "user": username, "password": password}
|
||||
|
||||
request, channel = self.make_request(
|
||||
"POST", "/_matrix/client/r0/login", json.dumps(body).encode('utf8')
|
||||
)
|
||||
self.render(request)
|
||||
self.assertEqual(channel.code, 403, channel.result)
|
||||
|
Loading…
Reference in New Issue
Block a user