Merge branch 'client_server_url_rename' into develop
						commit
						b07bc9bdbd
					
				|  | @ -6,7 +6,7 @@ CWD=$(pwd) | |||
| 
 | ||||
| cd "$DIR/.." | ||||
| 
 | ||||
| for port in "8080" "8081" "8082"; do | ||||
| for port in 8080 8081 8082; do | ||||
|     echo "Starting server on port $port... " | ||||
| 
 | ||||
|     python -m synapse.app.homeserver \ | ||||
|  | @ -15,7 +15,8 @@ for port in "8080" "8081" "8082"; do | |||
|         -f "$DIR/$port.log" \ | ||||
|         -d "$DIR/$port.db" \ | ||||
|         -vv \ | ||||
|         -D --pid-file "$DIR/$port.pid" | ||||
|         -D --pid-file "$DIR/$port.pid" \ | ||||
|         --manhole $((port + 1000)) | ||||
| done | ||||
| 
 | ||||
| echo "Starting webclient on port 8000..." | ||||
|  |  | |||
|  | @ -31,6 +31,7 @@ from synapse.api.urls import ( | |||
| ) | ||||
| 
 | ||||
| from daemonize import Daemonize | ||||
| import twisted.manhole.telnet | ||||
| 
 | ||||
| import argparse | ||||
| import logging | ||||
|  | @ -238,6 +239,8 @@ def setup(): | |||
|                         default="hs.pid") | ||||
|     parser.add_argument("-W", "--webclient", dest="webclient", default=True, | ||||
|                         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() | ||||
| 
 | ||||
|     verbosity = int(args.verbose) if args.verbose else None | ||||
|  | @ -281,6 +284,13 @@ def setup(): | |||
| 
 | ||||
|     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: | ||||
|         daemon = Daemonize( | ||||
|             app="synapse-homeserver", | ||||
|  |  | |||
|  | @ -159,12 +159,11 @@ class PresenceHandler(BaseHandler): | |||
|         if allowed_by_subscription: | ||||
|             defer.returnValue(True) | ||||
| 
 | ||||
|         rm_handler = self.homeserver.get_handlers().room_member_handler | ||||
|         for room_id in (yield rm_handler.get_rooms_for_user(observer_user)): | ||||
|             if observed_user in (yield rm_handler.get_room_members(room_id)): | ||||
|                 defer.returnValue(True) | ||||
|         share_room = yield self.store.do_users_share_a_room( | ||||
|             [observer_user, observed_user] | ||||
|         ) | ||||
| 
 | ||||
|         defer.returnValue(False) | ||||
|         defer.returnValue(share_room) | ||||
| 
 | ||||
|     @defer.inlineCallbacks | ||||
|     def get_state(self, target_user, auth_user): | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ | |||
| 
 | ||||
| """ This module contains base REST classes for constructing REST servlets. """ | ||||
| from synapse.api.urls import CLIENT_PREFIX | ||||
| from synapse.rest.transactions import HttpTransactionStore | ||||
| import re | ||||
| 
 | ||||
| 
 | ||||
|  | @ -59,6 +60,7 @@ class RestServlet(object): | |||
|         self.handlers = hs.get_handlers() | ||||
|         self.event_factory = hs.get_event_factory() | ||||
|         self.auth = hs.get_auth() | ||||
|         self.txns = HttpTransactionStore() | ||||
| 
 | ||||
|     def register(self, http_server): | ||||
|         """ Register this servlet with the given HTTP server. """ | ||||
|  |  | |||
|  | @ -366,6 +366,51 @@ class RoomTriggerBackfill(RestServlet): | |||
|         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): | ||||
|     try: | ||||
|         content = json.loads(request.content.read()) | ||||
|  | @ -377,6 +422,30 @@ def _parse_json(request): | |||
|         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): | ||||
|     RoomStateEventRestServlet(hs).register(http_server) | ||||
|     MessageRestServlet(hs).register(http_server) | ||||
|  | @ -386,3 +455,4 @@ def register_servlets(hs, http_server): | |||
|     RoomMessageListRestServlet(hs).register(http_server) | ||||
|     JoinRoomAliasServlet(hs).register(http_server) | ||||
|     RoomTriggerBackfill(hs).register(http_server) | ||||
|     RoomMembershipRestServlet(hs).register(http_server) | ||||
|  |  | |||
|  | @ -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] | ||||
|         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
 | ||||
|         join: function(room_id) { | ||||
|             return this.membershipChange(room_id, config.user_id, "join"); | ||||
|             return this.membershipChange(room_id, undefined, "join"); | ||||
|         }, | ||||
| 
 | ||||
|         joinAlias: function(room_alias) { | ||||
|  | @ -134,18 +134,22 @@ angular.module('matrixService', []) | |||
| 
 | ||||
|         // Leaves a room
 | ||||
|         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) { | ||||
|             // 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("$user_id", encodeURIComponent(user_id)); | ||||
|             path = path.replace("$membership", encodeURIComponent(membershipValue)); | ||||
| 
 | ||||
|             return doRequest("PUT", path, undefined, { | ||||
|                  membership: membershipValue | ||||
|             }); | ||||
|             var data = {}; | ||||
|             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
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Kegan Dougal
						Kegan Dougal