Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2950c44dd4 | |||
| 0f3293d2c4 | |||
| cef8ae272a | |||
| a83826ae99 | |||
| 5986a10f16 | |||
| f208f608cb | |||
| eb835bf65b | |||
| 03116da984 | |||
| 188945713e | |||
| 95c1f6500b | |||
| 1e2b065112 | |||
| bd52978fd7 | |||
| 2755a0d48a | |||
| f537432ef9 | |||
| df96177ca7 | |||
| 89ac2a5bdb | |||
| 989f1167af | |||
| 9b1c19e0c5 | |||
| 74c3606c53 | |||
| 25219b7b4e | |||
| fee831c040 | |||
| 466c1f3e01 | |||
| 91206e09f2 | |||
| dbf736ba66 | |||
| 912a843294 | |||
| 5a1c6f45be | |||
| 30da50a5b8 | |||
| 35e13477cf | |||
| c7401a697f | |||
| c588b9b9e4 | |||
| b0c24a66ec | |||
| 9a3e24a13d | |||
| e8d98466b0 | |||
| dece89d280 | |||
| fe324cb184 | |||
| 5f00cfa40d | |||
| e55983defe | |||
| a2ed0f287e | |||
| 956061732d | |||
| 75937e9033 | |||
| 4acd1a3549 | |||
| b164241814 | |||
| 1737753a62 | |||
| fd96dd75a3 | |||
| dd27e47b5c | |||
| 158ffb92f1 | |||
| 512e94d230 | |||
| b5ac0ffa0a | |||
| ecc23188f4 | |||
| f144c0a210 | |||
| 48972ce9d1 | |||
| a077e710a3 | |||
| a484735bb0 | |||
| 52e87fbfbe | |||
| 3518c28aa8 | |||
| 998ba41493 | |||
| d3c61ef906 | |||
| c03324294d | |||
| 44dc4c365b | |||
| 704c5298f0 | |||
| 455df4dda0 | |||
| e6018bcc1a |
@@ -4,8 +4,8 @@ jobs:
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- run: docker build -f docker/Dockerfile -t matrixdotorg/synapse:${CIRCLE_TAG} .
|
||||
- run: docker build -f docker/Dockerfile -t matrixdotorg/synapse:${CIRCLE_TAG}-py3 --build-arg PYTHON_VERSION=3.6 .
|
||||
- run: docker build -f docker/Dockerfile --label gitsha1=${CIRCLE_SHA1} -t matrixdotorg/synapse:${CIRCLE_TAG} .
|
||||
- run: docker build -f docker/Dockerfile --label gitsha1=${CIRCLE_SHA1} -t matrixdotorg/synapse:${CIRCLE_TAG}-py3 --build-arg PYTHON_VERSION=3.6 .
|
||||
- run: docker login --username $DOCKER_HUB_USERNAME --password $DOCKER_HUB_PASSWORD
|
||||
- run: docker push matrixdotorg/synapse:${CIRCLE_TAG}
|
||||
- run: docker push matrixdotorg/synapse:${CIRCLE_TAG}-py3
|
||||
@@ -13,13 +13,9 @@ jobs:
|
||||
machine: true
|
||||
steps:
|
||||
- checkout
|
||||
- run: docker build -f docker/Dockerfile -t matrixdotorg/synapse:${CIRCLE_SHA1} .
|
||||
- run: docker build -f docker/Dockerfile -t matrixdotorg/synapse:${CIRCLE_SHA1}-py3 --build-arg PYTHON_VERSION=3.6 .
|
||||
- run: docker build -f docker/Dockerfile --label gitsha1=${CIRCLE_SHA1} -t matrixdotorg/synapse:${CIRCLE_SHA1} .
|
||||
- run: docker build -f docker/Dockerfile --label gitsha1=${CIRCLE_SHA1} -t matrixdotorg/synapse:${CIRCLE_SHA1}-py3 --build-arg PYTHON_VERSION=3.6 .
|
||||
- run: docker login --username $DOCKER_HUB_USERNAME --password $DOCKER_HUB_PASSWORD
|
||||
- run: docker tag matrixdotorg/synapse:${CIRCLE_SHA1} matrixdotorg/synapse:latest
|
||||
- run: docker tag matrixdotorg/synapse:${CIRCLE_SHA1}-py3 matrixdotorg/synapse:latest-py3
|
||||
- run: docker push matrixdotorg/synapse:${CIRCLE_SHA1}
|
||||
- run: docker push matrixdotorg/synapse:${CIRCLE_SHA1}-py3
|
||||
- run: docker push matrixdotorg/synapse:latest
|
||||
- run: docker push matrixdotorg/synapse:latest-py3
|
||||
sytestpy2:
|
||||
|
||||
@@ -20,7 +20,7 @@ else
|
||||
fi
|
||||
|
||||
# Show what we are before
|
||||
git show -s
|
||||
git --no-pager show -s
|
||||
|
||||
# Set up username so it can do a merge
|
||||
git config --global user.email bot@matrix.org
|
||||
@@ -31,4 +31,4 @@ git fetch -u origin $GITBASE
|
||||
git merge --no-edit origin/$GITBASE
|
||||
|
||||
# Show what we are after.
|
||||
git show -s
|
||||
git --no-pager show -s
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# EditorConfig https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# 4 space indentation
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
+71
-1
@@ -1,3 +1,73 @@
|
||||
Synapse 0.34.0rc2 (2018-12-11)
|
||||
==============================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Add a welcome page for the client API port. Credit to @krombel! ([\#4289](https://github.com/matrix-org/synapse/issues/4289))
|
||||
- Remove Matrix console from the default distribution ([\#4290](https://github.com/matrix-org/synapse/issues/4290))
|
||||
|
||||
|
||||
Internal Changes
|
||||
----------------
|
||||
|
||||
- Disable pager when running git-show in CI ([\#4291](https://github.com/matrix-org/synapse/issues/4291))
|
||||
|
||||
|
||||
Synapse 0.34.0rc1 (2018-12-04)
|
||||
==============================
|
||||
|
||||
Synapse 0.34 is the first release to fully support Python 3. We recommend
|
||||
upgrading to Python 3, but make sure to read the
|
||||
[upgrade notes](UPGRADE.rst#upgrading-to-v0340) when doing so.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- Add option to track MAU stats (but not limit people) ([\#3830](https://github.com/matrix-org/synapse/issues/3830))
|
||||
- Add an option to enable recording IPs for appservice users ([\#3831](https://github.com/matrix-org/synapse/issues/3831))
|
||||
- Rename login type m.login.cas to m.login.sso ([\#4220](https://github.com/matrix-org/synapse/issues/4220))
|
||||
- Add an option to disable search for homeservers that may not be interested in it. ([\#4230](https://github.com/matrix-org/synapse/issues/4230))
|
||||
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Pushrules can now again be made with non-ASCII rule IDs. ([\#4165](https://github.com/matrix-org/synapse/issues/4165))
|
||||
- The media repository now no longer fails to decode UTF-8 filenames when downloading remote media. ([\#4176](https://github.com/matrix-org/synapse/issues/4176))
|
||||
- URL previews now correctly decode non-UTF-8 text if the header contains a `<meta http-equiv="Content-Type"` header. ([\#4183](https://github.com/matrix-org/synapse/issues/4183))
|
||||
- Fix an issue where public consent URLs had two slashes. ([\#4192](https://github.com/matrix-org/synapse/issues/4192))
|
||||
- Fallback auth now accepts the session parameter on Python 3. ([\#4197](https://github.com/matrix-org/synapse/issues/4197))
|
||||
- Remove riot.im from the list of trusted Identity Servers in the default configuration ([\#4207](https://github.com/matrix-org/synapse/issues/4207))
|
||||
- fix start up failure when mau_limit_reserved_threepids set and db is postgres ([\#4211](https://github.com/matrix-org/synapse/issues/4211))
|
||||
- Fix auto join failures for servers that require user consent ([\#4223](https://github.com/matrix-org/synapse/issues/4223))
|
||||
- Fix exception caused by non-ascii event IDs ([\#4241](https://github.com/matrix-org/synapse/issues/4241))
|
||||
- Pushers can now be unsubscribed from on Python 3. ([\#4250](https://github.com/matrix-org/synapse/issues/4250))
|
||||
- Fix UnicodeDecodeError when postgres is configured to give non-English errors ([\#4253](https://github.com/matrix-org/synapse/issues/4253))
|
||||
|
||||
|
||||
Internal Changes
|
||||
----------------
|
||||
|
||||
- A coveragerc file, as well as the py36-coverage tox target, have been added. ([\#4180](https://github.com/matrix-org/synapse/issues/4180))
|
||||
- Add a GitHub pull request template and add multiple issue templates ([\#4182](https://github.com/matrix-org/synapse/issues/4182))
|
||||
- Update README to reflect the fact that #1491 is fixed ([\#4188](https://github.com/matrix-org/synapse/issues/4188))
|
||||
- Run the AS senders as background processes to fix warnings ([\#4189](https://github.com/matrix-org/synapse/issues/4189))
|
||||
- Add some diagnostics to the tests to detect logcontext problems ([\#4190](https://github.com/matrix-org/synapse/issues/4190))
|
||||
- Add missing `jpeg` package prerequisite for OpenBSD in README. ([\#4193](https://github.com/matrix-org/synapse/issues/4193))
|
||||
- Add a note saying you need to manually reclaim disk space after using the Purge History API ([\#4200](https://github.com/matrix-org/synapse/issues/4200))
|
||||
- More logcontext checking in unittests ([\#4205](https://github.com/matrix-org/synapse/issues/4205))
|
||||
- Ignore __pycache__ directories in the database schema folder ([\#4214](https://github.com/matrix-org/synapse/issues/4214))
|
||||
- Add note to UPGRADE.rst about removing riot.im from list of trusted identity servers ([\#4224](https://github.com/matrix-org/synapse/issues/4224))
|
||||
- Added automated coverage reporting to CI. ([\#4225](https://github.com/matrix-org/synapse/issues/4225))
|
||||
- Garbage-collect after each unit test to fix logcontext leaks ([\#4227](https://github.com/matrix-org/synapse/issues/4227))
|
||||
- add more detail to logging regarding "More than one row matched" error ([\#4234](https://github.com/matrix-org/synapse/issues/4234))
|
||||
- Drop sent_transactions table ([\#4244](https://github.com/matrix-org/synapse/issues/4244))
|
||||
- Add a basic .editorconfig ([\#4257](https://github.com/matrix-org/synapse/issues/4257))
|
||||
- Update README.rst and UPGRADE.rst for Python 3. ([\#4260](https://github.com/matrix-org/synapse/issues/4260))
|
||||
- Remove obsolete `verbose` and `log_file` settings from `homeserver.yaml` for Docker image. ([\#4261](https://github.com/matrix-org/synapse/issues/4261))
|
||||
|
||||
|
||||
Synapse 0.33.9 (2018-11-19)
|
||||
===========================
|
||||
|
||||
@@ -71,7 +141,7 @@ Synapse 0.33.8rc2 (2018-10-31)
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- Searches that request profile info now no longer fail with a 500. Fixes
|
||||
- Searches that request profile info now no longer fail with a 500. Fixes
|
||||
a regression in 0.33.8rc1. ([\#4122](https://github.com/matrix-org/synapse/issues/4122))
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@ Sign off
|
||||
In order to have a concrete record that your contribution is intentional
|
||||
and you agree to license it under the same terms as the project's license, we've adopted the
|
||||
same lightweight approach that the Linux Kernel
|
||||
(https://www.kernel.org/doc/Documentation/SubmittingPatches), Docker
|
||||
`submitting patches process <https://www.kernel.org/doc/html/latest/process/submitting-patches.html#sign-your-work-the-developer-s-certificate-of-origin>`_, Docker
|
||||
(https://github.com/docker/docker/blob/master/CONTRIBUTING.md), and many other
|
||||
projects use: the DCO (Developer Certificate of Origin:
|
||||
http://developercertificate.org/). This is a simple declaration that you wrote
|
||||
|
||||
@@ -26,6 +26,7 @@ recursive-include synapse/static *.js
|
||||
exclude Dockerfile
|
||||
exclude .dockerignore
|
||||
exclude test_postgresql.sh
|
||||
exclude .editorconfig
|
||||
|
||||
include pyproject.toml
|
||||
recursive-include changelog.d *
|
||||
|
||||
+21
-59
@@ -86,7 +86,7 @@ Synapse is the reference Python/Twisted Matrix homeserver implementation.
|
||||
System requirements:
|
||||
|
||||
- POSIX-compliant system (tested on Linux & OS X)
|
||||
- Python 2.7
|
||||
- Python 3.5, 3.6, or 2.7
|
||||
- At least 1GB of free RAM if you want to join large public rooms like #matrix:matrix.org
|
||||
|
||||
Installing from source
|
||||
@@ -101,13 +101,13 @@ header files for Python C extensions.
|
||||
|
||||
Installing prerequisites on Ubuntu or Debian::
|
||||
|
||||
sudo apt-get install build-essential python2.7-dev libffi-dev \
|
||||
sudo apt-get install build-essential python3-dev libffi-dev \
|
||||
python-pip python-setuptools sqlite3 \
|
||||
libssl-dev python-virtualenv libjpeg-dev libxslt1-dev
|
||||
|
||||
Installing prerequisites on ArchLinux::
|
||||
|
||||
sudo pacman -S base-devel python2 python-pip \
|
||||
sudo pacman -S base-devel python python-pip \
|
||||
python-setuptools python-virtualenv sqlite3
|
||||
|
||||
Installing prerequisites on CentOS 7 or Fedora 25::
|
||||
@@ -126,12 +126,9 @@ Installing prerequisites on Mac OS X::
|
||||
|
||||
Installing prerequisites on Raspbian::
|
||||
|
||||
sudo apt-get install build-essential python2.7-dev libffi-dev \
|
||||
sudo apt-get install build-essential python3-dev libffi-dev \
|
||||
python-pip python-setuptools sqlite3 \
|
||||
libssl-dev python-virtualenv libjpeg-dev
|
||||
sudo pip install --upgrade pip
|
||||
sudo pip install --upgrade ndg-httpsclient
|
||||
sudo pip install --upgrade virtualenv
|
||||
|
||||
Installing prerequisites on openSUSE::
|
||||
|
||||
@@ -146,20 +143,21 @@ Installing prerequisites on OpenBSD::
|
||||
|
||||
To install the Synapse homeserver run::
|
||||
|
||||
virtualenv -p python2.7 ~/.synapse
|
||||
source ~/.synapse/bin/activate
|
||||
mkdir -p ~/synapse
|
||||
virtualenv -p python3 ~/synapse/env
|
||||
source ~/synapse/env/bin/activate
|
||||
pip install --upgrade pip
|
||||
pip install --upgrade setuptools
|
||||
pip install matrix-synapse
|
||||
|
||||
This installs Synapse, along with the libraries it uses, into a virtual
|
||||
environment under ``~/.synapse``. Feel free to pick a different directory
|
||||
environment under ``~/synapse/env``. Feel free to pick a different directory
|
||||
if you prefer.
|
||||
|
||||
This Synapse installation can then be later upgraded by using pip again with the
|
||||
update flag::
|
||||
|
||||
source ~/.synapse/bin/activate
|
||||
source ~/synapse/env/bin/activate
|
||||
pip install -U matrix-synapse
|
||||
|
||||
In case of problems, please see the _`Troubleshooting` section below.
|
||||
@@ -240,7 +238,7 @@ commandline script.
|
||||
|
||||
To get started, it is easiest to use the command line to register new users::
|
||||
|
||||
$ source ~/.synapse/bin/activate
|
||||
$ source ~/synapse/env/bin/activate
|
||||
$ synctl start # if not already running
|
||||
$ register_new_matrix_user -c homeserver.yaml https://localhost:8448
|
||||
New user localpart: erikj
|
||||
@@ -266,13 +264,12 @@ Running Synapse
|
||||
===============
|
||||
|
||||
To actually run your new homeserver, pick a working directory for Synapse to
|
||||
run (e.g. ``~/.synapse``), and::
|
||||
run (e.g. ``~/synapse``), and::
|
||||
|
||||
cd ~/.synapse
|
||||
source ./bin/activate
|
||||
cd ~/synapse
|
||||
source env/bin/activate
|
||||
synctl start
|
||||
|
||||
|
||||
Connecting to Synapse from a client
|
||||
===================================
|
||||
|
||||
@@ -292,10 +289,6 @@ go back in your web client and proceed further.
|
||||
If all goes well you should at least be able to log in, create a room, and
|
||||
start sending messages.
|
||||
|
||||
(The homeserver runs a web client by default at https://localhost:8448/, though
|
||||
as of the time of writing it is somewhat outdated and not really recommended -
|
||||
https://github.com/matrix-org/synapse/issues/1527).
|
||||
|
||||
.. _`client-user-reg`:
|
||||
|
||||
Registering a new user from a client
|
||||
@@ -333,7 +326,7 @@ content served to web browsers a matrix API from being able to attack webapps ho
|
||||
on the same domain. This is particularly true of sharing a matrix webclient and
|
||||
server on the same domain.
|
||||
|
||||
See https://github.com/vector-im/vector-web/issues/1977 and
|
||||
See https://github.com/vector-im/riot-web/issues/1977 and
|
||||
https://developer.github.com/changes/2014-04-25-user-content-security for more details.
|
||||
|
||||
|
||||
@@ -375,40 +368,19 @@ ArchLinux
|
||||
|
||||
The quickest way to get up and running with ArchLinux is probably with the community package
|
||||
https://www.archlinux.org/packages/community/any/matrix-synapse/, which should pull in most of
|
||||
the necessary dependencies. If the default web client is to be served (enabled by default in
|
||||
the generated config),
|
||||
https://www.archlinux.org/packages/community/any/python2-matrix-angular-sdk/ will also need to
|
||||
be installed.
|
||||
|
||||
Alternatively, to install using pip a few changes may be needed as ArchLinux
|
||||
defaults to python 3, but synapse currently assumes python 2.7 by default:
|
||||
the necessary dependencies.
|
||||
|
||||
pip may be outdated (6.0.7-1 and needs to be upgraded to 6.0.8-1 )::
|
||||
|
||||
sudo pip2.7 install --upgrade pip
|
||||
|
||||
You also may need to explicitly specify python 2.7 again during the install
|
||||
request::
|
||||
|
||||
pip2.7 install https://github.com/matrix-org/synapse/tarball/master
|
||||
sudo pip install --upgrade pip
|
||||
|
||||
If you encounter an error with lib bcrypt causing an Wrong ELF Class:
|
||||
ELFCLASS32 (x64 Systems), you may need to reinstall py-bcrypt to correctly
|
||||
compile it under the right architecture. (This should not be needed if
|
||||
installing under virtualenv)::
|
||||
|
||||
sudo pip2.7 uninstall py-bcrypt
|
||||
sudo pip2.7 install py-bcrypt
|
||||
|
||||
During setup of Synapse you need to call python2.7 directly again::
|
||||
|
||||
cd ~/.synapse
|
||||
python2.7 -m synapse.app.homeserver \
|
||||
--server-name machine.my.domain.name \
|
||||
--config-path homeserver.yaml \
|
||||
--generate-config
|
||||
|
||||
...substituting your host and domain name as appropriate.
|
||||
sudo pip uninstall py-bcrypt
|
||||
sudo pip install py-bcrypt
|
||||
|
||||
FreeBSD
|
||||
-------
|
||||
@@ -475,7 +447,7 @@ You can fix this by manually upgrading pip and virtualenv::
|
||||
|
||||
sudo pip install --upgrade virtualenv
|
||||
|
||||
You can next rerun ``virtualenv -p python2.7 synapse`` to update the virtual env.
|
||||
You can next rerun ``virtualenv -p python3 synapse`` to update the virtual env.
|
||||
|
||||
Installing may fail during installing virtualenv with ``InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.``
|
||||
You can fix this by manually installing ndg-httpsclient::
|
||||
@@ -524,16 +496,6 @@ log lines and looking for any 'Processed request' lines which take more than
|
||||
a few seconds to execute. Please let us know at #matrix-dev:matrix.org if
|
||||
you see this failure mode so we can help debug it, however.
|
||||
|
||||
ArchLinux
|
||||
~~~~~~~~~
|
||||
|
||||
If running `$ synctl start` fails with 'returned non-zero exit status 1',
|
||||
you will need to explicitly call Python2.7 - either running as::
|
||||
|
||||
python2.7 -m synapse.app.homeserver --daemonize -c homeserver.yaml
|
||||
|
||||
...or by editing synctl with the correct python executable.
|
||||
|
||||
|
||||
Upgrading an existing Synapse
|
||||
=============================
|
||||
@@ -731,7 +693,7 @@ port:
|
||||
|
||||
* Until v0.33.3, Synapse did not support SNI on the federation port
|
||||
(`bug #1491 <https://github.com/matrix-org/synapse/issues/1491>`_). This bug
|
||||
is now fixed, but means that federating with older servers can be unreliable
|
||||
is now fixed, but means that federating with older servers can be unreliable
|
||||
when using name-based virtual hosting.
|
||||
|
||||
Furthermore, a number of the normal reasons for using a reverse-proxy do not
|
||||
@@ -828,7 +790,7 @@ Password reset
|
||||
==============
|
||||
|
||||
If a user has registered an email address to their account using an identity
|
||||
server, they can request a password-reset token via clients such as Vector.
|
||||
server, they can request a password-reset token via clients such as Riot.
|
||||
|
||||
A manual password reset can be done via direct database access as follows.
|
||||
|
||||
|
||||
+72
@@ -48,6 +48,78 @@ returned by the Client-Server API:
|
||||
# configured on port 443.
|
||||
curl -kv https://<host.name>/_matrix/client/versions 2>&1 | grep "Server:"
|
||||
|
||||
Upgrading to v0.34.0
|
||||
====================
|
||||
|
||||
1. This release is the first to fully support Python 3. We recommend switching
|
||||
to Python 3, as it has been shown to give performance improvements.
|
||||
|
||||
For users who have installed Synapse into a virtualenv, we recommend doing
|
||||
this by creating a new virtualenv. For example::
|
||||
|
||||
virtualenv -p python3 ~/synapse/env3
|
||||
source ~/synapse/env3/bin/activate
|
||||
pip install matrix-synapse
|
||||
|
||||
You can then start synapse as normal, having activated the new virtualenv::
|
||||
|
||||
cd ~/synapse
|
||||
source env3/bin/activate
|
||||
synctl start
|
||||
|
||||
Users who have installed from distribution packages should see the relevant
|
||||
package documentation.
|
||||
|
||||
* When upgrading to Python 3, you **must** make sure that your log files are
|
||||
configured as UTF-8, by adding ``encoding: utf8`` to the
|
||||
``RotatingFileHandler`` configuration (if you have one) in your
|
||||
``<server>.log.config`` file. For example, if your ``log.config`` file
|
||||
contains::
|
||||
|
||||
handlers:
|
||||
file:
|
||||
class: logging.handlers.RotatingFileHandler
|
||||
formatter: precise
|
||||
filename: homeserver.log
|
||||
maxBytes: 104857600
|
||||
backupCount: 10
|
||||
filters: [context]
|
||||
console:
|
||||
class: logging.StreamHandler
|
||||
formatter: precise
|
||||
filters: [context]
|
||||
|
||||
Then you should update this to be::
|
||||
|
||||
handlers:
|
||||
file:
|
||||
class: logging.handlers.RotatingFileHandler
|
||||
formatter: precise
|
||||
filename: homeserver.log
|
||||
maxBytes: 104857600
|
||||
backupCount: 10
|
||||
filters: [context]
|
||||
encoding: utf8
|
||||
console:
|
||||
class: logging.StreamHandler
|
||||
formatter: precise
|
||||
filters: [context]
|
||||
|
||||
There is no need to revert this change if downgrading to Python 2.
|
||||
|
||||
2. This release removes the ``riot.im`` from the default list of trusted
|
||||
identity servers.
|
||||
|
||||
If ``riot.im`` is in your homeserver's list of
|
||||
``trusted_third_party_id_servers``, you should remove it. It was added in
|
||||
case a hypothetical future identity server was put there. If you don't
|
||||
remove it, users may be unable to deactivate their accounts.
|
||||
|
||||
3. This release no longer installs the (unmaintained) Matrix Console web client
|
||||
as part of the default installation. It is possible to re-enable it by
|
||||
installing it separately and setting the ``web_client_location`` config
|
||||
option, but please consider switching to another client.
|
||||
|
||||
Upgrading to v0.33.7
|
||||
====================
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Add option to track MAU stats (but not limit people)
|
||||
@@ -1 +0,0 @@
|
||||
The media repository now no longer fails to decode UTF-8 filenames when downloading remote media.
|
||||
@@ -1 +0,0 @@
|
||||
A coveragerc file, as well as the py36-coverage tox target, have been added.
|
||||
@@ -1 +0,0 @@
|
||||
Add a GitHub pull request template and add multiple issue templates
|
||||
@@ -1 +0,0 @@
|
||||
URL previews now correctly decode non-UTF-8 text if the header contains a `<meta http-equiv="Content-Type"` header.
|
||||
@@ -1 +0,0 @@
|
||||
Update README to reflect the fact that #1491 is fixed
|
||||
@@ -1 +0,0 @@
|
||||
Add some diagnostics to the tests to detect logcontext problems
|
||||
@@ -1 +0,0 @@
|
||||
Fix an issue where public consent URLs had two slashes.
|
||||
@@ -1 +0,0 @@
|
||||
Add missing `jpeg` package prerequisite for OpenBSD in README.
|
||||
@@ -1 +0,0 @@
|
||||
Fallback auth now accepts the session parameter on Python 3.
|
||||
@@ -1 +0,0 @@
|
||||
Add a note saying you need to manually reclaim disk space after using the Purge History API
|
||||
@@ -1 +0,0 @@
|
||||
Fix logcontext leaks in EmailPusher and in tests
|
||||
@@ -1 +0,0 @@
|
||||
Remove riot.im from the list of trusted Identity Servers in the default configuration
|
||||
@@ -1 +0,0 @@
|
||||
Fix logcontext leaks in EmailPusher and in tests
|
||||
@@ -1,2 +0,0 @@
|
||||
fix start up failure when mau_limit_reserved_threepids set and db is postgres
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Ignore __pycache__ directories in the database schema folder
|
||||
@@ -1 +0,0 @@
|
||||
Rename login type m.login.cas to m.login.sso
|
||||
@@ -1 +0,0 @@
|
||||
Fix auto join failures for servers that require user consent
|
||||
@@ -1 +0,0 @@
|
||||
Added automated coverage reporting to CI.
|
||||
@@ -1 +0,0 @@
|
||||
Garbage-collect after each unit test to fix logcontext leaks
|
||||
@@ -0,0 +1 @@
|
||||
Support for serving .well-known files
|
||||
@@ -0,0 +1 @@
|
||||
Fix CAS login when username is not valid in an MXID
|
||||
@@ -0,0 +1 @@
|
||||
Rework SAML2 authentication
|
||||
@@ -0,0 +1 @@
|
||||
drop undocumented dependency on dateutil
|
||||
@@ -0,0 +1 @@
|
||||
Rework SAML2 authentication
|
||||
@@ -0,0 +1 @@
|
||||
SAML2 authentication: Initialise user display name from SAML2 data
|
||||
@@ -0,0 +1 @@
|
||||
Update the example systemd config to use a virtualenv
|
||||
@@ -0,0 +1 @@
|
||||
Update link to kernel DCO guide
|
||||
@@ -0,0 +1 @@
|
||||
Send CORS headers for /media/config
|
||||
@@ -0,0 +1 @@
|
||||
Make isort tox check print diff when it fails
|
||||
@@ -0,0 +1 @@
|
||||
Add 'sandbox' to CSP for media reprository
|
||||
@@ -0,0 +1 @@
|
||||
Make the new landing page prettier.
|
||||
@@ -0,0 +1 @@
|
||||
Log room_id in Unknown room errors
|
||||
@@ -0,0 +1,31 @@
|
||||
# Example systemd configuration file for synapse. Copy into
|
||||
# /etc/systemd/system/, update the paths if necessary, then:
|
||||
#
|
||||
# systemctl enable matrix-synapse
|
||||
# systemctl start matrix-synapse
|
||||
#
|
||||
# This assumes that Synapse has been installed in a virtualenv in
|
||||
# /opt/synapse/env.
|
||||
#
|
||||
# **NOTE:** This is an example service file that may change in the future. If you
|
||||
# wish to use this please copy rather than symlink it.
|
||||
|
||||
[Unit]
|
||||
Description=Synapse Matrix homeserver
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=on-abort
|
||||
|
||||
User=synapse
|
||||
Group=nogroup
|
||||
|
||||
WorkingDirectory=/opt/synapse
|
||||
ExecStart=/opt/synapse/env/bin/python -m synapse.app.homeserver --config-path=/opt/synapse/homeserver.yaml
|
||||
|
||||
# adjust the cache factor if necessary
|
||||
# Environment=SYNAPSE_CACHE_FACTOR=2.0
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# This assumes that Synapse has been installed as a system package
|
||||
# (e.g. https://www.archlinux.org/packages/community/any/matrix-synapse/ for ArchLinux)
|
||||
# rather than in a user home directory or similar under virtualenv.
|
||||
|
||||
# **NOTE:** This is an example service file that may change in the future. If you
|
||||
# wish to use this please copy rather than symlink it.
|
||||
|
||||
[Unit]
|
||||
Description=Synapse Matrix homeserver
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=synapse
|
||||
Group=synapse
|
||||
WorkingDirectory=/var/lib/synapse
|
||||
ExecStart=/usr/bin/python2.7 -m synapse.app.homeserver --config-path=/etc/synapse/homeserver.yaml
|
||||
ExecStop=/usr/bin/synctl stop /etc/synapse/homeserver.yaml
|
||||
# EnvironmentFile=-/etc/sysconfig/synapse # Can be used to e.g. set SYNAPSE_CACHE_FACTOR
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -14,6 +14,7 @@ server_name: "{{ SYNAPSE_SERVER_NAME }}"
|
||||
pid_file: /homeserver.pid
|
||||
web_client: False
|
||||
soft_file_limit: 0
|
||||
log_config: "/compiled/log.config"
|
||||
|
||||
## Ports ##
|
||||
|
||||
@@ -67,9 +68,6 @@ database:
|
||||
## Performance ##
|
||||
|
||||
event_cache_size: "{{ SYNAPSE_EVENT_CACHE_SIZE or "10K" }}"
|
||||
verbose: 0
|
||||
log_file: "/data/homeserver.log"
|
||||
log_config: "/compiled/log.config"
|
||||
|
||||
## Ratelimiting ##
|
||||
|
||||
|
||||
+1
-1
@@ -27,4 +27,4 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
__version__ = "0.33.9"
|
||||
__version__ = "0.34.0rc2"
|
||||
|
||||
+22
-11
@@ -188,17 +188,33 @@ class Auth(object):
|
||||
"""
|
||||
# Can optionally look elsewhere in the request (e.g. headers)
|
||||
try:
|
||||
user_id, app_service = yield self._get_appservice_user_id(request)
|
||||
if user_id:
|
||||
request.authenticated_entity = user_id
|
||||
defer.returnValue(
|
||||
synapse.types.create_requester(user_id, app_service=app_service)
|
||||
)
|
||||
ip_addr = self.hs.get_ip_from_request(request)
|
||||
user_agent = request.requestHeaders.getRawHeaders(
|
||||
b"User-Agent",
|
||||
default=[b""]
|
||||
)[0].decode('ascii', 'surrogateescape')
|
||||
|
||||
access_token = self.get_access_token_from_request(
|
||||
request, self.TOKEN_NOT_FOUND_HTTP_STATUS
|
||||
)
|
||||
|
||||
user_id, app_service = yield self._get_appservice_user_id(request)
|
||||
if user_id:
|
||||
request.authenticated_entity = user_id
|
||||
|
||||
if ip_addr and self.hs.config.track_appservice_user_ips:
|
||||
yield self.store.insert_client_ip(
|
||||
user_id=user_id,
|
||||
access_token=access_token,
|
||||
ip=ip_addr,
|
||||
user_agent=user_agent,
|
||||
device_id="dummy-device", # stubbed
|
||||
)
|
||||
|
||||
defer.returnValue(
|
||||
synapse.types.create_requester(user_id, app_service=app_service)
|
||||
)
|
||||
|
||||
user_info = yield self.get_user_by_access_token(access_token, rights)
|
||||
user = user_info["user"]
|
||||
token_id = user_info["token_id"]
|
||||
@@ -208,11 +224,6 @@ class Auth(object):
|
||||
# stubbed out.
|
||||
device_id = user_info.get("device_id")
|
||||
|
||||
ip_addr = self.hs.get_ip_from_request(request)
|
||||
user_agent = request.requestHeaders.getRawHeaders(
|
||||
b"User-Agent",
|
||||
default=[b""]
|
||||
)[0].decode('ascii', 'surrogateescape')
|
||||
if user and access_token and ip_addr:
|
||||
yield self.store.insert_client_ip(
|
||||
user_id=user.to_string(),
|
||||
|
||||
+20
-32
@@ -54,12 +54,13 @@ from synapse.metrics import RegistryProxy
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.metrics.resource import METRICS_PREFIX, MetricsResource
|
||||
from synapse.module_api import ModuleApi
|
||||
from synapse.python_dependencies import CONDITIONAL_REQUIREMENTS, check_requirements
|
||||
from synapse.python_dependencies import check_requirements
|
||||
from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource
|
||||
from synapse.replication.tcp.resource import ReplicationStreamProtocolFactory
|
||||
from synapse.rest import ClientRestResource
|
||||
from synapse.rest.key.v2 import KeyApiV2Resource
|
||||
from synapse.rest.media.v0.content_repository import ContentRepoResource
|
||||
from synapse.rest.well_known import WellKnownResource
|
||||
from synapse.server import HomeServer
|
||||
from synapse.storage import DataStore, are_all_users_on_domain
|
||||
from synapse.storage.engines import IncorrectDatabaseSetup, create_engine
|
||||
@@ -79,36 +80,6 @@ def gz_wrap(r):
|
||||
return EncodingResourceWrapper(r, [GzipEncoderFactory()])
|
||||
|
||||
|
||||
def build_resource_for_web_client(hs):
|
||||
webclient_path = hs.get_config().web_client_location
|
||||
if not webclient_path:
|
||||
try:
|
||||
import syweb
|
||||
except ImportError:
|
||||
quit_with_error(
|
||||
"Could not find a webclient.\n\n"
|
||||
"Please either install the matrix-angular-sdk or configure\n"
|
||||
"the location of the source to serve via the configuration\n"
|
||||
"option `web_client_location`\n\n"
|
||||
"To install the `matrix-angular-sdk` via pip, run:\n\n"
|
||||
" pip install '%(dep)s'\n"
|
||||
"\n"
|
||||
"You can also disable hosting of the webclient via the\n"
|
||||
"configuration option `web_client`\n"
|
||||
% {"dep": CONDITIONAL_REQUIREMENTS["web_client"].keys()[0]}
|
||||
)
|
||||
syweb_path = os.path.dirname(syweb.__file__)
|
||||
webclient_path = os.path.join(syweb_path, "webclient")
|
||||
# GZip is disabled here due to
|
||||
# https://twistedmatrix.com/trac/ticket/7678
|
||||
# (It can stay enabled for the API resources: they call
|
||||
# write() with the whole body and then finish() straight
|
||||
# after and so do not trigger the bug.
|
||||
# GzipFile was removed in commit 184ba09
|
||||
# return GzipFile(webclient_path) # TODO configurable?
|
||||
return File(webclient_path) # TODO configurable?
|
||||
|
||||
|
||||
class SynapseHomeServer(HomeServer):
|
||||
DATASTORE_CLASS = DataStore
|
||||
|
||||
@@ -137,8 +108,11 @@ class SynapseHomeServer(HomeServer):
|
||||
handler = handler_cls(config, module_api)
|
||||
resources[path] = AdditionalResource(self, handler.handle_request)
|
||||
|
||||
# try to find something useful to redirect '/' to
|
||||
if WEB_CLIENT_PREFIX in resources:
|
||||
root_resource = RootRedirect(WEB_CLIENT_PREFIX)
|
||||
elif STATIC_PREFIX in resources:
|
||||
root_resource = RootRedirect(STATIC_PREFIX)
|
||||
else:
|
||||
root_resource = NoResource()
|
||||
|
||||
@@ -195,8 +169,13 @@ class SynapseHomeServer(HomeServer):
|
||||
"/_matrix/client/unstable": client_resource,
|
||||
"/_matrix/client/v2_alpha": client_resource,
|
||||
"/_matrix/client/versions": client_resource,
|
||||
"/.well-known/matrix/client": WellKnownResource(self),
|
||||
})
|
||||
|
||||
if self.get_config().saml2_enabled:
|
||||
from synapse.rest.saml2 import SAML2Resource
|
||||
resources["/_matrix/saml2"] = SAML2Resource(self)
|
||||
|
||||
if name == "consent":
|
||||
from synapse.rest.consent.consent_resource import ConsentResource
|
||||
consent_resource = ConsentResource(self)
|
||||
@@ -237,7 +216,16 @@ class SynapseHomeServer(HomeServer):
|
||||
resources[SERVER_KEY_V2_PREFIX] = KeyApiV2Resource(self)
|
||||
|
||||
if name == "webclient":
|
||||
resources[WEB_CLIENT_PREFIX] = build_resource_for_web_client(self)
|
||||
webclient_path = self.get_config().web_client_location
|
||||
|
||||
if webclient_path is None:
|
||||
logger.warning(
|
||||
"Not enabling webclient resource, as web_client_location is unset."
|
||||
)
|
||||
else:
|
||||
# GZip is disabled here due to
|
||||
# https://twistedmatrix.com/trac/ticket/7678
|
||||
resources[WEB_CLIENT_PREFIX] = File(webclient_path)
|
||||
|
||||
if name == "metrics" and self.get_config().enable_metrics:
|
||||
resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
|
||||
|
||||
@@ -53,8 +53,8 @@ import logging
|
||||
from twisted.internet import defer
|
||||
|
||||
from synapse.appservice import ApplicationServiceState
|
||||
from synapse.metrics.background_process_metrics import run_as_background_process
|
||||
from synapse.util.logcontext import run_in_background
|
||||
from synapse.util.metrics import Measure
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -104,14 +104,23 @@ class _ServiceQueuer(object):
|
||||
self.clock = clock
|
||||
|
||||
def enqueue(self, service, event):
|
||||
# if this service isn't being sent something
|
||||
self.queued_events.setdefault(service.id, []).append(event)
|
||||
run_in_background(self._send_request, service)
|
||||
|
||||
# start a sender for this appservice if we don't already have one
|
||||
|
||||
if service.id in self.requests_in_flight:
|
||||
return
|
||||
|
||||
run_as_background_process(
|
||||
"as-sender-%s" % (service.id, ),
|
||||
self._send_request, service,
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _send_request(self, service):
|
||||
if service.id in self.requests_in_flight:
|
||||
return
|
||||
# sanity-check: we shouldn't get here if this service already has a sender
|
||||
# running.
|
||||
assert(service.id not in self.requests_in_flight)
|
||||
|
||||
self.requests_in_flight.add(service.id)
|
||||
try:
|
||||
@@ -119,12 +128,10 @@ class _ServiceQueuer(object):
|
||||
events = self.queued_events.pop(service.id, [])
|
||||
if not events:
|
||||
return
|
||||
|
||||
with Measure(self.clock, "servicequeuer.send"):
|
||||
try:
|
||||
yield self.txn_ctrl.send(service, events)
|
||||
except Exception:
|
||||
logger.exception("AS request failed")
|
||||
try:
|
||||
yield self.txn_ctrl.send(service, events)
|
||||
except Exception:
|
||||
logger.exception("AS request failed")
|
||||
finally:
|
||||
self.requests_in_flight.discard(service.id)
|
||||
|
||||
@@ -223,7 +230,12 @@ class _Recoverer(object):
|
||||
self.backoff_counter = 1
|
||||
|
||||
def recover(self):
|
||||
self.clock.call_later((2 ** self.backoff_counter), self.retry)
|
||||
def _retry():
|
||||
run_as_background_process(
|
||||
"as-recoverer-%s" % (self.service.id,),
|
||||
self.retry,
|
||||
)
|
||||
self.clock.call_later((2 ** self.backoff_counter), _retry)
|
||||
|
||||
def _backoff(self):
|
||||
# cap the backoff to be around 8.5min => (2^9) = 512 secs
|
||||
|
||||
@@ -33,11 +33,16 @@ class AppServiceConfig(Config):
|
||||
def read_config(self, config):
|
||||
self.app_service_config_files = config.get("app_service_config_files", [])
|
||||
self.notify_appservices = config.get("notify_appservices", True)
|
||||
self.track_appservice_user_ips = config.get("track_appservice_user_ips", False)
|
||||
|
||||
def default_config(cls, **kwargs):
|
||||
return """\
|
||||
# A list of application service config file to use
|
||||
app_service_config_files: []
|
||||
|
||||
# Whether or not to track application service IP addresses. Implicitly
|
||||
# enables MAU tracking for application service users.
|
||||
track_appservice_user_ips: False
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ from .ratelimiting import RatelimitConfig
|
||||
from .registration import RegistrationConfig
|
||||
from .repository import ContentRepositoryConfig
|
||||
from .room_directory import RoomDirectoryConfig
|
||||
from .saml2 import SAML2Config
|
||||
from .saml2_config import SAML2Config
|
||||
from .server import ServerConfig
|
||||
from .server_notices_config import ServerNoticesConfig
|
||||
from .spam_checker import SpamCheckerConfig
|
||||
|
||||
@@ -37,6 +37,7 @@ class RegistrationConfig(Config):
|
||||
|
||||
self.bcrypt_rounds = config.get("bcrypt_rounds", 12)
|
||||
self.trusted_third_party_id_servers = config["trusted_third_party_id_servers"]
|
||||
self.default_identity_server = config.get("default_identity_server")
|
||||
self.allow_guest_access = config.get("allow_guest_access", False)
|
||||
|
||||
self.invite_3pid_guest = (
|
||||
@@ -91,6 +92,14 @@ class RegistrationConfig(Config):
|
||||
# accessible to anonymous users.
|
||||
allow_guest_access: False
|
||||
|
||||
# The identity server which we suggest that clients should use when users log
|
||||
# in on this server.
|
||||
#
|
||||
# (By default, no suggestion is made, so it is left up to the client.
|
||||
# This setting is ignored unless public_baseurl is also set.)
|
||||
#
|
||||
# default_identity_server: https://matrix.org
|
||||
|
||||
# The list of identity servers trusted to verify third party
|
||||
# identifiers by this server.
|
||||
#
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2015 Ericsson
|
||||
#
|
||||
# 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 ._base import Config
|
||||
|
||||
|
||||
class SAML2Config(Config):
|
||||
"""SAML2 Configuration
|
||||
Synapse uses pysaml2 libraries for providing SAML2 support
|
||||
|
||||
config_path: Path to the sp_conf.py configuration file
|
||||
idp_redirect_url: Identity provider URL which will redirect
|
||||
the user back to /login/saml2 with proper info.
|
||||
|
||||
sp_conf.py file is something like:
|
||||
https://github.com/rohe/pysaml2/blob/master/example/sp-repoze/sp_conf.py.example
|
||||
|
||||
More information: https://pythonhosted.org/pysaml2/howto/config.html
|
||||
"""
|
||||
|
||||
def read_config(self, config):
|
||||
saml2_config = config.get("saml2_config", None)
|
||||
if saml2_config:
|
||||
self.saml2_enabled = saml2_config.get("enabled", True)
|
||||
self.saml2_config_path = saml2_config["config_path"]
|
||||
self.saml2_idp_redirect_url = saml2_config["idp_redirect_url"]
|
||||
else:
|
||||
self.saml2_enabled = False
|
||||
self.saml2_config_path = None
|
||||
self.saml2_idp_redirect_url = None
|
||||
|
||||
def default_config(self, config_dir_path, server_name, **kwargs):
|
||||
return """
|
||||
# Enable SAML2 for registration and login. Uses pysaml2
|
||||
# config_path: Path to the sp_conf.py configuration file
|
||||
# idp_redirect_url: Identity provider URL which will redirect
|
||||
# the user back to /login/saml2 with proper info.
|
||||
# See pysaml2 docs for format of config.
|
||||
#saml2_config:
|
||||
# enabled: true
|
||||
# config_path: "%s/sp_conf.py"
|
||||
# idp_redirect_url: "http://%s/idp"
|
||||
""" % (config_dir_path, server_name)
|
||||
@@ -0,0 +1,110 @@
|
||||
# -*- 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.
|
||||
|
||||
from ._base import Config, ConfigError
|
||||
|
||||
|
||||
class SAML2Config(Config):
|
||||
def read_config(self, config):
|
||||
self.saml2_enabled = False
|
||||
|
||||
saml2_config = config.get("saml2_config")
|
||||
|
||||
if not saml2_config or not saml2_config.get("enabled", True):
|
||||
return
|
||||
|
||||
self.saml2_enabled = True
|
||||
|
||||
import saml2.config
|
||||
self.saml2_sp_config = saml2.config.SPConfig()
|
||||
self.saml2_sp_config.load(self._default_saml_config_dict())
|
||||
self.saml2_sp_config.load(saml2_config.get("sp_config", {}))
|
||||
|
||||
config_path = saml2_config.get("config_path", None)
|
||||
if config_path is not None:
|
||||
self.saml2_sp_config.load_file(config_path)
|
||||
|
||||
def _default_saml_config_dict(self):
|
||||
import saml2
|
||||
|
||||
public_baseurl = self.public_baseurl
|
||||
if public_baseurl is None:
|
||||
raise ConfigError(
|
||||
"saml2_config requires a public_baseurl to be set"
|
||||
)
|
||||
|
||||
metadata_url = public_baseurl + "_matrix/saml2/metadata.xml"
|
||||
response_url = public_baseurl + "_matrix/saml2/authn_response"
|
||||
return {
|
||||
"entityid": metadata_url,
|
||||
|
||||
"service": {
|
||||
"sp": {
|
||||
"endpoints": {
|
||||
"assertion_consumer_service": [
|
||||
(response_url, saml2.BINDING_HTTP_POST),
|
||||
],
|
||||
},
|
||||
"required_attributes": ["uid"],
|
||||
"optional_attributes": ["mail", "surname", "givenname"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
def default_config(self, config_dir_path, server_name, **kwargs):
|
||||
return """
|
||||
# Enable SAML2 for registration and login. Uses pysaml2.
|
||||
#
|
||||
# saml2_config:
|
||||
#
|
||||
# # The following is the configuration for the pysaml2 Service Provider.
|
||||
# # See pysaml2 docs for format of config.
|
||||
# #
|
||||
# # Default values will be used for the 'entityid' and 'service' settings,
|
||||
# # so it is not normally necessary to specify them unless you need to
|
||||
# # override them.
|
||||
#
|
||||
# sp_config:
|
||||
# # point this to the IdP's metadata. You can use either a local file or
|
||||
# # (preferably) a URL.
|
||||
# metadata:
|
||||
# # local: ["saml2/idp.xml"]
|
||||
# remote:
|
||||
# - url: https://our_idp/metadata.xml
|
||||
#
|
||||
# # The following is just used to generate our metadata xml, and you
|
||||
# # may well not need it, depending on your setup. Alternatively you
|
||||
# # may need a whole lot more detail - see the pysaml2 docs!
|
||||
#
|
||||
# description: ["My awesome SP", "en"]
|
||||
# name: ["Test SP", "en"]
|
||||
#
|
||||
# organization:
|
||||
# name: Example com
|
||||
# display_name:
|
||||
# - ["Example co", "en"]
|
||||
# url: "http://example.com"
|
||||
#
|
||||
# contact_person:
|
||||
# - given_name: Bob
|
||||
# sur_name: "the Sysadmin"
|
||||
# email_address": ["admin@example.com"]
|
||||
# contact_type": technical
|
||||
#
|
||||
# # Instead of putting the config inline as above, you can specify a
|
||||
# # separate pysaml2 configuration file:
|
||||
# #
|
||||
# # config_path: "%(config_dir_path)s/sp_conf.py"
|
||||
""" % {"config_dir_path": config_dir_path}
|
||||
+38
-16
@@ -34,7 +34,6 @@ class ServerConfig(Config):
|
||||
raise ConfigError(str(e))
|
||||
|
||||
self.pid_file = self.abspath(config.get("pid_file"))
|
||||
self.web_client = config["web_client"]
|
||||
self.web_client_location = config.get("web_client_location", None)
|
||||
self.soft_file_limit = config["soft_file_limit"]
|
||||
self.daemonize = config.get("daemonize")
|
||||
@@ -62,6 +61,11 @@ class ServerConfig(Config):
|
||||
# master, potentially causing inconsistency.
|
||||
self.enable_media_repo = config.get("enable_media_repo", True)
|
||||
|
||||
# whether to enable search. If disabled, new entries will not be inserted
|
||||
# into the search tables and they will not be indexed. Users will receive
|
||||
# errors when attempting to search for messages.
|
||||
self.enable_search = config.get("enable_search", True)
|
||||
|
||||
self.filter_timeline_limit = config.get("filter_timeline_limit", -1)
|
||||
|
||||
# Whether we should block invites sent to users on this server
|
||||
@@ -123,6 +127,9 @@ class ServerConfig(Config):
|
||||
elif not bind_addresses:
|
||||
bind_addresses.append('')
|
||||
|
||||
if not self.web_client_location:
|
||||
_warn_if_webclient_configured(self.listeners)
|
||||
|
||||
self.gc_thresholds = read_gc_thresholds(config.get("gc_thresholds", None))
|
||||
|
||||
bind_port = config.get("bind_port")
|
||||
@@ -131,8 +138,6 @@ class ServerConfig(Config):
|
||||
bind_host = config.get("bind_host", "")
|
||||
gzip_responses = config.get("gzip_responses", True)
|
||||
|
||||
names = ["client", "webclient"] if self.web_client else ["client"]
|
||||
|
||||
self.listeners.append({
|
||||
"port": bind_port,
|
||||
"bind_addresses": [bind_host],
|
||||
@@ -140,7 +145,7 @@ class ServerConfig(Config):
|
||||
"type": "http",
|
||||
"resources": [
|
||||
{
|
||||
"names": names,
|
||||
"names": ["client"],
|
||||
"compress": gzip_responses,
|
||||
},
|
||||
{
|
||||
@@ -159,7 +164,7 @@ class ServerConfig(Config):
|
||||
"type": "http",
|
||||
"resources": [
|
||||
{
|
||||
"names": names,
|
||||
"names": ["client"],
|
||||
"compress": gzip_responses,
|
||||
},
|
||||
{
|
||||
@@ -242,13 +247,9 @@ class ServerConfig(Config):
|
||||
#
|
||||
# cpu_affinity: 0xFFFFFFFF
|
||||
|
||||
# Whether to serve a web client from the HTTP/HTTPS root resource.
|
||||
web_client: True
|
||||
|
||||
# The root directory to server for the above web client.
|
||||
# If left undefined, synapse will serve the matrix-angular-sdk web client.
|
||||
# Make sure matrix-angular-sdk is installed with pip if web_client is True
|
||||
# and web_client_location is undefined
|
||||
# The path to the web client which will be served at /_matrix/client/
|
||||
# if 'webclient' is configured under the 'listeners' configuration.
|
||||
#
|
||||
# web_client_location: "/path/to/web/root"
|
||||
|
||||
# The public-facing base URL for the client API (not including _matrix/...)
|
||||
@@ -315,8 +316,8 @@ class ServerConfig(Config):
|
||||
-
|
||||
# List of resources to host on this listener.
|
||||
names:
|
||||
- client # The client-server APIs, both v1 and v2
|
||||
- webclient # The bundled webclient.
|
||||
- client # The client-server APIs, both v1 and v2
|
||||
# - webclient # A web client. Requires web_client_location to be set.
|
||||
|
||||
# Should synapse compress HTTP responses to clients that support it?
|
||||
# This should be disabled if running synapse behind a load balancer
|
||||
@@ -343,7 +344,7 @@ class ServerConfig(Config):
|
||||
x_forwarded: false
|
||||
|
||||
resources:
|
||||
- names: [client, webclient]
|
||||
- names: [client]
|
||||
compress: true
|
||||
- names: [federation]
|
||||
compress: false
|
||||
@@ -384,7 +385,12 @@ class ServerConfig(Config):
|
||||
# mau_limit_reserved_threepids:
|
||||
# - medium: 'email'
|
||||
# address: 'reserved_user@example.com'
|
||||
|
||||
#
|
||||
# Room searching
|
||||
#
|
||||
# If disabled, new messages will not be indexed for searching and users
|
||||
# will receive errors when searching for messages. Defaults to enabled.
|
||||
# enable_search: true
|
||||
""" % locals()
|
||||
|
||||
def read_arguments(self, args):
|
||||
@@ -442,3 +448,19 @@ def read_gc_thresholds(thresholds):
|
||||
raise ConfigError(
|
||||
"Value of `gc_threshold` must be a list of three integers if set"
|
||||
)
|
||||
|
||||
|
||||
NO_MORE_WEB_CLIENT_WARNING = """
|
||||
Synapse no longer includes a web client. To enable a web client, configure
|
||||
web_client_location. To remove this warning, remove 'webclient' from the 'listeners'
|
||||
configuration.
|
||||
"""
|
||||
|
||||
|
||||
def _warn_if_webclient_configured(listeners):
|
||||
for listener in listeners:
|
||||
for res in listener.get("resources", []):
|
||||
for name in res.get("names", []):
|
||||
if name == 'webclient':
|
||||
logger.warning(NO_MORE_WEB_CLIENT_WARNING)
|
||||
return
|
||||
|
||||
@@ -563,10 +563,10 @@ class AuthHandler(BaseHandler):
|
||||
insensitively, but return None if there are multiple inexact matches.
|
||||
|
||||
Args:
|
||||
(str) user_id: complete @user:id
|
||||
(unicode|bytes) user_id: complete @user:id
|
||||
|
||||
Returns:
|
||||
defer.Deferred: (str) canonical_user_id, or None if zero or
|
||||
defer.Deferred: (unicode) canonical_user_id, or None if zero or
|
||||
multiple matches
|
||||
"""
|
||||
res = yield self._find_user_id_and_pwd_hash(user_id)
|
||||
@@ -954,6 +954,15 @@ class MacaroonGenerator(object):
|
||||
return macaroon.serialize()
|
||||
|
||||
def generate_short_term_login_token(self, user_id, duration_in_ms=(2 * 60 * 1000)):
|
||||
"""
|
||||
|
||||
Args:
|
||||
user_id (unicode):
|
||||
duration_in_ms (int):
|
||||
|
||||
Returns:
|
||||
unicode
|
||||
"""
|
||||
macaroon = self._generate_base_macaroon(user_id)
|
||||
macaroon.add_first_party_caveat("type = login")
|
||||
now = self.hs.get_clock().time_msec()
|
||||
|
||||
@@ -126,6 +126,7 @@ class RegistrationHandler(BaseHandler):
|
||||
make_guest=False,
|
||||
admin=False,
|
||||
threepid=None,
|
||||
default_display_name=None,
|
||||
):
|
||||
"""Registers a new client on the server.
|
||||
|
||||
@@ -140,6 +141,8 @@ class RegistrationHandler(BaseHandler):
|
||||
since it offers no means of associating a device_id with the
|
||||
access_token. Instead you should call auth_handler.issue_access_token
|
||||
after registration.
|
||||
default_display_name (unicode|None): if set, the new user's displayname
|
||||
will be set to this. Defaults to 'localpart'.
|
||||
Returns:
|
||||
A tuple of (user_id, access_token).
|
||||
Raises:
|
||||
@@ -169,6 +172,13 @@ class RegistrationHandler(BaseHandler):
|
||||
user = UserID(localpart, self.hs.hostname)
|
||||
user_id = user.to_string()
|
||||
|
||||
if was_guest:
|
||||
# If the user was a guest then they already have a profile
|
||||
default_display_name = None
|
||||
|
||||
elif default_display_name is None:
|
||||
default_display_name = localpart
|
||||
|
||||
token = None
|
||||
if generate_token:
|
||||
token = self.macaroon_gen.generate_access_token(user_id)
|
||||
@@ -178,10 +188,7 @@ class RegistrationHandler(BaseHandler):
|
||||
password_hash=password_hash,
|
||||
was_guest=was_guest,
|
||||
make_guest=make_guest,
|
||||
create_profile_with_localpart=(
|
||||
# If the user was a guest then they already have a profile
|
||||
None if was_guest else user.localpart
|
||||
),
|
||||
create_profile_with_displayname=default_display_name,
|
||||
admin=admin,
|
||||
)
|
||||
|
||||
@@ -203,13 +210,15 @@ class RegistrationHandler(BaseHandler):
|
||||
yield self.check_user_id_not_appservice_exclusive(user_id)
|
||||
if generate_token:
|
||||
token = self.macaroon_gen.generate_access_token(user_id)
|
||||
if default_display_name is None:
|
||||
default_display_name = localpart
|
||||
try:
|
||||
yield self.store.register(
|
||||
user_id=user_id,
|
||||
token=token,
|
||||
password_hash=password_hash,
|
||||
make_guest=make_guest,
|
||||
create_profile_with_localpart=user.localpart,
|
||||
create_profile_with_displayname=default_display_name,
|
||||
)
|
||||
except SynapseError:
|
||||
# if user id is taken, just generate another
|
||||
@@ -300,7 +309,7 @@ class RegistrationHandler(BaseHandler):
|
||||
user_id=user_id,
|
||||
password_hash="",
|
||||
appservice_id=service_id,
|
||||
create_profile_with_localpart=user.localpart,
|
||||
create_profile_with_displayname=user.localpart,
|
||||
)
|
||||
defer.returnValue(user_id)
|
||||
|
||||
@@ -327,35 +336,6 @@ class RegistrationHandler(BaseHandler):
|
||||
else:
|
||||
logger.info("Valid captcha entered from %s", ip)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def register_saml2(self, localpart):
|
||||
"""
|
||||
Registers email_id as SAML2 Based Auth.
|
||||
"""
|
||||
if types.contains_invalid_mxid_characters(localpart):
|
||||
raise SynapseError(
|
||||
400,
|
||||
"User ID can only contain characters a-z, 0-9, or '=_-./'",
|
||||
)
|
||||
yield self.auth.check_auth_blocking()
|
||||
user = UserID(localpart, self.hs.hostname)
|
||||
user_id = user.to_string()
|
||||
|
||||
yield self.check_user_id_not_appservice_exclusive(user_id)
|
||||
token = self.macaroon_gen.generate_access_token(user_id)
|
||||
try:
|
||||
yield self.store.register(
|
||||
user_id=user_id,
|
||||
token=token,
|
||||
password_hash=None,
|
||||
create_profile_with_localpart=user.localpart,
|
||||
)
|
||||
except Exception as e:
|
||||
yield self.store.add_access_token_to_user(user_id, token)
|
||||
# Ignore Registration errors
|
||||
logger.exception(e)
|
||||
defer.returnValue((user_id, token))
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def register_email(self, threepidCreds):
|
||||
"""
|
||||
@@ -507,7 +487,7 @@ class RegistrationHandler(BaseHandler):
|
||||
user_id=user_id,
|
||||
token=token,
|
||||
password_hash=password_hash,
|
||||
create_profile_with_localpart=user.localpart,
|
||||
create_profile_with_displayname=user.localpart,
|
||||
)
|
||||
else:
|
||||
yield self._auth_handler.delete_access_tokens_for_user(user_id)
|
||||
|
||||
@@ -50,6 +50,9 @@ class SearchHandler(BaseHandler):
|
||||
dict to be returned to the client with results of search
|
||||
"""
|
||||
|
||||
if not self.hs.config.enable_search:
|
||||
raise SynapseError(400, "Search is disabled on this homeserver")
|
||||
|
||||
batch_group = None
|
||||
batch_group_key = None
|
||||
batch_token = None
|
||||
|
||||
@@ -53,7 +53,6 @@ REQUIREMENTS = {
|
||||
"pillow>=3.1.2": ["PIL"],
|
||||
"sortedcontainers>=1.4.4": ["sortedcontainers"],
|
||||
"psutil>=2.0.0": ["psutil>=2.0.0"],
|
||||
"pysaml2>=3.0.0": ["saml2"],
|
||||
"pymacaroons-pynacl>=0.9.3": ["pymacaroons"],
|
||||
"msgpack-python>=0.4.2": ["msgpack"],
|
||||
"phonenumbers>=8.2.0": ["phonenumbers"],
|
||||
@@ -69,9 +68,6 @@ REQUIREMENTS = {
|
||||
}
|
||||
|
||||
CONDITIONAL_REQUIREMENTS = {
|
||||
"web_client": {
|
||||
"matrix_angular_sdk>=0.6.8": ["syweb>=0.6.8"],
|
||||
},
|
||||
"email.enable_notifs": {
|
||||
"Jinja2>=2.8": ["Jinja2>=2.8"],
|
||||
"bleach>=1.4.2": ["bleach>=1.4.2"],
|
||||
@@ -81,7 +77,10 @@ CONDITIONAL_REQUIREMENTS = {
|
||||
},
|
||||
"postgres": {
|
||||
"psycopg2>=2.6": ["psycopg2"]
|
||||
}
|
||||
},
|
||||
"saml2": {
|
||||
"pysaml2>=4.5.0": ["saml2"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,17 +18,17 @@ import xml.etree.ElementTree as ET
|
||||
|
||||
from six.moves import urllib
|
||||
|
||||
from canonicaljson import json
|
||||
from saml2 import BINDING_HTTP_POST, config
|
||||
from saml2.client import Saml2Client
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.web.client import PartialDownloadError
|
||||
|
||||
from synapse.api.errors import Codes, LoginError, SynapseError
|
||||
from synapse.http.server import finish_request
|
||||
from synapse.http.servlet import RestServlet, parse_json_object_from_request
|
||||
from synapse.types import UserID
|
||||
from synapse.http.servlet import (
|
||||
RestServlet,
|
||||
parse_json_object_from_request,
|
||||
parse_string,
|
||||
)
|
||||
from synapse.types import UserID, map_username_to_mxid_localpart
|
||||
from synapse.util.msisdn import phone_number_to_msisdn
|
||||
|
||||
from .base import ClientV1RestServlet, client_path_patterns
|
||||
@@ -81,7 +81,6 @@ def login_id_thirdparty_from_phone(identifier):
|
||||
|
||||
class LoginRestServlet(ClientV1RestServlet):
|
||||
PATTERNS = client_path_patterns("/login$")
|
||||
SAML2_TYPE = "m.login.saml2"
|
||||
CAS_TYPE = "m.login.cas"
|
||||
SSO_TYPE = "m.login.sso"
|
||||
TOKEN_TYPE = "m.login.token"
|
||||
@@ -89,8 +88,6 @@ class LoginRestServlet(ClientV1RestServlet):
|
||||
|
||||
def __init__(self, hs):
|
||||
super(LoginRestServlet, self).__init__(hs)
|
||||
self.idp_redirect_url = hs.config.saml2_idp_redirect_url
|
||||
self.saml2_enabled = hs.config.saml2_enabled
|
||||
self.jwt_enabled = hs.config.jwt_enabled
|
||||
self.jwt_secret = hs.config.jwt_secret
|
||||
self.jwt_algorithm = hs.config.jwt_algorithm
|
||||
@@ -103,8 +100,6 @@ class LoginRestServlet(ClientV1RestServlet):
|
||||
flows = []
|
||||
if self.jwt_enabled:
|
||||
flows.append({"type": LoginRestServlet.JWT_TYPE})
|
||||
if self.saml2_enabled:
|
||||
flows.append({"type": LoginRestServlet.SAML2_TYPE})
|
||||
if self.cas_enabled:
|
||||
flows.append({"type": LoginRestServlet.SSO_TYPE})
|
||||
|
||||
@@ -134,18 +129,8 @@ class LoginRestServlet(ClientV1RestServlet):
|
||||
def on_POST(self, request):
|
||||
login_submission = parse_json_object_from_request(request)
|
||||
try:
|
||||
if self.saml2_enabled and (login_submission["type"] ==
|
||||
LoginRestServlet.SAML2_TYPE):
|
||||
relay_state = ""
|
||||
if "relay_state" in login_submission:
|
||||
relay_state = "&RelayState=" + urllib.parse.quote(
|
||||
login_submission["relay_state"])
|
||||
result = {
|
||||
"uri": "%s%s" % (self.idp_redirect_url, relay_state)
|
||||
}
|
||||
defer.returnValue((200, result))
|
||||
elif self.jwt_enabled and (login_submission["type"] ==
|
||||
LoginRestServlet.JWT_TYPE):
|
||||
if self.jwt_enabled and (login_submission["type"] ==
|
||||
LoginRestServlet.JWT_TYPE):
|
||||
result = yield self.do_jwt_login(login_submission)
|
||||
defer.returnValue(result)
|
||||
elif login_submission["type"] == LoginRestServlet.TOKEN_TYPE:
|
||||
@@ -345,50 +330,6 @@ class LoginRestServlet(ClientV1RestServlet):
|
||||
)
|
||||
|
||||
|
||||
class SAML2RestServlet(ClientV1RestServlet):
|
||||
PATTERNS = client_path_patterns("/login/saml2", releases=())
|
||||
|
||||
def __init__(self, hs):
|
||||
super(SAML2RestServlet, self).__init__(hs)
|
||||
self.sp_config = hs.config.saml2_config_path
|
||||
self.handlers = hs.get_handlers()
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def on_POST(self, request):
|
||||
saml2_auth = None
|
||||
try:
|
||||
conf = config.SPConfig()
|
||||
conf.load_file(self.sp_config)
|
||||
SP = Saml2Client(conf)
|
||||
saml2_auth = SP.parse_authn_request_response(
|
||||
request.args['SAMLResponse'][0], BINDING_HTTP_POST)
|
||||
except Exception as e: # Not authenticated
|
||||
logger.exception(e)
|
||||
if saml2_auth and saml2_auth.status_ok() and not saml2_auth.not_signed:
|
||||
username = saml2_auth.name_id.text
|
||||
handler = self.handlers.registration_handler
|
||||
(user_id, token) = yield handler.register_saml2(username)
|
||||
# Forward to the RelayState callback along with ava
|
||||
if 'RelayState' in request.args:
|
||||
request.redirect(urllib.parse.unquote(
|
||||
request.args['RelayState'][0]) +
|
||||
'?status=authenticated&access_token=' +
|
||||
token + '&user_id=' + user_id + '&ava=' +
|
||||
urllib.quote(json.dumps(saml2_auth.ava)))
|
||||
finish_request(request)
|
||||
defer.returnValue(None)
|
||||
defer.returnValue((200, {"status": "authenticated",
|
||||
"user_id": user_id, "token": token,
|
||||
"ava": saml2_auth.ava}))
|
||||
elif 'RelayState' in request.args:
|
||||
request.redirect(urllib.parse.unquote(
|
||||
request.args['RelayState'][0]) +
|
||||
'?status=not_authenticated')
|
||||
finish_request(request)
|
||||
defer.returnValue(None)
|
||||
defer.returnValue((200, {"status": "not_authenticated"}))
|
||||
|
||||
|
||||
class CasRedirectServlet(RestServlet):
|
||||
PATTERNS = client_path_patterns("/login/(cas|sso)/redirect")
|
||||
|
||||
@@ -421,17 +362,15 @@ class CasTicketServlet(ClientV1RestServlet):
|
||||
self.cas_server_url = hs.config.cas_server_url
|
||||
self.cas_service_url = hs.config.cas_service_url
|
||||
self.cas_required_attributes = hs.config.cas_required_attributes
|
||||
self.auth_handler = hs.get_auth_handler()
|
||||
self.handlers = hs.get_handlers()
|
||||
self.macaroon_gen = hs.get_macaroon_generator()
|
||||
self._sso_auth_handler = SSOAuthHandler(hs)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def on_GET(self, request):
|
||||
client_redirect_url = request.args[b"redirectUrl"][0]
|
||||
client_redirect_url = parse_string(request, "redirectUrl", required=True)
|
||||
http_client = self.hs.get_simple_http_client()
|
||||
uri = self.cas_server_url + "/proxyValidate"
|
||||
args = {
|
||||
"ticket": request.args[b"ticket"][0].decode('ascii'),
|
||||
"ticket": parse_string(request, "ticket", required=True),
|
||||
"service": self.cas_service_url
|
||||
}
|
||||
try:
|
||||
@@ -443,7 +382,6 @@ class CasTicketServlet(ClientV1RestServlet):
|
||||
result = yield self.handle_cas_response(request, body, client_redirect_url)
|
||||
defer.returnValue(result)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def handle_cas_response(self, request, cas_response_body, client_redirect_url):
|
||||
user, attributes = self.parse_cas_response(cas_response_body)
|
||||
|
||||
@@ -459,28 +397,9 @@ class CasTicketServlet(ClientV1RestServlet):
|
||||
if required_value != actual_value:
|
||||
raise LoginError(401, "Unauthorized", errcode=Codes.UNAUTHORIZED)
|
||||
|
||||
user_id = UserID(user, self.hs.hostname).to_string()
|
||||
auth_handler = self.auth_handler
|
||||
registered_user_id = yield auth_handler.check_user_exists(user_id)
|
||||
if not registered_user_id:
|
||||
registered_user_id, _ = (
|
||||
yield self.handlers.registration_handler.register(localpart=user)
|
||||
)
|
||||
|
||||
login_token = self.macaroon_gen.generate_short_term_login_token(
|
||||
registered_user_id
|
||||
return self._sso_auth_handler.on_successful_auth(
|
||||
user, request, client_redirect_url,
|
||||
)
|
||||
redirect_url = self.add_login_token_to_redirect_url(client_redirect_url,
|
||||
login_token)
|
||||
request.redirect(redirect_url)
|
||||
finish_request(request)
|
||||
|
||||
def add_login_token_to_redirect_url(self, url, token):
|
||||
url_parts = list(urllib.parse.urlparse(url))
|
||||
query = dict(urllib.parse.parse_qsl(url_parts[4]))
|
||||
query.update({"loginToken": token})
|
||||
url_parts[4] = urllib.parse.urlencode(query).encode('ascii')
|
||||
return urllib.parse.urlunparse(url_parts)
|
||||
|
||||
def parse_cas_response(self, cas_response_body):
|
||||
user = None
|
||||
@@ -515,10 +434,78 @@ class CasTicketServlet(ClientV1RestServlet):
|
||||
return user, attributes
|
||||
|
||||
|
||||
class SSOAuthHandler(object):
|
||||
"""
|
||||
Utility class for Resources and Servlets which handle the response from a SSO
|
||||
service
|
||||
|
||||
Args:
|
||||
hs (synapse.server.HomeServer)
|
||||
"""
|
||||
def __init__(self, hs):
|
||||
self._hostname = hs.hostname
|
||||
self._auth_handler = hs.get_auth_handler()
|
||||
self._registration_handler = hs.get_handlers().registration_handler
|
||||
self._macaroon_gen = hs.get_macaroon_generator()
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def on_successful_auth(
|
||||
self, username, request, client_redirect_url,
|
||||
user_display_name=None,
|
||||
):
|
||||
"""Called once the user has successfully authenticated with the SSO.
|
||||
|
||||
Registers the user if necessary, and then returns a redirect (with
|
||||
a login token) to the client.
|
||||
|
||||
Args:
|
||||
username (unicode|bytes): the remote user id. We'll map this onto
|
||||
something sane for a MXID localpath.
|
||||
|
||||
request (SynapseRequest): the incoming request from the browser. We'll
|
||||
respond to it with a redirect.
|
||||
|
||||
client_redirect_url (unicode): the redirect_url the client gave us when
|
||||
it first started the process.
|
||||
|
||||
user_display_name (unicode|None): if set, and we have to register a new user,
|
||||
we will set their displayname to this.
|
||||
|
||||
Returns:
|
||||
Deferred[none]: Completes once we have handled the request.
|
||||
"""
|
||||
localpart = map_username_to_mxid_localpart(username)
|
||||
user_id = UserID(localpart, self._hostname).to_string()
|
||||
registered_user_id = yield self._auth_handler.check_user_exists(user_id)
|
||||
if not registered_user_id:
|
||||
registered_user_id, _ = (
|
||||
yield self._registration_handler.register(
|
||||
localpart=localpart,
|
||||
generate_token=False,
|
||||
default_display_name=user_display_name,
|
||||
)
|
||||
)
|
||||
|
||||
login_token = self._macaroon_gen.generate_short_term_login_token(
|
||||
registered_user_id
|
||||
)
|
||||
redirect_url = self._add_login_token_to_redirect_url(
|
||||
client_redirect_url, login_token
|
||||
)
|
||||
request.redirect(redirect_url)
|
||||
finish_request(request)
|
||||
|
||||
@staticmethod
|
||||
def _add_login_token_to_redirect_url(url, token):
|
||||
url_parts = list(urllib.parse.urlparse(url))
|
||||
query = dict(urllib.parse.parse_qsl(url_parts[4]))
|
||||
query.update({"loginToken": token})
|
||||
url_parts[4] = urllib.parse.urlencode(query)
|
||||
return urllib.parse.urlunparse(url_parts)
|
||||
|
||||
|
||||
def register_servlets(hs, http_server):
|
||||
LoginRestServlet(hs).register(http_server)
|
||||
if hs.config.saml2_enabled:
|
||||
SAML2RestServlet(hs).register(http_server)
|
||||
if hs.config.cas_enabled:
|
||||
CasRedirectServlet(hs).register(http_server)
|
||||
CasTicketServlet(hs).register(http_server)
|
||||
|
||||
@@ -42,7 +42,7 @@ class PushRuleRestServlet(ClientV1RestServlet):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def on_PUT(self, request):
|
||||
spec = _rule_spec_from_path(request.postpath)
|
||||
spec = _rule_spec_from_path([x.decode('utf8') for x in request.postpath])
|
||||
try:
|
||||
priority_class = _priority_class_from_spec(spec)
|
||||
except InvalidRuleException as e:
|
||||
@@ -103,7 +103,7 @@ class PushRuleRestServlet(ClientV1RestServlet):
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def on_DELETE(self, request):
|
||||
spec = _rule_spec_from_path(request.postpath)
|
||||
spec = _rule_spec_from_path([x.decode('utf8') for x in request.postpath])
|
||||
|
||||
requester = yield self.auth.get_user_by_req(request)
|
||||
user_id = requester.user.to_string()
|
||||
@@ -134,7 +134,7 @@ class PushRuleRestServlet(ClientV1RestServlet):
|
||||
|
||||
rules = format_push_rules_for_user(requester.user, rules)
|
||||
|
||||
path = request.postpath[1:]
|
||||
path = [x.decode('utf8') for x in request.postpath][1:]
|
||||
|
||||
if path == []:
|
||||
# we're a reference impl: pedantry is our job.
|
||||
@@ -142,11 +142,10 @@ class PushRuleRestServlet(ClientV1RestServlet):
|
||||
PushRuleRestServlet.SLIGHTLY_PEDANTIC_TRAILING_SLASH_ERROR
|
||||
)
|
||||
|
||||
if path[0] == b'':
|
||||
if path[0] == '':
|
||||
defer.returnValue((200, rules))
|
||||
elif path[0] == b'global':
|
||||
path = [x.decode('ascii') for x in path[1:]]
|
||||
result = _filter_ruleset_with_path(rules['global'], path)
|
||||
elif path[0] == 'global':
|
||||
result = _filter_ruleset_with_path(rules['global'], path[1:])
|
||||
defer.returnValue((200, result))
|
||||
else:
|
||||
raise UnrecognizedRequestError()
|
||||
@@ -190,12 +189,24 @@ class PushRuleRestServlet(ClientV1RestServlet):
|
||||
|
||||
|
||||
def _rule_spec_from_path(path):
|
||||
"""Turn a sequence of path components into a rule spec
|
||||
|
||||
Args:
|
||||
path (sequence[unicode]): the URL path components.
|
||||
|
||||
Returns:
|
||||
dict: rule spec dict, containing scope/template/rule_id entries,
|
||||
and possibly attr.
|
||||
|
||||
Raises:
|
||||
UnrecognizedRequestError if the path components cannot be parsed.
|
||||
"""
|
||||
if len(path) < 2:
|
||||
raise UnrecognizedRequestError()
|
||||
if path[0] != b'pushrules':
|
||||
if path[0] != 'pushrules':
|
||||
raise UnrecognizedRequestError()
|
||||
|
||||
scope = path[1].decode('ascii')
|
||||
scope = path[1]
|
||||
path = path[2:]
|
||||
if scope != 'global':
|
||||
raise UnrecognizedRequestError()
|
||||
@@ -203,13 +214,13 @@ def _rule_spec_from_path(path):
|
||||
if len(path) == 0:
|
||||
raise UnrecognizedRequestError()
|
||||
|
||||
template = path[0].decode('ascii')
|
||||
template = path[0]
|
||||
path = path[1:]
|
||||
|
||||
if len(path) == 0 or len(path[0]) == 0:
|
||||
raise UnrecognizedRequestError()
|
||||
|
||||
rule_id = path[0].decode('ascii')
|
||||
rule_id = path[0]
|
||||
|
||||
spec = {
|
||||
'scope': scope,
|
||||
@@ -220,7 +231,7 @@ def _rule_spec_from_path(path):
|
||||
path = path[1:]
|
||||
|
||||
if len(path) > 0 and len(path[0]) > 0:
|
||||
spec['attr'] = path[0].decode('ascii')
|
||||
spec['attr'] = path[0]
|
||||
|
||||
return spec
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ class PushersRemoveRestServlet(RestServlet):
|
||||
To allow pusher to be delete by clicking a link (ie. GET request)
|
||||
"""
|
||||
PATTERNS = client_path_patterns("/pushers/remove$")
|
||||
SUCCESS_HTML = "<html><body>You have been unsubscribed</body><html>"
|
||||
SUCCESS_HTML = b"<html><body>You have been unsubscribed</body><html>"
|
||||
|
||||
def __init__(self, hs):
|
||||
super(PushersRemoveRestServlet, self).__init__()
|
||||
|
||||
@@ -41,7 +41,7 @@ class MediaConfigResource(Resource):
|
||||
@defer.inlineCallbacks
|
||||
def _async_render_GET(self, request):
|
||||
yield self.auth.get_user_by_req(request)
|
||||
respond_with_json(request, 200, self.limits_dict)
|
||||
respond_with_json(request, 200, self.limits_dict, send_cors=True)
|
||||
|
||||
def render_OPTIONS(self, request):
|
||||
respond_with_json(request, 200, {}, send_cors=True)
|
||||
|
||||
@@ -48,7 +48,8 @@ class DownloadResource(Resource):
|
||||
set_cors_headers(request)
|
||||
request.setHeader(
|
||||
b"Content-Security-Policy",
|
||||
b"default-src 'none';"
|
||||
b"sandbox;"
|
||||
b" default-src 'none';"
|
||||
b" script-src 'none';"
|
||||
b" plugin-types application/pdf;"
|
||||
b" style-src 'unsafe-inline';"
|
||||
|
||||
+11
-14
@@ -1,4 +1,5 @@
|
||||
# Copyright 2016 OpenMarket Ltd
|
||||
# -*- 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.
|
||||
@@ -11,22 +12,18 @@
|
||||
# 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
|
||||
|
||||
from synapse.storage.engines import PostgresEngine
|
||||
from twisted.web.resource import Resource
|
||||
|
||||
from synapse.rest.saml2.metadata_resource import SAML2MetadataResource
|
||||
from synapse.rest.saml2.response_resource import SAML2ResponseResource
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def run_create(cur, database_engine, *args, **kwargs):
|
||||
if isinstance(database_engine, PostgresEngine):
|
||||
cur.execute("TRUNCATE sent_transactions")
|
||||
else:
|
||||
cur.execute("DELETE FROM sent_transactions")
|
||||
|
||||
cur.execute("CREATE INDEX sent_transactions_ts ON sent_transactions(ts)")
|
||||
|
||||
|
||||
def run_upgrade(cur, database_engine, *args, **kwargs):
|
||||
pass
|
||||
class SAML2Resource(Resource):
|
||||
def __init__(self, hs):
|
||||
Resource.__init__(self)
|
||||
self.putChild(b"metadata.xml", SAML2MetadataResource(hs))
|
||||
self.putChild(b"authn_response", SAML2ResponseResource(hs))
|
||||
@@ -0,0 +1,36 @@
|
||||
# -*- 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 saml2.metadata
|
||||
|
||||
from twisted.web.resource import Resource
|
||||
|
||||
|
||||
class SAML2MetadataResource(Resource):
|
||||
"""A Twisted web resource which renders the SAML metadata"""
|
||||
|
||||
isLeaf = 1
|
||||
|
||||
def __init__(self, hs):
|
||||
Resource.__init__(self)
|
||||
self.sp_config = hs.config.saml2_sp_config
|
||||
|
||||
def render_GET(self, request):
|
||||
metadata_xml = saml2.metadata.create_metadata_string(
|
||||
configfile=None, config=self.sp_config,
|
||||
)
|
||||
request.setHeader(b"Content-Type", b"text/xml; charset=utf-8")
|
||||
return metadata_xml
|
||||
@@ -0,0 +1,74 @@
|
||||
# -*- 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 logging
|
||||
|
||||
import saml2
|
||||
from saml2.client import Saml2Client
|
||||
|
||||
from twisted.web.resource import Resource
|
||||
from twisted.web.server import NOT_DONE_YET
|
||||
|
||||
from synapse.api.errors import CodeMessageException
|
||||
from synapse.http.server import wrap_html_request_handler
|
||||
from synapse.http.servlet import parse_string
|
||||
from synapse.rest.client.v1.login import SSOAuthHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SAML2ResponseResource(Resource):
|
||||
"""A Twisted web resource which handles the SAML response"""
|
||||
|
||||
isLeaf = 1
|
||||
|
||||
def __init__(self, hs):
|
||||
Resource.__init__(self)
|
||||
|
||||
self._saml_client = Saml2Client(hs.config.saml2_sp_config)
|
||||
self._sso_auth_handler = SSOAuthHandler(hs)
|
||||
|
||||
def render_POST(self, request):
|
||||
self._async_render_POST(request)
|
||||
return NOT_DONE_YET
|
||||
|
||||
@wrap_html_request_handler
|
||||
def _async_render_POST(self, request):
|
||||
resp_bytes = parse_string(request, 'SAMLResponse', required=True)
|
||||
relay_state = parse_string(request, 'RelayState', required=True)
|
||||
|
||||
try:
|
||||
saml2_auth = self._saml_client.parse_authn_request_response(
|
||||
resp_bytes, saml2.BINDING_HTTP_POST,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning("Exception parsing SAML2 response", exc_info=1)
|
||||
raise CodeMessageException(
|
||||
400, "Unable to parse SAML2 response: %s" % (e,),
|
||||
)
|
||||
|
||||
if saml2_auth.not_signed:
|
||||
raise CodeMessageException(400, "SAML2 response was not signed")
|
||||
|
||||
if "uid" not in saml2_auth.ava:
|
||||
raise CodeMessageException(400, "uid not in SAML2 response")
|
||||
|
||||
username = saml2_auth.ava["uid"][0]
|
||||
|
||||
displayName = saml2_auth.ava.get("displayName", [None])[0]
|
||||
return self._sso_auth_handler.on_successful_auth(
|
||||
username, request, relay_state,
|
||||
user_display_name=displayName,
|
||||
)
|
||||
@@ -0,0 +1,70 @@
|
||||
# -*- 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 json
|
||||
import logging
|
||||
|
||||
from twisted.web.resource import Resource
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WellKnownBuilder(object):
|
||||
"""Utility to construct the well-known response
|
||||
|
||||
Args:
|
||||
hs (synapse.server.HomeServer):
|
||||
"""
|
||||
def __init__(self, hs):
|
||||
self._config = hs.config
|
||||
|
||||
def get_well_known(self):
|
||||
# if we don't have a public_base_url, we can't help much here.
|
||||
if self._config.public_baseurl is None:
|
||||
return None
|
||||
|
||||
result = {
|
||||
"m.homeserver": {
|
||||
"base_url": self._config.public_baseurl,
|
||||
},
|
||||
}
|
||||
|
||||
if self._config.default_identity_server:
|
||||
result["m.identity_server"] = {
|
||||
"base_url": self._config.default_identity_server,
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class WellKnownResource(Resource):
|
||||
"""A Twisted web resource which renders the .well-known file"""
|
||||
|
||||
isLeaf = 1
|
||||
|
||||
def __init__(self, hs):
|
||||
Resource.__init__(self)
|
||||
self._well_known_builder = WellKnownBuilder(hs)
|
||||
|
||||
def render_GET(self, request):
|
||||
r = self._well_known_builder.get_well_known()
|
||||
if not r:
|
||||
request.setResponseCode(404)
|
||||
request.setHeader(b"Content-Type", b"text/plain")
|
||||
return b'.well-known not available'
|
||||
|
||||
logger.error("returning: %s", r)
|
||||
request.setHeader(b"Content-Type", b"application/json")
|
||||
return json.dumps(r).encode("utf-8")
|
||||
+3
-1
@@ -298,6 +298,8 @@ def _resolve_normal_events(events, auth_events):
|
||||
|
||||
def _ordered_events(events):
|
||||
def key_func(e):
|
||||
return -int(e.depth), hashlib.sha1(e.event_id.encode('ascii')).hexdigest()
|
||||
# we have to use utf-8 rather than ascii here because it turns out we allow
|
||||
# people to send us events with non-ascii event IDs :/
|
||||
return -int(e.depth), hashlib.sha1(e.event_id.encode('utf-8')).hexdigest()
|
||||
|
||||
return sorted(events, key=key_func)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Synapse is running</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
|
||||
max-width: 40em;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
h1, p {
|
||||
margin: 1.5em;
|
||||
}
|
||||
hr {
|
||||
border: none;
|
||||
background-color: #ccc;
|
||||
color: #ccc;
|
||||
height: 1px;
|
||||
width: 7em;
|
||||
margin-top: 4em;
|
||||
}
|
||||
.logo {
|
||||
display: block;
|
||||
width: 12em;
|
||||
margin: 4em auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="logo">
|
||||
<svg role="img" aria-label="[Matrix logo]" viewBox="0 0 200 85" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="parent" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="child" transform="translate(-122.000000, -6.000000)" fill="#000000" fill-rule="nonzero">
|
||||
<g id="matrix-logo" transform="translate(122.000000, 6.000000)">
|
||||
<polygon id="left-bracket" points="2.24708861 1.93811009 2.24708861 82.7268844 8.10278481 82.7268844 8.10278481 84.6652459 0 84.6652459 0 0 8.10278481 0 8.10278481 1.93811009"></polygon>
|
||||
<path d="M24.8073418,27.5493174 L24.8073418,31.6376991 L24.924557,31.6376991 C26.0227848,30.0814294 27.3455696,28.8730642 28.8951899,28.0163743 C30.4437975,27.1611927 32.2189873,26.7318422 34.218481,26.7318422 C36.1394937,26.7318422 37.8946835,27.102622 39.4825316,27.8416679 C41.0708861,28.5819706 42.276962,29.8856073 43.1005063,31.7548404 C44.0017722,30.431345 45.2270886,29.2629486 46.7767089,28.2506569 C48.3253165,27.2388679 50.158481,26.7318422 52.2764557,26.7318422 C53.8843038,26.7318422 55.3736709,26.9269101 56.7473418,27.3162917 C58.1189873,27.7056734 59.295443,28.3285835 60.2759494,29.185022 C61.255443,30.0422147 62.02,31.1615927 62.5701266,32.5426532 C63.1187342,33.9262275 63.3936709,35.5898349 63.3936709,37.5372459 L63.3936709,57.7443688 L55.0410127,57.7441174 L55.0410127,40.6319376 C55.0410127,39.6201486 55.0020253,38.6661761 54.9232911,37.7700202 C54.8440506,36.8751211 54.6293671,36.0968606 54.2764557,35.4339817 C53.9232911,34.772611 53.403038,34.2464807 52.7177215,33.8568477 C52.0313924,33.4689743 51.0997468,33.2731523 49.9235443,33.2731523 C48.7473418,33.2731523 47.7962025,33.4983853 47.0706329,33.944578 C46.344557,34.393033 45.7764557,34.9774826 45.3650633,35.6969211 C44.9534177,36.4181193 44.6787342,37.2353431 44.5417722,38.150855 C44.4037975,39.0653615 44.3356962,39.9904257 44.3356962,40.9247908 L44.3356962,57.7443688 L35.9835443,57.7443688 L35.9835443,40.8079009 C35.9835443,39.9124991 35.963038,39.0263982 35.9253165,38.150855 C35.8853165,37.2743064 35.7192405,36.4666349 35.424557,35.7263321 C35.1303797,34.9872862 34.64,34.393033 33.9539241,33.944578 C33.2675949,33.4983853 32.2579747,33.2731523 30.9248101,33.2731523 C30.5321519,33.2731523 30.0126582,33.3608826 29.3663291,33.5365945 C28.7192405,33.7118037 28.0913924,34.0433688 27.4840506,34.5292789 C26.875443,35.0164459 26.3564557,35.7172826 25.9250633,36.6315376 C25.4934177,37.5470495 25.2779747,38.7436 25.2779747,40.2229486 L25.2779747,57.7441174 L16.9260759,57.7443688 L16.9260759,27.5493174 L24.8073418,27.5493174 Z" id="m"></path>
|
||||
<path d="M68.7455696,31.9886202 C69.6075949,30.7033339 70.7060759,29.672189 72.0397468,28.8926716 C73.3724051,28.1141596 74.8716456,27.5596239 76.5387342,27.2283101 C78.2050633,26.8977505 79.8817722,26.7315908 81.5678481,26.7315908 C83.0974684,26.7315908 84.6458228,26.8391798 86.2144304,27.0525982 C87.7827848,27.2675248 89.2144304,27.6865688 90.5086076,28.3087248 C91.8025316,28.9313835 92.8610127,29.7983798 93.6848101,30.9074514 C94.5083544,32.0170257 94.92,33.4870734 94.92,35.3173431 L94.92,51.026844 C94.92,52.3913138 94.998481,53.6941963 95.1556962,54.9400165 C95.3113924,56.1865908 95.5863291,57.120956 95.9787342,57.7436147 L87.5091139,57.7436147 C87.3518987,57.276055 87.2240506,56.7996972 87.1265823,56.3125303 C87.0278481,55.8266202 86.9592405,55.3301523 86.9207595,54.8236294 C85.5873418,56.1865908 84.0182278,57.1405633 82.2156962,57.6857982 C80.4113924,58.2295248 78.5683544,58.503022 76.6860759,58.503022 C75.2346835,58.503022 73.8817722,58.3275615 72.6270886,57.9776459 C71.3718987,57.6269761 70.2744304,57.082244 69.3334177,56.3411872 C68.3921519,55.602644 67.656962,54.6680275 67.1275949,53.5390972 C66.5982278,52.410167 66.3331646,51.065556 66.3331646,49.5087835 C66.3331646,47.7961578 66.6367089,46.384178 67.2455696,45.2756092 C67.8529114,44.1652807 68.6367089,43.2799339 69.5987342,42.6173064 C70.5589873,41.9556844 71.6567089,41.4592165 72.8924051,41.1284055 C74.1273418,40.7978459 75.3721519,40.5356606 76.6270886,40.3398385 C77.8820253,40.1457761 79.116962,39.9896716 80.3329114,39.873033 C81.5483544,39.7558917 82.6270886,39.5804312 83.5681013,39.3469028 C84.5093671,39.1133743 85.2536709,38.7732624 85.8032911,38.3250587 C86.3513924,37.8773578 86.6063291,37.2252881 86.5678481,36.3680954 C86.5678481,35.4731963 86.4210127,34.7620532 86.1268354,34.2366771 C85.8329114,33.7113009 85.4405063,33.3018092 84.9506329,33.0099615 C84.4602532,32.7181138 83.8916456,32.5232972 83.2450633,32.4255119 C82.5977215,32.3294862 81.9010127,32.2797138 81.156962,32.2797138 C79.5098734,32.2797138 78.2159494,32.6303835 77.2746835,33.3312202 C76.3339241,34.0320569 75.7837975,35.2007046 75.6275949,36.8354037 L67.275443,36.8354037 C67.3924051,34.8892495 67.8817722,33.2726495 68.7455696,31.9886202 Z M85.2440506,43.6984752 C84.7149367,43.873433 84.1460759,44.0189798 83.5387342,44.1361211 C82.9306329,44.253011 82.2936709,44.350545 81.6270886,44.4279688 C80.96,44.5066495 80.2934177,44.6034294 79.6273418,44.7203193 C78.9994937,44.8362037 78.3820253,44.9933138 77.7749367,45.1871248 C77.1663291,45.3829468 76.636962,45.6451321 76.1865823,45.9759431 C75.7349367,46.3070055 75.3724051,46.7263009 75.0979747,47.2313156 C74.8232911,47.7375872 74.6863291,48.380356 74.6863291,49.1588679 C74.6863291,49.8979138 74.8232911,50.5218294 75.0979747,51.026844 C75.3724051,51.5338697 75.7455696,51.9328037 76.2159494,52.2246514 C76.6863291,52.5164991 77.2349367,52.7213706 77.8632911,52.8375064 C78.4898734,52.9546477 79.136962,53.012967 79.8037975,53.012967 C81.4506329,53.012967 82.724557,52.740978 83.6273418,52.1952404 C84.5288608,51.6507596 85.1949367,50.9981872 85.6270886,50.2382771 C86.0579747,49.4793725 86.323038,48.7119211 86.4212658,47.9321523 C86.518481,47.1536404 86.5681013,46.5304789 86.5681013,46.063422 L86.5681013,42.9677248 C86.2146835,43.2799339 85.7736709,43.5230147 85.2440506,43.6984752 Z" id="a"></path>
|
||||
<path d="M116.917975,27.5493174 L116.917975,33.0976917 L110.801266,33.0976917 L110.801266,48.0492936 C110.801266,49.4502128 111.036203,50.3850807 111.507089,50.8518862 C111.976962,51.3191945 112.918734,51.5527229 114.33038,51.5527229 C114.801013,51.5527229 115.251392,51.5336183 115.683038,51.4944037 C116.114177,51.4561945 116.526076,51.3968697 116.917975,51.3194459 L116.917975,57.7438661 C116.212152,57.860756 115.427595,57.9381798 114.565316,57.9778972 C113.702785,58.0153523 112.859747,58.0357138 112.036203,58.0357138 C110.742278,58.0357138 109.516456,57.9477321 108.36,57.7722716 C107.202785,57.5975651 106.183544,57.2577046 105.301519,56.7509303 C104.418987,56.2454128 103.722785,55.5242147 103.213418,54.5898495 C102.703038,53.6562385 102.448608,52.4292716 102.448608,50.9099541 L102.448608,33.0976917 L97.3903797,33.0976917 L97.3903797,27.5493174 L102.448608,27.5493174 L102.448608,18.4967596 L110.801013,18.4967596 L110.801013,27.5493174 L116.917975,27.5493174 Z" id="t"></path>
|
||||
<path d="M128.857975,27.5493174 L128.857975,33.1565138 L128.975696,33.1565138 C129.367089,32.2213945 129.896203,31.3559064 130.563544,30.557033 C131.23038,29.7596679 131.99443,29.0776844 132.857215,28.5130936 C133.719241,27.9495083 134.641266,27.5113596 135.622532,27.1988991 C136.601772,26.8879468 137.622025,26.7315908 138.681013,26.7315908 C139.229873,26.7315908 139.836962,26.8296275 140.504304,27.0239413 L140.504304,34.7336477 C140.111646,34.6552183 139.641013,34.586844 139.092658,34.5290275 C138.543291,34.4704569 138.014177,34.4410459 137.504304,34.4410459 C135.974937,34.4410459 134.681013,34.6949358 133.622785,35.2004532 C132.564051,35.7067248 131.711392,36.397255 131.064051,37.2735523 C130.417215,38.1501009 129.955443,39.1714422 129.681266,40.3398385 C129.407089,41.5074807 129.269873,42.7736624 129.269873,44.1361211 L129.269873,57.7438661 L120.917722,57.7438661 L120.917722,27.5493174 L128.857975,27.5493174 Z" id="r"></path>
|
||||
<path d="M144.033165,22.8767376 L144.033165,16.0435798 L152.386076,16.0435798 L152.386076,22.8767376 L144.033165,22.8767376 Z M152.386076,27.5493174 L152.386076,57.7438661 L144.033165,57.7438661 L144.033165,27.5493174 L152.386076,27.5493174 Z" id="i"></path>
|
||||
<polygon id="x" points="156.738228 27.5493174 166.266582 27.5493174 171.619494 35.4337303 176.913418 27.5493174 186.147848 27.5493174 176.148861 41.6831927 187.383544 57.7441174 177.85443 57.7441174 171.501772 48.2245028 165.148861 57.7441174 155.797468 57.7441174 166.737468 41.8589046"></polygon>
|
||||
<polygon id="right-bracket" points="197.580759 82.7268844 197.580759 1.93811009 191.725063 1.93811009 191.725063 0 199.828354 0 199.828354 84.6652459 191.725063 84.6652459 191.725063 82.7268844"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<h1>It works! Synapse is running</h1>
|
||||
<p>Your Synapse server is listening on this port and is ready for messages.</p>
|
||||
<p>To use this server you'll need <a href="https://matrix.org/docs/projects/try-matrix-now.html#clients" target="_blank">a Matrix client</a>.
|
||||
</p>
|
||||
<p>Welcome to the Matrix universe :)</p>
|
||||
<hr>
|
||||
<p>
|
||||
<small>
|
||||
<a href="https://matrix.org" target="_blank">
|
||||
matrix.org
|
||||
</a>
|
||||
</small>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -14,12 +14,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import calendar
|
||||
import logging
|
||||
import time
|
||||
|
||||
from dateutil import tz
|
||||
|
||||
from synapse.api.constants import PresenceState
|
||||
from synapse.storage.devices import DeviceStore
|
||||
from synapse.storage.user_erasure_store import UserErasureStore
|
||||
@@ -119,7 +117,6 @@ class DataStore(RoomMemberStore, RoomStore,
|
||||
db_conn, "device_lists_stream", "stream_id",
|
||||
)
|
||||
|
||||
self._transaction_id_gen = IdGenerator(db_conn, "sent_transactions", "id")
|
||||
self._access_tokens_id_gen = IdGenerator(db_conn, "access_tokens", "id")
|
||||
self._event_reports_id_gen = IdGenerator(db_conn, "event_reports", "id")
|
||||
self._push_rule_id_gen = IdGenerator(db_conn, "push_rules", "id")
|
||||
@@ -358,10 +355,11 @@ class DataStore(RoomMemberStore, RoomStore,
|
||||
"""
|
||||
Returns millisecond unixtime for start of UTC day.
|
||||
"""
|
||||
now = datetime.datetime.utcnow()
|
||||
today_start = datetime.datetime(now.year, now.month,
|
||||
now.day, tzinfo=tz.tzutc())
|
||||
return int(time.mktime(today_start.timetuple())) * 1000
|
||||
now = time.gmtime()
|
||||
today_start = calendar.timegm((
|
||||
now.tm_year, now.tm_mon, now.tm_mday, 0, 0, 0,
|
||||
))
|
||||
return today_start * 1000
|
||||
|
||||
def generate_user_daily_visits(self):
|
||||
"""
|
||||
|
||||
+14
-13
@@ -29,6 +29,7 @@ from synapse.api.errors import StoreError
|
||||
from synapse.storage.engines import PostgresEngine
|
||||
from synapse.util.caches.descriptors import Cache
|
||||
from synapse.util.logcontext import LoggingContext, PreserveLoggingContext
|
||||
from synapse.util.stringutils import exception_to_unicode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -249,32 +250,32 @@ class SQLBaseStore(object):
|
||||
except self.database_engine.module.OperationalError as e:
|
||||
# This can happen if the database disappears mid
|
||||
# transaction.
|
||||
logger.warn(
|
||||
logger.warning(
|
||||
"[TXN OPERROR] {%s} %s %d/%d",
|
||||
name, e, i, N
|
||||
name, exception_to_unicode(e), i, N
|
||||
)
|
||||
if i < N:
|
||||
i += 1
|
||||
try:
|
||||
conn.rollback()
|
||||
except self.database_engine.module.Error as e1:
|
||||
logger.warn(
|
||||
logger.warning(
|
||||
"[TXN EROLL] {%s} %s",
|
||||
name, e1,
|
||||
name, exception_to_unicode(e1),
|
||||
)
|
||||
continue
|
||||
raise
|
||||
except self.database_engine.module.DatabaseError as e:
|
||||
if self.database_engine.is_deadlock(e):
|
||||
logger.warn("[TXN DEADLOCK] {%s} %d/%d", name, i, N)
|
||||
logger.warning("[TXN DEADLOCK] {%s} %d/%d", name, i, N)
|
||||
if i < N:
|
||||
i += 1
|
||||
try:
|
||||
conn.rollback()
|
||||
except self.database_engine.module.Error as e1:
|
||||
logger.warn(
|
||||
logger.warning(
|
||||
"[TXN EROLL] {%s} %s",
|
||||
name, e1,
|
||||
name, exception_to_unicode(e1),
|
||||
)
|
||||
continue
|
||||
raise
|
||||
@@ -849,9 +850,9 @@ class SQLBaseStore(object):
|
||||
rowcount = cls._simple_update_txn(txn, table, keyvalues, updatevalues)
|
||||
|
||||
if rowcount == 0:
|
||||
raise StoreError(404, "No row found")
|
||||
raise StoreError(404, "No row found (%s)" % (table,))
|
||||
if rowcount > 1:
|
||||
raise StoreError(500, "More than one row matched")
|
||||
raise StoreError(500, "More than one row matched (%s)" % (table,))
|
||||
|
||||
@staticmethod
|
||||
def _simple_select_one_txn(txn, table, keyvalues, retcols,
|
||||
@@ -868,9 +869,9 @@ class SQLBaseStore(object):
|
||||
if not row:
|
||||
if allow_none:
|
||||
return None
|
||||
raise StoreError(404, "No row found")
|
||||
raise StoreError(404, "No row found (%s)" % (table,))
|
||||
if txn.rowcount > 1:
|
||||
raise StoreError(500, "More than one row matched")
|
||||
raise StoreError(500, "More than one row matched (%s)" % (table,))
|
||||
|
||||
return dict(zip(retcols, row))
|
||||
|
||||
@@ -902,9 +903,9 @@ class SQLBaseStore(object):
|
||||
|
||||
txn.execute(sql, list(keyvalues.values()))
|
||||
if txn.rowcount == 0:
|
||||
raise StoreError(404, "No row found")
|
||||
raise StoreError(404, "No row found (%s)" % (table,))
|
||||
if txn.rowcount > 1:
|
||||
raise StoreError(500, "more than one row matched")
|
||||
raise StoreError(500, "More than one row matched (%s)" % (table,))
|
||||
|
||||
def _simple_delete(self, table, keyvalues, desc):
|
||||
return self.runInteraction(
|
||||
|
||||
@@ -25,7 +25,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Remember to update this number every time a change is made to database
|
||||
# schema files, so the users will be informed on server restarts.
|
||||
SCHEMA_VERSION = 52
|
||||
SCHEMA_VERSION = 53
|
||||
|
||||
dir_path = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ from twisted.internet import defer
|
||||
from synapse.api.errors import Codes, StoreError
|
||||
from synapse.storage import background_updates
|
||||
from synapse.storage._base import SQLBaseStore
|
||||
from synapse.types import UserID
|
||||
from synapse.util.caches.descriptors import cached, cachedInlineCallbacks
|
||||
|
||||
|
||||
@@ -167,7 +168,7 @@ class RegistrationStore(RegistrationWorkerStore,
|
||||
|
||||
def register(self, user_id, token=None, password_hash=None,
|
||||
was_guest=False, make_guest=False, appservice_id=None,
|
||||
create_profile_with_localpart=None, admin=False):
|
||||
create_profile_with_displayname=None, admin=False):
|
||||
"""Attempts to register an account.
|
||||
|
||||
Args:
|
||||
@@ -181,8 +182,8 @@ class RegistrationStore(RegistrationWorkerStore,
|
||||
make_guest (boolean): True if the the new user should be guest,
|
||||
false to add a regular user account.
|
||||
appservice_id (str): The ID of the appservice registering the user.
|
||||
create_profile_with_localpart (str): Optionally create a profile for
|
||||
the given localpart.
|
||||
create_profile_with_displayname (unicode): Optionally create a profile for
|
||||
the user, setting their displayname to the given value
|
||||
Raises:
|
||||
StoreError if the user_id could not be registered.
|
||||
"""
|
||||
@@ -195,7 +196,7 @@ class RegistrationStore(RegistrationWorkerStore,
|
||||
was_guest,
|
||||
make_guest,
|
||||
appservice_id,
|
||||
create_profile_with_localpart,
|
||||
create_profile_with_displayname,
|
||||
admin
|
||||
)
|
||||
|
||||
@@ -208,9 +209,11 @@ class RegistrationStore(RegistrationWorkerStore,
|
||||
was_guest,
|
||||
make_guest,
|
||||
appservice_id,
|
||||
create_profile_with_localpart,
|
||||
create_profile_with_displayname,
|
||||
admin,
|
||||
):
|
||||
user_id_obj = UserID.from_string(user_id)
|
||||
|
||||
now = int(self.clock.time())
|
||||
|
||||
next_id = self._access_tokens_id_gen.get_next()
|
||||
@@ -273,12 +276,15 @@ class RegistrationStore(RegistrationWorkerStore,
|
||||
(next_id, user_id, token,)
|
||||
)
|
||||
|
||||
if create_profile_with_localpart:
|
||||
if create_profile_with_displayname:
|
||||
# set a default displayname serverside to avoid ugly race
|
||||
# between auto-joins and clients trying to set displaynames
|
||||
#
|
||||
# *obviously* the 'profiles' table uses localpart for user_id
|
||||
# while everything else uses the full mxid.
|
||||
txn.execute(
|
||||
"INSERT INTO profiles(user_id, displayname) VALUES (?,?)",
|
||||
(create_profile_with_localpart, create_profile_with_localpart)
|
||||
(user_id_obj.localpart, create_profile_with_displayname)
|
||||
)
|
||||
|
||||
self._invalidate_cache_and_stream(
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
/* Copyright 2015, 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.
|
||||
@@ -13,4 +13,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
CREATE INDEX IF NOT EXISTS sent_transaction_txn_id ON sent_transactions(transaction_id);
|
||||
DROP TABLE IF EXISTS sent_transactions;
|
||||
@@ -25,25 +25,6 @@ CREATE TABLE IF NOT EXISTS received_transactions(
|
||||
|
||||
CREATE INDEX transactions_have_ref ON received_transactions(origin, has_been_referenced);-- WHERE has_been_referenced = 0;
|
||||
|
||||
|
||||
-- Stores what transactions we've sent, what their response was (if we got one) and whether we have
|
||||
-- since referenced the transaction in another outgoing transaction
|
||||
CREATE TABLE IF NOT EXISTS sent_transactions(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT, -- This is used to apply insertion ordering
|
||||
transaction_id TEXT,
|
||||
destination TEXT,
|
||||
response_code INTEGER DEFAULT 0,
|
||||
response_json TEXT,
|
||||
ts BIGINT
|
||||
);
|
||||
|
||||
CREATE INDEX sent_transaction_dest ON sent_transactions(destination);
|
||||
CREATE INDEX sent_transaction_txn_id ON sent_transactions(transaction_id);
|
||||
-- So that we can do an efficient look up of all transactions that have yet to be successfully
|
||||
-- sent.
|
||||
CREATE INDEX sent_transaction_sent ON sent_transactions(response_code);
|
||||
|
||||
|
||||
-- For sent transactions only.
|
||||
CREATE TABLE IF NOT EXISTS transaction_id_to_pdu(
|
||||
transaction_id INTEGER,
|
||||
|
||||
@@ -25,25 +25,6 @@ CREATE TABLE IF NOT EXISTS received_transactions(
|
||||
|
||||
CREATE INDEX transactions_have_ref ON received_transactions(origin, has_been_referenced);-- WHERE has_been_referenced = 0;
|
||||
|
||||
|
||||
-- Stores what transactions we've sent, what their response was (if we got one) and whether we have
|
||||
-- since referenced the transaction in another outgoing transaction
|
||||
CREATE TABLE IF NOT EXISTS sent_transactions(
|
||||
id BIGINT PRIMARY KEY, -- This is used to apply insertion ordering
|
||||
transaction_id TEXT,
|
||||
destination TEXT,
|
||||
response_code INTEGER DEFAULT 0,
|
||||
response_json TEXT,
|
||||
ts BIGINT
|
||||
);
|
||||
|
||||
CREATE INDEX sent_transaction_dest ON sent_transactions(destination);
|
||||
CREATE INDEX sent_transaction_txn_id ON sent_transactions(transaction_id);
|
||||
-- So that we can do an efficient look up of all transactions that have yet to be successfully
|
||||
-- sent.
|
||||
CREATE INDEX sent_transaction_sent ON sent_transactions(response_code);
|
||||
|
||||
|
||||
-- For sent transactions only.
|
||||
CREATE TABLE IF NOT EXISTS transaction_id_to_pdu(
|
||||
transaction_id INTEGER,
|
||||
|
||||
@@ -45,6 +45,10 @@ class SearchStore(BackgroundUpdateStore):
|
||||
|
||||
def __init__(self, db_conn, hs):
|
||||
super(SearchStore, self).__init__(db_conn, hs)
|
||||
|
||||
if not hs.config.enable_search:
|
||||
return
|
||||
|
||||
self.register_background_update_handler(
|
||||
self.EVENT_SEARCH_UPDATE_NAME, self._background_reindex_search
|
||||
)
|
||||
@@ -316,6 +320,8 @@ class SearchStore(BackgroundUpdateStore):
|
||||
entries (iterable[SearchEntry]):
|
||||
entries to be added to the table
|
||||
"""
|
||||
if not self.hs.config.enable_search:
|
||||
return
|
||||
if isinstance(self.database_engine, PostgresEngine):
|
||||
sql = (
|
||||
"INSERT INTO event_search"
|
||||
|
||||
@@ -432,7 +432,7 @@ class StateGroupWorkerStore(EventsWorkerStore, SQLBaseStore):
|
||||
create_id = state_ids.get((EventTypes.Create, ""))
|
||||
|
||||
if not create_id:
|
||||
raise NotFoundError("Unknown room")
|
||||
raise NotFoundError("Unknown room %s" % (room_id))
|
||||
|
||||
create_event = yield self.get_event(create_id)
|
||||
defer.returnValue(create_event.content.get("room_version", "1"))
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# 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 re
|
||||
import string
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -228,6 +229,71 @@ def contains_invalid_mxid_characters(localpart):
|
||||
return any(c not in mxid_localpart_allowed_characters for c in localpart)
|
||||
|
||||
|
||||
UPPER_CASE_PATTERN = re.compile(b"[A-Z_]")
|
||||
|
||||
# the following is a pattern which matches '=', and bytes which are not allowed in a mxid
|
||||
# localpart.
|
||||
#
|
||||
# It works by:
|
||||
# * building a string containing the allowed characters (excluding '=')
|
||||
# * escaping every special character with a backslash (to stop '-' being interpreted as a
|
||||
# range operator)
|
||||
# * wrapping it in a '[^...]' regex
|
||||
# * converting the whole lot to a 'bytes' sequence, so that we can use it to match
|
||||
# bytes rather than strings
|
||||
#
|
||||
NON_MXID_CHARACTER_PATTERN = re.compile(
|
||||
("[^%s]" % (
|
||||
re.escape("".join(mxid_localpart_allowed_characters - {"="}),),
|
||||
)).encode("ascii"),
|
||||
)
|
||||
|
||||
|
||||
def map_username_to_mxid_localpart(username, case_sensitive=False):
|
||||
"""Map a username onto a string suitable for a MXID
|
||||
|
||||
This follows the algorithm laid out at
|
||||
https://matrix.org/docs/spec/appendices.html#mapping-from-other-character-sets.
|
||||
|
||||
Args:
|
||||
username (unicode|bytes): username to be mapped
|
||||
case_sensitive (bool): true if TEST and test should be mapped
|
||||
onto different mxids
|
||||
|
||||
Returns:
|
||||
unicode: string suitable for a mxid localpart
|
||||
"""
|
||||
if not isinstance(username, bytes):
|
||||
username = username.encode('utf-8')
|
||||
|
||||
# first we sort out upper-case characters
|
||||
if case_sensitive:
|
||||
def f1(m):
|
||||
return b"_" + m.group().lower()
|
||||
|
||||
username = UPPER_CASE_PATTERN.sub(f1, username)
|
||||
else:
|
||||
username = username.lower()
|
||||
|
||||
# then we sort out non-ascii characters
|
||||
def f2(m):
|
||||
g = m.group()[0]
|
||||
if isinstance(g, str):
|
||||
# on python 2, we need to do a ord(). On python 3, the
|
||||
# byte itself will do.
|
||||
g = ord(g)
|
||||
return b"=%02x" % (g,)
|
||||
|
||||
username = NON_MXID_CHARACTER_PATTERN.sub(f2, username)
|
||||
|
||||
# we also do the =-escaping to mxids starting with an underscore.
|
||||
username = re.sub(b'^_', b'=5f', username)
|
||||
|
||||
# we should now only have ascii bytes left, so can decode back to a
|
||||
# unicode.
|
||||
return username.decode('ascii')
|
||||
|
||||
|
||||
class StreamToken(
|
||||
namedtuple("Token", (
|
||||
"room_key",
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
import random
|
||||
import string
|
||||
|
||||
from six import PY3
|
||||
import six
|
||||
from six import PY2, PY3
|
||||
from six.moves import range
|
||||
|
||||
_string_with_symbols = (
|
||||
@@ -71,3 +72,39 @@ def to_ascii(s):
|
||||
return s.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
return s
|
||||
|
||||
|
||||
def exception_to_unicode(e):
|
||||
"""Helper function to extract the text of an exception as a unicode string
|
||||
|
||||
Args:
|
||||
e (Exception): exception to be stringified
|
||||
|
||||
Returns:
|
||||
unicode
|
||||
"""
|
||||
# urgh, this is a mess. The basic problem here is that psycopg2 constructs its
|
||||
# exceptions with PyErr_SetString, with a (possibly non-ascii) argument. str() will
|
||||
# then produce the raw byte sequence. Under Python 2, this will then cause another
|
||||
# error if it gets mixed with a `unicode` object, as per
|
||||
# https://github.com/matrix-org/synapse/issues/4252
|
||||
|
||||
# First of all, if we're under python3, everything is fine because it will sort this
|
||||
# nonsense out for us.
|
||||
if not PY2:
|
||||
return str(e)
|
||||
|
||||
# otherwise let's have a stab at decoding the exception message. We'll circumvent
|
||||
# Exception.__str__(), which would explode if someone raised Exception(u'non-ascii')
|
||||
# and instead look at what is in the args member.
|
||||
|
||||
if len(e.args) == 0:
|
||||
return u""
|
||||
elif len(e.args) > 1:
|
||||
return six.text_type(repr(e.args))
|
||||
|
||||
msg = e.args[0]
|
||||
if isinstance(msg, bytes):
|
||||
return msg.decode('utf-8', errors='replace')
|
||||
else:
|
||||
return msg
|
||||
|
||||
+5
-2
@@ -1,5 +1,6 @@
|
||||
# -*- 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.
|
||||
@@ -15,7 +16,9 @@
|
||||
|
||||
from twisted.trial import util
|
||||
|
||||
from tests import utils
|
||||
import tests.patch_inline_callbacks
|
||||
|
||||
# attempt to do the patch before we load any synapse code
|
||||
tests.patch_inline_callbacks.do_patch()
|
||||
|
||||
util.DEFAULT_TIMEOUT_DURATION = 10
|
||||
utils.setupdb()
|
||||
|
||||
@@ -129,21 +129,6 @@ class RegistrationTestCase(unittest.TestCase):
|
||||
with self.assertRaises(ResourceLimitError):
|
||||
yield self.handler.register(localpart="local_part")
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_register_saml2_mau_blocked(self):
|
||||
self.hs.config.limit_usage_by_mau = True
|
||||
self.store.get_monthly_active_count = Mock(
|
||||
return_value=defer.succeed(self.lots_of_users)
|
||||
)
|
||||
with self.assertRaises(ResourceLimitError):
|
||||
yield self.handler.register_saml2(localpart="local_part")
|
||||
|
||||
self.store.get_monthly_active_count = Mock(
|
||||
return_value=defer.succeed(self.hs.config.max_mau_value)
|
||||
)
|
||||
with self.assertRaises(ResourceLimitError):
|
||||
yield self.handler.register_saml2(localpart="local_part")
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_auto_create_auto_join_rooms(self):
|
||||
room_alias_str = "#room:test"
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# -*- 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.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import functools
|
||||
import sys
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.defer import Deferred
|
||||
from twisted.python.failure import Failure
|
||||
|
||||
|
||||
def do_patch():
|
||||
"""
|
||||
Patch defer.inlineCallbacks so that it checks the state of the logcontext on exit
|
||||
"""
|
||||
|
||||
from synapse.util.logcontext import LoggingContext
|
||||
|
||||
orig_inline_callbacks = defer.inlineCallbacks
|
||||
|
||||
def new_inline_callbacks(f):
|
||||
|
||||
orig = orig_inline_callbacks(f)
|
||||
|
||||
@functools.wraps(f)
|
||||
def wrapped(*args, **kwargs):
|
||||
start_context = LoggingContext.current_context()
|
||||
|
||||
try:
|
||||
res = orig(*args, **kwargs)
|
||||
except Exception:
|
||||
if LoggingContext.current_context() != start_context:
|
||||
err = "%s changed context from %s to %s on exception" % (
|
||||
f, start_context, LoggingContext.current_context()
|
||||
)
|
||||
print(err, file=sys.stderr)
|
||||
raise Exception(err)
|
||||
raise
|
||||
|
||||
if not isinstance(res, Deferred) or res.called:
|
||||
if LoggingContext.current_context() != start_context:
|
||||
err = "%s changed context from %s to %s" % (
|
||||
f, start_context, LoggingContext.current_context()
|
||||
)
|
||||
# print the error to stderr because otherwise all we
|
||||
# see in travis-ci is the 500 error
|
||||
print(err, file=sys.stderr)
|
||||
raise Exception(err)
|
||||
return res
|
||||
|
||||
if LoggingContext.current_context() != LoggingContext.sentinel:
|
||||
err = (
|
||||
"%s returned incomplete deferred in non-sentinel context "
|
||||
"%s (start was %s)"
|
||||
) % (
|
||||
f, LoggingContext.current_context(), start_context,
|
||||
)
|
||||
print(err, file=sys.stderr)
|
||||
raise Exception(err)
|
||||
|
||||
def check_ctx(r):
|
||||
if LoggingContext.current_context() != start_context:
|
||||
err = "%s completion of %s changed context from %s to %s" % (
|
||||
"Failure" if isinstance(r, Failure) else "Success",
|
||||
f, start_context, LoggingContext.current_context(),
|
||||
)
|
||||
print(err, file=sys.stderr)
|
||||
raise Exception(err)
|
||||
return r
|
||||
|
||||
res.addBoth(check_ctx)
|
||||
return res
|
||||
|
||||
return wrapped
|
||||
|
||||
defer.inlineCallbacks = new_inline_callbacks
|
||||
@@ -0,0 +1,58 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 New Vector
|
||||
#
|
||||
# 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.rest.well_known import WellKnownResource
|
||||
|
||||
from tests import unittest
|
||||
|
||||
|
||||
class WellKnownTests(unittest.HomeserverTestCase):
|
||||
def setUp(self):
|
||||
super(WellKnownTests, self).setUp()
|
||||
|
||||
# replace the JsonResource with a WellKnownResource
|
||||
self.resource = WellKnownResource(self.hs)
|
||||
|
||||
def test_well_known(self):
|
||||
self.hs.config.public_baseurl = "https://tesths"
|
||||
self.hs.config.default_identity_server = "https://testis"
|
||||
|
||||
request, channel = self.make_request(
|
||||
"GET",
|
||||
"/.well-known/matrix/client",
|
||||
shorthand=False,
|
||||
)
|
||||
self.render(request)
|
||||
|
||||
self.assertEqual(request.code, 200)
|
||||
self.assertEqual(
|
||||
channel.json_body, {
|
||||
"m.homeserver": {"base_url": "https://tesths"},
|
||||
"m.identity_server": {"base_url": "https://testis"},
|
||||
}
|
||||
)
|
||||
|
||||
def test_well_known_no_public_baseurl(self):
|
||||
self.hs.config.public_baseurl = None
|
||||
|
||||
request, channel = self.make_request(
|
||||
"GET",
|
||||
"/.well-known/matrix/client",
|
||||
shorthand=False,
|
||||
)
|
||||
self.render(request)
|
||||
|
||||
self.assertEqual(request.code, 404)
|
||||
@@ -149,7 +149,7 @@ class MonthlyActiveUsersTestCase(HomeserverTestCase):
|
||||
|
||||
def test_populate_monthly_users_is_guest(self):
|
||||
# Test that guest users are not added to mau list
|
||||
user_id = "user_id"
|
||||
user_id = "@user_id:host"
|
||||
self.store.register(
|
||||
user_id=user_id, token="123", password_hash=None, make_guest=True
|
||||
)
|
||||
|
||||
+30
-1
@@ -14,7 +14,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
from synapse.api.errors import SynapseError
|
||||
from synapse.types import GroupID, RoomAlias, UserID
|
||||
from synapse.types import GroupID, RoomAlias, UserID, map_username_to_mxid_localpart
|
||||
|
||||
from tests import unittest
|
||||
from tests.utils import TestHomeServer
|
||||
@@ -79,3 +79,32 @@ class GroupIDTestCase(unittest.TestCase):
|
||||
except SynapseError as exc:
|
||||
self.assertEqual(400, exc.code)
|
||||
self.assertEqual("M_UNKNOWN", exc.errcode)
|
||||
|
||||
|
||||
class MapUsernameTestCase(unittest.TestCase):
|
||||
def testPassThrough(self):
|
||||
self.assertEqual(map_username_to_mxid_localpart("test1234"), "test1234")
|
||||
|
||||
def testUpperCase(self):
|
||||
self.assertEqual(map_username_to_mxid_localpart("tEST_1234"), "test_1234")
|
||||
self.assertEqual(
|
||||
map_username_to_mxid_localpart("tEST_1234", case_sensitive=True),
|
||||
"t_e_s_t__1234",
|
||||
)
|
||||
|
||||
def testSymbols(self):
|
||||
self.assertEqual(
|
||||
map_username_to_mxid_localpart("test=$?_1234"),
|
||||
"test=3d=24=3f_1234",
|
||||
)
|
||||
|
||||
def testLeadingUnderscore(self):
|
||||
self.assertEqual(map_username_to_mxid_localpart("_test_1234"), "=5ftest_1234")
|
||||
|
||||
def testNonAscii(self):
|
||||
# this should work with either a unicode or a bytes
|
||||
self.assertEqual(map_username_to_mxid_localpart(u'têst'), "t=c3=aast")
|
||||
self.assertEqual(
|
||||
map_username_to_mxid_localpart(u'têst'.encode('utf-8')),
|
||||
"t=c3=aast",
|
||||
)
|
||||
|
||||
+3
-1
@@ -34,7 +34,9 @@ from synapse.types import UserID, create_requester
|
||||
from synapse.util.logcontext import LoggingContext, LoggingContextFilter
|
||||
|
||||
from tests.server import get_clock, make_request, render, setup_test_homeserver
|
||||
from tests.utils import default_config
|
||||
from tests.utils import default_config, setupdb
|
||||
|
||||
setupdb()
|
||||
|
||||
# Set up putting Synapse's logs into Trial's.
|
||||
rootLogger = logging.getLogger()
|
||||
|
||||
@@ -139,6 +139,7 @@ def default_config(name):
|
||||
config.admin_contact = None
|
||||
config.rc_messages_per_second = 10000
|
||||
config.rc_message_burst_count = 10000
|
||||
config.saml2_enabled = False
|
||||
|
||||
config.use_frozen_dicts = False
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ commands = /bin/sh -c "flake8 synapse tests scripts scripts-dev scripts/hash_pas
|
||||
[testenv:check_isort]
|
||||
skip_install = True
|
||||
deps = isort
|
||||
commands = /bin/sh -c "isort -c -sp setup.cfg -rc synapse tests"
|
||||
commands = /bin/sh -c "isort -c -df -sp setup.cfg -rc synapse tests"
|
||||
|
||||
[testenv:check-newsfragment]
|
||||
skip_install = True
|
||||
@@ -150,4 +150,4 @@ deps =
|
||||
codecov
|
||||
commands =
|
||||
coverage combine
|
||||
codecov -X gcov
|
||||
codecov -X gcov
|
||||
|
||||
Reference in New Issue
Block a user