Merge branch 'client_server_url_rename' into develop

This commit is contained in:
Kegan Dougal 2014-08-26 15:06:24 +01:00
commit b07bc9bdbd
8 changed files with 217 additions and 14 deletions

View File

@ -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..."

View File

@ -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",

View File

@ -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):

View File

@ -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. """

View File

@ -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)

View 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

View File

@ -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)

View File

@ -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