mirror of
https://mau.dev/maunium/synapse.git
synced 2024-11-18 07:52:56 +01:00
Merge branch 'client_server_url_rename' into develop
This commit is contained in:
commit
b07bc9bdbd
8 changed files with 217 additions and 14 deletions
|
@ -6,7 +6,7 @@ CWD=$(pwd)
|
||||||
|
|
||||||
cd "$DIR/.."
|
cd "$DIR/.."
|
||||||
|
|
||||||
for port in "8080" "8081" "8082"; do
|
for port in 8080 8081 8082; do
|
||||||
echo "Starting server on port $port... "
|
echo "Starting server on port $port... "
|
||||||
|
|
||||||
python -m synapse.app.homeserver \
|
python -m synapse.app.homeserver \
|
||||||
|
@ -15,7 +15,8 @@ for port in "8080" "8081" "8082"; do
|
||||||
-f "$DIR/$port.log" \
|
-f "$DIR/$port.log" \
|
||||||
-d "$DIR/$port.db" \
|
-d "$DIR/$port.db" \
|
||||||
-vv \
|
-vv \
|
||||||
-D --pid-file "$DIR/$port.pid"
|
-D --pid-file "$DIR/$port.pid" \
|
||||||
|
--manhole $((port + 1000))
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Starting webclient on port 8000..."
|
echo "Starting webclient on port 8000..."
|
||||||
|
|
|
@ -31,6 +31,7 @@ from synapse.api.urls import (
|
||||||
)
|
)
|
||||||
|
|
||||||
from daemonize import Daemonize
|
from daemonize import Daemonize
|
||||||
|
import twisted.manhole.telnet
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
@ -238,6 +239,8 @@ def setup():
|
||||||
default="hs.pid")
|
default="hs.pid")
|
||||||
parser.add_argument("-W", "--webclient", dest="webclient", default=True,
|
parser.add_argument("-W", "--webclient", dest="webclient", default=True,
|
||||||
action="store_false", help="Don't host a web client.")
|
action="store_false", help="Don't host a web client.")
|
||||||
|
parser.add_argument("--manhole", dest="manhole", type=int, default=None,
|
||||||
|
help="Turn on the twisted telnet manhole service.")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
verbosity = int(args.verbose) if args.verbose else None
|
verbosity = int(args.verbose) if args.verbose else None
|
||||||
|
@ -281,6 +284,13 @@ def setup():
|
||||||
|
|
||||||
hs.build_db_pool()
|
hs.build_db_pool()
|
||||||
|
|
||||||
|
if args.manhole:
|
||||||
|
f = twisted.manhole.telnet.ShellFactory()
|
||||||
|
f.username = "matrix"
|
||||||
|
f.password = "rabbithole"
|
||||||
|
f.namespace['hs'] = hs
|
||||||
|
reactor.listenTCP(args.manhole, f, interface='127.0.0.1')
|
||||||
|
|
||||||
if args.daemonize:
|
if args.daemonize:
|
||||||
daemon = Daemonize(
|
daemon = Daemonize(
|
||||||
app="synapse-homeserver",
|
app="synapse-homeserver",
|
||||||
|
|
|
@ -159,12 +159,11 @@ class PresenceHandler(BaseHandler):
|
||||||
if allowed_by_subscription:
|
if allowed_by_subscription:
|
||||||
defer.returnValue(True)
|
defer.returnValue(True)
|
||||||
|
|
||||||
rm_handler = self.homeserver.get_handlers().room_member_handler
|
share_room = yield self.store.do_users_share_a_room(
|
||||||
for room_id in (yield rm_handler.get_rooms_for_user(observer_user)):
|
[observer_user, observed_user]
|
||||||
if observed_user in (yield rm_handler.get_room_members(room_id)):
|
)
|
||||||
defer.returnValue(True)
|
|
||||||
|
|
||||||
defer.returnValue(False)
|
defer.returnValue(share_room)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def get_state(self, target_user, auth_user):
|
def get_state(self, target_user, auth_user):
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
""" This module contains base REST classes for constructing REST servlets. """
|
""" This module contains base REST classes for constructing REST servlets. """
|
||||||
from synapse.api.urls import CLIENT_PREFIX
|
from synapse.api.urls import CLIENT_PREFIX
|
||||||
|
from synapse.rest.transactions import HttpTransactionStore
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
@ -59,6 +60,7 @@ class RestServlet(object):
|
||||||
self.handlers = hs.get_handlers()
|
self.handlers = hs.get_handlers()
|
||||||
self.event_factory = hs.get_event_factory()
|
self.event_factory = hs.get_event_factory()
|
||||||
self.auth = hs.get_auth()
|
self.auth = hs.get_auth()
|
||||||
|
self.txns = HttpTransactionStore()
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
""" Register this servlet with the given HTTP server. """
|
""" Register this servlet with the given HTTP server. """
|
||||||
|
|
|
@ -366,6 +366,51 @@ class RoomTriggerBackfill(RestServlet):
|
||||||
defer.returnValue((200, res))
|
defer.returnValue((200, res))
|
||||||
|
|
||||||
|
|
||||||
|
class RoomMembershipRestServlet(RestServlet):
|
||||||
|
|
||||||
|
def register(self, http_server):
|
||||||
|
# /rooms/$roomid/[invite|join|leave]
|
||||||
|
PATTERN = ("/rooms/(?P<room_id>[^/]*)/" +
|
||||||
|
"(?P<membership_action>join|invite|leave)")
|
||||||
|
register_txn_path(self, PATTERN, http_server)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def on_POST(self, request, room_id, membership_action):
|
||||||
|
user = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
|
content = _parse_json(request)
|
||||||
|
|
||||||
|
# target user is you unless it is an invite
|
||||||
|
state_key = user.to_string()
|
||||||
|
if membership_action == "invite":
|
||||||
|
if "user_id" not in content:
|
||||||
|
raise SynapseError(400, "Missing user_id key.")
|
||||||
|
state_key = content["user_id"]
|
||||||
|
|
||||||
|
event = self.event_factory.create_event(
|
||||||
|
etype=RoomMemberEvent.TYPE,
|
||||||
|
content={"membership": unicode(membership_action)},
|
||||||
|
room_id=urllib.unquote(room_id),
|
||||||
|
user_id=user.to_string(),
|
||||||
|
state_key=state_key
|
||||||
|
)
|
||||||
|
handler = self.handlers.room_member_handler
|
||||||
|
yield handler.change_membership(event)
|
||||||
|
defer.returnValue((200, ""))
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def on_PUT(self, request, room_id, membership_action, txn_id):
|
||||||
|
try:
|
||||||
|
defer.returnValue(self.txns.get_client_transaction(request, txn_id))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
response = yield self.on_POST(request, room_id, membership_action)
|
||||||
|
|
||||||
|
self.txns.store_client_transaction(request, txn_id, response)
|
||||||
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
|
||||||
def _parse_json(request):
|
def _parse_json(request):
|
||||||
try:
|
try:
|
||||||
content = json.loads(request.content.read())
|
content = json.loads(request.content.read())
|
||||||
|
@ -377,6 +422,30 @@ def _parse_json(request):
|
||||||
raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
|
raise SynapseError(400, "Content not JSON.", errcode=Codes.NOT_JSON)
|
||||||
|
|
||||||
|
|
||||||
|
def register_txn_path(servlet, regex_string, http_server):
|
||||||
|
"""Registers a transaction-based path.
|
||||||
|
|
||||||
|
This registers two paths:
|
||||||
|
PUT regex_string/$txnid
|
||||||
|
POST regex_string
|
||||||
|
|
||||||
|
Args:
|
||||||
|
regex_string (str): The regex string to register. Must NOT have a
|
||||||
|
trailing $ as this string will be appended to.
|
||||||
|
http_server : The http_server to register paths with.
|
||||||
|
"""
|
||||||
|
http_server.register_path(
|
||||||
|
"POST",
|
||||||
|
client_path_pattern(regex_string + "$"),
|
||||||
|
servlet.on_POST
|
||||||
|
)
|
||||||
|
http_server.register_path(
|
||||||
|
"PUT",
|
||||||
|
client_path_pattern(regex_string + "/(?P<txn_id>[^/]*)$"),
|
||||||
|
servlet.on_PUT
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def register_servlets(hs, http_server):
|
def register_servlets(hs, http_server):
|
||||||
RoomStateEventRestServlet(hs).register(http_server)
|
RoomStateEventRestServlet(hs).register(http_server)
|
||||||
MessageRestServlet(hs).register(http_server)
|
MessageRestServlet(hs).register(http_server)
|
||||||
|
@ -386,3 +455,4 @@ def register_servlets(hs, http_server):
|
||||||
RoomMessageListRestServlet(hs).register(http_server)
|
RoomMessageListRestServlet(hs).register(http_server)
|
||||||
JoinRoomAliasServlet(hs).register(http_server)
|
JoinRoomAliasServlet(hs).register(http_server)
|
||||||
RoomTriggerBackfill(hs).register(http_server)
|
RoomTriggerBackfill(hs).register(http_server)
|
||||||
|
RoomMembershipRestServlet(hs).register(http_server)
|
||||||
|
|
96
synapse/rest/transactions.py
Normal file
96
synapse/rest/transactions.py
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2014 matrix.org
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""This module contains logic for storing HTTP PUT transactions. This is used
|
||||||
|
to ensure idempotency when performing PUTs using the REST API."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HttpTransactionStore(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# { key : (txn_id, response) }
|
||||||
|
self.transactions = {}
|
||||||
|
|
||||||
|
def get_response(self, key, txn_id):
|
||||||
|
"""Retrieve a response for this request.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key (str): A transaction-independent key for this request. Typically
|
||||||
|
this is a combination of the path (without the transaction id) and
|
||||||
|
the user's access token.
|
||||||
|
txn_id (str): The transaction ID for this request
|
||||||
|
Returns:
|
||||||
|
A tuple of (HTTP response code, response content) or None.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.debug("get_response Key: %s TxnId: %s", key, txn_id)
|
||||||
|
(last_txn_id, response) = self.transactions[key]
|
||||||
|
if txn_id == last_txn_id:
|
||||||
|
logger.info("get_response: Returning a response for %s", key)
|
||||||
|
return response
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def store_response(self, key, txn_id, response):
|
||||||
|
"""Stores an HTTP response tuple.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key (str): A transaction-independent key for this request. Typically
|
||||||
|
this is a combination of the path (without the transaction id) and
|
||||||
|
the user's access token.
|
||||||
|
txn_id (str): The transaction ID for this request.
|
||||||
|
response (tuple): A tuple of (HTTP response code, response content)
|
||||||
|
"""
|
||||||
|
logger.debug("store_response Key: %s TxnId: %s", key, txn_id)
|
||||||
|
self.transactions[key] = (txn_id, response)
|
||||||
|
|
||||||
|
def store_client_transaction(self, request, txn_id, response):
|
||||||
|
"""Stores the request/response pair of an HTTP transaction.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (twisted.web.http.Request): The twisted HTTP request. This
|
||||||
|
request must have the transaction ID as the last path segment.
|
||||||
|
response (tuple): A tuple of (response code, response dict)
|
||||||
|
txn_id (str): The transaction ID for this request.
|
||||||
|
"""
|
||||||
|
self.store_response(self._get_key(request), txn_id, response)
|
||||||
|
|
||||||
|
def get_client_transaction(self, request, txn_id):
|
||||||
|
"""Retrieves a stored response if there was one.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (twisted.web.http.Request): The twisted HTTP request. This
|
||||||
|
request must have the transaction ID as the last path segment.
|
||||||
|
txn_id (str): The transaction ID for this request.
|
||||||
|
Returns:
|
||||||
|
The response tuple.
|
||||||
|
Raises:
|
||||||
|
KeyError if the transaction was not found.
|
||||||
|
"""
|
||||||
|
response = self.get_response(self._get_key(request), txn_id)
|
||||||
|
if response is None:
|
||||||
|
raise KeyError("Transaction not found.")
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _get_key(self, request):
|
||||||
|
token = request.args["access_token"][0]
|
||||||
|
path_without_txn_id = request.path.rsplit("/", 1)[0]
|
||||||
|
return path_without_txn_id + "/" + token
|
||||||
|
|
||||||
|
|
|
@ -150,3 +150,24 @@ class RoomMemberStore(SQLBaseStore):
|
||||||
|
|
||||||
results = [self._parse_event_from_row(r) for r in rows]
|
results = [self._parse_event_from_row(r) for r in rows]
|
||||||
defer.returnValue(results)
|
defer.returnValue(results)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def do_users_share_a_room(self, user_list):
|
||||||
|
""" Checks whether a list of users share a room.
|
||||||
|
"""
|
||||||
|
user_list_clause = " OR ".join(["m.user_id = ?"] * len(user_list))
|
||||||
|
sql = (
|
||||||
|
"SELECT m.room_id FROM room_memberships as m "
|
||||||
|
"INNER JOIN current_state_events as c "
|
||||||
|
"ON m.event_id = c.event_id "
|
||||||
|
"WHERE m.membership = 'join' "
|
||||||
|
"AND (%(clause)s) "
|
||||||
|
"GROUP BY m.room_id HAVING COUNT(m.room_id) = ?"
|
||||||
|
) % {"clause": user_list_clause}
|
||||||
|
|
||||||
|
args = user_list
|
||||||
|
args.append(len(user_list))
|
||||||
|
|
||||||
|
rows = yield self._execute(None, sql, *args)
|
||||||
|
|
||||||
|
defer.returnValue(len(rows) > 0)
|
||||||
|
|
|
@ -115,7 +115,7 @@ angular.module('matrixService', [])
|
||||||
|
|
||||||
// Joins a room
|
// Joins a room
|
||||||
join: function(room_id) {
|
join: function(room_id) {
|
||||||
return this.membershipChange(room_id, config.user_id, "join");
|
return this.membershipChange(room_id, undefined, "join");
|
||||||
},
|
},
|
||||||
|
|
||||||
joinAlias: function(room_alias) {
|
joinAlias: function(room_alias) {
|
||||||
|
@ -134,18 +134,22 @@ angular.module('matrixService', [])
|
||||||
|
|
||||||
// Leaves a room
|
// Leaves a room
|
||||||
leave: function(room_id) {
|
leave: function(room_id) {
|
||||||
return this.membershipChange(room_id, config.user_id, "leave");
|
return this.membershipChange(room_id, undefined, "leave");
|
||||||
},
|
},
|
||||||
|
|
||||||
membershipChange: function(room_id, user_id, membershipValue) {
|
membershipChange: function(room_id, user_id, membershipValue) {
|
||||||
// The REST path spec
|
// The REST path spec
|
||||||
var path = "/rooms/$room_id/state/m.room.member/$user_id";
|
var path = "/rooms/$room_id/$membership";
|
||||||
path = path.replace("$room_id", encodeURIComponent(room_id));
|
path = path.replace("$room_id", encodeURIComponent(room_id));
|
||||||
path = path.replace("$user_id", encodeURIComponent(user_id));
|
path = path.replace("$membership", encodeURIComponent(membershipValue));
|
||||||
|
|
||||||
return doRequest("PUT", path, undefined, {
|
var data = {};
|
||||||
membership: membershipValue
|
if (user_id !== undefined) {
|
||||||
});
|
data = { user_id: user_id };
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Use PUT with transaction IDs
|
||||||
|
return doRequest("POST", path, undefined, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Retrieves the room ID corresponding to a room alias
|
// Retrieves the room ID corresponding to a room alias
|
||||||
|
|
Loading…
Reference in a new issue