mirror of
https://mau.dev/maunium/synapse.git
synced 2024-10-01 01:36:05 -04:00
Merge branch 'develop' of github.com:matrix-org/synapse into stream_refactor
Conflicts: synapse/handlers/events.py synapse/rest/events.py synapse/rest/room.py
This commit is contained in:
commit
47519cd8c2
2
.gitignore
vendored
2
.gitignore
vendored
@ -10,7 +10,7 @@ docs/build/
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
|
|
||||||
cmdclient_config.json
|
cmdclient_config.json
|
||||||
homeserver.db
|
homeserver*.db
|
||||||
|
|
||||||
.coverage
|
.coverage
|
||||||
htmlcov
|
htmlcov
|
||||||
|
@ -471,7 +471,7 @@ class SynapseCmd(cmd.Cmd):
|
|||||||
room_name = args["vis"]
|
room_name = args["vis"]
|
||||||
body["room_alias_name"] = room_name
|
body["room_alias_name"] = room_name
|
||||||
|
|
||||||
reactor.callFromThread(self._run_and_pprint, "POST", "/rooms", body)
|
reactor.callFromThread(self._run_and_pprint, "POST", "/createRoom", body)
|
||||||
|
|
||||||
def do_raw(self, line):
|
def do_raw(self, line):
|
||||||
"""Directly send a JSON object: "raw <method> <path> <data> <notoken>"
|
"""Directly send a JSON object: "raw <method> <path> <data> <notoken>"
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
{
|
{
|
||||||
"path": "/presence",
|
"path": "/presence",
|
||||||
"description": "Presence operations"
|
"description": "Presence operations"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/events",
|
||||||
|
"description": "Event operations"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"authorizations": {
|
"authorizations": {
|
||||||
|
@ -1,256 +1,68 @@
|
|||||||
{
|
{
|
||||||
"apiVersion": "1.0.0",
|
"apiVersion": "1.0.0",
|
||||||
"swaggerVersion": "1.2",
|
"swaggerVersion": "1.2",
|
||||||
"basePath": "http://petstore.swagger.wordnik.com/api",
|
"basePath": "http://localhost:8080/matrix/client/api/v1",
|
||||||
"resourcePath": "/user",
|
"resourcePath": "/events",
|
||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"apis": [
|
"apis": [
|
||||||
{
|
{
|
||||||
"path": "/user",
|
"path": "/events",
|
||||||
"operations": [
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"summary": "Create user",
|
|
||||||
"notes": "This can only be done by the logged in user.",
|
|
||||||
"type": "void",
|
|
||||||
"nickname": "createUser",
|
|
||||||
"authorizations": {
|
|
||||||
"oauth2": [
|
|
||||||
{
|
|
||||||
"scope": "test:anything",
|
|
||||||
"description": "anything"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"description": "Created user object",
|
|
||||||
"required": true,
|
|
||||||
"type": "User",
|
|
||||||
"paramType": "body"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/user/logout",
|
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"summary": "Logs out current logged in user session",
|
"summary": "Listen on the event stream",
|
||||||
"notes": "",
|
"notes": "This can only be done by the logged in user. This will block until an event is received, or until the timeout is reached.",
|
||||||
"type": "void",
|
"type": "PaginationChunk",
|
||||||
"nickname": "logoutUser",
|
"nickname": "get_event_stream"
|
||||||
"authorizations": {},
|
|
||||||
"parameters": []
|
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
},
|
"parameters": [
|
||||||
{
|
|
||||||
"path": "/user/createWithArray",
|
|
||||||
"operations": [
|
|
||||||
{
|
{
|
||||||
"method": "POST",
|
"name": "from",
|
||||||
"summary": "Creates list of users with given input array",
|
"description": "The token to stream from.",
|
||||||
"notes": "",
|
"required": false,
|
||||||
"type": "void",
|
|
||||||
"nickname": "createUsersWithArrayInput",
|
|
||||||
"authorizations": {
|
|
||||||
"oauth2": [
|
|
||||||
{
|
|
||||||
"scope": "test:anything",
|
|
||||||
"description": "anything"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"description": "List of user object",
|
|
||||||
"required": true,
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "User"
|
|
||||||
},
|
|
||||||
"paramType": "body"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/user/createWithList",
|
|
||||||
"operations": [
|
|
||||||
{
|
|
||||||
"method": "POST",
|
|
||||||
"summary": "Creates list of users with given list input",
|
|
||||||
"notes": "",
|
|
||||||
"type": "void",
|
|
||||||
"nickname": "createUsersWithListInput",
|
|
||||||
"authorizations": {
|
|
||||||
"oauth2": [
|
|
||||||
{
|
|
||||||
"scope": "test:anything",
|
|
||||||
"description": "anything"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"description": "List of user object",
|
|
||||||
"required": true,
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "User"
|
|
||||||
},
|
|
||||||
"paramType": "body"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/user/{username}",
|
|
||||||
"operations": [
|
|
||||||
{
|
|
||||||
"method": "PUT",
|
|
||||||
"summary": "Updated user",
|
|
||||||
"notes": "This can only be done by the logged in user.",
|
|
||||||
"type": "void",
|
|
||||||
"nickname": "updateUser",
|
|
||||||
"authorizations": {
|
|
||||||
"oauth2": [
|
|
||||||
{
|
|
||||||
"scope": "test:anything",
|
|
||||||
"description": "anything"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"description": "name that need to be deleted",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "body",
|
|
||||||
"description": "Updated user object",
|
|
||||||
"required": true,
|
|
||||||
"type": "User",
|
|
||||||
"paramType": "body"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 400,
|
|
||||||
"message": "Invalid username supplied"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": 404,
|
|
||||||
"message": "User not found"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "DELETE",
|
|
||||||
"summary": "Delete user",
|
|
||||||
"notes": "This can only be done by the logged in user.",
|
|
||||||
"type": "void",
|
|
||||||
"nickname": "deleteUser",
|
|
||||||
"authorizations": {
|
|
||||||
"oauth2": [
|
|
||||||
{
|
|
||||||
"scope": "test:anything",
|
|
||||||
"description": "anything"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"description": "The name that needs to be deleted",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 400,
|
|
||||||
"message": "Invalid username supplied"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": 404,
|
|
||||||
"message": "User not found"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"summary": "Get user by user name",
|
|
||||||
"notes": "",
|
|
||||||
"type": "User",
|
|
||||||
"nickname": "getUserByName",
|
|
||||||
"authorizations": {},
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"description": "The name that needs to be fetched. Use user1 for testing.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 400,
|
|
||||||
"message": "Invalid username supplied"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": 404,
|
|
||||||
"message": "User not found"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/user/login",
|
|
||||||
"operations": [
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"summary": "Logs user into the system",
|
|
||||||
"notes": "",
|
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"nickname": "loginUser",
|
"paramType": "query"
|
||||||
"authorizations": {},
|
},
|
||||||
|
{
|
||||||
|
"name": "timeout",
|
||||||
|
"description": "The maximum time in milliseconds to wait for an event.",
|
||||||
|
"required": false,
|
||||||
|
"type": "integer",
|
||||||
|
"paramType": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responseMessages": [
|
||||||
|
{
|
||||||
|
"code": 400,
|
||||||
|
"message": "Bad pagination token."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/events/{eventId}",
|
||||||
|
"operations": [
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"summary": "Get information about a single event.",
|
||||||
|
"notes": "Get information about a single event.",
|
||||||
|
"type": "Event",
|
||||||
|
"nickname": "get_event",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "username",
|
"name": "eventId",
|
||||||
"description": "The user name for login",
|
"description": "The event ID to get.",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "query"
|
"paramType": "path"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "password",
|
|
||||||
"description": "The password for login in clear text",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "query"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responseMessages": [
|
"responseMessages": [
|
||||||
{
|
{
|
||||||
"code": 400,
|
"code": 404,
|
||||||
"message": "Invalid username and password combination"
|
"message": "Event not found."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -258,40 +70,41 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"models": {
|
"models": {
|
||||||
"User": {
|
"PaginationChunk": {
|
||||||
"id": "User",
|
"id": "PaginationChunk",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"start": {
|
||||||
"type": "integer",
|
"type": "string",
|
||||||
"format": "int64"
|
"description": "A token which correlates to the first value in \"chunk\" for paginating.",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
"firstName": {
|
"end": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
|
"description": "A token which correlates to the last value in \"chunk\" for paginating.",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
"username": {
|
"chunk": {
|
||||||
"type": "string"
|
"type": "array",
|
||||||
|
"description": "An array of events.",
|
||||||
|
"required": true,
|
||||||
|
"items": {
|
||||||
|
"$ref": "Event"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Event": {
|
||||||
|
"id": "Event",
|
||||||
|
"properties": {
|
||||||
|
"event_id": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "An ID which uniquely identifies this event.",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
"lastName": {
|
"room_id": {
|
||||||
"type": "string"
|
"type": "string",
|
||||||
},
|
"description": "The room in which this event occurred.",
|
||||||
"email": {
|
"required": true
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"phone": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"userStatus": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int32",
|
|
||||||
"description": "User Status",
|
|
||||||
"enum": [
|
|
||||||
"1-registered",
|
|
||||||
"2-active",
|
|
||||||
"3-closed"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/presence_list/{userId}",
|
"path": "/presence/list/{userId}",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
},
|
},
|
||||||
"apis": [
|
"apis": [
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/messages/{userId}/{messageId}",
|
"path": "/rooms/{roomId}/send/m.room.message/{txnId}",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
@ -41,67 +41,18 @@
|
|||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "userId",
|
"name": "txnId",
|
||||||
"description": "The fully qualified message sender's user ID.",
|
"description": "A client transaction ID to ensure idempotency.",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "messageId",
|
|
||||||
"description": "A message ID which is unique for each room and user.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 403,
|
|
||||||
"message": "Must send messages as yourself."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"summary": "Get a message from this room.",
|
|
||||||
"notes": "Get a message from this room.",
|
|
||||||
"type": "Message",
|
|
||||||
"nickname": "get_message",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "roomId",
|
|
||||||
"description": "The room to send the message in.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "userId",
|
|
||||||
"description": "The fully qualified message sender's user ID.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "messageId",
|
|
||||||
"description": "A message ID which is unique for each room and user.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 404,
|
|
||||||
"message": "Message not found."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/topic",
|
"path": "/rooms/{roomId}/state/m.room.topic",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
@ -127,12 +78,6 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 403,
|
|
||||||
"message": "Must send messages as yourself."
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -160,7 +105,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/messages/{msgSenderId}/{messageId}/feedback/{senderId}/{feedbackType}",
|
"path": "/rooms/{roomId}/send/m.room.message.feedback/{txnId}",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
@ -187,105 +132,33 @@
|
|||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "msgSenderId",
|
"name": "txnId",
|
||||||
"description": "The fully qualified message sender's user ID.",
|
"description": "A client transaction ID to ensure idempotency.",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "messageId",
|
|
||||||
"description": "A message ID which is unique for each room and user.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "senderId",
|
|
||||||
"description": "The fully qualified feedback sender's user ID.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "feedbackType",
|
|
||||||
"description": "The type of feedback being sent.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path",
|
|
||||||
"enum": [
|
|
||||||
"d",
|
|
||||||
"r"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responseMessages": [
|
"responseMessages": [
|
||||||
{
|
|
||||||
"code": 403,
|
|
||||||
"message": "Must send feedback as yourself."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"code": 400,
|
"code": 400,
|
||||||
"message": "Bad feedback type."
|
"message": "Bad feedback type."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"method": "GET",
|
|
||||||
"summary": "Get feedback for a message.",
|
|
||||||
"notes": "Get feedback for a message.",
|
|
||||||
"type": "Feedback",
|
|
||||||
"nickname": "get_feedback",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "roomId",
|
|
||||||
"description": "The room to send the message in.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "msgSenderId",
|
|
||||||
"description": "The fully qualified message sender's user ID.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "messageId",
|
|
||||||
"description": "A message ID which is unique for each room and user.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "senderId",
|
|
||||||
"description": "The fully qualified feedback sender's user ID.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "feedbackType",
|
|
||||||
"description": "Enum: The type of feedback being sent.",
|
|
||||||
"required": true,
|
|
||||||
"type": "string",
|
|
||||||
"paramType": "path",
|
|
||||||
"enum": [
|
|
||||||
"d",
|
|
||||||
"r"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responseMessages": [
|
|
||||||
{
|
|
||||||
"code": 404,
|
|
||||||
"message": "Feedback not found."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/members/{userId}/state",
|
"path": "/rooms/{roomId}/members/{userId}/state",
|
||||||
"operations": [
|
"operations": [
|
||||||
@ -412,22 +285,30 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"path": "/join/{roomAlias}",
|
"path": "/join/{roomAliasOrId}",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "PUT",
|
"method": "PUT",
|
||||||
"summary": "Join a room via a room alias.",
|
"summary": "Join a room via a room alias or room ID.",
|
||||||
"notes": "Join a room via a room alias.",
|
"notes": "Join a room via a room alias or room ID.",
|
||||||
"type": "RoomInfo",
|
"type": "RoomInfo",
|
||||||
"nickname": "join_room_via_alias",
|
"nickname": "join",
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "roomAlias",
|
"name": "roomAliasOrId",
|
||||||
"description": "The room alias to join.",
|
"description": "The room alias or room ID to join.",
|
||||||
"required": true,
|
"required": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"paramType": "path"
|
"paramType": "path"
|
||||||
@ -443,7 +324,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms",
|
"path": "/createRoom",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
@ -477,7 +358,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/messages/list",
|
"path": "/rooms/{roomId}/messages",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
@ -519,7 +400,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "/rooms/{roomId}/members/list",
|
"path": "/rooms/{roomId}/members",
|
||||||
"operations": [
|
"operations": [
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
@ -732,76 +613,6 @@
|
|||||||
"type": "Member"
|
"type": "Member"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Tag": {
|
|
||||||
"id": "Tag",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int64"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Pet": {
|
|
||||||
"id": "Pet",
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"name"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int64",
|
|
||||||
"description": "unique identifier for the pet",
|
|
||||||
"minimum": "0.0",
|
|
||||||
"maximum": "100.0"
|
|
||||||
},
|
|
||||||
"category": {
|
|
||||||
"$ref": "Category"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"photoUrls": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "Tag"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"status": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "pet status in the store",
|
|
||||||
"enum": [
|
|
||||||
"available",
|
|
||||||
"pending",
|
|
||||||
"sold"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Category": {
|
|
||||||
"id": "Category",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int64"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"pet": {
|
|
||||||
"$ref": "Pet"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
=========================
|
|
||||||
Client-Server URL Summary
|
|
||||||
=========================
|
|
||||||
|
|
||||||
A brief overview of the URL scheme involved in the Synapse Client-Server API.
|
|
||||||
|
|
||||||
|
|
||||||
URLs
|
|
||||||
====
|
|
||||||
|
|
||||||
Fetch events:
|
|
||||||
GET /events
|
|
||||||
|
|
||||||
Registering an account
|
|
||||||
POST /register
|
|
||||||
|
|
||||||
Unregistering an account
|
|
||||||
POST /unregister
|
|
||||||
|
|
||||||
Rooms
|
|
||||||
-----
|
|
||||||
|
|
||||||
Creating a room by ID
|
|
||||||
PUT /rooms/$roomid
|
|
||||||
|
|
||||||
Creating an anonymous room
|
|
||||||
POST /rooms
|
|
||||||
|
|
||||||
Room topic
|
|
||||||
GET /rooms/$roomid/topic
|
|
||||||
PUT /rooms/$roomid/topic
|
|
||||||
|
|
||||||
List rooms
|
|
||||||
GET /rooms/list
|
|
||||||
|
|
||||||
Invite/Join/Leave
|
|
||||||
GET /rooms/$roomid/members/$userid/state
|
|
||||||
PUT /rooms/$roomid/members/$userid/state
|
|
||||||
DELETE /rooms/$roomid/members/$userid/state
|
|
||||||
|
|
||||||
List members
|
|
||||||
GET /rooms/$roomid/members/list
|
|
||||||
|
|
||||||
Sending/reading messages
|
|
||||||
PUT /rooms/$roomid/messages/$sender/$msgid
|
|
||||||
|
|
||||||
Feedback
|
|
||||||
GET /rooms/$roomid/messages/$sender/$msgid/feedback/$feedbackuser/$feedback
|
|
||||||
PUT /rooms/$roomid/messages/$sender/$msgid/feedback/$feedbackuser/$feedback
|
|
||||||
|
|
||||||
Paginating messages
|
|
||||||
GET /rooms/$roomid/messages/list
|
|
||||||
|
|
||||||
Profiles
|
|
||||||
--------
|
|
||||||
|
|
||||||
Display name
|
|
||||||
GET /profile/$userid/displayname
|
|
||||||
PUT /profile/$userid/displayname
|
|
||||||
|
|
||||||
Avatar URL
|
|
||||||
GET /profile/$userid/avatar_url
|
|
||||||
PUT /profile/$userid/avatar_url
|
|
||||||
|
|
||||||
Metadata
|
|
||||||
GET /profile/$userid/metadata
|
|
||||||
POST /profile/$userid/metadata
|
|
||||||
|
|
||||||
Presence
|
|
||||||
--------
|
|
||||||
|
|
||||||
My state or status message
|
|
||||||
GET /presence/$userid/status
|
|
||||||
PUT /presence/$userid/status
|
|
||||||
also 'GET' for fetching others
|
|
||||||
|
|
||||||
TODO(paul): per-device idle time, device type; similar to above
|
|
||||||
|
|
||||||
My presence list
|
|
||||||
GET /presence_list/$myuserid
|
|
||||||
POST /presence_list/$myuserid
|
|
||||||
body is JSON-encoded dict of keys:
|
|
||||||
invite: list of UserID strings to invite
|
|
||||||
drop: list of UserID strings to remove
|
|
||||||
TODO(paul): define other ops: accept, group management, ordering?
|
|
||||||
|
|
||||||
Presence polling start/stop
|
|
||||||
POST /presence_list/$myuserid?op=start
|
|
||||||
POST /presence_list/$myuserid?op=stop
|
|
||||||
|
|
||||||
Presence invite
|
|
||||||
POST /presence_list/$myuserid/invite/$targetuserid
|
|
@ -162,6 +162,8 @@ class Auth(object):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
user_id = yield self.store.get_user_by_token(token=token)
|
user_id = yield self.store.get_user_by_token(token=token)
|
||||||
|
if not user_id:
|
||||||
|
raise StoreError()
|
||||||
defer.returnValue(self.hs.parse_userid(user_id))
|
defer.returnValue(self.hs.parse_userid(user_id))
|
||||||
except StoreError:
|
except StoreError:
|
||||||
raise AuthError(403, "Unrecognised access token.",
|
raise AuthError(403, "Unrecognised access token.",
|
||||||
|
@ -43,6 +43,22 @@ import re
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMAS = [
|
||||||
|
"transactions",
|
||||||
|
"pdu",
|
||||||
|
"users",
|
||||||
|
"profiles",
|
||||||
|
"presence",
|
||||||
|
"im",
|
||||||
|
"room_aliases",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Remember to update this number every time an incompatible change is made to
|
||||||
|
# database schema files, so the users will be informed on server restarts.
|
||||||
|
SCHEMA_VERSION = 1
|
||||||
|
|
||||||
|
|
||||||
class SynapseHomeServer(HomeServer):
|
class SynapseHomeServer(HomeServer):
|
||||||
|
|
||||||
def build_http_client(self):
|
def build_http_client(self):
|
||||||
@ -65,31 +81,39 @@ class SynapseHomeServer(HomeServer):
|
|||||||
don't have to worry about overwriting existing content.
|
don't have to worry about overwriting existing content.
|
||||||
"""
|
"""
|
||||||
logging.info("Preparing database: %s...", self.db_name)
|
logging.info("Preparing database: %s...", self.db_name)
|
||||||
|
|
||||||
|
with sqlite3.connect(self.db_name) as db_conn:
|
||||||
|
c = db_conn.cursor()
|
||||||
|
c.execute("PRAGMA user_version")
|
||||||
|
row = c.fetchone()
|
||||||
|
|
||||||
|
if row and row[0]:
|
||||||
|
user_version = row[0]
|
||||||
|
|
||||||
|
if user_version < SCHEMA_VERSION:
|
||||||
|
# TODO(paul): add some kind of intelligent fixup here
|
||||||
|
raise ValueError("Cannot use this database as the " +
|
||||||
|
"schema version (%d) does not match (%d)" %
|
||||||
|
(user_version, SCHEMA_VERSION)
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
for sql_loc in SCHEMAS:
|
||||||
|
sql_script = read_schema(sql_loc)
|
||||||
|
|
||||||
|
c.executescript(sql_script)
|
||||||
|
db_conn.commit()
|
||||||
|
|
||||||
|
c.execute("PRAGMA user_version = %d" % SCHEMA_VERSION)
|
||||||
|
|
||||||
|
c.close()
|
||||||
|
|
||||||
|
logging.info("Database prepared in %s.", self.db_name)
|
||||||
|
|
||||||
pool = adbapi.ConnectionPool(
|
pool = adbapi.ConnectionPool(
|
||||||
'sqlite3', self.db_name, check_same_thread=False,
|
'sqlite3', self.db_name, check_same_thread=False,
|
||||||
cp_min=1, cp_max=1)
|
cp_min=1, cp_max=1)
|
||||||
|
|
||||||
schemas = [
|
|
||||||
"transactions",
|
|
||||||
"pdu",
|
|
||||||
"users",
|
|
||||||
"profiles",
|
|
||||||
"presence",
|
|
||||||
"im",
|
|
||||||
"room_aliases",
|
|
||||||
]
|
|
||||||
|
|
||||||
for sql_loc in schemas:
|
|
||||||
sql_script = read_schema(sql_loc)
|
|
||||||
|
|
||||||
with sqlite3.connect(self.db_name) as db_conn:
|
|
||||||
c = db_conn.cursor()
|
|
||||||
c.executescript(sql_script)
|
|
||||||
c.close()
|
|
||||||
db_conn.commit()
|
|
||||||
|
|
||||||
logging.info("Database prepared in %s.", self.db_name)
|
|
||||||
|
|
||||||
return pool
|
return pool
|
||||||
|
|
||||||
def create_resource_tree(self, web_client, redirect_root_to_web_client):
|
def create_resource_tree(self, web_client, redirect_root_to_web_client):
|
||||||
@ -184,6 +208,7 @@ class SynapseHomeServer(HomeServer):
|
|||||||
|
|
||||||
def start_listening(self, port):
|
def start_listening(self, port):
|
||||||
reactor.listenTCP(port, Site(self.root_resource))
|
reactor.listenTCP(port, Site(self.root_resource))
|
||||||
|
logger.info("Synapse now listening on port %d", port)
|
||||||
|
|
||||||
|
|
||||||
def setup_logging(verbosity=0, filename=None, config_path=None):
|
def setup_logging(verbosity=0, filename=None, config_path=None):
|
||||||
@ -282,7 +307,7 @@ def setup():
|
|||||||
redirect_root_to_web_client=True)
|
redirect_root_to_web_client=True)
|
||||||
hs.start_listening(args.port)
|
hs.start_listening(args.port)
|
||||||
|
|
||||||
hs.build_db_pool()
|
hs.get_db_pool()
|
||||||
|
|
||||||
if args.manhole:
|
if args.manhole:
|
||||||
f = twisted.manhole.telnet.ShellFactory()
|
f = twisted.manhole.telnet.ShellFactory()
|
||||||
|
@ -17,12 +17,13 @@ from .register import RegistrationHandler
|
|||||||
from .room import (
|
from .room import (
|
||||||
MessageHandler, RoomCreationHandler, RoomMemberHandler, RoomListHandler
|
MessageHandler, RoomCreationHandler, RoomMemberHandler, RoomListHandler
|
||||||
)
|
)
|
||||||
from .events import EventStreamHandler
|
from .events import EventStreamHandler, EventHandler
|
||||||
from .federation import FederationHandler
|
from .federation import FederationHandler
|
||||||
from .login import LoginHandler
|
from .login import LoginHandler
|
||||||
from .profile import ProfileHandler
|
from .profile import ProfileHandler
|
||||||
from .presence import PresenceHandler
|
from .presence import PresenceHandler
|
||||||
from .directory import DirectoryHandler
|
from .directory import DirectoryHandler
|
||||||
|
from .typing import TypingNotificationHandler
|
||||||
|
|
||||||
|
|
||||||
class Handlers(object):
|
class Handlers(object):
|
||||||
@ -39,9 +40,11 @@ class Handlers(object):
|
|||||||
self.room_creation_handler = RoomCreationHandler(hs)
|
self.room_creation_handler = RoomCreationHandler(hs)
|
||||||
self.room_member_handler = RoomMemberHandler(hs)
|
self.room_member_handler = RoomMemberHandler(hs)
|
||||||
self.event_stream_handler = EventStreamHandler(hs)
|
self.event_stream_handler = EventStreamHandler(hs)
|
||||||
|
self.event_handler = EventHandler(hs)
|
||||||
self.federation_handler = FederationHandler(hs)
|
self.federation_handler = FederationHandler(hs)
|
||||||
self.profile_handler = ProfileHandler(hs)
|
self.profile_handler = ProfileHandler(hs)
|
||||||
self.presence_handler = PresenceHandler(hs)
|
self.presence_handler = PresenceHandler(hs)
|
||||||
self.room_list_handler = RoomListHandler(hs)
|
self.room_list_handler = RoomListHandler(hs)
|
||||||
self.login_handler = LoginHandler(hs)
|
self.login_handler = LoginHandler(hs)
|
||||||
self.directory_handler = DirectoryHandler(hs)
|
self.directory_handler = DirectoryHandler(hs)
|
||||||
|
self.typing_notification_handler = TypingNotificationHandler(hs)
|
||||||
|
@ -47,26 +47,81 @@ class EventStreamHandler(BaseHandler):
|
|||||||
def get_stream(self, auth_user_id, pagin_config, timeout=0):
|
def get_stream(self, auth_user_id, pagin_config, timeout=0):
|
||||||
auth_user = self.hs.parse_userid(auth_user_id)
|
auth_user = self.hs.parse_userid(auth_user_id)
|
||||||
|
|
||||||
if pagin_config.from_token is None:
|
try:
|
||||||
pagin_config.from_token = None
|
if auth_user not in self._streams_per_user:
|
||||||
|
self._streams_per_user[auth_user] = 0
|
||||||
|
if auth_user in self._stop_timer_per_user:
|
||||||
|
self.clock.cancel_call_later(
|
||||||
|
self._stop_timer_per_user.pop(auth_user))
|
||||||
|
else:
|
||||||
|
self.distributor.fire(
|
||||||
|
"started_user_eventstream", auth_user
|
||||||
|
)
|
||||||
|
self._streams_per_user[auth_user] += 1
|
||||||
|
|
||||||
rm_handler = self.hs.get_handlers().room_member_handler
|
|
||||||
room_ids = yield rm_handler.get_rooms_for_user(auth_user)
|
|
||||||
|
|
||||||
events, tokens = yield self.notifier.get_events_for(
|
if pagin_config.from_token is None:
|
||||||
auth_user, room_ids, pagin_config, timeout
|
pagin_config.from_token = None
|
||||||
)
|
|
||||||
|
|
||||||
chunks = [
|
rm_handler = self.hs.get_handlers().room_member_handler
|
||||||
e.get_dict() if isinstance(e, SynapseEvent) else e
|
room_ids = yield rm_handler.get_rooms_for_user(auth_user)
|
||||||
for e in events
|
|
||||||
]
|
|
||||||
|
|
||||||
chunk = {
|
events, tokens = yield self.notifier.get_events_for(
|
||||||
"chunk": chunks,
|
auth_user, room_ids, pagin_config, timeout
|
||||||
"start": tokens[0].to_string(),
|
)
|
||||||
"end": tokens[1].to_string(),
|
|
||||||
}
|
|
||||||
|
|
||||||
defer.returnValue(chunk)
|
chunks = [
|
||||||
|
e.get_dict() if isinstance(e, SynapseEvent) else e
|
||||||
|
for e in events
|
||||||
|
]
|
||||||
|
|
||||||
|
chunk = {
|
||||||
|
"chunk": chunks,
|
||||||
|
"start": tokens[0].to_string(),
|
||||||
|
"end": tokens[1].to_string(),
|
||||||
|
}
|
||||||
|
|
||||||
|
defer.returnValue(chunk)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
self._streams_per_user[auth_user] -= 1
|
||||||
|
if not self._streams_per_user[auth_user]:
|
||||||
|
del self._streams_per_user[auth_user]
|
||||||
|
|
||||||
|
# 10 seconds of grace to allow the client to reconnect again
|
||||||
|
# before we think they're gone
|
||||||
|
def _later():
|
||||||
|
self.distributor.fire(
|
||||||
|
"stopped_user_eventstream", auth_user
|
||||||
|
)
|
||||||
|
del self._stop_timer_per_user[auth_user]
|
||||||
|
|
||||||
|
self._stop_timer_per_user[auth_user] = (
|
||||||
|
self.clock.call_later(5, _later)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EventHandler(BaseHandler):
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def get_event(self, user, event_id):
|
||||||
|
"""Retrieve a single specified event.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (synapse.types.UserID): The user requesting the event
|
||||||
|
event_id (str): The event ID to obtain.
|
||||||
|
Returns:
|
||||||
|
dict: An event, or None if there is no event matching this ID.
|
||||||
|
Raises:
|
||||||
|
SynapseError if there was a problem retrieving this event, or
|
||||||
|
AuthError if the user does not have the rights to inspect this
|
||||||
|
event.
|
||||||
|
"""
|
||||||
|
event = yield self.store.get_event(event_id)
|
||||||
|
|
||||||
|
if not event:
|
||||||
|
defer.returnValue(None)
|
||||||
|
return
|
||||||
|
|
||||||
|
yield self.auth.check(event, raises=True)
|
||||||
|
defer.returnValue(event)
|
||||||
|
146
synapse/handlers/typing.py
Normal file
146
synapse/handlers/typing.py
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
# -*- 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.
|
||||||
|
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from ._base import BaseHandler
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# A tiny object useful for storing a user's membership in a room, as a mapping
|
||||||
|
# key
|
||||||
|
RoomMember = namedtuple("RoomMember", ("room_id", "user"))
|
||||||
|
|
||||||
|
|
||||||
|
class TypingNotificationHandler(BaseHandler):
|
||||||
|
def __init__(self, hs):
|
||||||
|
super(TypingNotificationHandler, self).__init__(hs)
|
||||||
|
|
||||||
|
self.homeserver = hs
|
||||||
|
|
||||||
|
self.clock = hs.get_clock()
|
||||||
|
|
||||||
|
self.federation = hs.get_replication_layer()
|
||||||
|
|
||||||
|
self.federation.register_edu_handler("m.typing", self._recv_edu)
|
||||||
|
|
||||||
|
self._member_typing_until = {}
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def started_typing(self, target_user, auth_user, room_id, timeout):
|
||||||
|
if not target_user.is_mine:
|
||||||
|
raise SynapseError(400, "User is not hosted on this Home Server")
|
||||||
|
|
||||||
|
if target_user != auth_user:
|
||||||
|
raise AuthError(400, "Cannot set another user's typing state")
|
||||||
|
|
||||||
|
until = self.clock.time_msec() + timeout
|
||||||
|
member = RoomMember(room_id=room_id, user=target_user)
|
||||||
|
|
||||||
|
was_present = member in self._member_typing_until
|
||||||
|
|
||||||
|
self._member_typing_until[member] = until
|
||||||
|
|
||||||
|
if was_present:
|
||||||
|
# No point sending another notification
|
||||||
|
defer.returnValue(None)
|
||||||
|
|
||||||
|
yield self._push_update(
|
||||||
|
room_id=room_id,
|
||||||
|
user=target_user,
|
||||||
|
typing=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def stopped_typing(self, target_user, auth_user, room_id):
|
||||||
|
if not target_user.is_mine:
|
||||||
|
raise SynapseError(400, "User is not hosted on this Home Server")
|
||||||
|
|
||||||
|
if target_user != auth_user:
|
||||||
|
raise AuthError(400, "Cannot set another user's typing state")
|
||||||
|
|
||||||
|
member = RoomMember(room_id=room_id, user=target_user)
|
||||||
|
|
||||||
|
if member not in self._member_typing_until:
|
||||||
|
# No point
|
||||||
|
defer.returnValue(None)
|
||||||
|
|
||||||
|
yield self._push_update(
|
||||||
|
room_id=room_id,
|
||||||
|
user=target_user,
|
||||||
|
typing=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _push_update(self, room_id, user, typing):
|
||||||
|
localusers = set()
|
||||||
|
remotedomains = set()
|
||||||
|
|
||||||
|
rm_handler = self.homeserver.get_handlers().room_member_handler
|
||||||
|
yield rm_handler.fetch_room_distributions_into(room_id,
|
||||||
|
localusers=localusers, remotedomains=remotedomains,
|
||||||
|
ignore_user=user)
|
||||||
|
|
||||||
|
for u in localusers:
|
||||||
|
self.push_update_to_clients(
|
||||||
|
room_id=room_id,
|
||||||
|
observer_user=u,
|
||||||
|
observed_user=user,
|
||||||
|
typing=typing,
|
||||||
|
)
|
||||||
|
|
||||||
|
deferreds = []
|
||||||
|
for domain in remotedomains:
|
||||||
|
deferreds.append(self.federation.send_edu(
|
||||||
|
destination=domain,
|
||||||
|
edu_type="m.typing",
|
||||||
|
content={
|
||||||
|
"room_id": room_id,
|
||||||
|
"user_id": user.to_string(),
|
||||||
|
"typing": typing,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
|
yield defer.DeferredList(deferreds, consumeErrors=False)
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def _recv_edu(self, origin, content):
|
||||||
|
room_id = content["room_id"]
|
||||||
|
user = self.homeserver.parse_userid(content["user_id"])
|
||||||
|
|
||||||
|
localusers = set()
|
||||||
|
|
||||||
|
rm_handler = self.homeserver.get_handlers().room_member_handler
|
||||||
|
yield rm_handler.fetch_room_distributions_into(room_id,
|
||||||
|
localusers=localusers)
|
||||||
|
|
||||||
|
for u in localusers:
|
||||||
|
self.push_update_to_clients(
|
||||||
|
room_id=room_id,
|
||||||
|
observer_user=u,
|
||||||
|
observed_user=user,
|
||||||
|
typing=content["typing"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def push_update_to_clients(self, room_id, observer_user, observed_user,
|
||||||
|
typing):
|
||||||
|
# TODO(paul) steal this from presence.py
|
||||||
|
pass
|
@ -48,6 +48,22 @@ class EventStreamRestServlet(RestServlet):
|
|||||||
return (200, {})
|
return (200, {})
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Unit test gets, with and without auth, with different kinds of events.
|
||||||
|
class EventRestServlet(RestServlet):
|
||||||
|
PATTERN = client_path_pattern("/events/(?P<event_id>[^/]*)$")
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def on_GET(self, request, event_id):
|
||||||
|
auth_user = yield self.auth.get_user_by_req(request)
|
||||||
|
handler = self.handlers.event_handler
|
||||||
|
event = yield handler.get_event(auth_user, event_id)
|
||||||
|
|
||||||
|
if event:
|
||||||
|
defer.returnValue((200, event.get_dict()))
|
||||||
|
else:
|
||||||
|
defer.returnValue((404, "Event not found."))
|
||||||
|
|
||||||
|
|
||||||
def register_servlets(hs, http_server):
|
def register_servlets(hs, http_server):
|
||||||
EventStreamRestServlet(hs).register(http_server)
|
EventStreamRestServlet(hs).register(http_server)
|
||||||
|
EventRestServlet(hs).register(http_server)
|
||||||
|
@ -68,7 +68,7 @@ class PresenceStatusRestServlet(RestServlet):
|
|||||||
|
|
||||||
|
|
||||||
class PresenceListRestServlet(RestServlet):
|
class PresenceListRestServlet(RestServlet):
|
||||||
PATTERN = client_path_pattern("/presence_list/(?P<user_id>[^/]*)")
|
PATTERN = client_path_pattern("/presence/list/(?P<user_id>[^/]*)")
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_GET(self, request, user_id):
|
def on_GET(self, request, user_id):
|
||||||
|
@ -18,11 +18,9 @@ from twisted.internet import defer
|
|||||||
|
|
||||||
from base import RestServlet, client_path_pattern
|
from base import RestServlet, client_path_pattern
|
||||||
from synapse.api.errors import SynapseError, Codes
|
from synapse.api.errors import SynapseError, Codes
|
||||||
from synapse.api.events.room import (
|
|
||||||
MessageEvent, RoomMemberEvent, FeedbackEvent
|
|
||||||
)
|
|
||||||
from synapse.api.constants import Feedback
|
|
||||||
from synapse.streams.config import PaginationConfig
|
from synapse.streams.config import PaginationConfig
|
||||||
|
from synapse.api.events.room import RoomMemberEvent
|
||||||
|
from synapse.api.constants import Membership
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@ -36,31 +34,28 @@ class RoomCreateRestServlet(RestServlet):
|
|||||||
# No PATTERN; we have custom dispatch rules here
|
# No PATTERN; we have custom dispatch rules here
|
||||||
|
|
||||||
def register(self, http_server):
|
def register(self, http_server):
|
||||||
# /rooms OR /rooms/<roomid>
|
PATTERN = "/createRoom"
|
||||||
http_server.register_path("POST",
|
register_txn_path(self, PATTERN, http_server)
|
||||||
client_path_pattern("/rooms$"),
|
|
||||||
self.on_POST)
|
|
||||||
http_server.register_path("PUT",
|
|
||||||
client_path_pattern(
|
|
||||||
"/rooms/(?P<room_id>[^/]*)$"),
|
|
||||||
self.on_PUT)
|
|
||||||
# define CORS for all of /rooms in RoomCreateRestServlet for simplicity
|
# define CORS for all of /rooms in RoomCreateRestServlet for simplicity
|
||||||
http_server.register_path("OPTIONS",
|
http_server.register_path("OPTIONS",
|
||||||
client_path_pattern("/rooms(?:/.*)?$"),
|
client_path_pattern("/rooms(?:/.*)?$"),
|
||||||
self.on_OPTIONS)
|
self.on_OPTIONS)
|
||||||
|
# define CORS for /createRoom[/txnid]
|
||||||
|
http_server.register_path("OPTIONS",
|
||||||
|
client_path_pattern("/createRoom(?:/.*)?$"),
|
||||||
|
self.on_OPTIONS)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, room_id):
|
def on_PUT(self, request, txn_id):
|
||||||
room_id = urllib.unquote(room_id)
|
try:
|
||||||
auth_user = yield self.auth.get_user_by_req(request)
|
defer.returnValue(self.txns.get_client_transaction(request, txn_id))
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
if not room_id:
|
response = yield self.on_POST(request)
|
||||||
raise SynapseError(400, "PUT must specify a room ID")
|
|
||||||
|
|
||||||
room_config = self.get_room_config(request)
|
self.txns.store_client_transaction(request, txn_id, response)
|
||||||
info = yield self.make_room(room_config, auth_user, room_id)
|
defer.returnValue(response)
|
||||||
room_config.update(info)
|
|
||||||
defer.returnValue((200, info))
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_POST(self, request):
|
def on_POST(self, request):
|
||||||
@ -210,24 +205,63 @@ class RoomSendEventRestServlet(RestServlet):
|
|||||||
defer.returnValue(response)
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Needs unit testing for room ID + alias joins
|
||||||
class JoinRoomAliasServlet(RestServlet):
|
class JoinRoomAliasServlet(RestServlet):
|
||||||
PATTERN = client_path_pattern("/join/(?P<room_alias>[^/]+)$")
|
|
||||||
|
def register(self, http_server):
|
||||||
|
# /join/$room_identifier[/$txn_id]
|
||||||
|
PATTERN = ("/join/(?P<room_identifier>[^/]*)")
|
||||||
|
register_txn_path(self, PATTERN, http_server)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def on_PUT(self, request, room_alias):
|
def on_POST(self, request, room_identifier):
|
||||||
user = yield self.auth.get_user_by_req(request)
|
user = yield self.auth.get_user_by_req(request)
|
||||||
|
|
||||||
if not user:
|
# the identifier could be a room alias or a room id. Try one then the
|
||||||
defer.returnValue((403, "Unrecognized user"))
|
# other if it fails to parse, without swallowing other valid
|
||||||
|
# SynapseErrors.
|
||||||
|
|
||||||
logger.debug("room_alias: %s", room_alias)
|
identifier = None
|
||||||
|
is_room_alias = False
|
||||||
|
try:
|
||||||
|
identifier = self.hs.parse_roomalias(
|
||||||
|
urllib.unquote(room_identifier)
|
||||||
|
)
|
||||||
|
is_room_alias = True
|
||||||
|
except SynapseError:
|
||||||
|
identifier = self.hs.parse_roomid(
|
||||||
|
urllib.unquote(room_identifier)
|
||||||
|
)
|
||||||
|
|
||||||
room_alias = self.hs.parse_roomalias(urllib.unquote(room_alias))
|
# TODO: Support for specifying the home server to join with?
|
||||||
|
|
||||||
handler = self.handlers.room_member_handler
|
if is_room_alias:
|
||||||
ret_dict = yield handler.join_room_alias(user, room_alias)
|
handler = self.handlers.room_member_handler
|
||||||
|
ret_dict = yield handler.join_room_alias(user, identifier)
|
||||||
|
defer.returnValue((200, ret_dict))
|
||||||
|
else: # room id
|
||||||
|
event = self.event_factory.create_event(
|
||||||
|
etype=RoomMemberEvent.TYPE,
|
||||||
|
content={"membership": Membership.JOIN},
|
||||||
|
room_id=urllib.unquote(identifier.to_string()),
|
||||||
|
user_id=user.to_string(),
|
||||||
|
state_key=user.to_string()
|
||||||
|
)
|
||||||
|
handler = self.handlers.room_member_handler
|
||||||
|
yield handler.change_membership(event)
|
||||||
|
defer.returnValue((200, ""))
|
||||||
|
|
||||||
defer.returnValue((200, ret_dict))
|
@defer.inlineCallbacks
|
||||||
|
def on_PUT(self, request, room_identifier, txn_id):
|
||||||
|
try:
|
||||||
|
defer.returnValue(self.txns.get_client_transaction(request, txn_id))
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
response = yield self.on_POST(request, room_identifier)
|
||||||
|
|
||||||
|
self.txns.store_client_transaction(request, txn_id, response)
|
||||||
|
defer.returnValue(response)
|
||||||
|
|
||||||
|
|
||||||
# TODO: Needs unit testing
|
# TODO: Needs unit testing
|
||||||
|
@ -28,7 +28,7 @@ from synapse.handlers import Handlers
|
|||||||
from synapse.rest import RestServletFactory
|
from synapse.rest import RestServletFactory
|
||||||
from synapse.state import StateHandler
|
from synapse.state import StateHandler
|
||||||
from synapse.storage import DataStore
|
from synapse.storage import DataStore
|
||||||
from synapse.types import UserID, RoomAlias
|
from synapse.types import UserID, RoomAlias, RoomID
|
||||||
from synapse.util import Clock
|
from synapse.util import Clock
|
||||||
from synapse.util.distributor import Distributor
|
from synapse.util.distributor import Distributor
|
||||||
from synapse.util.lockutils import LockManager
|
from synapse.util.lockutils import LockManager
|
||||||
@ -119,17 +119,30 @@ class BaseHomeServer(object):
|
|||||||
|
|
||||||
setattr(BaseHomeServer, "get_%s" % (depname), _get)
|
setattr(BaseHomeServer, "get_%s" % (depname), _get)
|
||||||
|
|
||||||
|
# TODO: Why are these parse_ methods so high up along with other globals?
|
||||||
|
# Surely these should be in a util package or in the api package?
|
||||||
|
|
||||||
# Other utility methods
|
# Other utility methods
|
||||||
def parse_userid(self, s):
|
def parse_userid(self, s):
|
||||||
"""Parse the string given by 's' as a User ID and return a UserID
|
"""Parse the string given by 's' as a User ID and return a UserID
|
||||||
object."""
|
object."""
|
||||||
return UserID.from_string(s, hs=self)
|
return UserID.from_string(s, hs=self)
|
||||||
|
|
||||||
|
def parse_roomid(self, s):
|
||||||
|
"""Parse the string given by 's' as a Room ID and return a RoomID
|
||||||
|
object."""
|
||||||
|
return RoomID.from_string(s, hs=self)
|
||||||
|
|
||||||
def parse_roomalias(self, s):
|
def parse_roomalias(self, s):
|
||||||
"""Parse the string given by 's' as a Room Alias and return a RoomAlias
|
"""Parse the string given by 's' as a Room Alias and return a RoomAlias
|
||||||
object."""
|
object."""
|
||||||
return RoomAlias.from_string(s, hs=self)
|
return RoomAlias.from_string(s, hs=self)
|
||||||
|
|
||||||
|
def parse_roomid(self, s):
|
||||||
|
"""Parse the string given by 's' as a Room ID and return a RoomID
|
||||||
|
object."""
|
||||||
|
return RoomID.from_string(s, hs=self)
|
||||||
|
|
||||||
# Build magic accessors for every dependency
|
# Build magic accessors for every dependency
|
||||||
for depname in BaseHomeServer.DEPENDENCIES:
|
for depname in BaseHomeServer.DEPENDENCIES:
|
||||||
BaseHomeServer._make_dependency_method(depname)
|
BaseHomeServer._make_dependency_method(depname)
|
||||||
|
@ -80,7 +80,6 @@ class DataStore(RoomMemberStore, RoomStore,
|
|||||||
[
|
[
|
||||||
"event_id",
|
"event_id",
|
||||||
"type",
|
"type",
|
||||||
"sender",
|
|
||||||
"room_id",
|
"room_id",
|
||||||
"content",
|
"content",
|
||||||
"unrecognized_keys"
|
"unrecognized_keys"
|
||||||
|
250
tests/handlers/test_typing.py
Normal file
250
tests/handlers/test_typing.py
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
# -*- 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.
|
||||||
|
|
||||||
|
|
||||||
|
from twisted.trial import unittest
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from mock import Mock, call, ANY
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ..utils import MockHttpResource, MockClock, DeferredMockCallable
|
||||||
|
|
||||||
|
from synapse.server import HomeServer
|
||||||
|
from synapse.handlers.typing import TypingNotificationHandler
|
||||||
|
|
||||||
|
|
||||||
|
logging.getLogger().addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
|
||||||
|
def _expect_edu(destination, edu_type, content, origin="test"):
|
||||||
|
return {
|
||||||
|
"origin": origin,
|
||||||
|
"ts": 1000000,
|
||||||
|
"pdus": [],
|
||||||
|
"edus": [
|
||||||
|
{
|
||||||
|
"origin": origin,
|
||||||
|
"destination": destination,
|
||||||
|
"edu_type": edu_type,
|
||||||
|
"content": content,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _make_edu_json(origin, edu_type, content):
|
||||||
|
return json.dumps(_expect_edu("test", edu_type, content, origin=origin))
|
||||||
|
|
||||||
|
|
||||||
|
class JustTypingNotificationHandlers(object):
|
||||||
|
def __init__(self, hs):
|
||||||
|
self.typing_notification_handler = TypingNotificationHandler(hs)
|
||||||
|
|
||||||
|
|
||||||
|
class TypingNotificationsTestCase(unittest.TestCase):
|
||||||
|
"""Tests typing notifications to rooms."""
|
||||||
|
def setUp(self):
|
||||||
|
self.clock = MockClock()
|
||||||
|
|
||||||
|
self.mock_http_client = Mock(spec=[])
|
||||||
|
self.mock_http_client.put_json = DeferredMockCallable()
|
||||||
|
|
||||||
|
self.mock_federation_resource = MockHttpResource()
|
||||||
|
|
||||||
|
hs = HomeServer("test",
|
||||||
|
clock=self.clock,
|
||||||
|
db_pool=None,
|
||||||
|
datastore=Mock(spec=[
|
||||||
|
# Bits that Federation needs
|
||||||
|
"prep_send_transaction",
|
||||||
|
"delivered_txn",
|
||||||
|
"get_received_txn_response",
|
||||||
|
"set_received_txn_response",
|
||||||
|
]),
|
||||||
|
handlers=None,
|
||||||
|
resource_for_client=Mock(),
|
||||||
|
resource_for_federation=self.mock_federation_resource,
|
||||||
|
http_client=self.mock_http_client,
|
||||||
|
)
|
||||||
|
hs.handlers = JustTypingNotificationHandlers(hs)
|
||||||
|
|
||||||
|
self.mock_update_client = Mock()
|
||||||
|
self.mock_update_client.return_value = defer.succeed(None)
|
||||||
|
|
||||||
|
self.handler = hs.get_handlers().typing_notification_handler
|
||||||
|
self.handler.push_update_to_clients = self.mock_update_client
|
||||||
|
|
||||||
|
self.datastore = hs.get_datastore()
|
||||||
|
|
||||||
|
def get_received_txn_response(*args):
|
||||||
|
return defer.succeed(None)
|
||||||
|
self.datastore.get_received_txn_response = get_received_txn_response
|
||||||
|
|
||||||
|
self.room_id = "a-room"
|
||||||
|
|
||||||
|
# Mock the RoomMemberHandler
|
||||||
|
hs.handlers.room_member_handler = Mock(spec=[])
|
||||||
|
self.room_member_handler = hs.handlers.room_member_handler
|
||||||
|
|
||||||
|
self.room_members = []
|
||||||
|
|
||||||
|
def get_rooms_for_user(user):
|
||||||
|
if user in self.room_members:
|
||||||
|
return defer.succeed([self.room_id])
|
||||||
|
else:
|
||||||
|
return defer.succeed([])
|
||||||
|
self.room_member_handler.get_rooms_for_user = get_rooms_for_user
|
||||||
|
|
||||||
|
def get_room_members(room_id):
|
||||||
|
if room_id == self.room_id:
|
||||||
|
return defer.succeed(self.room_members)
|
||||||
|
else:
|
||||||
|
return defer.succeed([])
|
||||||
|
self.room_member_handler.get_room_members = get_room_members
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def fetch_room_distributions_into(room_id, localusers=None,
|
||||||
|
remotedomains=None, ignore_user=None):
|
||||||
|
|
||||||
|
members = yield get_room_members(room_id)
|
||||||
|
for member in members:
|
||||||
|
if ignore_user is not None and member == ignore_user:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if member.is_mine:
|
||||||
|
if localusers is not None:
|
||||||
|
localusers.add(member)
|
||||||
|
else:
|
||||||
|
if remotedomains is not None:
|
||||||
|
remotedomains.add(member.domain)
|
||||||
|
self.room_member_handler.fetch_room_distributions_into = (
|
||||||
|
fetch_room_distributions_into)
|
||||||
|
|
||||||
|
# Some local users to test with
|
||||||
|
self.u_apple = hs.parse_userid("@apple:test")
|
||||||
|
self.u_banana = hs.parse_userid("@banana:test")
|
||||||
|
|
||||||
|
# Remote user
|
||||||
|
self.u_onion = hs.parse_userid("@onion:farm")
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_started_typing_local(self):
|
||||||
|
self.room_members = [self.u_apple, self.u_banana]
|
||||||
|
|
||||||
|
yield self.handler.started_typing(
|
||||||
|
target_user=self.u_apple,
|
||||||
|
auth_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
timeout=20000,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mock_update_client.assert_has_calls([
|
||||||
|
call(observer_user=self.u_banana,
|
||||||
|
observed_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
typing=True),
|
||||||
|
])
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_started_typing_remote_send(self):
|
||||||
|
self.room_members = [self.u_apple, self.u_onion]
|
||||||
|
|
||||||
|
put_json = self.mock_http_client.put_json
|
||||||
|
put_json.expect_call_and_return(
|
||||||
|
call("farm",
|
||||||
|
path="/matrix/federation/v1/send/1000000/",
|
||||||
|
data=_expect_edu("farm", "m.typing",
|
||||||
|
content={
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"user_id": self.u_apple.to_string(),
|
||||||
|
"typing": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
defer.succeed((200, "OK"))
|
||||||
|
)
|
||||||
|
|
||||||
|
yield self.handler.started_typing(
|
||||||
|
target_user=self.u_apple,
|
||||||
|
auth_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
timeout=20000,
|
||||||
|
)
|
||||||
|
|
||||||
|
yield put_json.await_calls()
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_started_typing_remote_recv(self):
|
||||||
|
self.room_members = [self.u_apple, self.u_onion]
|
||||||
|
|
||||||
|
yield self.mock_federation_resource.trigger("PUT",
|
||||||
|
"/matrix/federation/v1/send/1000000/",
|
||||||
|
_make_edu_json("farm", "m.typing",
|
||||||
|
content={
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"user_id": self.u_onion.to_string(),
|
||||||
|
"typing": True,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mock_update_client.assert_has_calls([
|
||||||
|
call(observer_user=self.u_apple,
|
||||||
|
observed_user=self.u_onion,
|
||||||
|
room_id=self.room_id,
|
||||||
|
typing=True),
|
||||||
|
])
|
||||||
|
|
||||||
|
@defer.inlineCallbacks
|
||||||
|
def test_stopped_typing(self):
|
||||||
|
self.room_members = [self.u_apple, self.u_banana, self.u_onion]
|
||||||
|
|
||||||
|
put_json = self.mock_http_client.put_json
|
||||||
|
put_json.expect_call_and_return(
|
||||||
|
call("farm",
|
||||||
|
path="/matrix/federation/v1/send/1000000/",
|
||||||
|
data=_expect_edu("farm", "m.typing",
|
||||||
|
content={
|
||||||
|
"room_id": self.room_id,
|
||||||
|
"user_id": self.u_apple.to_string(),
|
||||||
|
"typing": False,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
defer.succeed((200, "OK"))
|
||||||
|
)
|
||||||
|
|
||||||
|
# Gut-wrenching
|
||||||
|
from synapse.handlers.typing import RoomMember
|
||||||
|
self.handler._member_typing_until[
|
||||||
|
RoomMember(self.room_id, self.u_apple)
|
||||||
|
] = 1002000
|
||||||
|
|
||||||
|
yield self.handler.stopped_typing(
|
||||||
|
target_user=self.u_apple,
|
||||||
|
auth_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.mock_update_client.assert_has_calls([
|
||||||
|
call(observer_user=self.u_banana,
|
||||||
|
observed_user=self.u_apple,
|
||||||
|
room_id=self.room_id,
|
||||||
|
typing=False),
|
||||||
|
])
|
||||||
|
|
||||||
|
yield put_json.await_calls()
|
@ -178,9 +178,8 @@ class EventStreamPermissionsTestCase(RestTestCase):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_stream_room_permissions(self):
|
def test_stream_room_permissions(self):
|
||||||
room_id = "!rid1:test"
|
room_id = yield self.create_room_as(self.other_user,
|
||||||
yield self.create_room_as(room_id, self.other_user,
|
tok=self.other_token)
|
||||||
tok=self.other_token)
|
|
||||||
yield self.send(room_id, tok=self.other_token)
|
yield self.send(room_id, tok=self.other_token)
|
||||||
|
|
||||||
# invited to room (expect no content for room)
|
# invited to room (expect no content for room)
|
||||||
|
@ -171,7 +171,7 @@ class PresenceListTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("GET",
|
(code, response) = yield self.mock_resource.trigger("GET",
|
||||||
"/presence_list/%s" % (myid), None)
|
"/presence/list/%s" % (myid), None)
|
||||||
|
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertEquals(
|
self.assertEquals(
|
||||||
@ -192,7 +192,7 @@ class PresenceListTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("POST",
|
(code, response) = yield self.mock_resource.trigger("POST",
|
||||||
"/presence_list/%s" % (myid),
|
"/presence/list/%s" % (myid),
|
||||||
"""{"invite": ["@banana:test"]}"""
|
"""{"invite": ["@banana:test"]}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -212,7 +212,7 @@ class PresenceListTestCase(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("POST",
|
(code, response) = yield self.mock_resource.trigger("POST",
|
||||||
"/presence_list/%s" % (myid),
|
"/presence/list/%s" % (myid),
|
||||||
"""{"drop": ["@banana:test"]}"""
|
"""{"drop": ["@banana:test"]}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -74,13 +74,11 @@ class RoomPermissionsTestCase(RestTestCase):
|
|||||||
# create some rooms under the name rmcreator_id
|
# create some rooms under the name rmcreator_id
|
||||||
self.uncreated_rmid = "!aa:test"
|
self.uncreated_rmid = "!aa:test"
|
||||||
|
|
||||||
self.created_rmid = "!abc:test"
|
self.created_rmid = yield self.create_room_as(self.rmcreator_id,
|
||||||
yield self.create_room_as(self.created_rmid, self.rmcreator_id,
|
is_public=False)
|
||||||
is_public=False)
|
|
||||||
|
|
||||||
self.created_public_rmid = "!def1234ghi:test"
|
self.created_public_rmid = yield self.create_room_as(self.rmcreator_id,
|
||||||
yield self.create_room_as(self.created_public_rmid, self.rmcreator_id,
|
is_public=True)
|
||||||
is_public=True)
|
|
||||||
|
|
||||||
# send a message in one of the rooms
|
# send a message in one of the rooms
|
||||||
self.created_rmid_msg_path = ("/rooms/%s/send/m.room.message/a1" %
|
self.created_rmid_msg_path = ("/rooms/%s/send/m.room.message/a1" %
|
||||||
@ -423,8 +421,7 @@ class RoomsMemberListTestCase(RestTestCase):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_get_member_list(self):
|
def test_get_member_list(self):
|
||||||
room_id = "!aa:test"
|
room_id = yield self.create_room_as(self.user_id)
|
||||||
yield self.create_room_as(room_id, self.user_id)
|
|
||||||
(code, response) = yield self.mock_resource.trigger_get(
|
(code, response) = yield self.mock_resource.trigger_get(
|
||||||
"/rooms/%s/members" % room_id)
|
"/rooms/%s/members" % room_id)
|
||||||
self.assertEquals(200, code, msg=str(response))
|
self.assertEquals(200, code, msg=str(response))
|
||||||
@ -437,18 +434,16 @@ class RoomsMemberListTestCase(RestTestCase):
|
|||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_get_member_list_no_permission(self):
|
def test_get_member_list_no_permission(self):
|
||||||
room_id = "!bb:test"
|
room_id = yield self.create_room_as("@some_other_guy:red")
|
||||||
yield self.create_room_as(room_id, "@some_other_guy:red")
|
|
||||||
(code, response) = yield self.mock_resource.trigger_get(
|
(code, response) = yield self.mock_resource.trigger_get(
|
||||||
"/rooms/%s/members" % room_id)
|
"/rooms/%s/members" % room_id)
|
||||||
self.assertEquals(403, code, msg=str(response))
|
self.assertEquals(403, code, msg=str(response))
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_get_member_list_mixed_memberships(self):
|
def test_get_member_list_mixed_memberships(self):
|
||||||
room_id = "!bb:test"
|
|
||||||
room_creator = "@some_other_guy:blue"
|
room_creator = "@some_other_guy:blue"
|
||||||
|
room_id = yield self.create_room_as(room_creator)
|
||||||
room_path = "/rooms/%s/members" % room_id
|
room_path = "/rooms/%s/members" % room_id
|
||||||
yield self.create_room_as(room_id, room_creator)
|
|
||||||
yield self.invite(room=room_id, src=room_creator,
|
yield self.invite(room=room_id, src=room_creator,
|
||||||
targ=self.user_id)
|
targ=self.user_id)
|
||||||
# can't see list if you're just invited.
|
# can't see list if you're just invited.
|
||||||
@ -503,107 +498,57 @@ class RoomsCreateTestCase(RestTestCase):
|
|||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_post_room_no_keys(self):
|
def test_post_room_no_keys(self):
|
||||||
# POST with no config keys, expect new room id
|
# POST with no config keys, expect new room id
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
(code, response) = yield self.mock_resource.trigger("POST",
|
||||||
"{}")
|
"/createRoom",
|
||||||
|
"{}")
|
||||||
self.assertEquals(200, code, response)
|
self.assertEquals(200, code, response)
|
||||||
self.assertTrue("room_id" in response)
|
self.assertTrue("room_id" in response)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_post_room_visibility_key(self):
|
def test_post_room_visibility_key(self):
|
||||||
# POST with visibility config key, expect new room id
|
# POST with visibility config key, expect new room id
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
(code, response) = yield self.mock_resource.trigger(
|
||||||
'{"visibility":"private"}')
|
"POST",
|
||||||
|
"/createRoom",
|
||||||
|
'{"visibility":"private"}')
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertTrue("room_id" in response)
|
self.assertTrue("room_id" in response)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_post_room_custom_key(self):
|
def test_post_room_custom_key(self):
|
||||||
# POST with custom config keys, expect new room id
|
# POST with custom config keys, expect new room id
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
(code, response) = yield self.mock_resource.trigger(
|
||||||
'{"custom":"stuff"}')
|
"POST",
|
||||||
|
"/createRoom",
|
||||||
|
'{"custom":"stuff"}')
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertTrue("room_id" in response)
|
self.assertTrue("room_id" in response)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_post_room_known_and_unknown_keys(self):
|
def test_post_room_known_and_unknown_keys(self):
|
||||||
# POST with custom + known config keys, expect new room id
|
# POST with custom + known config keys, expect new room id
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
(code, response) = yield self.mock_resource.trigger(
|
||||||
'{"visibility":"private","custom":"things"}')
|
"POST",
|
||||||
|
"/createRoom",
|
||||||
|
'{"visibility":"private","custom":"things"}')
|
||||||
self.assertEquals(200, code)
|
self.assertEquals(200, code)
|
||||||
self.assertTrue("room_id" in response)
|
self.assertTrue("room_id" in response)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def test_post_room_invalid_content(self):
|
def test_post_room_invalid_content(self):
|
||||||
# POST with invalid content / paths, expect 400
|
# POST with invalid content / paths, expect 400
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
|
||||||
'{"visibili')
|
|
||||||
self.assertEquals(400, code)
|
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger("POST", "/rooms",
|
|
||||||
'["hello"]')
|
|
||||||
self.assertEquals(400, code)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_no_keys(self):
|
|
||||||
# PUT with no config keys, expect new room id
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
(code, response) = yield self.mock_resource.trigger(
|
||||||
"PUT", "/rooms/%21aa%3Atest", "{}"
|
"POST",
|
||||||
)
|
"/createRoom",
|
||||||
self.assertEquals(200, code)
|
'{"visibili')
|
||||||
self.assertTrue("room_id" in response)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_visibility_key(self):
|
|
||||||
# PUT with known config keys, expect new room id
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
|
||||||
"PUT", "/rooms/%21bb%3Atest", '{"visibility":"private"}'
|
|
||||||
)
|
|
||||||
self.assertEquals(200, code)
|
|
||||||
self.assertTrue("room_id" in response)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_custom_key(self):
|
|
||||||
# PUT with custom config keys, expect new room id
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
|
||||||
"PUT", "/rooms/%21cc%3Atest", '{"custom":"stuff"}'
|
|
||||||
)
|
|
||||||
self.assertEquals(200, code)
|
|
||||||
self.assertTrue("room_id" in response)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_known_and_unknown_keys(self):
|
|
||||||
# PUT with custom + known config keys, expect new room id
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
|
||||||
"PUT", "/rooms/%21dd%3Atest",
|
|
||||||
'{"visibility":"private","custom":"things"}'
|
|
||||||
)
|
|
||||||
self.assertEquals(200, code)
|
|
||||||
self.assertTrue("room_id" in response)
|
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_invalid_content(self):
|
|
||||||
# PUT with invalid content / room names, expect 400
|
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
|
||||||
"PUT", "/rooms/ee", '{"sdf"'
|
|
||||||
)
|
|
||||||
self.assertEquals(400, code)
|
self.assertEquals(400, code)
|
||||||
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
(code, response) = yield self.mock_resource.trigger(
|
||||||
"PUT", "/rooms/ee", '["hello"]'
|
"POST",
|
||||||
)
|
"/createRoom",
|
||||||
|
'["hello"]')
|
||||||
self.assertEquals(400, code)
|
self.assertEquals(400, code)
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
|
||||||
def test_put_room_conflict(self):
|
|
||||||
yield self.create_room_as("!aa:test", self.user_id)
|
|
||||||
|
|
||||||
# PUT with conflicting room ID, expect 409
|
|
||||||
(code, response) = yield self.mock_resource.trigger(
|
|
||||||
"PUT", "/rooms/%21aa%3Atest", "{}"
|
|
||||||
)
|
|
||||||
self.assertEquals(409, code)
|
|
||||||
|
|
||||||
|
|
||||||
class RoomTopicTestCase(RestTestCase):
|
class RoomTopicTestCase(RestTestCase):
|
||||||
""" Tests /rooms/$room_id/topic REST events. """
|
""" Tests /rooms/$room_id/topic REST events. """
|
||||||
@ -613,8 +558,6 @@ class RoomTopicTestCase(RestTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
||||||
self.auth_user_id = self.user_id
|
self.auth_user_id = self.user_id
|
||||||
self.room_id = "!rid1:test"
|
|
||||||
self.path = "/rooms/%s/state/m.room.topic" % self.room_id
|
|
||||||
|
|
||||||
state_handler = Mock(spec=["handle_new_event"])
|
state_handler = Mock(spec=["handle_new_event"])
|
||||||
state_handler.handle_new_event.return_value = True
|
state_handler.handle_new_event.return_value = True
|
||||||
@ -640,7 +583,8 @@ class RoomTopicTestCase(RestTestCase):
|
|||||||
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
||||||
|
|
||||||
# create the room
|
# create the room
|
||||||
yield self.create_room_as(self.room_id, self.user_id)
|
self.room_id = yield self.create_room_as(self.user_id)
|
||||||
|
self.path = "/rooms/%s/state/m.room.topic" % self.room_id
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
@ -717,7 +661,6 @@ class RoomMemberStateTestCase(RestTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
||||||
self.auth_user_id = self.user_id
|
self.auth_user_id = self.user_id
|
||||||
self.room_id = "!rid1:test"
|
|
||||||
|
|
||||||
state_handler = Mock(spec=["handle_new_event"])
|
state_handler = Mock(spec=["handle_new_event"])
|
||||||
state_handler.handle_new_event.return_value = True
|
state_handler.handle_new_event.return_value = True
|
||||||
@ -742,7 +685,7 @@ class RoomMemberStateTestCase(RestTestCase):
|
|||||||
|
|
||||||
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
||||||
|
|
||||||
yield self.create_room_as(self.room_id, self.user_id)
|
self.room_id = yield self.create_room_as(self.user_id)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
@ -843,7 +786,6 @@ class RoomMessagesTestCase(RestTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
self.mock_resource = MockHttpResource(prefix=PATH_PREFIX)
|
||||||
self.auth_user_id = self.user_id
|
self.auth_user_id = self.user_id
|
||||||
self.room_id = "!rid1:test"
|
|
||||||
|
|
||||||
state_handler = Mock(spec=["handle_new_event"])
|
state_handler = Mock(spec=["handle_new_event"])
|
||||||
state_handler.handle_new_event.return_value = True
|
state_handler.handle_new_event.return_value = True
|
||||||
@ -868,7 +810,7 @@ class RoomMessagesTestCase(RestTestCase):
|
|||||||
|
|
||||||
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
synapse.rest.room.register_servlets(hs, self.mock_resource)
|
||||||
|
|
||||||
yield self.create_room_as(self.room_id, self.user_id)
|
self.room_id = yield self.create_room_as(self.user_id)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
pass
|
pass
|
||||||
|
@ -24,6 +24,7 @@ from synapse.api.constants import Membership
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
class RestTestCase(unittest.TestCase):
|
class RestTestCase(unittest.TestCase):
|
||||||
"""Contains extra helper functions to quickly and clearly perform a given
|
"""Contains extra helper functions to quickly and clearly perform a given
|
||||||
REST action, which isn't the focus of the test.
|
REST action, which isn't the focus of the test.
|
||||||
@ -40,18 +41,19 @@ class RestTestCase(unittest.TestCase):
|
|||||||
return self.auth_user_id
|
return self.auth_user_id
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def create_room_as(self, room_id, room_creator, is_public=True, tok=None):
|
def create_room_as(self, room_creator, is_public=True, tok=None):
|
||||||
temp_id = self.auth_user_id
|
temp_id = self.auth_user_id
|
||||||
self.auth_user_id = room_creator
|
self.auth_user_id = room_creator
|
||||||
path = "/rooms/%s" % room_id
|
path = "/createRoom"
|
||||||
content = "{}"
|
content = "{}"
|
||||||
if not is_public:
|
if not is_public:
|
||||||
content = '{"visibility":"private"}'
|
content = '{"visibility":"private"}'
|
||||||
if tok:
|
if tok:
|
||||||
path = path + "?access_token=%s" % tok
|
path = path + "?access_token=%s" % tok
|
||||||
(code, response) = yield self.mock_resource.trigger("PUT", path, content)
|
(code, response) = yield self.mock_resource.trigger("POST", path, content)
|
||||||
self.assertEquals(200, code, msg=str(response))
|
self.assertEquals(200, code, msg=str(response))
|
||||||
self.auth_user_id = temp_id
|
self.auth_user_id = temp_id
|
||||||
|
defer.returnValue(response["room_id"])
|
||||||
|
|
||||||
@defer.inlineCallbacks
|
@defer.inlineCallbacks
|
||||||
def invite(self, room=None, src=None, targ=None, expect_code=200, tok=None):
|
def invite(self, room=None, src=None, targ=None, expect_code=200, tok=None):
|
||||||
|
@ -97,7 +97,7 @@ angular.module('matrixService', [])
|
|||||||
// Create a room
|
// Create a room
|
||||||
create: function(room_id, visibility) {
|
create: function(room_id, visibility) {
|
||||||
// The REST path spec
|
// The REST path spec
|
||||||
var path = "/rooms";
|
var path = "/createRoom";
|
||||||
|
|
||||||
return doRequest("POST", path, undefined, {
|
return doRequest("POST", path, undefined, {
|
||||||
visibility: visibility,
|
visibility: visibility,
|
||||||
@ -124,7 +124,8 @@ angular.module('matrixService', [])
|
|||||||
|
|
||||||
path = path.replace("$room_alias", room_alias);
|
path = path.replace("$room_alias", room_alias);
|
||||||
|
|
||||||
return doRequest("PUT", path, undefined, {});
|
// TODO: PUT with txn ID
|
||||||
|
return doRequest("POST", path, undefined, {});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Invite a user to a room
|
// Invite a user to a room
|
||||||
|
Loading…
Reference in New Issue
Block a user