Compare commits

...

344 Commits

Author SHA1 Message Date
Erik Johnston
d12feed623 Merge branch 'release-v0.2.2' of github.com:matrix-org/synapse 2014-09-06 18:18:55 +01:00
Kegan Dougal
9e0c3e7838 Center recaptcha dialog. 2014-09-06 10:15:05 -07:00
Erik Johnston
a9afb7cba3 Remove disabled change from CHANGES 2014-09-06 18:14:56 +01:00
Matthew Hodgson
44bd5e04dd disable broken event dup suppression, and fix echo for /me 2014-09-06 10:14:05 -07:00
Kegan Dougal
9be1b2cb23 Set the room_alias field when we encounter a new one, rather than only from local storage. 2014-09-06 09:57:13 -07:00
Kegan Dougal
92800afd95 Use the room_display_name when presenting on the home page, and not the room_alias which may not be set. 2014-09-06 09:53:39 -07:00
Kegan Dougal
929cb12e7e https when loading recaptcha js 2014-09-06 09:47:30 -07:00
Erik Johnston
de55ba218f Fix race in presence handler where we evicted things from cache while handling a key therein 2014-09-06 17:38:11 +01:00
Erik Johnston
71fb748d70 Bump version and changelog 2014-09-06 17:27:42 +01:00
Matthew Hodgson
6e341aebab dedup all events 2014-09-06 00:36:55 -07:00
Matthew Hodgson
a1bf28b7f0 handle m.room.aliases for id<->alias mapping; remove local_storage map; stop local echo flickering by removing opacity transition for now; implement /join 2014-09-06 00:32:39 -07:00
Matthew Hodgson
aa90e53312 add todo 2014-09-06 00:32:39 -07:00
Erik Johnston
ea5b5b1f64 Fix state unit test 2014-09-06 07:44:00 +01:00
Erik Johnston
2205aba3ed Fix bug where we used an event_id as a pdu_id 2014-09-06 07:41:51 +01:00
Kegan Dougal
027f51763e Unit tests do not need captchas. 2014-09-05 23:41:18 -07:00
Kegan Dougal
1a298aad9c Added captcha support on both the HS and web client.
Merge branch 'captcha' of github.com:matrix-org/synapse into develop
2014-09-05 23:32:51 -07:00
Kegan Dougal
a342867d3f Added instructions for setting up captcha in an obviously named file. 2014-09-05 23:32:07 -07:00
Kegan Dougal
b5749c75d9 Reload captchas when they fail. Cleanup on success. 2014-09-05 23:08:39 -07:00
Kegan Dougal
3ea6f01b4e 80 chars please 2014-09-05 22:55:29 -07:00
Kegan Dougal
37e53513b6 Add config opion for XFF headers when performing ReCaptcha auth. 2014-09-05 22:51:11 -07:00
Kegan Dougal
1829b55bb0 Captchas now work on registration. Missing x-forwarded-for config arg support. Missing reloading a new captcha on the web client / displaying a sensible error message. 2014-09-05 19:18:23 -07:00
Erik Johnston
6d19fe1481 Fix generation of event ids so that they are consistent between local and remote ids 2014-09-06 02:48:13 +01:00
Erik Johnston
781ff713ba When getting a state event also include the previous content 2014-09-06 02:23:36 +01:00
Kegan Dougal
0b9e1e7b56 Added a captcha config to the HS, to enable registration captcha checking and for the recaptcha private key. 2014-09-05 17:58:06 -07:00
Kegan Dougal
c80f739461 Added webclient config.js for storing recaptcha public key. 2014-09-05 17:36:09 -07:00
Erik Johnston
684001ac62 Document new invite key added to createRoom api 2014-09-06 01:12:12 +01:00
Erik Johnston
f47f42090d Add support for inviting people when you create a room 2014-09-06 01:10:07 +01:00
David Baker
c03c255304 Better call bar (visually: still lacks ring[back] tones). 2014-09-06 00:14:02 +01:00
Erik Johnston
fc65b68f30 Add m.roo.aliases 2014-09-05 22:01:10 +01:00
Kegan Dougal
130458385e Modified matrixService.register to specify if captcha results should be sent with the registration request. This is toggleable via useCaptcha in register-controller. 2014-09-05 13:56:36 -07:00
Erik Johnston
480438eee6 Validate power levels event changes. Change error messages to be more helpful. Fix bug where we checked the wrong power levels 2014-09-05 21:54:16 +01:00
Erik Johnston
9dd4570b68 Generate m.room.aliases event when the HS creates a room alias 2014-09-05 21:35:56 +01:00
Kegan Dougal
0280176ccd Added basic captcha, not hooked up 2014-09-05 13:31:47 -07:00
Kegan Dougal
b4e1c1f51e Minor spec tweaks. 2014-09-05 12:46:48 -07:00
Erik Johnston
1c7bb34ffd Merge branch 'develop' of github.com:matrix-org/synapse into develop 2014-09-05 20:39:57 +01:00
Emmanuel ROHEE
b3be06667d BF: tab completion did not work with commands. $scope.input contained only the typed chars not the result of the completion.
Needed to fire an event so that ng update the input model
2014-09-05 18:46:34 +02:00
Erik Johnston
982604fbf2 Empty string is not a valid JSON object, so don't return them in HTTP responses. 2014-09-05 17:13:26 +01:00
Erik Johnston
250ee2ea7d AUth the contents of power level events 2014-09-05 17:13:19 +01:00
Erik Johnston
95037d8d9d Change the default power levels to be 0, 50 and 100 2014-09-05 17:13:03 +01:00
Emmanuel ROHEE
8a7f7f5004 BF: Update the members list on banned & kicked "events" 2014-09-05 18:05:23 +02:00
Emmanuel ROHEE
12a23f01b4 autoscroll down(if the scroller was already at the bottom) when receiving member events 2014-09-05 17:52:11 +02:00
Emmanuel ROHEE
3a88808983 doc: kick can take a reason arg 2014-09-05 17:32:35 +02:00
Emmanuel ROHEE
3be6156774 Created kick & unban methods in matrixService. Made some factorisation. 2014-09-05 17:30:50 +02:00
Emmanuel ROHEE
cf4c17deaf Added sanity checks in commands 2014-09-05 17:23:41 +02:00
Emmanuel ROHEE
3501478828 BF: Make /unban work again 2014-09-05 16:56:50 +02:00
Emmanuel ROHEE
dcf0a6fbfd Display ban & kick reason 2014-09-05 16:45:59 +02:00
Emmanuel ROHEE
4b7a5b7bfa Fixed empty display name (content.displayname in a room member can be null) 2014-09-05 15:54:34 +02:00
Emmanuel ROHEE
ec1cc29ecb Revert "Fixed empty display name (content.displayname in a room member can be null)"
This reverts commit f286a4fcd4.
2014-09-05 15:53:44 +02:00
Emmanuel ROHEE
f286a4fcd4 Fixed empty display name (content.displayname in a room member can be null) 2014-09-05 15:50:44 +02:00
Mark Haines
e2ae8af072 Add demo/etc to .gitignore 2014-09-05 14:38:56 +01:00
Emmanuel ROHEE
585e98fe2b BF: Fixed members list layout when the scrollbar appears 2014-09-05 15:37:51 +02:00
Emmanuel ROHEE
c407ed070c BF: Show "Bob invited you" in recents when Bob invites the user 2014-09-05 14:55:17 +02:00
David Baker
6baaa18224 hide the forgot password link until it works 2014-09-05 13:11:11 +01:00
Emmanuel ROHEE
584591c3e3 Fixed duplicated messages sending in slow network condition.
Show the message sending flow state in the messages list:
  - While sending, the message appears semi transparent in the chat.
  - If successfully sent, it appears as before, ie normal
  - In case of failure, it appears in red with an Unsent text.
2014-09-05 14:09:14 +02:00
Emmanuel ROHEE
43369cbe06 Cleaned all sending references as it not used 2014-09-05 11:13:33 +02:00
Emmanuel ROHEE
3bfffab201 Do not systematically scroll to the bottom on new events in the room 2014-09-05 10:40:59 +02:00
Matthew Hodgson
0d1d9f3e9c merge spec changes 2014-09-04 23:16:04 -07:00
Matthew Hodgson
3bc7bba262 switch IRC-style command parser to use regexps rather than split(" ") so that it doesn't choke on consecutive whitespaces
yield better errors for invalid commands
don't pass invalid commands through as messages
support kick reasons
2014-09-04 23:14:52 -07:00
David Baker
9c82276760 Add version 0 of the VoIP specification. 2014-09-04 18:20:27 +01:00
Mark Haines
3578046101 Merge branch 'master' into develop 2014-09-04 15:06:04 +01:00
Emmanuel ROHEE
26efd6f151 BF: presence PUT requests stopped to work with old "state" param yesterday evening :( -cda31fb755 2014-09-04 15:04:49 +01:00
Emmanuel ROHEE
1bf6c3faad BF: presence PUT requests stopped to work with old "state" param yesterday evening :( -cda31fb755 2014-09-04 15:10:43 +02:00
Emmanuel ROHEE
9faf780740 Fixed registration flow when registering with matrixID & password and no email 2014-09-04 13:15:09 +01:00
Emmanuel ROHEE
3ab8cfbc14 Fixed registration flow when registering with matrixID & password and no email 2014-09-04 14:04:35 +02:00
Emmanuel ROHEE
3983bae160 Added mUserDisplayName, a filter to resolve a user display name from a user_id 2014-09-04 13:57:27 +02:00
Emmanuel ROHEE
7346ea85c0 Moved mRoomName filter into matrix-filter.js, a place for all generic filters using Matrix data. 2014-09-04 13:43:48 +02:00
David Baker
eb7d7ce354 Re-apply fixes to the link-email screen to make it work again (in a somewhat temporary way until home servers sign associations). Unhide the linked emails box. 2014-09-04 11:38:26 +01:00
Emmanuel ROHEE
b1b57a3f28 BF: Do not filter incoming member events. Before, only invitations to the current user were showned in the recents. 2014-09-04 11:03:49 +02:00
Emmanuel ROHEE
82cf76a8f9 Report ban/unban messages to recents lists 2014-09-04 09:08:34 +02:00
Erik Johnston
d76e548ec1 Merge branch 'develop' of github.com:matrix-org/synapse 2014-09-04 07:39:52 +01:00
Erik Johnston
9f633bc125 Merge branch 'master' of github.com:matrix-org/synapse into develop
Conflicts:
	CHANGES.rst
2014-09-04 07:38:38 +01:00
Matthew Hodgson
3b38d2f507 big warning 2014-09-04 02:35:18 +01:00
Matthew Hodgson
a751a80a05 target live site 2014-09-04 02:02:06 +01:00
Matthew Hodgson
77e628e840 changelog for v0.2.1 2014-09-04 01:57:27 +01:00
Matthew Hodgson
822d0e5520 update README to know about synctl 2014-09-04 01:51:01 +01:00
Matthew Hodgson
0d5c7718c0 make synctl default to homesever.log 2014-09-03 22:49:47 +01:00
Erik Johnston
0538a4098d Merge branch 'release-v0.2.1' 2014-09-03 19:47:32 +01:00
Erik Johnston
300816ffa1 Bump versions. Update change logs. 2014-09-03 19:39:45 +01:00
Paul "LeoNerd" Evans
804199d9b6 Added a simple start/stop control script 2014-09-03 19:30:48 +01:00
Paul "LeoNerd" Evans
4c3512a45c Added a TODO note about YAML modeline for editors 2014-09-03 19:30:48 +01:00
Erik Johnston
bcaea74352 Error code must be an integer 2014-09-03 19:19:24 +01:00
Erik Johnston
c9d1ee24ca Import SynapseError 2014-09-03 19:15:55 +01:00
Erik Johnston
9b18151104 Handle timeouts slightly nicer. 2014-09-03 19:13:41 +01:00
Erik Johnston
34a7f0ca93 Fix test to assert that we don't do auth 2014-09-03 19:13:29 +01:00
Erik Johnston
5b645f9d34 Don't do auth for change_membership in federation handler, it doesn't work and federation doesn't do auth in general either. Add a hacky timeout when trying to join a remote room. 2014-09-03 19:08:39 +01:00
David Baker
284d6b279b Hide email link UI from the settings because it doesn't work. 2014-09-03 19:00:56 +01:00
Erik Johnston
dce6395395 When creating a room and a user supplies a room_alias but no name, use the room_alias as the name. 2014-09-03 18:52:34 +01:00
Emmanuel ROHEE
6322aa154b Added more ng-if to make nice sentences like "Bob kicked Alice" or "Bob unbanned Alice" 2014-09-03 19:52:09 +02:00
Matthew Hodgson
7f01d1d8c8 better error msgs 2014-09-03 18:39:09 +01:00
Emmanuel ROHEE
069a9745b0 Ignore leave members in the list too 2014-09-03 19:37:54 +02:00
Emmanuel ROHEE
78087617d1 Fixed used of state_key instead of user_id in member events.
Needed to split into 2 conditional spans for grammar reason
2014-09-03 19:29:36 +02:00
David Baker
d72ce4da64 Merge branch 'develop' of github.com:matrix-org/synapse into develop
Conflicts:
	synapse/http/client.py
2014-09-03 18:25:17 +01:00
David Baker
a25d1530ef Make registering and logging in with a threepid work in the webclient. 2014-09-03 18:23:56 +01:00
David Baker
d6ecbbdf0a Add support for registering with a threepid to the HS (get credentials from the client and check them against an ID server). 2014-09-03 18:22:27 +01:00
Erik Johnston
66a5bc4fad Fix ban path 2014-09-03 18:19:57 +01:00
Paul "LeoNerd" Evans
d703e712f7 Add support to _simple_insert() to do INSERT OR REPLACE 2014-09-03 18:18:41 +01:00
Kegan Dougal
f196d77f66 Added federation protocol urls section from other docs. 2014-09-03 18:07:42 +01:00
Erik Johnston
0d75b9fa96 Fix a few cases where we used user_id instead of state_key 2014-09-03 18:03:10 +01:00
Kegan Dougal
5391ccdfe6 Marked docs/client-server/specification.rst as old. 2014-09-03 18:02:33 +01:00
Kegan Dougal
f68dbbd3da More explanation of federation keys. 2014-09-03 18:00:00 +01:00
Emmanuel ROHEE
1a32b1f002 Normalise users power levels so that the user with the higher power level will have a bar covering 100% of the width of his avatar 2014-09-03 18:55:48 +02:00
Kegan Dougal
79bf9d25db Added more terms. 2014-09-03 17:48:08 +01:00
Erik Johnston
1b491e50c9 Implement a kick api 2014-09-03 17:46:52 +01:00
Mark Haines
7c4ce957c7 Unindent list in specification to remove blockquote 2014-09-03 17:37:19 +01:00
Paul "LeoNerd" Evans
4081413876 Default PID file should be 'homeserver.pid' to match the other 'homeserver.*' naming convention 2014-09-03 17:34:07 +01:00
Emmanuel ROHEE
5dd1a738f8 Use /rooms/$room_id/state/m.room.member/$user_id to change the membership of another user 2014-09-03 18:33:51 +02:00
Matthew Hodgson
8a7c1d6a00 fix the copyright holder from matrix.org to OpenMarket Ltd, as matrix.org hasn't been incorporated in time for launch. 2014-09-03 17:31:57 +01:00
Mark Haines
f93aba1d66 Fix formating for threat model 2014-09-03 17:28:35 +01:00
Mark Haines
e3b261b0b7 Move securitY threat model docs into specification 2014-09-03 17:26:16 +01:00
Mark Haines
ee2bcdec65 Limit the size of uploads 2014-09-03 17:04:16 +01:00
Kegan Dougal
beaf50f5c6 Bubble up SynapseErrors so expected failures aren't masked. 2014-09-03 16:31:01 +01:00
Kegan Dougal
581c54bebe Add exception handling to directory servlet, so we don't 500. Mark directory API as volatile in the api docs. 2014-09-03 16:27:01 +01:00
Erik Johnston
30bcbc433a Fix up directory server to not require uploading room hosts. Update the room hosts table with the current room hosts (if we have them) on GET. 2014-09-03 16:04:27 +01:00
Emmanuel ROHEE
5f7cdbe0b8 List commands and their usage in the settings page 2014-09-03 17:02:02 +02:00
Emmanuel ROHEE
ede161d296 Added /kick $user_id 2014-09-03 17:02:02 +02:00
Erik Johnston
b5f9d47c89 Handle new state events which don't have a common ancestor 2014-09-03 15:50:05 +01:00
Erik Johnston
e4c40158c5 Snapshot prev_state for generic events 2014-09-03 15:48:51 +01:00
Paul "LeoNerd" Evans
cda31fb755 Kill the state
... key from all the Presence messages
2014-09-03 15:37:10 +01:00
Paul "LeoNerd" Evans
dada11dc5f Bugfix for back-pagination of presence 2014-09-03 15:28:03 +01:00
Emmanuel ROHEE
277fd2250a Added /unban $user_id 2014-09-03 15:58:40 +02:00
Erik Johnston
073a42cc95 Add support for room names 2014-09-03 14:41:38 +01:00
Kegan Dougal
7fc84c7019 Make retrying requests on DNS failures configurable, and turn off retrying only in directory.get_association 2014-09-03 14:26:52 +01:00
Emmanuel ROHEE
c06d07a276 Send unrecognized commands as text message (as before) 2014-09-03 15:25:59 +02:00
Emmanuel ROHEE
4c7da89219 Do not define power level limits in setUserPowerLevel. Update the function to be used as a resetUserPowerLevel 2014-09-03 15:20:09 +02:00
Emmanuel ROHEE
932f35a7f0 Added /deop $user_id 2014-09-03 15:20:09 +02:00
Paul "LeoNerd" Evans
756e171ad0 Store SQL DDL deltas as well; attempt to upgrade the database on startup if it's too old 2014-09-03 14:14:39 +01:00
Emmanuel ROHEE
4777c1cd5b BF: Do not be polluted by ops events that come when paginating back 2014-09-03 15:00:19 +02:00
Kegan Dougal
b1195c125f hs: Updated synapse.http.client to handle DNSLookupErrors and bail immediately. 2014-09-03 13:51:17 +01:00
Paul "LeoNerd" Evans
da31b96b55 Implement presence state visibilty limiting when polling eventsource for stream 2014-09-03 13:46:52 +01:00
Paul "LeoNerd" Evans
86d6232236 Don't eat federation transmit errors during unit tests; fix remote presence EDU-sending test because of this 2014-09-03 13:46:52 +01:00
Paul "LeoNerd" Evans
061e814195 Make sure to print exceptions properly from notifier failures 2014-09-03 13:46:52 +01:00
Kegan Dougal
56bc57cf50 apidocs: Added m.room.name 2014-09-03 13:42:56 +01:00
Kegan Dougal
27cdbf7b94 apidocs: Tweak join response format. Explicitly state empty JSON objects where they are required by the spec. Mark unimplemented room GET APIs clearly. 2014-09-03 13:32:33 +01:00
Emmanuel ROHEE
4b85c5f52c Added /op $user_id $powerLevel 2014-09-03 14:12:56 +02:00
Kegan Dougal
cd0afb85c4 Updated feedback api docs and fixed feedback content template bug 2014-09-03 13:08:17 +01:00
Kegan Dougal
dfea1730dc apidocs: mtime_age > last_active_ago. Presence REST: Sanity check values in invite/drop arrays. 2014-09-03 12:09:20 +01:00
Erik Johnston
b50ea730b1 Merge branch 'develop' of github.com:matrix-org/synapse into develop
Conflicts:
	synapse/http/server.py
        synapse/http/content_repository.py
2014-09-03 12:01:14 +01:00
Erik Johnston
bc21350298 Add option to change content repo location 2014-09-03 11:57:23 +01:00
Kegan Dougal
10afd895c4 Edited /presence REST servlet to raise SynapseErrors to return a standard error response, rather than a string. 2014-09-03 11:56:47 +01:00
Kegan Dougal
c54d8df504 Update API docs to use 'presence' key not 'state'. Fixed error messages when setting presence. 2014-09-03 11:45:30 +01:00
Kegan Dougal
acfabfff9c Fixed /presence APIs to urldecode user IDs. 2014-09-03 11:31:48 +01:00
Kegan Dougal
65693e9e15 Fixed GET /events/$id to be not broken. 2014-09-03 11:24:45 +01:00
Mark Haines
bf10cf5f1a move contentrepo class to it's own file 2014-09-03 11:10:44 +01:00
Kegan Dougal
2385d396c3 URL decode user IDs for /profile REST path segments. 2014-09-03 11:05:29 +01:00
Kegan Dougal
3a3fadcece Make API docs not lie on registration/login. 2014-09-03 10:58:53 +01:00
Kegan Dougal
ce5c88006e Update port numbers. 2014-09-03 10:47:07 +01:00
Emmanuel ROHEE
d29d41322a Better indicate in the page feedback section that a user can't join a room (because he has been banned for ex). May be worth to be put in the middle of screen 2014-09-03 11:45:40 +02:00
Erik Johnston
46ac4a2f85 Fill out power level bits in the spec 2014-09-03 10:44:46 +01:00
Paul "LeoNerd" Evans
da3e04df8b Rename the 'do_users_share_a_room' to something slightly less verb-sounding 2014-09-03 10:40:48 +01:00
Paul "LeoNerd" Evans
967b45bc1a Allow optional non-suppression of exceptions through the Distributor 2014-09-03 10:40:21 +01:00
Paul "LeoNerd" Evans
ddf3ca7ab3 Neater is_presence_visible() code 2014-09-03 10:40:21 +01:00
Emmanuel ROHEE
4ba5b4b55d Do not show banned people in member list. Format ban events strings in room and recent flows 2014-09-03 11:38:24 +02:00
Kegan Dougal
8ad056b207 Prefix API links with /docs/api/client-server so they should link through correctly on matrix.org 2014-09-03 10:32:51 +01:00
Kegan Dougal
e4eb5cb443 cmdclient: Fixed /join to work with the updated c-s API. 2014-09-03 10:27:11 +01:00
Emmanuel ROHEE
56427b8057 Added /ban command 2014-09-03 11:17:58 +02:00
Emmanuel ROHEE
65c7f78e9f Made IRC style command easier to handle specifically for possible arguments 2014-09-03 11:07:53 +02:00
Kegan Dougal
8166ebd91a cmdclient: Now works correctly with user localpart login. Default to 8008 not 8080. 2014-09-03 09:53:17 +01:00
Erik Johnston
ddc16d8642 Merge branch 'master' into develop 2014-09-03 09:46:39 +01:00
Kegan Dougal
c77add6d21 Add ban support: /rooms/$roomid/ban with { user_id : foo } 2014-09-03 09:43:32 +01:00
Mark Haines
c6eafdfbaf Add copyright notices and fix pyflakes errors 2014-09-03 09:43:11 +01:00
Mark Haines
112c7ea315 Set status message for ratelimit error responses 2014-09-03 09:37:44 +01:00
Mark Haines
30ad0c5674 Merge branch 'ratelimiting' into develop 2014-09-03 09:15:52 +01:00
Mark Haines
cdd8602e74 Fix tests to support ratelimiting 2014-09-03 09:15:22 +01:00
Kegan Dougal
8c793e0704 howto: Link jsfiddles correctly. Hide ugly TODOs. 2014-09-03 09:14:16 +01:00
Mark Haines
683596f91e Raise LimitExceedError when the ratelimiting is throttling requests 2014-09-03 08:58:48 +01:00
Matthew Hodgson
84430a4a8a doc generator 2014-09-03 04:58:01 +01:00
Matthew Hodgson
9fae76107f fix rst 2014-09-03 04:30:51 +01:00
Matthew Hodgson
bd7d47fcea don't echo password to stdout... 2014-09-03 00:02:29 +01:00
Matthew Hodgson
2b9afa775e more critique 2014-09-02 23:50:40 +01:00
Kegan Dougal
70aa4b9231 Edited room creation section to mention all the events created when the room is. 2014-09-02 21:45:36 +01:00
Kegan Dougal
0aacab43ca Added Qs which should be answered in the marked TODOs 2014-09-02 21:17:47 +01:00
Matthew Hodgson
dcbdfcc9d2 only need one voip section :) 2014-09-02 21:16:59 +01:00
Matthew Hodgson
7819a1010c general documentation review and editing 2014-09-02 21:15:03 +01:00
Matthew Hodgson
3bffd14b02 fix quickstart slightly 2014-09-02 21:15:03 +01:00
Kegan Dougal
ab6e1abe9c Added the new power level related events which were recently introduced. 2014-09-02 21:12:46 +01:00
Kegan Dougal
707cd32b13 Added more room alias bullet points. 2014-09-02 20:38:25 +01:00
Kegan Dougal
2f5182b2d2 Finished up Identity section in the architecture section. 2014-09-02 20:32:09 +01:00
Mark Haines
780548b577 rate limiting for message sending 2014-09-02 18:22:15 +01:00
Mark Haines
0a1260b03a Add ratelimiting config 2014-09-02 18:00:15 +01:00
Kegan Dougal
3167d47882 Minor formatting tweaks. 2014-09-02 17:58:16 +01:00
Mark Haines
c7a7cdf734 Add ratelimiting function to basehandler 2014-09-02 17:57:04 +01:00
Kegan Dougal
9f94b11d4c Added section on rate limiting. 2014-09-02 17:51:45 +01:00
Kegan Dougal
b175179e47 ALL THE LINKS! Most APIs now link to relative paths off the doc, outlined at the bottom of the .rst. 2014-09-02 17:34:24 +01:00
Erik Johnston
d8a921f6a6 Add database upgrade script 2014-09-02 17:31:40 +01:00
Emmanuel ROHEE
6e2ce83d57 roomName directive: Take into account invited users.
Use the last user display name
2014-09-02 18:26:20 +02:00
Erik Johnston
47c7dd590d Un block quote list 2014-09-02 17:12:07 +01:00
Erik Johnston
9a7f7cb74f Try and remove annoying italics in github rst viewer 2014-09-02 17:10:28 +01:00
Erik Johnston
18f0247491 Remove an annoying 'the' 2014-09-02 17:07:07 +01:00
Erik Johnston
731c33dd97 Don't so heavily suggest you need to edit the default config. List the changed parameters. 2014-09-02 17:06:08 +01:00
Kegan Dougal
1952a1c68d More formatting, more TODOs. Settled on a way of linking to external API docs; started converting references to relative links. 2014-09-02 17:05:30 +01:00
Erik Johnston
c2c3ee8e4a Fix spelling 2014-09-02 17:01:04 +01:00
Erik Johnston
211a8b288a Hide the email textbox during registeration has it has not been wired up yet. 2014-09-02 16:59:45 +01:00
Erik Johnston
e166e29e87 Bump version and changelog 2014-09-02 16:57:10 +01:00
Erik Johnston
235f686da9 Update default endpoint port to match the default ports in the config 2014-09-02 16:56:57 +01:00
Kegan Dougal
9613d65756 spec: Added internal links to different sections. Added NOTE and WARNING admonitions and hide away loooong TODO lists behind comments. Smaller ones remain. 2014-09-02 16:38:21 +01:00
Matthew Hodgson
044daf4fe2 make power level more visible 2014-09-02 16:07:00 +01:00
Emmanuel ROHEE
d3c7567369 Attempt to make avatar change clearer 2014-09-02 17:00:47 +02:00
Emmanuel ROHEE
bcf30b29ad BF: Reset base timer time ($scope.now) everytime last_active_ago is touched 2014-09-02 16:39:29 +02:00
Kegan Dougal
b4984d5e15 Updated howto.rst to use the new APIs. Updated JSFiddles to use 8008. Linked new fiddles with howto.rst. Added more explanations. 2014-09-02 15:29:43 +01:00
Erik Johnston
464e1fcfa5 Merge branch 'master' of github.com:matrix-org/synapse into release-v0.2.0
Conflicts:
	synapse/notifier.py
	webclient/room/room-controller.js
	webclient/room/room.html
2014-09-02 15:26:11 +01:00
Mark Haines
dd2cd9312a Test ratelimiter 2014-09-02 15:16:26 +01:00
Emmanuel ROHEE
e565a4bfc4 BF: Prevent controllers from being instantiated twice.
AngularJS will instantiate it when discovering ng-controller in their respective html files
2014-09-02 15:12:51 +02:00
Emmanuel ROHEE
d5da6b0cef Handle "m.room.create" in order to inform controllers about new rooms 2014-09-02 13:55:23 +02:00
Paul "LeoNerd" Evans
aa337f588c Order matters when adding REST servlets 2014-09-02 12:53:36 +01:00
Erik Johnston
4b8244fbf8 Fix bug where we didn't correctly store the ops power levels event. 2014-09-02 12:12:14 +01:00
Kegan Dougal
4ac80b8570 Whoops: Put /events parameters object in the right part of the api tree. 2014-09-02 12:09:47 +01:00
Kegan Dougal
5539251d82 Updated API docs with new keys on /createRoom and new API /rooms/$roomid/ban 2014-09-02 11:54:01 +01:00
David Baker
5c778f2f15 Add rest endpoint for the whole user profile 2014-09-02 11:49:17 +01:00
Kegan Dougal
fdcb876495 More ban spec. Added 'name' and 'topic' keys to /createRoom. 2014-09-02 11:46:53 +01:00
Paul "LeoNerd" Evans
d9d6fbb085 Safer handling of incoming presence EDUs that may or maynot contain 'presence' or 'state' 2014-09-02 11:17:43 +01:00
Mark Haines
436b3c7d0c Ratelimiter object 2014-09-02 11:16:21 +01:00
Paul "LeoNerd" Evans
7b56a7a3cb 'continue' to go to the next item in a for loop, not 'break' 2014-09-02 11:15:46 +01:00
Erik Johnston
10e7821461 By default, only room ops can change the name and topic. 2014-09-02 11:11:47 +01:00
Mark Haines
cf890e9d43 Remove option for disabling webclient because it was confusing 2014-09-02 11:09:41 +01:00
Erik Johnston
a808c06a10 Fix room permissions test 2014-09-02 11:03:03 +01:00
Erik Johnston
db02021aba Implement auth for kicking. 2014-09-02 10:58:33 +01:00
Erik Johnston
ed25abe05f Add message to assertion 2014-09-02 10:58:33 +01:00
Erik Johnston
08d2f902dd Default HTTP and HTTPS ports to 8008 and 8448 2014-09-02 10:58:33 +01:00
Erik Johnston
0393e87519 Make the demo listen on both HTTP and HTTPS ports 2014-09-02 10:58:33 +01:00
Mark Haines
45570e4695 os.makedirs is almost but not entirely unlike mkdir -p 2014-09-02 10:58:05 +01:00
Mark Haines
64b341cc10 Fix typo when reading TLS config 2014-09-02 10:54:56 +01:00
Emmanuel ROHEE
828101dd51 Represent user power level in a room by a red bar at the bottom of his avatar image. The width of this bar depends on the power level. 2014-09-02 11:54:35 +02:00
Paul "LeoNerd" Evans
7e22afbc7c Ensure that event source unit tests could cope with get_new_events_for_user() returning a Deferred 2014-09-02 10:53:16 +01:00
Mark Haines
30572e28c2 Make the config directory if the directory doesn't exisit when generating config 2014-09-02 10:52:25 +01:00
Mark Haines
d45f89c95b More helpful error messages for missing config 2014-09-02 10:49:11 +01:00
Kegan Dougal
ab0637c2c3 Yet more bullet points on various sections. 2014-09-02 10:41:00 +01:00
Emmanuel ROHEE
1bc05aef20 BF: Armed updateMemberListPresenceAge only once 2014-09-02 11:14:58 +02:00
David Baker
772c117e68 Make the intro bit a separate section rather than have a 'quick start' section that only tells you how to get started half way through. 2014-09-02 10:05:26 +01:00
Erik Johnston
040d985908 Add support for setting room name and topic when creating rooms 2014-09-02 10:02:14 +01:00
Kegan Dougal
15a7312273 Added section on banning users, currently in flux as the API changes / is implemented. 2014-09-02 09:34:28 +01:00
Emmanuel ROHEE
3122ff2433 Merge remote-tracking branch 'origin/develop' into develop 2014-09-02 10:20:47 +02:00
Emmanuel ROHEE
027857b261 Merge branch 'withoutYAML' into develop 2014-09-02 10:19:37 +02:00
Kegan Dougal
94bb4031f3 Expanded on bullet points in several sections. 2014-09-02 08:58:53 +01:00
Emmanuel ROHEE
07d609cbc2 BF: Take into account MEMBER_EVENT and PRESENCE_EVENT events only after the initialSync is done 2014-09-02 09:39:43 +02:00
Matthew Hodgson
68a04b9282 given everything's apparently gone https:// by default for c-s, change this hardcoding... 2014-09-01 23:28:43 +01:00
Mark Haines
399e004884 Add unsecure listener port to homeserver 2014-09-01 22:38:52 +01:00
Mark Haines
79650f795f enable ECDHE ciphers 2014-09-01 22:29:44 +01:00
Paul "LeoNerd" Evans
270d302834 Reënable presence visibility permission checking now it's not used on redundant code paths 2014-09-01 22:15:02 +01:00
Paul "LeoNerd" Evans
32fdf8efd6 No need to test is_presence_visible in _start_polling_local because we know it will be by when it's called 2014-09-01 22:15:02 +01:00
Matthew Hodgson
61e28cdb6f specify metavars in argparse 2014-09-01 22:11:08 +01:00
Mark Haines
118b250473 add pyyaml dependency for config files 2014-09-01 20:50:32 +01:00
Mark Haines
6fd730c96b Use yaml for config file 2014-09-01 20:44:51 +01:00
Erik Johnston
48142a01dd Don't set a 'default' key in the creation event 2014-09-01 20:39:43 +01:00
Mark Haines
8b69468e5f Use pregenerated DH params when generating config 2014-09-01 20:35:18 +01:00
Erik Johnston
bcfaaf7da6 That was a breaking db change. You need to recreate the databases. (In reality, it's enough to just run the im.sql through your db and change the schema version) 2014-09-01 20:34:07 +01:00
Erik Johnston
a85612baf8 Merge branch 'room_config' into develop 2014-09-01 20:28:11 +01:00
Erik Johnston
6d28560626 Don't put required power levels on permission state events 2014-09-01 20:27:45 +01:00
Mark Haines
86fa1138be Update readme with instructions on how to generate configs for homeservers 2014-09-01 20:22:16 +01:00
Erik Johnston
f452899fe2 Merge branch 'develop' of github.com:matrix-org/synapse into room_config 2014-09-01 19:57:28 +01:00
Erik Johnston
3f5ebccbff Fix the tests to include new db calls 2014-09-01 19:57:17 +01:00
Mark Haines
ff79437d9b Merge branch 'server2server_tls' into develop 2014-09-01 19:56:22 +01:00
Mark Haines
5452a8ee29 Fix SSL for federation http client 2014-09-01 18:43:08 +01:00
Mark Haines
00b042a3eb Merge remote-tracking branch 'origin/develop' into server2server_tls 2014-09-01 18:30:07 +01:00
Mark Haines
a53946a8a1 Enable SSL for s2s http client 2014-09-01 18:30:00 +01:00
Erik Johnston
b8ab9f1c0a Add all the necessary checks to make banning work. 2014-09-01 18:24:56 +01:00
Erik Johnston
3faa2ae78c Merge branch 'develop' of github.com:matrix-org/synapse into room_config 2014-09-01 18:21:29 +01:00
Emmanuel ROHEE
0271e8e692 Follow API renaming. state -> presence. mtime_ago -> last_active_ago 2014-09-01 19:09:49 +02:00
Kegan Dougal
74cffcf51a Small formatting errors 2014-09-01 17:57:44 +01:00
David Baker
6d07a28a29 fix lying docs 2014-09-01 17:55:41 +01:00
Mark Haines
6200630904 Add server TLS context factory 2014-09-01 17:55:35 +01:00
Emmanuel ROHEE
7d99cee3ef Added roomName, a directive to compute a room name based on its alias (not aliases for now) and its users displaynames or ids 2014-09-01 18:52:24 +02:00
Kegan Dougal
99ce820cc8 Little bit more on syncing APIs, need to know more about responses before more work can be done on it. 2014-09-01 17:51:31 +01:00
David Baker
ab8de33c76 Oops, show the voice call button once a call has ended 2014-09-01 17:41:42 +01:00
Paul "LeoNerd" Evans
32bfd567ac Added docs on presence 'last_active' field 2014-09-01 17:22:33 +01:00
David Baker
57f047a05a Fairly simple move of the call status widget to the header bar (and therefore into the index page rather than the rooms page). 2014-09-01 17:16:27 +01:00
Kegan Dougal
5a11a8ef69 Flesh out Room Events. 2014-09-01 16:52:33 +01:00
Emmanuel ROHEE
9b61076d42 BF: Check config exists (=defined in the localstorage) before using it 2014-09-01 17:43:27 +02:00
Mark Haines
46dcb0d890 Merge branch 'develop' into server2server_tls 2014-09-01 16:31:09 +01:00
Mark Haines
ef6a8e4f32 Listen using SSL 2014-09-01 16:30:43 +01:00
David Baker
b9172b982f missing semicolon 2014-09-01 16:23:13 +01:00
Paul "LeoNerd" Evans
1c6ab2d759 Bump a user's presence last_active time every time they send a message to a room 2014-09-01 16:16:35 +01:00
Paul "LeoNerd" Evans
59d3955db1 Have MemoryDataStore's get_rooms_for_user_where_membership_is() return room membership event objects, as per interface contract 2014-09-01 16:15:45 +01:00
Erik Johnston
db7109c43b Add beginnings of ban support. 2014-09-01 16:15:34 +01:00
Paul "LeoNerd" Evans
fd696f1243 Perform room unit tests with 'local' users actually in the right domain ;) 2014-09-01 15:58:30 +01:00
Kegan Dougal
401c16559d Specced out state/non-state events. Start working on the myriad of syncing APIs. 2014-09-01 15:58:20 +01:00
David Baker
fa6b3490e2 Handle call events in the recents view as well to get consistency with what it displays when refreshed. 2014-09-01 15:54:06 +01:00
David Baker
4e14e38bd5 Just show 'Call' for call events. Don't just splat out the content of the event for other events: it's rarely going to be sesnible. 2014-09-01 15:54:06 +01:00
Mark Haines
f5755bcadf Merge branch 'develop' into server2server_tls 2014-09-01 15:51:44 +01:00
Mark Haines
9ea1de432d Fix homeserver config parsing 2014-09-01 15:51:15 +01:00
Paul "LeoNerd" Evans
468d94c920 Rename API-visible 'mtime' presence field to 'last_active'; slightly different semantics 2014-09-01 15:39:50 +01:00
Emmanuel ROHEE
26a95988da Fixed presence change that occurs -Xs ago 2014-09-01 16:27:11 +02:00
Emmanuel ROHEE
c9ee9b45c7 Fixed presence state update. Specifically, the current user was sometimes indicated as offline 2014-09-01 16:22:59 +02:00
Paul "LeoNerd" Evans
02f4e3b3ff Rename 'state' presence key to the much more obvious 'presence'; maintain a legacy 'state' copy for now 2014-09-01 14:45:41 +01:00
Kegan Dougal
f500dd627a Fleshed out joining/leaving rooms. Added M_LIMIT_EXCEEDED standard error code for rate limited requests. 2014-09-01 14:44:25 +01:00
Erik Johnston
865469f233 Implement power level lists, default power levels and send_evnet_level/add_state_level events. 2014-09-01 14:39:09 +01:00
David Baker
67ffc00d48 Don't show the hang up button once tha call has ended 2014-09-01 14:15:32 +01:00
Kegan Dougal
389ee3624c Added /web folder which contains html/css (based off the default swagger-ui) for viewing the c-s json api. 2014-09-01 14:11:26 +01:00
Kegan Dougal
51b0b5c5ab Renamed json files because: paths. 2014-09-01 14:05:28 +01:00
Erik Johnston
10efca1a74 Merge branch 'develop' of github.com:matrix-org/synapse into room_config 2014-09-01 13:41:44 +01:00
Mark Haines
a9512d0994 Merge branch 'develop' into server2server_tls 2014-09-01 13:29:17 +01:00
Matthew Hodgson
fad58dbd08 add another public wishlist item 2014-09-01 13:22:50 +01:00
Matthew Hodgson
0b01c8560d make the little logos clickable for Erik 2014-09-01 13:22:42 +01:00
Kegan Dougal
3bb93abb34 Start digging into invite/join/leave sections. 2014-09-01 10:38:48 +01:00
Emmanuel ROHEE
f81002df60 BF: Pass desired_user_id to register API. And update the app header to avoid to display the previous matrix id 2014-09-01 11:12:24 +02:00
Matthew Hodgson
df752a15ce licenses, please... 2014-09-01 00:42:03 +01:00
Matthew Hodgson
d27e1ab148 fix footer on non-room pages 2014-09-01 00:41:53 +01:00
Mark Haines
3eb45eba0e Merge branch 'develop' into server2server_tls 2014-08-31 16:08:20 +01:00
Mark Haines
d9ebe531ed Add config tree to synapse. Add support for reading config from a file 2014-08-31 16:06:39 +01:00
Matthew Hodgson
7ca6d4e8f7 don't make HS ports explicit if it's the default for the protocol 2014-08-31 15:38:27 +01:00
Matthew Hodgson
2b7918bd6f missed a s#/matrix#/_matrix/g 2014-08-31 14:54:58 +01:00
Matthew Hodgson
8fe912d95c change the world: make the default matrix API URL prefix /_matrix rather than /matrix to make it easier for existing websites to mount a HS in their namespace without collisions.
perl -pi -e 's#/matrix#/_matrix#g' ./cmdclient/console.py ./docs/client-server/howto.rst ./docs/client-server/specification.rst ./docs/client-server/swagger_matrix/directory ./docs/client-server/swagger_matrix/events ./docs/client-server/swagger_matrix/login ./docs/client-server/swagger_matrix/presence ./docs/client-server/swagger_matrix/profile ./docs/client-server/swagger_matrix/registration ./docs/client-server/swagger_matrix/rooms ./docs/server-server/specification.rst ./graph/graph.py ./jsfiddles/create_room_send_msg/demo.js ./jsfiddles/event_stream/demo.js ./jsfiddles/example_app/demo.js ./jsfiddles/register_login/demo.js ./jsfiddles/room_memberships/demo.js ./synapse/api/urls.py ./tests/federation/test_federation.py ./tests/handlers/test_presence.py ./tests/handlers/test_typing.py ./tests/rest/test_events.py ./tests/rest/test_presence.py ./tests/rest/test_profile.py ./tests/rest/test_rooms.py ./webclient/components/fileUpload/file-upload-service.js ./webclient/components/matrix/matrix-service.js
2014-08-31 14:51:37 +01:00
Matthew Hodgson
820ef6e9d8 make lightbox a bit less ugly 2014-08-31 12:03:22 +01:00
Matthew Hodgson
0a65a2384c only show presence timings if we know them 2014-08-31 11:55:48 +01:00
Matthew Hodgson
1bc036a12d nasty big monolithic commit of a whole bunch of UI/UX improvements:
- add a simple CSS template across the app for navigation & cosmetics
 - split login into login & register, and totally reskin it
 - restructure room CSS to play nicely with it
 - implement basis 1:1 chat from user pages
 - disable autofocus on iOS to improve UX
2014-08-31 00:40:42 +01:00
Matthew Hodgson
b040bd6157 factor out mobile css into its own file 2014-08-31 00:38:45 +01:00
Matthew Hodgson
3ef312fb95 factor out the signup process into its own controller 2014-08-31 00:38:10 +01:00
Matthew Hodgson
91753655b7 moar logos! 2014-08-31 00:37:47 +01:00
Matthew Hodgson
17a4bc10bc switch default profile image to one of our own devising (dating back to communicator(!)) 2014-08-31 00:37:01 +01:00
Matthew Hodgson
885e0c8b76 add a basic wishlist 2014-08-31 00:36:11 +01:00
Matthew Hodgson
2a0e79bbfa tweaks to settings page 2014-08-30 00:50:51 +01:00
Matthew Hodgson
f64ce52305 actually add a 'home' button 2014-08-30 00:44:56 +01:00
Matthew Hodgson
c715660cb8 shrink text bubble font a bit; make image thumbnails always fit in their bubbles 2014-08-30 00:41:36 +01:00
Paul "LeoNerd" Evans
93407cf7cf Bugfixes on presence pushes on user joining:
* No need to inform clients of status of remote users; as that will
   arrive in due course anyway. We don't -have- the state currently, so
   we'd only send an unknown message

 * Remember to bump the presence serial for the event source, so the
   notifiers will wake up and report it
2014-08-29 19:53:33 +01:00
Paul "LeoNerd" Evans
a8e8d1d06c Renamed get_current_token_part to get_current_key 2014-08-29 19:15:23 +01:00
Paul "LeoNerd" Evans
eec67a675f Have EventSource's get_new_events_for_user() API work only on keys within that source, not overall eventstream tokens 2014-08-29 19:13:55 +01:00
Paul "LeoNerd" Evans
56424eca5c Bugfix for rest presence test - datastore needs to implement profile methods 2014-08-29 19:12:22 +01:00
Paul "LeoNerd" Evans
6797c7f1b1 TypingNotificationEventSource has to return proper tokens, not int 0 2014-08-29 19:00:55 +01:00
Paul "LeoNerd" Evans
4bfdec1eb2 Rename 'events_key' to 'room_key' so it matches the name of the event source 2014-08-29 19:00:55 +01:00
David Baker
490f142d73 Give basic feedback on the state of VoIP calls in the UI. 2014-08-29 18:01:01 +01:00
Matthew Hodgson
26766c22eb todo 2014-08-29 17:54:18 +01:00
Matthew Hodgson
e006f101c3 fix mobile skin by hiding recents LHS in /rooms 2014-08-29 17:54:18 +01:00
Kegan Dougal
74cc722b96 Added case-sensitivity notes on IDs, added TODO on UTF-8. 2014-08-29 17:45:09 +01:00
Paul "LeoNerd" Evans
6dd50da54e Define a new event stream data source for typing notifications (currently null) 2014-08-29 17:39:33 +01:00
Paul "LeoNerd" Evans
f85a3757cf Avoid hardcoding names of individual stream token keys in its own implementation; this at least reduces the number of places in source code the individual parts are stored 2014-08-29 17:31:39 +01:00
Emmanuel ROHEE
95cbd026cc oops. Should not have c+p all lines 2014-08-29 18:29:04 +02:00
Emmanuel ROHEE
e1f249ce20 Implemented /nick 2014-08-29 18:24:13 +02:00
Emmanuel ROHEE
67f42b2f26 Get user display name and avatar from the server rather than storing them in the local storage 2014-08-29 18:23:18 +02:00
Matthew Hodgson
b86d2a2d4f update presence times in realtime through the magic of two-way binding 2014-08-29 17:22:48 +01:00
Paul "LeoNerd" Evans
d4145abd33 Use str.join() properly 2014-08-29 17:17:11 +01:00
Paul "LeoNerd" Evans
20d0db6cfb Move the *EventSource classes into the handlers they relate to, so it's easier to find the code 2014-08-29 17:10:28 +01:00
Kegan Dougal
ca025c2b1d Rooms: More subsections, more bullet points, more detail. 2014-08-29 16:50:41 +01:00
Emmanuel ROHEE
c3a774e414 Show desktop notification state. Provide help if the user has previously denied permission to display them. 2014-08-29 17:13:57 +02:00
David Baker
0ef54caa28 better support for call hangups 2014-08-29 15:18:57 +01:00
Erik Johnston
1118f02689 Start adding storage for new events. 2014-08-29 15:18:30 +01:00
Kegan Dougal
1cdc29e260 Fix JSFiddles to work with the new C-S API. 2014-08-29 15:01:55 +01:00
Kegan Dougal
339dd3dc6c Update API swagger JSON to default to POSTs not PUTs when describing the API. 2014-08-29 15:01:55 +01:00
Emmanuel ROHEE
27047d8f51 Merge branch 'release-v0.1.0' into develop 2014-08-29 15:33:17 +02:00
Emmanuel ROHEE
7c4b47652e The away state is unavailable not offline 2014-08-29 15:33:02 +02:00
Erik Johnston
8e2d4c6da5 Merge branch 'develop' of github.com:matrix-org/synapse into room_config 2014-08-29 14:22:04 +01:00
David Baker
e76cd252fe Merge branch 'release-v0.1.0' into develop 2014-08-29 14:01:30 +01:00
Erik Johnston
fbdacce3fe Merge branch 'develop' of github.com:matrix-org/synapse into develop 2014-08-29 13:52:10 +01:00
Kegan Dougal
898dde8812 Flesh out API standards. 2014-08-29 13:50:31 +01:00
Mark Haines
d7ae9b90a0 Add store for server certificates and keys 2014-08-28 18:19:47 +01:00
Erik Johnston
d2bc5d6f29 Create the correct events with the right configuration when creating a new room. 2014-08-28 10:59:15 +01:00
228 changed files with 19818 additions and 2392 deletions

3
.gitignore vendored
View File

@@ -18,9 +18,12 @@ htmlcov
demo/*.db
demo/*.log
demo/*.pid
demo/etc
graph/*.svg
graph/*.png
graph/*.dot
webclient/config.js
uploads

View File

@@ -1,3 +1,62 @@
Changes in synapse 0.2.2 (2014-09-06)
=====================================
Homeserver:
* When the server returns state events it now also includes the previous
content.
* Add support for inviting people when creating a new room.
* Make the homeserver inform the room via `m.room.aliases` when a new alias
is added for a room.
* Validate `m.room.power_level` events.
Webclient:
* Add support for captchas on registration.
* Handle `m.room.aliases` events.
* Asynchronously send messages and show a local echo.
* Inform the UI when a message failed to send.
* Only autoscroll on receiving a new message if the user was already at the
bottom of the screen.
* Add support for ban/kick reasons.
Changes in synapse 0.2.1 (2014-09-03)
=====================================
Homeserver:
* Added support for signing up with a third party id.
* Add synctl scripts.
* Added rate limiting.
* Add option to change the external address the content repo uses.
* Presence bug fixes.
Webclient:
* Added support for signing up with a third party id.
* Added support for banning and kicking users.
* Added support for displaying and setting ops.
* Added support for room names.
* Fix bugs with room membership event display.
Changes in synapse 0.2.0 (2014-09-02)
=====================================
This update changes many configuration options, updates the
database schema and mandates SSL for server-server connections.
Homeserver:
* Require SSL for server-server connections.
* Add SSL listener for client-server connections.
* Add ability to use config files.
* Add support for kicking/banning and power levels.
* Allow setting of room names and topics on creation.
* Change presence to include last seen time of the user.
* Change url path prefix to /_matrix/...
* Bug fixes to presence.
Webclient:
* Reskin the CSS for registration and login.
* Various improvements to rooms CSS.
* Support changes in client-server API.
* Bug fixes to VOIP UI.
* Various bug fixes to handling of changes to room member list.
Changes in synapse 0.1.2 (2014-08-29)
=====================================

View File

@@ -1,12 +1,12 @@
Quick Start
===========
Introduction
============
Matrix is an ambitious new ecosystem for open federated Instant Messaging and
VoIP[1]. The basics you need to know to get up and running are:
VoIP. The basics you need to know to get up and running are:
- Chatrooms are distributed and do not exist on any single server. Rooms
can be found using names like ``#matrix:matrix.org`` or
``#test:localhost:8080`` or they can be ephemeral.
can be found using aliases like ``#matrix:matrix.org`` or
``#test:localhost:8008`` or they can be ephemeral.
- Matrix user IDs look like ``@matthew:matrix.org`` (although in the future
you will normally refer to yourself and others using a 3PID: email
@@ -14,30 +14,52 @@ VoIP[1]. The basics you need to know to get up and running are:
The overall architecture is::
client <----> homeserver <=================> homeserver <-----> client
e.g. matrix.org:8080 e.g. mydomain.net:8080
client <----> homeserver <=====================> homeserver <----> client
https://somewhere.org/_matrix https://elsewhere.net/_matrix
WARNING
=======
**Synapse is currently in a state of rapid development, and not all features are yet functional.
Critically, some security features are still in development, which means Synapse can *not*
be considered secure or reliable at this point.** For instance:
- **SSL Certificates used by server-server federation are not yet validated.**
- **Room permissions are not yet enforced on traffic received via federation.**
- **Homeservers do not yet cryptographically sign their events to avoid tampering**
- Default configuration provides open signup to the service from the internet
Despite this, we believe Synapse is more than useful as a way for experimenting and
exploring Synapse, and the missing features will land shortly. **Until then, please do *NOT*
use Synapse for any remotely important or secure communication.**
Quick Start
===========
System requirements:
- POSIX-compliant system (tested on Linux & OSX)
- Python 2.7
To get up and running:
- To simply play with an **existing** homeserver you can
just go straight to http://matrix.org/alpha.
- To run your own **private** homeserver on localhost:8080, install synapse
with ``python setup.py develop --user`` and then run one with
``python synapse/app/homeserver.py`` - you will find a webclient running
at http://localhost:8080 (use a recent Chrome, Safari or Firefox for now,
please...)
- To run your own **private** homeserver on localhost:8008, install synapse with
``python setup.py develop --user`` and then run ``./synctl start`` twice (once to
generate a config; once to actually run) - you will find a webclient running at
http://localhost:8008. Please use a recent Chrome, Safari or Firefox for now...
- To make the homeserver **public** and let it exchange messages with
other homeservers and participate in the overall Matrix federation, open
up port 8080 and run ``python synapse/app/homeserver.py --host
machine.my.domain.name``. Then come join ``#matrix:matrix.org`` and
say hi! :)
- To run a **public** homeserver and let it exchange messages with other homeservers
and participate in the global Matrix federation, you must expose port 8448 to the
internet and edit homeserver.yaml to specify server_name (the public DNS entry for
this server) and then run ``synctl start``. If you changed the server_name, you may
need to move the old database (homeserver.db) out of the way first. Then come join
``#matrix:matrix.org`` and say hi! :)
For more detailed setup instructions, please see further down this document.
[1] VoIP currently in development
About Matrix
============
@@ -47,15 +69,15 @@ which handle:
- Creating and managing fully distributed chat rooms with no
single points of control or failure
- Eventually-consistent cryptographically secure[2] synchronisation of room
- Eventually-consistent cryptographically secure[1] synchronisation of room
state across a global open network of federated servers and services
- Sending and receiving extensible messages in a room with (optional)
end-to-end encryption[3]
end-to-end encryption[2]
- Inviting, joining, leaving, kicking, banning room members
- Managing user accounts (registration, login, logout)
- Using 3rd Party IDs (3PIDs) such as email addresses, phone numbers,
Facebook accounts to authenticate, identify and discover users on Matrix.
- Placing 1:1 VoIP and Video calls (in development)
- Placing 1:1 VoIP and Video calls
These APIs are intended to be implemented on a wide range of servers, services
and clients, letting developers build messaging and VoIP functionality on top of
@@ -89,9 +111,9 @@ https://github.com/matrix-org/synapse/issues or at matrix@matrix.org.
Thanks for trying Matrix!
[2] Cryptographic signing of messages isn't turned on yet
[1] Cryptographic signing of messages isn't turned on yet
[3] End-to-end encryption is currently in development
[2] End-to-end encryption is currently in development
Homeserver Installation
@@ -99,7 +121,6 @@ Homeserver Installation
First, the dependencies need to be installed. Start by installing
'python2.7-dev' and the various tools of the compiler toolchain.
N.B. synapse requires python 2.x where x >= 7
Installing prerequisites on ubuntu::
@@ -130,6 +151,9 @@ you can check PyNaCl out of git directly (https://github.com/pyca/pynacl) and
installing it. Installing PyNaCl using pip may also work (remember to remove any
other versions installed by setuputils in, for example, ~/.local/lib).
On OSX, if you encounter ``clang: error: unknown argument: '-mno-fused-madd'`` you will
need to ``export CFLAGS=-Qunused-arguments``.
This will run a process of downloading and installing into your
user's .local/lib directory all of the required dependencies that are
missing.
@@ -173,7 +197,15 @@ IDs:
For the first form, simply pass the required hostname (of the machine) as the
--host parameter::
$ python synapse/app/homeserver.py --host machine.my.domain.name
$ python synapse/app/homeserver.py \
--server-name machine.my.domain.name \
--config-path homeserver.config \
--generate-config
$ python synapse/app/homeserver.py --config-path homeserver.config
Alternatively, you can run synapse via synctl - running ``synctl start`` to generate a
homeserver.yaml config file, where you can then edit server-name to specify
machine.my.domain.name, and then set the actual server running again with synctl start.
For the second form, first create your SRV record and publish it in DNS. This
needs to be named _matrix._tcp.YOURDOMAIN, and point at at least one hostname
@@ -186,7 +218,13 @@ record would then look something like::
At this point, you should then run the homeserver with the hostname of this
SRV record, as that is the name other machines will expect it to have::
$ python synapse/app/homeserver.py --host my.domain.name --port 8448
$ python synapse/app/homeserver.py \
--server-name YOURDOMAIN \
--bind-port 8448 \
--config-path homeserver.config \
--generate-config
$ python synapse/app/homeserver.py --config-path homeserver.config
You may additionally want to pass one or more "-v" options, in order to
increase the verbosity of logging output; at least for initial testing.
@@ -255,7 +293,7 @@ track 3PID logins and publish end-user public keys.
It's currently early days for identity servers as Matrix is not yet using 3PIDs
as the primary means of identity and E2E encryption is not complete. As such,
we're not yet running an identity server in public.
we are running a single identity server (http://matrix.org:8090) at the current time.
Where's the spec?!

View File

@@ -1,3 +1,29 @@
Upgrading to v0.2.0
===================
The home server now requires setting up of SSL config before it can run. To
automatically generate default config use::
$ python synapse/app/homeserver.py \
--server-name machine.my.domain.name \
--bind-port 8448 \
--config-path homeserver.config \
--generate-config
This config can be edited if desired, for example to specify a different SSL
certificate to use. Once done you can run the home server using::
$ python synapse/app/homeserver.py --config-path homeserver.config
See the README.rst for more information.
Also note that some config options have been renamed, including:
- "host" to "server-name"
- "database" to "database-path"
- "port" to "bind-port" and "unsecure-port"
Upgrading to v0.0.1
===================

View File

@@ -1 +1 @@
0.1.2
0.2.2

7
WISHLIST.rst Normal file
View File

@@ -0,0 +1,7 @@
Broad-sweeping stuff which would be nice to have
================================================
- Additional SQL backends beyond sqlite
- homeserver implementation in go
- homeserver implementation in node.js
- client SDKs

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -60,7 +60,7 @@ class SynapseCmd(cmd.Cmd):
"complete_usernames": "on",
"send_delivery_receipts": "on"
}
self.path_prefix = "/matrix/client/api/v1"
self.path_prefix = "/_matrix/client/api/v1"
self.event_stream_token = "END"
self.prompt = ">>> "
@@ -88,6 +88,8 @@ class SynapseCmd(cmd.Cmd):
return False
def _domain(self):
if "user" not in self.config or not self.config["user"]:
return None
return self.config["user"].split(":")[1]
def do_config(self, line):
@@ -191,10 +193,12 @@ class SynapseCmd(cmd.Cmd):
p = getpass.getpass("Enter your password: ")
user = args["user_id"]
if self._is_on("complete_usernames") and not user.startswith("@"):
user = "@" + user + ":" + self._domain()
domain = self._domain()
if domain:
user = "@" + user + ":" + domain
reactor.callFromThread(self._do_login, user, p)
print " got %s " % p
#print " got %s " % p
except Exception as e:
print e
@@ -252,7 +256,7 @@ class SynapseCmd(cmd.Cmd):
@defer.inlineCallbacks
def _do_emailrequest(self, args):
url = self._identityServerUrl()+"/matrix/identity/api/v1/validate/email/requestToken"
url = self._identityServerUrl()+"/_matrix/identity/api/v1/validate/email/requestToken"
json_res = yield self.http_client.do_request("POST", url, data=urllib.urlencode(args), jsonreq=False,
headers={'Content-Type': ['application/x-www-form-urlencoded']})
@@ -274,7 +278,7 @@ class SynapseCmd(cmd.Cmd):
@defer.inlineCallbacks
def _do_emailvalidate(self, args):
url = self._identityServerUrl()+"/matrix/identity/api/v1/validate/email/submitToken"
url = self._identityServerUrl()+"/_matrix/identity/api/v1/validate/email/submitToken"
json_res = yield self.http_client.do_request("POST", url, data=urllib.urlencode(args), jsonreq=False,
headers={'Content-Type': ['application/x-www-form-urlencoded']})
@@ -294,7 +298,7 @@ class SynapseCmd(cmd.Cmd):
@defer.inlineCallbacks
def _do_3pidbind(self, args):
url = self._identityServerUrl()+"/matrix/identity/api/v1/3pid/bind"
url = self._identityServerUrl()+"/_matrix/identity/api/v1/3pid/bind"
json_res = yield self.http_client.do_request("POST", url, data=urllib.urlencode(args), jsonreq=False,
headers={'Content-Type': ['application/x-www-form-urlencoded']})
@@ -312,7 +316,7 @@ class SynapseCmd(cmd.Cmd):
try:
args = self._parse(line, ["roomname"], force_keys=True)
path = "/join/%s" % urllib.quote(args["roomname"])
reactor.callFromThread(self._run_and_pprint, "PUT", path, {})
reactor.callFromThread(self._run_and_pprint, "POST", path, {})
except Exception as e:
print e
@@ -360,14 +364,14 @@ class SynapseCmd(cmd.Cmd):
def _do_invite(self, roomid, userstring):
if (not userstring.startswith('@') and
self._is_on("complete_usernames")):
url = self._identityServerUrl()+"/matrix/identity/api/v1/lookup"
url = self._identityServerUrl()+"/_matrix/identity/api/v1/lookup"
json_res = yield self.http_client.do_request("GET", url, qparams={'medium':'email','address':userstring})
mxid = None
if 'mxid' in json_res and 'signatures' in json_res:
url = self._identityServerUrl()+"/matrix/identity/api/v1/pubkey/ed25519"
url = self._identityServerUrl()+"/_matrix/identity/api/v1/pubkey/ed25519"
pubKey = None
pubKeyObj = yield self.http_client.do_request("GET", url)
@@ -700,7 +704,7 @@ def main(server_url, identity_server_url, username, token, config_path):
if __name__ == '__main__':
parser = argparse.ArgumentParser("Starts a synapse client.")
parser.add_argument(
"-s", "--server", dest="server", default="http://localhost:8080",
"-s", "--server", dest="server", default="http://localhost:8008",
help="The URL of the home server to talk to.")
parser.add_argument(
"-i", "--identity-server", dest="identityserver", default="http://localhost:8090",

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

9
demo/demo.tls.dh Normal file
View File

@@ -0,0 +1,9 @@
2048-bit DH parameters taken from rfc3526
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxOb
IlFKCHmONATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjft
awv/XLb0Brft7jhr+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXT
mmkWP6j9JM9fg2VdI9yjrZYcYvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhgh
fDKQXkYuNs474553LBgOhgObJ4Oi7Aeij7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq
5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg==
-----END DH PARAMETERS-----

View File

@@ -6,17 +6,29 @@ CWD=$(pwd)
cd "$DIR/.."
mkdir -p demo/etc
for port in 8080 8081 8082; do
echo "Starting server on port $port... "
https_port=$((port + 400))
python -m synapse.app.homeserver \
-p "$port" \
-H "localhost:$port" \
--generate-config \
--config-path "demo/etc/$port.config" \
-p "$https_port" \
--unsecure-port "$port" \
-H "localhost:$https_port" \
-f "$DIR/$port.log" \
-d "$DIR/$port.db" \
-vv \
-D --pid-file "$DIR/$port.pid" \
--manhole $((port + 1000))
--manhole $((port + 1000)) \
--tls-dh-params-path "demo/demo.tls.dh"
python -m synapse.app.homeserver \
--config-path "demo/etc/$port.config" \
-vv \
done
echo "Starting webclient on port 8000..."

View File

@@ -2,6 +2,20 @@
Matrix Client-Server API
========================
.. WARNING::
This specification is old. Please see /docs/specification.rst instead.
The following specification outlines how a client can send and receive data from
a home server.
@@ -306,11 +320,11 @@ POST requests MUST be submitted as application/json.
All paths MUST be namespaced by the version of the API being used. This should
be:
/matrix/client/api/v1
/_matrix/client/api/v1
All REST paths in this section MUST be prefixed with this. E.g.
REST Path: /rooms/$room_id
Absolute Path: /matrix/client/api/v1/rooms/$room_id
Absolute Path: /_matrix/client/api/v1/rooms/$room_id
Registration
============
@@ -1007,26 +1021,15 @@ for users from other servers entirely.
Presence
========
In the following messages, the presence state is an integer enumeration of the
following states:
0 : OFFLINE
1 : BUSY
2 : ONLINE
3 : FREE_TO_CHAT
Aside from OFFLINE, the protocol doesn't assign any special meaning to these
states; they are provided as an approximate signal for users to give to other
users and for clients to present them in some way that may be useful. Clients
could have different behaviours for different states of the user's presence, for
example to decide how much prominence or sound to use for incoming event
notifications.
In the following messages, the presence state is a presence string as described in
the main specification document.
Getting/Setting your own presence state
---------------------------------------
REST Path: /presence/$user_id/status
Valid methods: GET/PUT
Required keys:
state : [0|1|2|3] - The user's new presence state
presence : <string> - The user's new presence state
Optional keys:
status_msg : text string provided by the user to explain their status
@@ -1039,7 +1042,7 @@ Fetching your presence list
following keys:
{
"user_id" : string giving the observed user's ID
"state" : int giving their status
"presence" : int giving their status
"status_msg" : optional text string
"displayname" : optional text string from the user's profile
"avatar_url" : optional text string from the user's profile

View File

@@ -1,9 +1,8 @@
TODO(kegan): Tweak joinalias API keys/path? Event stream historical > live needs
a token (currently doesn't). im/sync responses include outdated event formats
(room membership change messages). Room config (specifically: message history,
public rooms). /register seems super simplistic compared to /login, maybe it
would be better if /register used the same technique as /login? /register should
be "user" not "user_id".
.. TODO kegan
Room config (specifically: message history,
public rooms). /register seems super simplistic compared to /login, maybe it
would be better if /register used the same technique as /login? /register should
be "user" not "user_id".
How to use the client-server API
@@ -15,7 +14,7 @@ implementation, there may be variations in relation to registering/logging in
which are not covered in extensive detail in this guide.
If you haven't already, get a home server up and running on
``http://localhost:8080``.
``http://localhost:8008``.
Accounts
@@ -23,14 +22,16 @@ Accounts
Before you can send and receive messages, you must **register** for an account.
If you already have an account, you must **login** into it.
**Try out the fiddle: http://jsfiddle.net/jrf1h02d/**
`Try out the fiddle`__
.. __: http://jsfiddle.net/4q2jyxng/
Registration
------------
The aim of registration is to get a user ID and access token which you will need
when accessing other APIs::
curl -XPOST -d '{"user_id":"example", "password":"wordpass"}' "http://localhost:8080/matrix/client/api/v1/register"
curl -XPOST -d '{"user_id":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/register"
{
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
@@ -51,13 +52,17 @@ Login
-----
The aim when logging in is to get an access token for your existing user ID::
curl -XGET "http://localhost:8080/matrix/client/api/v1/login"
curl -XGET "http://localhost:8008/_matrix/client/api/v1/login"
{
"type": "m.login.password"
"flows": [
{
"type": "m.login.password"
}
]
}
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8080/matrix/client/api/v1/login"
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8008/_matrix/client/api/v1/login"
{
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd",
@@ -80,14 +85,16 @@ Communicating
In order to communicate with another user, you must **create a room** with that
user and **send a message** to that room.
**Try out the fiddle: http://jsfiddle.net/jnwqcshc/**
`Try out the fiddle`__
.. __: http://jsfiddle.net/zL3zto9g/
Creating a room
---------------
If you want to send a message to someone, you have to be in a room with them. To
create a room::
curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8080/matrix/client/api/v1/rooms?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
curl -XPOST -d '{"room_alias_name":"tutorial"}' "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token=YOUR_ACCESS_TOKEN"
{
"room_alias": "#tutorial:localhost",
@@ -98,20 +105,27 @@ The "room alias" is a human-readable string which can be shared with other users
so they can join a room, rather than the room ID which is a randomly generated
string. You can have multiple room aliases per room.
TODO(kegan): How to add/remove aliases from an existing room.
.. TODO(kegan)
How to add/remove aliases from an existing room.
Sending messages
----------------
You can now send messages to this room::
curl -XPUT -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8080/matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/messages/%40example%3Alocalhost/msgid1?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
curl -XPOST -d '{"msgtype":"m.text", "body":"hello"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/send/m.room.message?access_token=YOUR_ACCESS_TOKEN"
{
"event_id": "YUwRidLecu"
}
The event ID returned is a unique ID which identifies this message.
NB: There are no limitations to the types of messages which can be exchanged.
The only requirement is that ``"msgtype"`` is specified.
NB: Depending on the room config, users who join the room may be able to see
message history from before they joined.
The only requirement is that ``"msgtype"`` is specified. The Matrix
specification outlines the following standard types: ``m.text``, ``m.image``,
``m.audio``, ``m.video``, ``m.location``, ``m.emote``. See the specification for
more information on these types.
Users and rooms
===============
@@ -121,33 +135,34 @@ these rules may specify if you require an **invitation** from someone already in
the room in order to **join the room**. In addition, you may also be able to
join a room **via a room alias** if one was set up.
**Try out the fiddle: http://jsfiddle.net/og1xokcr/**
`Try out the fiddle`__
.. __: http://jsfiddle.net/7fhotf1b/
Inviting a user to a room
-------------------------
You can directly invite a user to a room like so::
curl -XPUT -d '{"membership":"invite"}' "http://localhost:8080/matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/members/%40myfriend%3Alocalhost/state?access_token=QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd"
curl -XPOST -d '{"user_id":"@myfriend:localhost"}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/invite?access_token=YOUR_ACCESS_TOKEN"
This informs ``@myfriend:localhost`` of the room ID
``!CvcvRuDYDzTOzfKKgh:localhost`` and allows them to join the room.
Joining a room via an invite
----------------------------
If you receive an invite, you can join the room by changing the membership to
join::
If you receive an invite, you can join the room::
curl -XPUT -d '{"membership":"join"}' "http://localhost:8080/matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/members/%40myfriend%3Alocalhost/state?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh%3Alocalhost/join?access_token=YOUR_ACCESS_TOKEN"
NB: Only the person invited (``@myfriend:localhost``) can change the membership
state to ``"join"``.
state to ``"join"``. Repeatedly joining a room does nothing.
Joining a room via an alias
---------------------------
Alternatively, if you know the room alias for this room and the room config
allows it, you can directly join a room via the alias::
curl -XPUT -d '{}' "http://localhost:8080/matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
curl -XPOST -d '{}' "http://localhost:8008/_matrix/client/api/v1/join/%23tutorial%3Alocalhost?access_token=YOUR_ACCESS_TOKEN"
{
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
@@ -166,128 +181,444 @@ An event is some interesting piece of data that a client may be interested in.
It can be a message in a room, a room invite, etc. There are many different ways
of getting events, depending on what the client already knows.
**Try out the fiddle: http://jsfiddle.net/5uk4dqe2/**
`Try out the fiddle`__
.. __: http://jsfiddle.net/vw11mg37/
Getting all state
-----------------
If the client doesn't know any information on the rooms the user is
invited/joined on, they can get all the user's state for all rooms::
curl -XGET "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=YOUR_ACCESS_TOKEN"
[
{
"membership": "join",
"messages": {
"chunk": [
{
"content": {
"body": "@example:localhost joined the room.",
"hsob_ts": 1408444664249,
{
"end": "s39_18_0",
"presence": [
{
"content": {
"last_active_ago": 1061436,
"user_id": "@example:localhost"
},
"type": "m.presence"
}
],
"rooms": [
{
"membership": "join",
"messages": {
"chunk": [
{
"content": {
"@example:localhost": 10,
"default": 0
},
"event_id": "wAumPSTsWF",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.power_levels",
"user_id": "@example:localhost"
},
{
"content": {
"join_rule": "public"
},
"event_id": "jrLVqKHKiI",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.join_rules",
"user_id": "@example:localhost"
},
{
"content": {
"level": 10
},
"event_id": "WpmTgsNWUZ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.add_state_level",
"user_id": "@example:localhost"
},
{
"content": {
"level": 0
},
"event_id": "qUMBJyKsTQ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.send_event_level",
"user_id": "@example:localhost"
},
{
"content": {
"ban_level": 5,
"kick_level": 5
},
"event_id": "YAaDmKvoUW",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.ops_levels",
"user_id": "@example:localhost"
},
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join"
},
"event_id": "RJbPMtCutf",
"membership": "join",
"membership_source": "@example:localhost",
"membership_target": "@example:localhost",
"msgtype": "m.text"
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409665586730,
"type": "m.room.member",
"user_id": "@example:localhost"
},
"event_id": "lZjmmlrEvo",
"msg_id": "m1408444664249",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"user_id": "_homeserver_"
},
{
"content": {
"body": "hello",
"hsob_ts": 1409665660439,
"msgtype": "m.text"
},
"event_id": "YUwRidLecu",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"ts": 1409665660439,
"type": "m.room.message",
"user_id": "@example:localhost"
},
{
"content": {
"membership": "invite"
},
"event_id": "YjNuBKnPsb",
"membership": "invite",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@myfriend:localhost",
"ts": 1409666426819,
"type": "m.room.member",
"user_id": "@example:localhost"
},
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join",
"prev": "join"
},
"event_id": "KWwdDjNZnm",
"membership": "join",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409666551582,
"type": "m.room.member",
"user_id": "@example:localhost"
},
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join"
},
"event_id": "JFLVteSvQc",
"membership": "join",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409666587265,
"type": "m.room.member",
"user_id": "@example:localhost"
}
],
"end": "s39_18_0",
"start": "t1-11_18_0"
},
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state": [
{
"content": {
"body": "hello",
"hsob_ts": 1408445405672,
"msgtype": "m.text"
"creator": "@example:localhost"
},
"event_id": "BiBJqamISg",
"msg_id": "msgid1",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"event_id": "dMUoqVTZca",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.create",
"user_id": "@example:localhost"
},
[...]
{
"content": {
"body": "@myfriend:localhost joined the room.",
"hsob_ts": 1408446501661,
"membership": "join",
"membership_source": "@myfriend:localhost",
"membership_target": "@myfriend:localhost",
"msgtype": "m.text"
"@example:localhost": 10,
"default": 0
},
"event_id": "IMmXbOzFAa",
"msg_id": "m1408446501661",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"user_id": "_homeserver_"
"event_id": "wAumPSTsWF",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.power_levels",
"user_id": "@example:localhost"
},
{
"content": {
"join_rule": "public"
},
"event_id": "jrLVqKHKiI",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.join_rules",
"user_id": "@example:localhost"
},
{
"content": {
"level": 10
},
"event_id": "WpmTgsNWUZ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.add_state_level",
"user_id": "@example:localhost"
},
{
"content": {
"level": 0
},
"event_id": "qUMBJyKsTQ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.send_event_level",
"user_id": "@example:localhost"
},
{
"content": {
"ban_level": 5,
"kick_level": 5
},
"event_id": "YAaDmKvoUW",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.ops_levels",
"user_id": "@example:localhost"
},
{
"content": {
"membership": "invite"
},
"event_id": "YjNuBKnPsb",
"membership": "invite",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@myfriend:localhost",
"ts": 1409666426819,
"type": "m.room.member",
"user_id": "@example:localhost"
},
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join"
},
"event_id": "JFLVteSvQc",
"membership": "join",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409666587265,
"type": "m.room.member",
"user_id": "@example:localhost"
}
],
"end": "20",
"start": "0"
},
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
}
]
]
}
]
}
This returns all the room IDs of rooms the user is invited/joined on, as well as
all of the messages and feedback for these rooms. This can be a LOT of data. You
may just want the most recent message for each room. This can be achieved by
applying pagination stream parameters to this request::
This returns all the room information the user is invited/joined on, as well as
all of the presences relevant for these rooms. This can be a LOT of data. You
may just want the most recent event for each room. This can be achieved by
applying query parameters to ``limit`` this request::
curl -XGET "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK&from=END&to=START&limit=1"
curl -XGET "http://localhost:8008/_matrix/client/api/v1/initialSync?limit=1&access_token=YOUR_ACCESS_TOKEN"
[
{
"membership": "join",
"messages": {
"chunk": [
{
"end": "s39_18_0",
"presence": [
{
"content": {
"last_active_ago": 1279484,
"user_id": "@example:localhost"
},
"type": "m.presence"
}
],
"rooms": [
{
"membership": "join",
"messages": {
"chunk": [
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join"
},
"event_id": "JFLVteSvQc",
"membership": "join",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409666587265,
"type": "m.room.member",
"user_id": "@example:localhost"
}
],
"end": "s39_18_0",
"start": "t10-30_18_0"
},
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state": [
{
"content": {
"body": "@myfriend:localhost joined the room.",
"hsob_ts": 1408446501661,
"membership": "join",
"membership_source": "@myfriend:localhost",
"membership_target": "@myfriend:localhost",
"msgtype": "m.text"
"creator": "@example:localhost"
},
"event_id": "IMmXbOzFAa",
"msg_id": "m1408446501661",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"user_id": "_homeserver_"
"event_id": "dMUoqVTZca",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.create",
"user_id": "@example:localhost"
},
{
"content": {
"@example:localhost": 10,
"default": 0
},
"event_id": "wAumPSTsWF",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.power_levels",
"user_id": "@example:localhost"
},
{
"content": {
"join_rule": "public"
},
"event_id": "jrLVqKHKiI",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.join_rules",
"user_id": "@example:localhost"
},
{
"content": {
"level": 10
},
"event_id": "WpmTgsNWUZ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.add_state_level",
"user_id": "@example:localhost"
},
{
"content": {
"level": 0
},
"event_id": "qUMBJyKsTQ",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.send_event_level",
"user_id": "@example:localhost"
},
{
"content": {
"ban_level": 5,
"kick_level": 5
},
"event_id": "YAaDmKvoUW",
"required_power_level": 10,
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "",
"ts": 1409665585188,
"type": "m.room.ops_levels",
"user_id": "@example:localhost"
},
{
"content": {
"membership": "invite"
},
"event_id": "YjNuBKnPsb",
"membership": "invite",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@myfriend:localhost",
"ts": 1409666426819,
"type": "m.room.member",
"user_id": "@example:localhost"
},
{
"content": {
"avatar_url": null,
"displayname": null,
"membership": "join"
},
"event_id": "JFLVteSvQc",
"membership": "join",
"room_id": "!MkDbyRqnvTYnoxjLYx:localhost",
"state_key": "@example:localhost",
"ts": 1409666587265,
"type": "m.room.member",
"user_id": "@example:localhost"
}
],
"end": "20",
"start": "21"
},
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
}
]
]
}
]
}
Getting live state
------------------
Once you know which rooms the client has previously interacted with, you need to
listen for incoming events. This can be done like so::
curl -XGET "http://localhost:8080/matrix/client/api/v1/events?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK&from=END"
curl -XGET "http://localhost:8008/_matrix/client/api/v1/events?access_token=YOUR_ACCESS_TOKEN"
{
"chunk": [],
"end": "215",
"start": "215"
"end": "s39_18_0",
"start": "s39_18_0"
}
This will block waiting for an incoming event, timing out after several seconds.
Even if there are no new events (as in the example above), there will be some
pagination stream response keys. The client should make subsequent requests
using the value of the ``"end"`` key (in this case ``215``) as the ``from``
query parameter. This value should be stored so when the client reopens your app
after a period of inactivity, you can resume from where you got up to in the
event stream. If it has been a long period of inactivity, there may be LOTS of
events waiting for the user. In this case, you may wish to get all state instead
and then resume getting live state from a newer end token.
using the value of the ``"end"`` key (in this case ``s39_18_0``) as the ``from``
query parameter e.g. ``http://localhost:8008/_matrix/client/api/v1/events?access
_token=YOUR_ACCESS_TOKEN&from=s39_18_0``. This value should be stored so when the
client reopens your app after a period of inactivity, you can resume from where
you got up to in the event stream. If it has been a long period of inactivity,
there may be LOTS of events waiting for the user. In this case, you may wish to
get all state instead and then resume getting live state from a newer end token.
NB: The timeout can be changed by adding a ``timeout`` query parameter, which is
in milliseconds. A timeout of 0 will not block.
@@ -300,4 +631,6 @@ creating and joining rooms, sending messages, getting member lists and getting
historical messages for a room. This covers most functionality of a messaging
application.
**Try out the fiddle: http://jsfiddle.net/L8r3o1wr/**
`Try out the fiddle`__
.. __: http://jsfiddle.net/uztL3yme/

View File

@@ -3,31 +3,31 @@
"swaggerVersion": "1.2",
"apis": [
{
"path": "/login",
"path": "-login",
"description": "Login operations"
},
{
"path": "/registration",
"path": "-registration",
"description": "Registration operations"
},
{
"path": "/rooms",
"path": "-rooms",
"description": "Room operations"
},
{
"path": "/profile",
"path": "-profile",
"description": "Profile operations"
},
{
"path": "/presence",
"path": "-presence",
"description": "Presence operations"
},
{
"path": "/events",
"path": "-events",
"description": "Event operations"
},
{
"path": "/directory",
"path": "-directory",
"description": "Directory operations"
}
],

View File

@@ -1,7 +1,7 @@
{
"apiVersion": "1.0.0",
"swaggerVersion": "1.2",
"basePath": "http://localhost:8080/matrix/client/api/v1",
"basePath": "http://localhost:8008/_matrix/client/api/v1",
"resourcePath": "/directory",
"produces": [
"application/json"
@@ -13,6 +13,7 @@
{
"method": "GET",
"summary": "Get the room ID corresponding to this room alias.",
"notes": "Volatile: This API is likely to change.",
"type": "DirectoryResponse",
"nickname": "get_room_id_for_alias",
"parameters": [
@@ -28,6 +29,7 @@
{
"method": "PUT",
"summary": "Create a new mapping from room alias to room ID.",
"notes": "Volatile: This API is likely to change.",
"type": "void",
"nickname": "add_room_alias",
"parameters": [

View File

@@ -1,7 +1,7 @@
{
"apiVersion": "1.0.0",
"swaggerVersion": "1.2",
"basePath": "http://localhost:8080/matrix/client/api/v1",
"basePath": "http://localhost:8008/_matrix/client/api/v1",
"resourcePath": "/events",
"produces": [
"application/json"
@@ -15,25 +15,26 @@
"summary": "Listen on the event stream",
"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": "PaginationChunk",
"nickname": "get_event_stream"
}
],
"parameters": [
{
"name": "from",
"description": "The token to stream from.",
"required": false,
"type": "string",
"paramType": "query"
},
{
"name": "timeout",
"description": "The maximum time in milliseconds to wait for an event.",
"required": false,
"type": "integer",
"paramType": "query"
"nickname": "get_event_stream",
"parameters": [
{
"name": "from",
"description": "The token to stream from.",
"required": false,
"type": "string",
"paramType": "query"
},
{
"name": "timeout",
"description": "The maximum time in milliseconds to wait for an event.",
"required": false,
"type": "integer",
"paramType": "query"
}
]
}
],
"responseMessages": [
{
"code": 400,

View File

@@ -8,7 +8,7 @@
"nickname": "get_login_info",
"notes": "All login stages MUST be mentioned if there is >1 login type.",
"summary": "Get the login mechanism to use when logging in.",
"type": "LoginInfo"
"type": "LoginFlows"
},
{
"method": "POST",
@@ -40,17 +40,31 @@
"path": "/login"
}
],
"basePath": "http://localhost:8080/matrix/client/api/v1",
"basePath": "http://localhost:8008/_matrix/client/api/v1",
"consumes": [
"application/json"
],
"models": {
"LoginFlows": {
"id": "LoginFlows",
"properties": {
"flows": {
"description": "A list of valid login flows.",
"type": "array",
"items": {
"$ref": "LoginInfo"
}
}
}
},
"LoginInfo": {
"id": "LoginInfo",
"properties": {
"stages": {
"description": "Multi-stage login only: An array of all the login types required to login.",
"format": "string",
"items": {
"$ref": "string"
},
"type": "array"
},
"type": {
@@ -65,6 +79,10 @@
"access_token": {
"description": "The access token for this user's login if this is the final stage of the login process.",
"type": "string"
},
"user_id": {
"description": "The user's fully-qualified user ID.",
"type": "string"
},
"next": {
"description": "Multi-stage login only: The next login type to submit.",

View File

@@ -1,7 +1,7 @@
{
"apiVersion": "1.0.0",
"swaggerVersion": "1.2",
"basePath": "http://localhost:8080/matrix/client/api/v1",
"basePath": "http://localhost:8008/_matrix/client/api/v1",
"resourcePath": "/presence",
"produces": [
"application/json"
@@ -106,7 +106,7 @@
"PresenceUpdate": {
"id": "PresenceUpdate",
"properties": {
"state": {
"presence": {
"type": "string",
"description": "Enum: The presence state.",
"enum": [
@@ -128,10 +128,10 @@
"Presence": {
"id": "Presence",
"properties": {
"mtime_age": {
"last_active_ago": {
"type": "integer",
"format": "int64",
"description": "The last time this user's presence state changed, in milliseconds."
"description": "The last time this user performed an action on their home server."
},
"user_id": {
"type": "string",

View File

@@ -1,7 +1,7 @@
{
"apiVersion": "1.0.0",
"swaggerVersion": "1.2",
"basePath": "http://localhost:8080/matrix/client/api/v1",
"basePath": "http://localhost:8008/_matrix/client/api/v1",
"resourcePath": "/profile",
"produces": [
"application/json"

View File

@@ -37,7 +37,7 @@
"path": "/register"
}
],
"basePath": "http://localhost:8080/matrix/client/api/v1",
"basePath": "http://localhost:8008/_matrix/client/api/v1",
"consumes": [
"application/json"
],
@@ -52,6 +52,10 @@
"user_id": {
"description": "The fully-qualified user ID.",
"type": "string"
},
"home_server": {
"description": "The name of the home server.",
"type": "string"
}
}
},

View File

@@ -1,7 +1,7 @@
{
"apiVersion": "1.0.0",
"swaggerVersion": "1.2",
"basePath": "http://localhost:8080/matrix/client/api/v1",
"basePath": "http://localhost:8008/_matrix/client/api/v1",
"resourcePath": "/rooms",
"produces": [
"application/json"
@@ -14,12 +14,12 @@
},
"apis": [
{
"path": "/rooms/{roomId}/send/{eventType}/{txnId}",
"path": "/rooms/{roomId}/send/{eventType}",
"operations": [
{
"method": "PUT",
"method": "POST",
"summary": "Send a generic non-state event to this room.",
"notes": "This operation can also be done as a POST to /rooms/{roomId}/send/{eventType}",
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
"type": "EventId",
"nickname": "send_non_state_event",
"consumes": [
@@ -46,13 +46,6 @@
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "txnId",
"description": "A client transaction ID to ensure idempotency. This can only be omitted if the HTTP method becomes a POST.",
"required": true,
"type": "string",
"paramType": "path"
}
]
}
@@ -104,12 +97,12 @@
]
},
{
"path": "/rooms/{roomId}/send/m.room.message/{txnId}",
"path": "/rooms/{roomId}/send/m.room.message",
"operations": [
{
"method": "PUT",
"method": "POST",
"summary": "Send a message in this room.",
"notes": "This operation can also be done as a POST to /rooms/{roomId}/send/m.room.message",
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
"type": "EventId",
"nickname": "send_message",
"consumes": [
@@ -129,13 +122,6 @@
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "txnId",
"description": "A client transaction ID to ensure idempotency. This can only be omitted if the HTTP method becomes a POST.",
"required": true,
"type": "string",
"paramType": "path"
}
]
}
@@ -195,12 +181,65 @@
]
},
{
"path": "/rooms/{roomId}/send/m.room.message.feedback/{txnId}",
"path": "/rooms/{roomId}/state/m.room.name",
"operations": [
{
"method": "PUT",
"summary": "Set the name of this room.",
"notes": "Set the name of this room.",
"type": "void",
"nickname": "set_room_name",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "body",
"description": "The name contents",
"required": true,
"type": "RoomName",
"paramType": "body"
},
{
"name": "roomId",
"description": "The room to set the name of.",
"required": true,
"type": "string",
"paramType": "path"
}
]
},
{
"method": "GET",
"summary": "Get the room's name.",
"notes": "",
"type": "RoomName",
"nickname": "get_room_name",
"parameters": [
{
"name": "roomId",
"description": "The room to get the name of.",
"required": true,
"type": "string",
"paramType": "path"
}
],
"responseMessages": [
{
"code": 404,
"message": "Name not found."
}
]
}
]
},
{
"path": "/rooms/{roomId}/send/m.room.message.feedback",
"operations": [
{
"method": "POST",
"summary": "Send feedback to a message.",
"notes": "This operation can also be done as a POST to /rooms/{roomId}/send/m.room.message.feedback",
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
"type": "EventId",
"nickname": "send_feedback",
"consumes": [
@@ -220,13 +259,6 @@
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "txnId",
"description": "A client transaction ID to ensure idempotency. This can only be omitted if the HTTP method becomes a POST.",
"required": true,
"type": "string",
"paramType": "path"
}
],
"responseMessages": [
@@ -239,12 +271,12 @@
]
},
{
"path": "/rooms/{roomId}/invite/{txnId}",
"path": "/rooms/{roomId}/invite",
"operations": [
{
"method": "PUT",
"method": "POST",
"summary": "Invite a user to this room.",
"notes": "This operation can also be done as a POST to /rooms/{roomId}/invite",
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
"type": "void",
"nickname": "invite",
"consumes": [
@@ -258,13 +290,6 @@
"type": "string",
"paramType": "path"
},
{
"name": "txnId",
"description": "A client transaction ID for this PUT to ensure idempotency. This can only be omitted if the HTTP method becomes a POST. ",
"required": false,
"type": "string",
"paramType": "path"
},
{
"name": "body",
"description": "The user to invite.",
@@ -277,12 +302,12 @@
]
},
{
"path": "/rooms/{roomId}/join/{txnId}",
"path": "/rooms/{roomId}/join",
"operations": [
{
"method": "PUT",
"method": "POST",
"summary": "Join this room.",
"notes": "This operation can also be done as a POST to /rooms/{roomId}/join",
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
"type": "void",
"nickname": "join_room",
"consumes": [
@@ -297,23 +322,22 @@
"paramType": "path"
},
{
"name": "txnId",
"description": "A client transaction ID for this PUT to ensure idempotency. This can only be omitted if the HTTP method becomes a POST. ",
"required": false,
"type": "string",
"paramType": "path"
"name": "body",
"required": true,
"type": "JoinRequest",
"paramType": "body"
}
]
}
]
},
{
"path": "/rooms/{roomId}/leave/{txnId}",
"path": "/rooms/{roomId}/leave",
"operations": [
{
"method": "PUT",
"method": "POST",
"summary": "Leave this room.",
"notes": "This operation can also be done as a POST to /rooms/{roomId}/leave",
"notes": "This operation can also be done as a PUT by suffixing /{txnId}.",
"type": "void",
"nickname": "leave",
"consumes": [
@@ -328,11 +352,41 @@
"paramType": "path"
},
{
"name": "txnId",
"description": "A client transaction ID for this PUT to ensure idempotency. This can only be omitted if the HTTP method becomes a POST. ",
"required": false,
"name": "body",
"required": true,
"type": "LeaveRequest",
"paramType": "body"
}
]
}
]
},
{
"path": "/rooms/{roomId}/ban",
"operations": [
{
"method": "POST",
"summary": "Ban a user in the room.",
"notes": "This operation can also be done as a PUT by suffixing /{txnId}. The caller must have the required power level to do this operation.",
"type": "void",
"nickname": "ban",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "roomId",
"description": "The room which has the user to ban.",
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "body",
"description": "The user to ban.",
"required": true,
"type": "BanRequest",
"paramType": "body"
}
]
}
@@ -435,10 +489,10 @@
"path": "/join/{roomAliasOrId}",
"operations": [
{
"method": "PUT",
"method": "POST",
"summary": "Join a room via a room alias or room ID.",
"notes": "Join a room via a room alias or room ID.",
"type": "RoomInfo",
"type": "JoinRoomInfo",
"nickname": "join",
"consumes": [
"application/json"
@@ -476,7 +530,7 @@
"parameters": [
{
"name": "body",
"description": "The desired configuration for the room.",
"description": "The desired configuration for the room. This operation can also be done as a PUT by suffixing /{txnId}.",
"required": true,
"type": "RoomConfig",
"paramType": "body"
@@ -585,7 +639,7 @@
{
"method": "GET",
"summary": "Get a list of all the current state events for this room.",
"notes": "Get a list of all the current state events for this room.",
"notes": "NOT YET IMPLEMENTED.",
"type": "array",
"items": {
"$ref": "Event"
@@ -609,7 +663,7 @@
{
"method": "GET",
"summary": "Get all the current information for this room, including messages and state events.",
"notes": "Get all the current information for this room, including messages and state events.",
"notes": "NOT YET IMPLEMENTED.",
"type": "InitialSyncRoomData",
"nickname": "get_room_sync_data",
"parameters": [
@@ -635,6 +689,15 @@
}
}
},
"RoomName": {
"id": "RoomName",
"properties": {
"name": {
"type": "string",
"description": "The human-readable name for the room. Can contain spaces."
}
}
},
"Message": {
"id": "Message",
"properties": {
@@ -651,6 +714,16 @@
"Feedback": {
"id": "Feedback",
"properties": {
"target_event_id": {
"type": "string",
"description": "The event ID being acknowledged.",
"required": true
},
"type": {
"type": "string",
"description": "The type of feedback. Either 'delivered' or 'read'.",
"required": true
}
}
},
"Member": {
@@ -663,7 +736,7 @@
"invite",
"join",
"leave",
"knock"
"ban"
]
}
}
@@ -683,6 +756,16 @@
}
}
},
"JoinRoomInfo": {
"id": "JoinRoomInfo",
"properties": {
"room_id": {
"type": "string",
"description": "The room ID joined, if joined via a room alias only.",
"required": true
}
}
},
"RoomConfig": {
"id": "RoomConfig",
"properties": {
@@ -699,6 +782,16 @@
"type": "string",
"description": "The alias to give the new room.",
"required": false
},
"name": {
"type": "string",
"description": "Sets the name of the room. Send a m.room.name event after creating the room with the 'name' key specified.",
"required": false
},
"topic": {
"type": "string",
"description": "Sets the topic for the room. Send a m.room.topic event after creating the room with the 'topic' key specified.",
"required": false
}
}
},
@@ -831,6 +924,27 @@
}
}
},
"JoinRequest": {
"id": "JoinRequest",
"properties": {}
},
"LeaveRequest": {
"id": "LeaveRequest",
"properties": {}
},
"BanRequest": {
"id": "BanRequest",
"properties": {
"user_id": {
"type": "string",
"description": "The fully-qualified user ID."
},
"reason": {
"type": "string",
"description": "The reason for the ban."
}
}
},
"InitialSyncRoomData": {
"id": "InitialSyncRoomData",
"properties": {

View File

@@ -0,0 +1,5 @@
To get this running:
ln -s ../swagger_matrix
python -m SimpleHTTPServer
Go to http://localhost:8000/swagger.html

View File

@@ -0,0 +1,38 @@
// Backbone.js 0.9.2
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
(function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=
{});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=
z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=
{};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==
b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:
b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};
a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,
h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();
return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
{};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||
!this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);
this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c<d;c++){if(!(e=a[c]=this._prepareModel(a[c],b)))throw Error("Can't add an invalid model to a collection");g=e.cid;i=e.id;j[g]||this._byCid[g]||null!=i&&(k[i]||this._byId[i])?
l.push(c):j[g]=k[i]=e}for(c=l.length;c--;)a.splice(l[c],1);c=0;for(d=a.length;c<d;c++)(e=a[c]).on("all",this._onModelEvent,this),this._byCid[e.cid]=e,null!=e.id&&(this._byId[e.id]=e);this.length+=d;A.apply(this.models,[null!=b.at?b.at:this.models.length,0].concat(a));this.comparator&&this.sort({silent:!0});if(b.silent)return this;c=0;for(d=this.models.length;c<d;c++)if(j[(e=this.models[c]).cid])b.index=c,e.trigger("add",e,this,b);return this},remove:function(a,b){var c,d,e,g;b||(b={});a=f.isArray(a)?
a.slice():[a];c=0;for(d=a.length;c<d;c++)if(g=this.getByCid(a[c])||this.get(a[c]))delete this._byId[g.id],delete this._byCid[g.cid],e=this.indexOf(g),this.models.splice(e,1),this.length--,b.silent||(b.index=e,g.trigger("remove",g,this,b)),this._removeReference(g);return this},push:function(a,b){a=this._prepareModel(a,b);this.add(a,b);return a},pop:function(a){var b=this.at(this.length-1);this.remove(b,a);return b},unshift:function(a,b){a=this._prepareModel(a,b);this.add(a,f.extend({at:0},b));return a},
shift:function(a){var b=this.at(0);this.remove(b,a);return b},get:function(a){return null==a?void 0:this._byId[null!=a.id?a.id:a]},getByCid:function(a){return a&&this._byCid[a.cid||a]},at:function(a){return this.models[a]},where:function(a){return f.isEmpty(a)?[]:this.filter(function(b){for(var c in a)if(a[c]!==b.get(c))return!1;return!0})},sort:function(a){a||(a={});if(!this.comparator)throw Error("Cannot sort a set without a comparator");var b=f.bind(this.comparator,this);1==this.comparator.length?
this.models=this.sortBy(b):this.models.sort(b);a.silent||this.trigger("reset",this,a);return this},pluck:function(a){return f.map(this.models,function(b){return b.get(a)})},reset:function(a,b){a||(a=[]);b||(b={});for(var c=0,d=this.models.length;c<d;c++)this._removeReference(this.models[c]);this._reset();this.add(a,f.extend({silent:!0},b));b.silent||this.trigger("reset",this,b);return this},fetch:function(a){a=a?f.clone(a):{};void 0===a.parse&&(a.parse=!0);var b=this,c=a.success;a.success=function(d,
e,f){b[a.add?"add":"reset"](b.parse(d,f),a);c&&c(b,d)};a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},create:function(a,b){var c=this,b=b?f.clone(b):{},a=this._prepareModel(a,b);if(!a)return!1;b.wait||c.add(a,b);var d=b.success;b.success=function(e,f){b.wait&&c.add(e,b);d?d(e,f):e.trigger("sync",a,f,b)};a.save(null,b);return a},parse:function(a){return a},chain:function(){return f(this.models).chain()},_reset:function(){this.length=0;this.models=[];this._byId=
{};this._byCid={}},_prepareModel:function(a,b){b||(b={});a instanceof o?a.collection||(a.collection=this):(b.collection=this,a=new this.model(a,b),a._validate(a.attributes,b)||(a=!1));return a},_removeReference:function(a){this==a.collection&&delete a.collection;a.off("all",this._onModelEvent,this)},_onModelEvent:function(a,b,c,d){("add"==a||"remove"==a)&&c!=this||("destroy"==a&&this.remove(b,d),b&&a==="change:"+b.idAttribute&&(delete this._byId[b.previous(b.idAttribute)],this._byId[b.id]=b),this.trigger.apply(this,
arguments))}});f.each("forEach,each,map,reduce,reduceRight,find,detect,filter,select,reject,every,all,some,any,include,contains,invoke,max,min,sortBy,sortedIndex,toArray,size,first,initial,rest,last,without,indexOf,shuffle,lastIndexOf,isEmpty,groupBy".split(","),function(a){r.prototype[a]=function(){return f[a].apply(f,[this.models].concat(f.toArray(arguments)))}});var u=g.Router=function(a){a||(a={});a.routes&&(this.routes=a.routes);this._bindRoutes();this.initialize.apply(this,arguments)},B=/:\w+/g,
C=/\*\w+/g,D=/[-[\]{}()+?.,\\^$|#\s]/g;f.extend(u.prototype,k,{initialize:function(){},route:function(a,b,c){g.history||(g.history=new m);f.isRegExp(a)||(a=this._routeToRegExp(a));c||(c=this[b]);g.history.route(a,f.bind(function(d){d=this._extractParameters(a,d);c&&c.apply(this,d);this.trigger.apply(this,["route:"+b].concat(d));g.history.trigger("route",this,b,d)},this));return this},navigate:function(a,b){g.history.navigate(a,b)},_bindRoutes:function(){if(this.routes){var a=[],b;for(b in this.routes)a.unshift([b,
this.routes[b]]);b=0;for(var c=a.length;b<c;b++)this.route(a[b][0],a[b][1],this[a[b][1]])}},_routeToRegExp:function(a){a=a.replace(D,"\\$&").replace(B,"([^/]+)").replace(C,"(.*?)");return RegExp("^"+a+"$")},_extractParameters:function(a,b){return a.exec(b).slice(1)}});var m=g.History=function(){this.handlers=[];f.bindAll(this,"checkUrl")},s=/^[#\/]/,E=/msie [\w.]+/;m.started=!1;f.extend(m.prototype,k,{interval:50,getHash:function(a){return(a=(a?a.location:window.location).href.match(/#(.*)$/))?a[1]:
""},getFragment:function(a,b){if(null==a)if(this._hasPushState||b){var a=window.location.pathname,c=window.location.search;c&&(a+=c)}else a=this.getHash();a.indexOf(this.options.root)||(a=a.substr(this.options.root.length));return a.replace(s,"")},start:function(a){if(m.started)throw Error("Backbone.history has already been started");m.started=!0;this.options=f.extend({},{root:"/"},this.options,a);this._wantsHashChange=!1!==this.options.hashChange;this._wantsPushState=!!this.options.pushState;this._hasPushState=
!(!this.options.pushState||!window.history||!window.history.pushState);var a=this.getFragment(),b=document.documentMode;if(b=E.exec(navigator.userAgent.toLowerCase())&&(!b||7>=b))this.iframe=i('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b<c;b++){var d=w[b];a[d]&&(this[d]=a[d])}this.options=a},_ensureElement:function(){if(this.el)this.setElement(this.el,
!1);else{var a=n(this,"attributes")||{};this.id&&(a.id=this.id);this.className&&(a["class"]=this.className);this.setElement(this.make(this.tagName,a),!1)}}});o.extend=r.extend=u.extend=v.extend=function(a,b){var c=G(this,a,b);c.extend=this.extend;return c};var H={create:"POST",update:"PUT","delete":"DELETE",read:"GET"};g.sync=function(a,b,c){var d=H[a];c||(c={});var e={type:d,dataType:"json"};c.url||(e.url=n(b,"url")||t());if(!c.data&&b&&("create"==a||"update"==a))e.contentType="application/json",
e.data=JSON.stringify(b.toJSON());g.emulateJSON&&(e.contentType="application/x-www-form-urlencoded",e.data=e.data?{model:e.data}:{});if(g.emulateHTTP&&("PUT"===d||"DELETE"===d))g.emulateJSON&&(e.data._method=d),e.type="POST",e.beforeSend=function(a){a.setRequestHeader("X-HTTP-Method-Override",d)};"GET"!==e.type&&!g.emulateJSON&&(e.processData=!1);return i.ajax(f.extend(e,c))};g.wrapError=function(a,b,c){return function(d,e){e=d===b?e:d;a?a(b,e,c):b.trigger("error",b,e,c)}};var x=function(){},G=function(a,
b,c){var d;d=b&&b.hasOwnProperty("constructor")?b.constructor:function(){a.apply(this,arguments)};f.extend(d,a);x.prototype=a.prototype;d.prototype=new x;b&&f.extend(d.prototype,b);c&&f.extend(d,c);d.prototype.constructor=d;d.__super__=a.prototype;return d},n=function(a,b){return!a||!a[b]?null:f.isFunction(a[b])?a[b]():a[b]},t=function(){throw Error('A "url" property or function must be specified');}}).call(this);

View File

@@ -0,0 +1,16 @@
/* latin */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 400;
src: local('Droid Sans'), local('DroidSans'), url(http://fonts.gstatic.com/s/droidsans/v5/s-BiyweUPV0v-yRb-cjciPk_vArhqVIZ0nv9q090hN8.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}
/* latin */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 700;
src: local('Droid Sans Bold'), local('DroidSans-Bold'), url(http://fonts.gstatic.com/s/droidsans/v5/EFpQQyG9GqCrobXxL-KRMYWiMMZ7xLd792ULpGE4W_Y.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
/*
* jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010
* http://benalman.com/projects/jquery-bbq-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,p){var i,m=Array.prototype.slice,r=decodeURIComponent,a=$.param,c,l,v,b=$.bbq=$.bbq||{},q,u,j,e=$.event.special,d="hashchange",A="querystring",D="fragment",y="elemUrlAttr",g="location",k="href",t="src",x=/^.*\?|#.*$/g,w=/^.*\#/,h,C={};function E(F){return typeof F==="string"}function B(G){var F=m.call(arguments,1);return function(){return G.apply(this,F.concat(m.call(arguments)))}}function n(F){return F.replace(/^[^#]*#?(.*)$/,"$1")}function o(F){return F.replace(/(?:^[^?#]*\?([^#]*).*$)?.*/,"$1")}function f(H,M,F,I,G){var O,L,K,N,J;if(I!==i){K=F.match(H?/^([^#]*)\#?(.*)$/:/^([^#?]*)\??([^#]*)(#?.*)/);J=K[3]||"";if(G===2&&E(I)){L=I.replace(H?w:x,"")}else{N=l(K[2]);I=E(I)?l[H?D:A](I):I;L=G===2?I:G===1?$.extend({},I,N):$.extend({},N,I);L=a(L);if(H){L=L.replace(h,r)}}O=K[1]+(H?"#":L||!K[1]?"?":"")+L+J}else{O=M(F!==i?F:p[g][k])}return O}a[A]=B(f,0,o);a[D]=c=B(f,1,n);c.noEscape=function(G){G=G||"";var F=$.map(G.split(""),encodeURIComponent);h=new RegExp(F.join("|"),"g")};c.noEscape(",/");$.deparam=l=function(I,F){var H={},G={"true":!0,"false":!1,"null":null};$.each(I.replace(/\+/g," ").split("&"),function(L,Q){var K=Q.split("="),P=r(K[0]),J,O=H,M=0,R=P.split("]["),N=R.length-1;if(/\[/.test(R[0])&&/\]$/.test(R[N])){R[N]=R[N].replace(/\]$/,"");R=R.shift().split("[").concat(R);N=R.length-1}else{N=0}if(K.length===2){J=r(K[1]);if(F){J=J&&!isNaN(J)?+J:J==="undefined"?i:G[J]!==i?G[J]:J}if(N){for(;M<=N;M++){P=R[M]===""?O.length:R[M];O=O[P]=M<N?O[P]||(R[M+1]&&isNaN(R[M+1])?{}:[]):J}}else{if($.isArray(H[P])){H[P].push(J)}else{if(H[P]!==i){H[P]=[H[P],J]}else{H[P]=J}}}}else{if(P){H[P]=F?i:""}}});return H};function z(H,F,G){if(F===i||typeof F==="boolean"){G=F;F=a[H?D:A]()}else{F=E(F)?F.replace(H?w:x,""):F}return l(F,G)}l[A]=B(z,0);l[D]=v=B(z,1);$[y]||($[y]=function(F){return $.extend(C,F)})({a:k,base:k,iframe:t,img:t,input:t,form:"action",link:k,script:t});j=$[y];function s(I,G,H,F){if(!E(H)&&typeof H!=="object"){F=H;H=G;G=i}return this.each(function(){var L=$(this),J=G||j()[(this.nodeName||"").toLowerCase()]||"",K=J&&L.attr(J)||"";L.attr(J,a[I](K,H,F))})}$.fn[A]=B(s,A);$.fn[D]=B(s,D);b.pushState=q=function(I,F){if(E(I)&&/^#/.test(I)&&F===i){F=2}var H=I!==i,G=c(p[g][k],H?I:{},H?F:2);p[g][k]=G+(/#/.test(G)?"":"#")};b.getState=u=function(F,G){return F===i||typeof F==="boolean"?v(F):v(G)[F]};b.removeState=function(F){var G={};if(F!==i){G=u();$.each($.isArray(F)?F:arguments,function(I,H){delete G[H]})}q(G,2)};e[d]=$.extend(e[d],{add:function(F){var H;function G(J){var I=J[D]=c();J.getState=function(K,L){return K===i||typeof K==="boolean"?l(I,K):l(I,L)[K]};H.apply(this,arguments)}if($.isFunction(F)){H=F;return G}else{H=F.handler;F.handler=G}}})})(jQuery,this);
/*
* jQuery hashchange event - v1.2 - 2/11/2010
* http://benalman.com/projects/jquery-hashchange-plugin/
*
* Copyright (c) 2010 "Cowboy" Ben Alman
* Dual licensed under the MIT and GPL licenses.
* http://benalman.com/about/license/
*/
(function($,i,b){var j,k=$.event.special,c="location",d="hashchange",l="href",f=$.browser,g=document.documentMode,h=f.msie&&(g===b||g<8),e="on"+d in i&&!h;function a(m){m=m||i[c][l];return m.replace(/^[^#]*#?(.*)$/,"$1")}$[d+"Delay"]=100;k[d]=$.extend(k[d],{setup:function(){if(e){return false}$(j.start)},teardown:function(){if(e){return false}$(j.stop)}});j=(function(){var m={},r,n,o,q;function p(){o=q=function(s){return s};if(h){n=$('<iframe src="javascript:0"/>').hide().insertAfter("body")[0].contentWindow;q=function(){return a(n.document[c][l])};o=function(u,s){if(u!==s){var t=n.document;t.open().close();t[c].hash="#"+u}};o(a())}}m.start=function(){if(r){return}var t=a();o||p();(function s(){var v=a(),u=q(t);if(v!==t){o(t=v,u);$(i).trigger(d)}else{if(u!==t){i[c][l]=i[c][l].replace(/#.*/,"")+"#"+u}}r=setTimeout(s,$[d+"Delay"])})()};m.stop=function(){if(!n){r&&clearTimeout(r);r=0}};return m})()})(jQuery,this);

View File

@@ -0,0 +1 @@
(function(b){b.fn.slideto=function(a){a=b.extend({slide_duration:"slow",highlight_duration:3E3,highlight:true,highlight_color:"#FFFF99"},a);return this.each(function(){obj=b(this);b("body").animate({scrollTop:obj.offset().top},a.slide_duration,function(){a.highlight&&b.ui.version&&obj.effect("highlight",{color:a.highlight_color},a.highlight_duration)})})}})(jQuery);

View File

@@ -0,0 +1,8 @@
/*
jQuery Wiggle
Author: WonderGroup, Jordan Thomas
URL: http://labs.wondergroup.com/demos/mini-ui/index.html
License: MIT (http://en.wikipedia.org/wiki/MIT_License)
*/
jQuery.fn.wiggle=function(o){var d={speed:50,wiggles:3,travel:5,callback:null};var o=jQuery.extend(d,o);return this.each(function(){var cache=this;var wrap=jQuery(this).wrap('<div class="wiggle-wrap"></div>').css("position","relative");var calls=0;for(i=1;i<=o.wiggles;i++){jQuery(this).animate({left:"-="+o.travel},o.speed).animate({left:"+="+o.travel*2},o.speed*2).animate({left:"-="+o.travel},o.speed,function(){calls++;if(jQuery(cache).parent().hasClass('wiggle-wrap')){jQuery(cache).parent().replaceWith(cache);}
if(calls==o.wiggles&&jQuery.isFunction(o.callback)){o.callback();}});}});};

View File

@@ -0,0 +1,125 @@
/* http://meyerweb.com/eric/tools/css/reset/ v2.0 | 20110126 */
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
}
ol,
ul {
list-style: none;
}
blockquote,
q {
quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,211 @@
var appName;
var popupMask;
var popupDialog;
var clientId;
var realm;
function handleLogin() {
var scopes = [];
if(window.swaggerUi.api.authSchemes
&& window.swaggerUi.api.authSchemes.oauth2
&& window.swaggerUi.api.authSchemes.oauth2.scopes) {
scopes = window.swaggerUi.api.authSchemes.oauth2.scopes;
}
if(window.swaggerUi.api
&& window.swaggerUi.api.info) {
appName = window.swaggerUi.api.info.title;
}
if(popupDialog.length > 0)
popupDialog = popupDialog.last();
else {
popupDialog = $(
[
'<div class="api-popup-dialog">',
'<div class="api-popup-title">Select OAuth2.0 Scopes</div>',
'<div class="api-popup-content">',
'<p>Scopes are used to grant an application different levels of access to data on behalf of the end user. Each API may declare one or more scopes.',
'<a href="#">Learn how to use</a>',
'</p>',
'<p><strong>' + appName + '</strong> API requires the following scopes. Select which ones you want to grant to Swagger UI.</p>',
'<ul class="api-popup-scopes">',
'</ul>',
'<p class="error-msg"></p>',
'<div class="api-popup-actions"><button class="api-popup-authbtn api-button green" type="button">Authorize</button><button class="api-popup-cancel api-button gray" type="button">Cancel</button></div>',
'</div>',
'</div>'].join(''));
$(document.body).append(popupDialog);
popup = popupDialog.find('ul.api-popup-scopes').empty();
for (i = 0; i < scopes.length; i ++) {
scope = scopes[i];
str = '<li><input type="checkbox" id="scope_' + i + '" scope="' + scope.scope + '"/>' + '<label for="scope_' + i + '">' + scope.scope;
if (scope.description) {
str += '<br/><span class="api-scope-desc">' + scope.description + '</span>';
}
str += '</label></li>';
popup.append(str);
}
}
var $win = $(window),
dw = $win.width(),
dh = $win.height(),
st = $win.scrollTop(),
dlgWd = popupDialog.outerWidth(),
dlgHt = popupDialog.outerHeight(),
top = (dh -dlgHt)/2 + st,
left = (dw - dlgWd)/2;
popupDialog.css({
top: (top < 0? 0 : top) + 'px',
left: (left < 0? 0 : left) + 'px'
});
popupDialog.find('button.api-popup-cancel').click(function() {
popupMask.hide();
popupDialog.hide();
});
popupDialog.find('button.api-popup-authbtn').click(function() {
popupMask.hide();
popupDialog.hide();
var authSchemes = window.swaggerUi.api.authSchemes;
var host = window.location;
var redirectUrl = host.protocol + '//' + host.host + "/o2c.html";
var url = null;
var p = window.swaggerUi.api.authSchemes;
for (var key in p) {
if (p.hasOwnProperty(key)) {
var o = p[key].grantTypes;
for(var t in o) {
if(o.hasOwnProperty(t) && t === 'implicit') {
var dets = o[t];
url = dets.loginEndpoint.url + "?response_type=token";
window.swaggerUi.tokenName = dets.tokenName;
}
}
}
}
var scopes = []
var o = $('.api-popup-scopes').find('input:checked');
for(k =0; k < o.length; k++) {
scopes.push($(o[k]).attr("scope"));
}
window.enabledScopes=scopes;
url += '&redirect_uri=' + encodeURIComponent(redirectUrl);
url += '&realm=' + encodeURIComponent(realm);
url += '&client_id=' + encodeURIComponent(clientId);
url += '&scope=' + encodeURIComponent(scopes);
window.open(url);
});
popupMask.show();
popupDialog.show();
return;
}
function handleLogout() {
for(key in window.authorizations.authz){
window.authorizations.remove(key)
}
window.enabledScopes = null;
$('.api-ic.ic-on').addClass('ic-off');
$('.api-ic.ic-on').removeClass('ic-on');
// set the info box
$('.api-ic.ic-warning').addClass('ic-error');
$('.api-ic.ic-warning').removeClass('ic-warning');
}
function initOAuth(opts) {
var o = (opts||{});
var errors = [];
appName = (o.appName||errors.push("missing appName"));
popupMask = (o.popupMask||$('#api-common-mask'));
popupDialog = (o.popupDialog||$('.api-popup-dialog'));
clientId = (o.clientId||errors.push("missing client id"));
realm = (o.realm||errors.push("missing realm"));
if(errors.length > 0){
log("auth unable initialize oauth: " + errors);
return;
}
$('pre code').each(function(i, e) {hljs.highlightBlock(e)});
$('.api-ic').click(function(s) {
if($(s.target).hasClass('ic-off'))
handleLogin();
else {
handleLogout();
}
false;
});
}
function onOAuthComplete(token) {
if(token) {
if(token.error) {
var checkbox = $('input[type=checkbox],.secured')
checkbox.each(function(pos){
checkbox[pos].checked = false;
});
alert(token.error);
}
else {
var b = token[window.swaggerUi.tokenName];
if(b){
// if all roles are satisfied
var o = null;
$.each($('.auth #api_information_panel'), function(k, v) {
var children = v;
if(children && children.childNodes) {
var requiredScopes = [];
$.each((children.childNodes), function (k1, v1){
var inner = v1.innerHTML;
if(inner)
requiredScopes.push(inner);
});
var diff = [];
for(var i=0; i < requiredScopes.length; i++) {
var s = requiredScopes[i];
if(window.enabledScopes && window.enabledScopes.indexOf(s) == -1) {
diff.push(s);
}
}
if(diff.length > 0){
o = v.parentNode;
$(o.parentNode).find('.api-ic.ic-on').addClass('ic-off');
$(o.parentNode).find('.api-ic.ic-on').removeClass('ic-on');
// sorry, not all scopes are satisfied
$(o).find('.api-ic').addClass('ic-warning');
$(o).find('.api-ic').removeClass('ic-error');
}
else {
o = v.parentNode;
$(o.parentNode).find('.api-ic.ic-off').addClass('ic-on');
$(o.parentNode).find('.api-ic.ic-off').removeClass('ic-off');
// all scopes are satisfied
$(o).find('.api-ic').addClass('ic-info');
$(o).find('.api-ic').removeClass('ic-warning');
$(o).find('.api-ic').removeClass('ic-error');
}
}
});
window.authorizations.add("oauth2", new ApiKeyAuthorization("Authorization", "Bearer " + b, "header"));
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
// Underscore.js 1.3.3
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);

View File

@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Matrix Client-Server API Documentation</title>
<link href="./files/css" rel="stylesheet" type="text/css">
<link href="./files/reset.css" media="screen" rel="stylesheet" type="text/css">
<link href="./files/screen.css" media="screen" rel="stylesheet" type="text/css">
<link href="./files/reset.css" media="print" rel="stylesheet" type="text/css">
<link href="./files/screen.css" media="print" rel="stylesheet" type="text/css">
<script type="text/javascript" src="./files/shred.bundle.js"></script>
<script src="./files/jquery-1.8.0.min.js" type="text/javascript"></script>
<script src="./files/jquery.slideto.min.js" type="text/javascript"></script>
<script src="./files/jquery.wiggle.min.js" type="text/javascript"></script>
<script src="./files/jquery.ba-bbq.min.js" type="text/javascript"></script>
<script src="./files/handlebars-1.0.0.js" type="text/javascript"></script>
<script src="./files/underscore-min.js" type="text/javascript"></script>
<script src="./files/backbone-min.js" type="text/javascript"></script>
<script src="./files/swagger.js" type="text/javascript"></script>
<script src="./files/swagger-ui.js" type="text/javascript"></script>
<script src="./files/highlight.7.3.pack.js" type="text/javascript"></script>
<!-- enabling this will enable oauth2 implicit scope support -->
<script src="./files/swagger-oauth.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
window.swaggerUi = new SwaggerUi({
url: "http://localhost:8000/swagger_matrix/api-docs",
dom_id: "swagger-ui-container",
supportedSubmitMethods: ['get', 'post', 'put', 'delete'],
onComplete: function(swaggerApi, swaggerUi){
log("Loaded SwaggerUI");
if(typeof initOAuth == "function") {
initOAuth({
clientId: "your-client-id",
realm: "your-realms",
appName: "your-app-name"
});
}
$('pre code').each(function(i, e) {
hljs.highlightBlock(e)
});
},
onFailure: function(data) {
log("Unable to Load SwaggerUI");
},
docExpansion: "none"
});
$('#input_apiKey').change(function() {
var key = $('#input_apiKey')[0].value;
log("key: " + key);
if(key && key.trim() != "") {
log("added key " + key);
window.authorizations.add("key", new ApiKeyAuthorization("access_token", key, "query"));
}
})
window.swaggerUi.load();
});
</script>
</head>
<body class="swagger-section">
<div id="header">
<div class="swagger-ui-wrap">
<a id="logo" href="http://swagger.wordnik.com/">swagger</a>
<form id="api_selector">
<div class="input"><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"></div>
<div class="input"><input placeholder="access_token" id="input_apiKey" name="apiKey" type="text"></div>
</form>
</div>
</div>
<div id="message-bar" class="swagger-ui-wrap message-fail">Can't read from server. It may not have the appropriate access-control-origin settings.</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
</body></html>

View File

@@ -1,141 +0,0 @@
Overview
========
Scope
-----
This document considers threats specific to the server to server federation
synapse protocol.
Attacker
--------
It is assumed that the attacker can see and manipulate all network traffic
between any of the servers and may be in control of one or more homeservers
participating in the federation protocol.
Threat Model
============
Denial of Service
-----------------
The attacker could attempt to prevent delivery of messages to or from the
victim in order to:
* Disrupt service or marketing campaign of a commercial competitor.
* Censor a discussion or censor a participant in a discussion.
* Perform general vandalism.
Threat: Resource Exhaustion
~~~~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could cause the victims server to exhaust a particular resource
(e.g. open TCP connections, CPU, memory, disk storage)
Threat: Unrecoverable Consistency Violations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could send messages which created an unrecoverable "split-brain"
state in the cluster such that the victim's servers could no longer dervive a
consistent view of the chatroom state.
Threat: Bad History
~~~~~~~~~~~~~~~~~~~
An attacker could convince the victim to accept invalid messages which the
victim would then include in their view of the chatroom history. Other servers
in the chatroom would reject the invalid messages and potentially reject the
victims messages as well since they depended on the invalid messages.
Threat: Block Network Traffic
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could try to firewall traffic between the victim's server and some
or all of the other servers in the chatroom.
Threat: High Volume of Messages
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could send large volumes of messages to a chatroom with the victim
making the chatroom unusable.
Threat: Banning users without necessary authorisation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could attempt to ban a user from a chatroom with the necessary
authorisation.
Spoofing
--------
An attacker could try to send a message claiming to be from the victim without
the victim having sent the message in order to:
* Impersonate the victim while performing illict activity.
* Obtain privileges of the victim.
Threat: Altering Message Contents
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could try to alter the contents of an existing message from the
victim.
Threat: Fake Message "origin" Field
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could try to send a new message purporting to be from the victim
with a phony "origin" field.
Spamming
--------
The attacker could try to send a high volume of solicicted or unsolicted
messages to the victim in order to:
* Find victims for scams.
* Market unwanted products.
Threat: Unsoliticted Messages
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could try to send messages to victims who do not wish to receive
them.
Threat: Abusive Messages
~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could send abusive or threatening messages to the victim
Spying
------
The attacker could try to access message contents or metadata for messages sent
by the victim or to the victim that were not intended to reach the attacker in
order to:
* Gain sensitive personal or commercial information.
* Impersonate the victim using credentials contained in the messages.
(e.g. password reset messages)
* Discover who the victim was talking to and when.
Threat: Disclosure during Transmission
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could try to expose the message contents or metadata during
transmission between the servers.
Threat: Disclosure to Servers Outside Chatroom
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could try to convince servers within a chatroom to send messages to
a server it controls that was not authorised to be within the chatroom.
Threat: Disclosure to Servers Within Chatroom
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
An attacker could take control of a server within a chatroom to expose message
contents or metadata for messages in that room.

View File

@@ -155,7 +155,7 @@ Protocol URLs
All these URLs are namespaced within a prefix of
/matrix/federation/v1/...
/_matrix/federation/v1/...
For active pushing of messages representing live activity "as it happens":

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,4 +1,4 @@
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -120,7 +120,7 @@ def make_graph(pdus, room, filename_prefix):
def get_pdus(host, room):
transaction = json.loads(
urllib2.urlopen(
"http://%s/matrix/federation/v1/context/%s/" % (host, room)
"http://%s/_matrix/federation/v1/context/%s/" % (host, room)
).read()
)

View File

@@ -1,5 +1,5 @@
<div>
<p>This room creation / message sending demo requires a home server to be running on http://localhost:8080</p>
<p>This room creation / message sending demo requires a home server to be running on http://localhost:8008</p>
</div>
<form class="loginForm">
<input type="text" id="userLogin" placeholder="Username"></input>

View File

@@ -10,7 +10,7 @@ $('.login').live('click', function() {
var user = $("#userLogin").val();
var password = $("#passwordLogin").val();
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/login",
url: "http://localhost:8008/_matrix/client/api/v1/login",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
@@ -25,11 +25,12 @@ $('.login').live('click', function() {
});
var getCurrentRoomList = function() {
var url = "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=1";
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
$.getJSON(url, function(data) {
for (var i=0; i<data.length; ++i) {
data[i].latest_message = data[i].messages.chunk[0].content.body;
addRoom(data[i]);
var rooms = data.rooms;
for (var i=0; i<rooms.length; ++i) {
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
addRoom(rooms[i]);
}
}).fail(function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
@@ -43,7 +44,7 @@ $('.createRoom').live('click', function() {
data.room_alias_name = roomAlias;
}
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/rooms?access_token="+accountInfo.access_token,
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
@@ -78,11 +79,9 @@ $('.sendMessage').live('click', function() {
return;
}
var url = "http://localhost:8080/matrix/client/api/v1/rooms/$roomid/messages/$user/$msgid?access_token=$token";
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomid", encodeURIComponent(roomId));
url = url.replace("$user", encodeURIComponent(accountInfo.user_id));
url = url.replace("$msgid", msgId);
var data = {
msgtype: "m.text",
@@ -91,7 +90,7 @@ $('.sendMessage').live('click', function() {
$.ajax({
url: url,
type: "PUT",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",

View File

@@ -1,5 +1,5 @@
<div>
<p>This event stream demo requires a home server to be running on http://localhost:8080</p>
<p>This event stream demo requires a home server to be running on http://localhost:8008</p>
</div>
<form class="loginForm">
<input type="text" id="userLogin" placeholder="Username"></input>

View File

@@ -7,7 +7,7 @@ var eventStreamInfo = {
var roomInfo = [];
var longpollEventStream = function() {
var url = "http://localhost:8080/matrix/client/api/v1/events?access_token=$token&from=$from";
var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$from", eventStreamInfo.from);
@@ -48,7 +48,7 @@ $('.login').live('click', function() {
var user = $("#userLogin").val();
var password = $("#passwordLogin").val();
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/login",
url: "http://localhost:8008/_matrix/client/api/v1/login",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
@@ -65,14 +65,15 @@ $('.login').live('click', function() {
var getCurrentRoomList = function() {
$("#roomId").val("");
var url = "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=1";
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
$.getJSON(url, function(data) {
for (var i=0; i<data.length; ++i) {
if ("messages" in data[i]) {
data[i].latest_message = data[i].messages.chunk[0].content.body;
var rooms = data.rooms;
for (var i=0; i<rooms.length; ++i) {
if ("messages" in rooms[i]) {
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
}
}
roomInfo = data;
roomInfo = rooms;
setRooms(roomInfo);
}).fail(function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
@@ -92,17 +93,14 @@ $('.sendMessage').live('click', function() {
var sendMessage = function(roomId) {
var body = "jsfiddle message @" + $.now();
var msgId = $.now();
if (roomId.length === 0) {
return;
}
var url = "http://localhost:8080/matrix/client/api/v1/rooms/$roomid/messages/$user/$msgid?access_token=$token";
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomid", encodeURIComponent(roomId));
url = url.replace("$user", encodeURIComponent(accountInfo.user_id));
url = url.replace("$msgid", msgId);
var data = {
msgtype: "m.text",
@@ -111,7 +109,7 @@ var sendMessage = function(roomId) {
$.ajax({
url: url,
type: "PUT",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",

View File

@@ -1,5 +1,5 @@
<div class="signUp">
<p>Matrix example application: Requires a local home server running at http://localhost:8080</p>
<p>Matrix example application: Requires a local home server running at http://localhost:8008</p>
<form class="registrationForm">
<p>No account? Register:</p>
<input type="text" id="userReg" placeholder="Username"></input>

View File

@@ -10,7 +10,7 @@ var viewingRoomId;
// ************** Event Streaming **************
var longpollEventStream = function() {
var url = "http://localhost:8080/matrix/client/api/v1/events?access_token=$token&from=$from";
var url = "http://localhost:8008/_matrix/client/api/v1/events?access_token=$token&from=$from";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$from", eventStreamInfo.from);
@@ -38,8 +38,9 @@ var longpollEventStream = function() {
else if (data.chunk[i].type === "m.room.member") {
if (viewingRoomId === data.chunk[i].room_id) {
console.log("Got new member: " + JSON.stringify(data.chunk[i]));
addMessage(data.chunk[i]);
for (j=0; j<memberInfo.length; ++j) {
if (memberInfo[j].target_user_id === data.chunk[i].target_user_id) {
if (memberInfo[j].state_key === data.chunk[i].state_key) {
memberInfo[j] = data.chunk[i];
updatedMemberList = true;
break;
@@ -50,7 +51,7 @@ var longpollEventStream = function() {
updatedMemberList = true;
}
}
if (data.chunk[i].target_user_id === accountInfo.user_id) {
if (data.chunk[i].state_key === accountInfo.user_id) {
getCurrentRoomList(); // update our join/invite list
}
}
@@ -88,7 +89,7 @@ $('.login').live('click', function() {
var user = $("#userLogin").val();
var password = $("#passwordLogin").val();
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/login",
url: "http://localhost:8008/_matrix/client/api/v1/login",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
@@ -106,7 +107,7 @@ $('.register').live('click', function() {
var user = $("#userReg").val();
var password = $("#passwordReg").val();
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/register",
url: "http://localhost:8008/_matrix/client/api/v1/register",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user_id: user, password: password }),
@@ -133,7 +134,7 @@ $('.createRoom').live('click', function() {
data.room_alias_name = roomAlias;
}
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/rooms?access_token="+accountInfo.access_token,
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
@@ -154,14 +155,15 @@ $('.createRoom').live('click', function() {
// ************** Getting current state **************
var getCurrentRoomList = function() {
var url = "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=1";
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
$.getJSON(url, function(data) {
for (var i=0; i<data.length; ++i) {
if ("messages" in data[i]) {
data[i].latest_message = data[i].messages.chunk[0].content.body;
var rooms = data.rooms;
for (var i=0; i<rooms.length; ++i) {
if ("messages" in rooms[i]) {
rooms[i].latest_message = rooms[i].messages.chunk[0].content.body;
}
}
roomInfo = data;
roomInfo = rooms;
setRooms(roomInfo);
}).fail(function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
@@ -179,7 +181,8 @@ var loadRoomContent = function(roomId) {
var getMessages = function(roomId) {
$("#messages").empty();
var url = "http://localhost:8080/matrix/client/api/v1/rooms/" + roomId + "/messages/list?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=10";
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" +
encodeURIComponent(roomId) + "/messages?access_token=" + accountInfo.access_token + "&from=END&dir=b&limit=10";
$.getJSON(url, function(data) {
for (var i=data.chunk.length-1; i>=0; --i) {
addMessage(data.chunk[i]);
@@ -190,7 +193,8 @@ var getMessages = function(roomId) {
var getMemberList = function(roomId) {
$("#members").empty();
memberInfo = [];
var url = "http://localhost:8080/matrix/client/api/v1/rooms/" + roomId + "/members/list?access_token=" + accountInfo.access_token;
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/" +
encodeURIComponent(roomId) + "/members?access_token=" + accountInfo.access_token;
$.getJSON(url, function(data) {
for (var i=0; i<data.chunk.length; ++i) {
memberInfo.push(data.chunk[i]);
@@ -212,11 +216,9 @@ $('.sendMessage').live('click', function() {
var sendMessage = function(roomId, body) {
var msgId = $.now();
var url = "http://localhost:8080/matrix/client/api/v1/rooms/$roomid/messages/$user/$msgid?access_token=$token";
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/send/m.room.message?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomid", encodeURIComponent(roomId));
url = url.replace("$user", encodeURIComponent(accountInfo.user_id));
url = url.replace("$msgid", msgId);
var data = {
msgtype: "m.text",
@@ -225,7 +227,7 @@ var sendMessage = function(roomId, body) {
$.ajax({
url: url,
type: "PUT",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",
@@ -260,13 +262,12 @@ var setRooms = function(roomList) {
var membership = $(this).find('td:eq(1)').text();
if (membership !== "join") {
console.log("Joining room " + roomId);
var url = "http://localhost:8080/matrix/client/api/v1/rooms/$roomid/members/$user/state?access_token=$token";
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/join?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomid", encodeURIComponent(roomId));
url = url.replace("$user", encodeURIComponent(accountInfo.user_id));
$.ajax({
url: url,
type: "PUT",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({membership: "join"}),
dataType: "json",
@@ -286,16 +287,39 @@ var setRooms = function(roomList) {
};
var addMessage = function(data) {
var msg = data.content.body;
if (data.type === "m.room.member") {
if (data.content.membership === undefined) {
return;
}
if (data.content.membership === "invite") {
msg = "<em>invited " + data.state_key + " to the room</em>";
}
else if (data.content.membership === "join") {
msg = "<em>joined the room</em>";
}
else if (data.content.membership === "leave") {
msg = "<em>left the room</em>";
}
else if (data.content.membership === "ban") {
msg = "<em>was banned from the room</em>";
}
}
if (msg === undefined) {
return;
}
var row = "<tr>" +
"<td>"+data.user_id+"</td>" +
"<td>"+data.content.body+"</td>" +
"<td>"+msg+"</td>" +
"</tr>";
$("#messages").append(row);
};
var addMember = function(data) {
var row = "<tr>" +
"<td>"+data.target_user_id+"</td>" +
"<td>"+data.state_key+"</td>" +
"<td>"+data.content.membership+"</td>" +
"</tr>";
$("#members").append(row);

View File

@@ -1,5 +1,5 @@
<div>
<p>This registration/login demo requires a home server to be running on http://localhost:8080</p>
<p>This registration/login demo requires a home server to be running on http://localhost:8008</p>
</div>
<form class="registrationForm">
<input type="text" id="user" placeholder="Username"></input>

View File

@@ -11,7 +11,7 @@ $('.register').live('click', function() {
var user = $("#user").val();
var password = $("#password").val();
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/register",
url: "http://localhost:8008/_matrix/client/api/v1/register",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user_id: user, password: password }),
@@ -27,7 +27,7 @@ $('.register').live('click', function() {
var login = function(user, password) {
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/login",
url: "http://localhost:8008/_matrix/client/api/v1/login",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
@@ -44,8 +44,8 @@ var login = function(user, password) {
$('.login').live('click', function() {
var user = $("#userLogin").val();
var password = $("#passwordLogin").val();
$.getJSON("http://localhost:8080/matrix/client/api/v1/login", function(data) {
if (data.type !== "m.login.password") {
$.getJSON("http://localhost:8008/_matrix/client/api/v1/login", function(data) {
if (data.flows[0].type !== "m.login.password") {
alert("I don't know how to login with this type: " + data.type);
return;
}
@@ -60,7 +60,7 @@ $('.logout').live('click', function() {
});
$('.testToken').live('click', function() {
var url = "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=1";
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
$.getJSON(url, function(data) {
$("#imSyncText").text(JSON.stringify(data, undefined, 2));
}).fail(function(err) {

View File

@@ -1,5 +1,5 @@
<div>
<p>This room membership demo requires a home server to be running on http://localhost:8080</p>
<p>This room membership demo requires a home server to be running on http://localhost:8008</p>
</div>
<form class="loginForm">
<input type="text" id="userLogin" placeholder="Username"></input>
@@ -14,9 +14,9 @@
<input type="text" id="roomId" placeholder="Room ID"></input>
<input type="text" id="targetUser" placeholder="Target User ID"></input>
<select id="membership">
<option value="invite">Invite</option>
<option value="join">Join</option>
<option value="leave">Leave</option>
<option value="invite">invite</option>
<option value="join">join</option>
<option value="leave">leave</option>
</select>
<input type="button" class="changeMembership" value="Change Membership"></input>
</form>

View File

@@ -4,13 +4,21 @@ var showLoggedIn = function(data) {
accountInfo = data;
getCurrentRoomList();
$(".loggedin").css({visibility: "visible"});
$("#membership").change(function() {
if ($("#membership").val() === "invite") {
$("#targetUser").css({visibility: "visible"});
}
else {
$("#targetUser").css({visibility: "hidden"});
}
});
};
$('.login').live('click', function() {
var user = $("#userLogin").val();
var password = $("#passwordLogin").val();
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/login",
url: "http://localhost:8008/_matrix/client/api/v1/login",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
@@ -31,10 +39,11 @@ var getCurrentRoomList = function() {
// solution but that is out of scope of this fiddle.
$("#rooms").find("tr:gt(0)").remove();
var url = "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=" + accountInfo.access_token + "&from=END&to=START&limit=1";
var url = "http://localhost:8008/_matrix/client/api/v1/initialSync?access_token=" + accountInfo.access_token + "&limit=1";
$.getJSON(url, function(data) {
for (var i=0; i<data.length; ++i) {
addRoom(data[i]);
var rooms = data.rooms;
for (var i=0; i<rooms.length; ++i) {
addRoom(rooms[i]);
}
}).fail(function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
@@ -44,7 +53,7 @@ var getCurrentRoomList = function() {
$('.createRoom').live('click', function() {
var data = {};
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/rooms?access_token="+accountInfo.access_token,
url: "http://localhost:8008/_matrix/client/api/v1/createRoom?access_token="+accountInfo.access_token,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
@@ -78,54 +87,42 @@ $('.changeMembership').live('click', function() {
return;
}
var url = "http://localhost:8080/matrix/client/api/v1/rooms/$roomid/members/$user/state?access_token=$token";
var url = "http://localhost:8008/_matrix/client/api/v1/rooms/$roomid/$membership?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomid", encodeURIComponent(roomId));
url = url.replace("$user", encodeURIComponent(member));
url = url.replace("$membership", membership);
if (membership === "leave") {
$.ajax({
url: url,
type: "DELETE",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
getCurrentRoomList();
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
}
else {
var data = {
membership: membership
var data = {};
if (membership === "invite") {
data = {
user_id: member
};
$.ajax({
url: url,
type: "PUT",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",
success: function(data) {
getCurrentRoomList();
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
}
$.ajax({
url: url,
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(data),
dataType: "json",
success: function(data) {
getCurrentRoomList();
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
});
$('.joinAlias').live('click', function() {
var roomAlias = $("#roomAlias").val();
var url = "http://localhost:8080/matrix/client/api/v1/join/$roomalias?access_token=$token";
var url = "http://localhost:8008/_matrix/client/api/v1/join/$roomalias?access_token=$token";
url = url.replace("$token", accountInfo.access_token);
url = url.replace("$roomalias", encodeURIComponent(roomAlias));
$.ajax({
url: url,
type: "PUT",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({}),
dataType: "json",

510
scripts/basic.css Normal file
View File

@@ -0,0 +1,510 @@
/*
* basic.css
* ~~~~~~~~~
*
* Sphinx stylesheet -- basic theme.
*
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/* -- main layout ----------------------------------------------------------- */
div.clearer {
clear: both;
}
/* -- relbar ---------------------------------------------------------------- */
div.related {
width: 100%;
font-size: 90%;
}
div.related h3 {
display: none;
}
div.related ul {
margin: 0;
padding: 0 0 0 10px;
list-style: none;
}
div.related li {
display: inline;
}
div.related li.right {
float: right;
margin-right: 5px;
}
/* -- sidebar --------------------------------------------------------------- */
div.sphinxsidebarwrapper {
padding: 10px 5px 0 10px;
}
div.sphinxsidebar {
float: left;
width: 230px;
margin-left: -100%;
font-size: 90%;
}
div.sphinxsidebar ul {
list-style: none;
}
div.sphinxsidebar ul ul,
div.sphinxsidebar ul.want-points {
margin-left: 20px;
list-style: square;
}
div.sphinxsidebar ul ul {
margin-top: 0;
margin-bottom: 0;
}
div.sphinxsidebar form {
margin-top: 10px;
}
div.sphinxsidebar input {
border: 1px solid #98dbcc;
font-family: sans-serif;
font-size: 1em;
}
img {
border: 0;
}
/* -- search page ----------------------------------------------------------- */
ul.search {
margin: 10px 0 0 20px;
padding: 0;
}
ul.search li {
padding: 5px 0 5px 20px;
background-image: url(file.png);
background-repeat: no-repeat;
background-position: 0 7px;
}
ul.search li a {
font-weight: bold;
}
ul.search li div.context {
color: #888;
margin: 2px 0 0 30px;
text-align: left;
}
ul.keywordmatches li.goodmatch a {
font-weight: bold;
}
/* -- index page ------------------------------------------------------------ */
table.contentstable {
width: 90%;
}
table.contentstable p.biglink {
line-height: 150%;
}
a.biglink {
font-size: 1.3em;
}
span.linkdescr {
font-style: italic;
padding-top: 5px;
font-size: 90%;
}
/* -- general index --------------------------------------------------------- */
table.indextable {
width: 100%;
}
table.indextable td {
text-align: left;
vertical-align: top;
}
table.indextable dl, table.indextable dd {
margin-top: 0;
margin-bottom: 0;
}
table.indextable tr.pcap {
height: 10px;
}
table.indextable tr.cap {
margin-top: 10px;
background-color: #f2f2f2;
}
img.toggler {
margin-right: 3px;
margin-top: 3px;
cursor: pointer;
}
div.modindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
div.genindex-jumpbox {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
margin: 1em 0 1em 0;
padding: 0.4em;
}
/* -- general body styles --------------------------------------------------- */
a.headerlink {
visibility: hidden;
}
h1:hover > a.headerlink,
h2:hover > a.headerlink,
h3:hover > a.headerlink,
h4:hover > a.headerlink,
h5:hover > a.headerlink,
h6:hover > a.headerlink,
dt:hover > a.headerlink {
visibility: visible;
}
div.document p.caption {
text-align: inherit;
}
div.document td {
text-align: left;
}
.field-list ul {
padding-left: 1em;
}
.first {
margin-top: 0 !important;
}
p.rubric {
margin-top: 30px;
font-weight: bold;
}
.align-left {
text-align: left;
}
.align-center {
clear: both;
text-align: center;
}
.align-right {
text-align: right;
}
/* -- sidebars -------------------------------------------------------------- */
div.sidebar {
margin: 0 0 0.5em 1em;
border: 1px solid #ddb;
padding: 7px 7px 0 7px;
background-color: #ffe;
width: 40%;
float: right;
}
p.sidebar-title {
font-weight: bold;
}
/* -- topics ---------------------------------------------------------------- */
div.topic {
border: 1px solid #ccc;
padding: 7px 7px 0 7px;
margin: 10px 0 10px 0;
}
p.topic-title {
font-size: 1.1em;
font-weight: bold;
margin-top: 10px;
}
/* -- admonitions ----------------------------------------------------------- */
div.admonition {
margin-top: 10px;
margin-bottom: 10px;
padding: 7px;
}
div.admonition dt {
font-weight: bold;
}
div.admonition dl {
margin-bottom: 0;
}
p.admonition-title {
margin: 0px 10px 5px 0px;
font-weight: bold;
}
div.document p.centered {
text-align: center;
margin-top: 25px;
}
/* -- tables ---------------------------------------------------------------- */
table.docutils {
border: 0;
border-collapse: collapse;
}
table.docutils td, table.docutils th {
padding: 1px 8px 1px 5px;
border-top: 0;
border-left: 0;
border-right: 0;
border-bottom: 1px solid #aaa;
}
table.field-list td, table.field-list th {
border: 0 !important;
}
table.footnote td, table.footnote th {
border: 0 !important;
}
th {
text-align: left;
padding-right: 5px;
}
table.citation {
border-left: solid 1px gray;
margin-left: 1px;
}
table.citation td {
border-bottom: none;
}
/* -- other body styles ----------------------------------------------------- */
ol.arabic {
list-style: decimal;
}
ol.loweralpha {
list-style: lower-alpha;
}
ol.upperalpha {
list-style: upper-alpha;
}
ol.lowerroman {
list-style: lower-roman;
}
ol.upperroman {
list-style: upper-roman;
}
dl {
margin-bottom: 15px;
}
dd p {
margin-top: 0px;
}
dd ul, dd table {
margin-bottom: 10px;
}
dd {
margin-top: 3px;
margin-bottom: 10px;
margin-left: 30px;
}
dt:target, .highlighted {
background-color: #fbe54e;
}
dl.glossary dt {
font-weight: bold;
font-size: 1.1em;
}
.field-list ul {
margin: 0;
padding-left: 1em;
}
.field-list p {
margin: 0;
}
.refcount {
color: #060;
}
.optional {
font-size: 1.3em;
}
.versionmodified {
font-style: italic;
}
.system-message {
background-color: #fda;
padding: 5px;
border: 3px solid red;
}
.footnote:target {
background-color: #ffa
}
.line-block {
display: block;
margin-top: 1em;
margin-bottom: 1em;
}
.line-block .line-block {
margin-top: 0;
margin-bottom: 0;
margin-left: 1.5em;
}
.guilabel, .menuselection {
font-family: sans-serif;
}
.accelerator {
text-decoration: underline;
}
.classifier {
font-style: oblique;
}
/* -- code displays --------------------------------------------------------- */
pre {
overflow: auto;
}
td.linenos pre {
padding: 5px 0px;
border: 0;
background-color: transparent;
color: #aaa;
}
table.highlighttable {
margin-left: 0.5em;
}
table.highlighttable td {
padding: 0 0.5em 0 0.5em;
}
tt.descname {
background-color: transparent;
font-weight: bold;
font-size: 1.2em;
}
tt.descclassname {
background-color: transparent;
}
tt.xref, a tt {
background-color: transparent;
font-weight: bold;
}
h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
background-color: transparent;
}
.viewcode-link {
float: right;
}
.viewcode-back {
float: right;
font-family: sans-serif;
}
div.viewcode-block:target {
margin: -1px -10px;
padding: 0 10px;
}
/* -- math display ---------------------------------------------------------- */
img.math {
vertical-align: middle;
}
div.document div.math p {
text-align: center;
}
span.eqno {
float: right;
}
/* -- printout stylesheet --------------------------------------------------- */
@media print {
div.document,
div.documentwrapper,
div.bodywrapper {
margin: 0 !important;
width: 100%;
}
div.sphinxsidebar,
div.related,
div.footer,
#top-link {
display: none;
}
}

View File

@@ -1,5 +1,5 @@
#!/usr/bin/perl -pi
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
# limitations under the License.
$copyright = <<EOT;
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

14
scripts/gendoc.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
MATRIXDOTORG=$HOME/workspace/matrix.org
rst2html-2.7.py --stylesheet=basic.css,nature.css ../docs/specification.rst > $MATRIXDOTORG/docs/spec/index.html
rst2html-2.7.py --stylesheet=basic.css,nature.css ../docs/client-server/howto.rst > $MATRIXDOTORG/docs/howtos/client-server.html
perl -pi -e 's#<head>#<head><link rel="stylesheet" href="/site.css">#' $MATRIXDOTORG/docs/spec/index.html $MATRIXDOTORG/docs/howtos/client-server.html
perl -pi -e 's#<body>#<body><div id="header"><div id="headerContent">&nbsp;</div></div><div id="page"><div id="wrapper"><div style="text-align: center; padding: 40px;"><a href="/"><img src="/matrix.png" width="305" height="130" alt="[matrix]"/></a></div>#' $MATRIXDOTORG/docs/spec/index.html $MATRIXDOTORG/docs/howtos/client-server.html
perl -pi -e 's#</body>#</div></div><div id="footer"><div id="footerContent">&copy 2014 Matrix.org</div></div></body>#' $MATRIXDOTORG/docs/spec/index.html $MATRIXDOTORG/docs/howtos/client-server.html
scp -r $MATRIXDOTORG/docs matrix@ldc-prd-matrix-001:/sites/matrix

270
scripts/nature.css Normal file
View File

@@ -0,0 +1,270 @@
/*
* nature.css_t
* ~~~~~~~~~~~~
*
* Sphinx stylesheet -- nature theme.
*
* :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/* -- page layout ----------------------------------------------------------- */
body {
font-family: Arial, sans-serif;
font-size: 100%;
/*background-color: #111;*/
color: #555;
margin: 0;
padding: 0;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 230px;
}
hr {
border: 1px solid #B1B4B6;
}
/*
div.document {
background-color: #eee;
}
*/
div.document {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 30px 30px;
font-size: 0.9em;
}
div.footer {
color: #555;
width: 100%;
padding: 13px 0;
text-align: center;
font-size: 75%;
}
div.footer a {
color: #444;
text-decoration: underline;
}
div.related {
background-color: #6BA81E;
line-height: 32px;
color: #fff;
text-shadow: 0px 1px 0 #444;
font-size: 0.9em;
}
div.related a {
color: #E2F3CC;
}
div.sphinxsidebar {
font-size: 0.75em;
line-height: 1.5em;
}
div.sphinxsidebarwrapper{
padding: 20px 0;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: Arial, sans-serif;
color: #222;
font-size: 1.2em;
font-weight: normal;
margin: 0;
padding: 5px 10px;
background-color: #ddd;
text-shadow: 1px 1px 0 white
}
div.sphinxsidebar h4{
font-size: 1.1em;
}
div.sphinxsidebar h3 a {
color: #444;
}
div.sphinxsidebar p {
color: #888;
padding: 5px 20px;
}
div.sphinxsidebar p.topless {
}
div.sphinxsidebar ul {
margin: 10px 20px;
padding: 0;
color: #000;
}
div.sphinxsidebar a {
color: #444;
}
div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: sans-serif;
font-size: 1em;
}
div.sphinxsidebar input[type=text]{
margin-left: 20px;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #005B81;
text-decoration: none;
}
a:hover {
color: #E32E00;
text-decoration: underline;
}
div.document h1,
div.document h2,
div.document h3,
div.document h4,
div.document h5,
div.document h6 {
font-family: Arial, sans-serif;
background-color: #BED4EB;
font-weight: normal;
color: #212224;
margin: 30px 0px 10px 0px;
padding: 5px 0 5px 10px;
text-shadow: 0px 1px 0 white
}
div.document h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
div.document h2 { font-size: 150%; background-color: #C8D5E3; }
div.document h3 { font-size: 120%; background-color: #D8DEE3; }
div.document h4 { font-size: 110%; background-color: #D8DEE3; }
div.document h5 { font-size: 100%; background-color: #D8DEE3; }
div.document h6 { font-size: 100%; background-color: #D8DEE3; }
a.headerlink {
color: #c60f0f;
font-size: 0.8em;
padding: 0 4px 0 4px;
text-decoration: none;
}
a.headerlink:hover {
background-color: #c60f0f;
color: white;
}
div.document p, div.document dd, div.document li {
line-height: 1.5em;
}
div.admonition p.admonition-title + p {
display: inline;
}
div.highlight{
background-color: white;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre {
padding: 10px;
background-color: White;
color: #222;
line-height: 1.2em;
border: 1px solid #C6C9CB;
font-size: 1.1em;
margin: 1.5em 0 1.5em 0;
-webkit-box-shadow: 1px 1px 1px #d8d8d8;
-moz-box-shadow: 1px 1px 1px #d8d8d8;
}
tt {
background-color: #ecf0f3;
color: #222;
/* padding: 1px 2px; */
font-size: 1.1em;
font-family: monospace;
}
.viewcode-back {
font-family: Arial, sans-serif;
}
div.viewcode-block:target {
background-color: #f4debf;
border-top: 1px solid #ac9;
border-bottom: 1px solid #ac9;
}
p {
margin: 0;
}
ul li dd {
margin-top: 0;
}
ul li dl {
margin-bottom: 0;
}
li dl dd {
margin-bottom: 0;
}
dd ul {
padding-left: 0;
}
li dd ul {
margin-bottom: 0;
}

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@ setup(
"syutil==0.0.1",
"Twisted>=14.0.0",
"service_identity>=1.0.0",
"pyyaml",
"pyasn1",
"pynacl",
"daemonize",

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,4 +16,4 @@
""" This is a reference implementation of a synapse home server.
"""
__version__ = "0.1.2"
__version__ = "0.2.2"

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,9 +17,10 @@
from twisted.internet import defer
from synapse.api.constants import Membership
from synapse.api.errors import AuthError, StoreError, Codes
from synapse.api.events.room import RoomMemberEvent
from synapse.api.constants import Membership, JoinRules
from synapse.api.errors import AuthError, StoreError, Codes, SynapseError
from synapse.api.events.room import RoomMemberEvent, RoomPowerLevelsEvent
from synapse.util.logutils import log_function
import logging
@@ -44,16 +45,32 @@ class Auth(object):
"""
try:
if hasattr(event, "room_id"):
is_state = hasattr(event, "state_key")
if event.type == RoomMemberEvent.TYPE:
yield self._can_replace_state(event)
allowed = yield self.is_membership_change_allowed(event)
defer.returnValue(allowed)
return
self._check_joined_room(
member=snapshot.membership_state,
user_id=snapshot.user_id,
room_id=snapshot.room_id,
)
if is_state:
# TODO (erikj): This really only should be called for *new*
# state
yield self._can_add_state(event)
yield self._can_replace_state(event)
else:
self._check_joined_room(
member=snapshot.membership_state,
user_id=snapshot.user_id,
room_id=snapshot.room_id,
)
defer.returnValue(True)
yield self._can_send_event(event)
if event.type == RoomPowerLevelsEvent.TYPE:
yield self._check_power_levels(event)
defer.returnValue(True)
else:
raise AuthError(500, "Unknown event: %s" % event)
except AuthError as e:
@@ -111,7 +128,14 @@ class Auth(object):
membership = event.content["membership"]
join_rule = yield self.store.get_room_join_rule(event.room_id)
if not join_rule:
join_rule = JoinRules.INVITE
if Membership.INVITE == membership:
# TODO (erikj): We should probably handle this more intelligently
# PRIVATE join rules.
# Invites are valid iff caller is in the room and target isn't.
if not caller_in_room: # caller isn't joined
raise AuthError(403, "You are not in room %s." % event.room_id)
@@ -124,18 +148,54 @@ class Auth(object):
# joined: It's a NOOP
if event.user_id != target_user_id:
raise AuthError(403, "Cannot force another user to join.")
elif room.is_public:
pass # anyone can join public rooms.
elif (not caller or caller.membership not in
[Membership.INVITE, Membership.JOIN]):
raise AuthError(403, "You are not invited to this room.")
elif join_rule == JoinRules.PUBLIC or room.is_public:
pass
elif join_rule == JoinRules.INVITE:
if (
not caller or caller.membership not in
[Membership.INVITE, Membership.JOIN]
):
raise AuthError(403, "You are not invited to this room.")
else:
# TODO (erikj): may_join list
# TODO (erikj): private rooms
raise AuthError(403, "You are not allowed to join this room")
elif Membership.LEAVE == membership:
# TODO (erikj): Implement kicks.
if not caller_in_room: # trying to leave a room you aren't joined
raise AuthError(403, "You are not in room %s." % event.room_id)
elif target_user_id != event.user_id:
# trying to force another user to leave
raise AuthError(403, "Cannot force %s to leave." %
target_user_id)
user_level = yield self.store.get_power_level(
event.room_id,
event.user_id,
)
_, kick_level = yield self.store.get_ops_levels(event.room_id)
if kick_level:
kick_level = int(kick_level)
else:
kick_level = 50
if user_level < kick_level:
raise AuthError(
403, "You cannot kick user %s." % target_user_id
)
elif Membership.BAN == membership:
user_level = yield self.store.get_power_level(
event.room_id,
event.user_id,
)
ban_level, _ = yield self.store.get_ops_levels(event.room_id)
if ban_level:
ban_level = int(ban_level)
else:
ban_level = 50 # FIXME (erikj): What should we do here?
if user_level < ban_level:
raise AuthError(403, "You don't have permission to ban")
else:
raise AuthError(500, "Unknown membership %s" % membership)
@@ -176,3 +236,185 @@ class Auth(object):
except StoreError:
raise AuthError(403, "Unrecognised access token.",
errcode=Codes.UNKNOWN_TOKEN)
@defer.inlineCallbacks
@log_function
def _can_send_event(self, event):
send_level = yield self.store.get_send_event_level(event.room_id)
if send_level:
send_level = int(send_level)
else:
send_level = 0
user_level = yield self.store.get_power_level(
event.room_id,
event.user_id,
)
if user_level:
user_level = int(user_level)
else:
user_level = 0
if user_level < send_level:
raise AuthError(
403, "You don't have permission to post to the room"
)
defer.returnValue(True)
@defer.inlineCallbacks
def _can_add_state(self, event):
add_level = yield self.store.get_add_state_level(event.room_id)
if not add_level:
defer.returnValue(True)
add_level = int(add_level)
user_level = yield self.store.get_power_level(
event.room_id,
event.user_id,
)
user_level = int(user_level)
if user_level < add_level:
raise AuthError(
403, "You don't have permission to add state to the room"
)
defer.returnValue(True)
@defer.inlineCallbacks
def _can_replace_state(self, event):
current_state = yield self.store.get_current_state(
event.room_id,
event.type,
event.state_key,
)
if current_state:
current_state = current_state[0]
user_level = yield self.store.get_power_level(
event.room_id,
event.user_id,
)
if user_level:
user_level = int(user_level)
else:
user_level = 0
logger.debug(
"Checking power level for %s, %s", event.user_id, user_level
)
if current_state and hasattr(current_state, "required_power_level"):
req = current_state.required_power_level
logger.debug("Checked power level for %s, %s", event.user_id, req)
if user_level < req:
raise AuthError(
403,
"You don't have permission to change that state"
)
@defer.inlineCallbacks
def _check_power_levels(self, event):
for k, v in event.content.items():
if k == "default":
continue
# FIXME (erikj): We don't want hsob_Ts in content.
if k == "hsob_ts":
continue
try:
self.hs.parse_userid(k)
except:
raise SynapseError(400, "Not a valid user_id: %s" % (k,))
try:
int(v)
except:
raise SynapseError(400, "Not a valid power level: %s" % (v,))
current_state = yield self.store.get_current_state(
event.room_id,
event.type,
event.state_key,
)
if not current_state:
return
else:
current_state = current_state[0]
user_level = yield self.store.get_power_level(
event.room_id,
event.user_id,
)
if user_level:
user_level = int(user_level)
else:
user_level = 0
old_list = current_state.content
# FIXME (erikj)
old_people = {k: v for k, v in old_list.items() if k.startswith("@")}
new_people = {
k: v for k, v in event.content.items()
if k.startswith("@")
}
removed = set(old_people.keys()) - set(new_people.keys())
added = set(old_people.keys()) - set(new_people.keys())
same = set(old_people.keys()) & set(new_people.keys())
for r in removed:
if int(old_list.content[r]) > user_level:
raise AuthError(
403,
"You don't have permission to remove user: %s" % (r, )
)
for n in added:
if int(event.content[n]) > user_level:
raise AuthError(
403,
"You don't have permission to add ops level greater "
"than your own"
)
for s in same:
if int(event.content[s]) != int(old_list[s]):
if int(event.content[s]) > user_level:
raise AuthError(
403,
"You don't have permission to add ops level greater "
"than your own"
)
if "default" in old_list:
old_default = int(old_list["default"])
if old_default > user_level:
raise AuthError(
403,
"You don't have permission to add ops level greater than "
"your own"
)
if "default" in event.content:
new_default = int(event.content["default"])
if new_default > user_level:
raise AuthError(
403,
"You don't have permission to add ops level greater "
"than your own"
)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -23,7 +23,8 @@ class Membership(object):
JOIN = u"join"
KNOCK = u"knock"
LEAVE = u"leave"
LIST = (INVITE, JOIN, KNOCK, LEAVE)
BAN = u"ban"
LIST = (INVITE, JOIN, KNOCK, LEAVE, BAN)
class Feedback(object):
@@ -42,3 +43,10 @@ class PresenceState(object):
UNAVAILABLE = u"unavailable"
ONLINE = u"online"
FREE_FOR_CHAT = u"free_for_chat"
class JoinRules(object):
PUBLIC = u"public"
KNOCK = u"knock"
INVITE = u"invite"
PRIVATE = u"private"

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -28,6 +28,9 @@ class Codes(object):
UNKNOWN = "M_UNKNOWN"
NOT_FOUND = "M_NOT_FOUND"
UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
LIMIT_EXCEEDED = "M_LIMIT_EXCEEDED"
CAPTCHA_NEEDED = "M_CAPTCHA_NEEDED"
CAPTCHA_INVALID = "M_CAPTCHA_INVALID"
class CodeMessageException(Exception):
@@ -38,11 +41,15 @@ class CodeMessageException(Exception):
super(CodeMessageException, self).__init__("%d: %s" % (code, msg))
self.code = code
self.msg = msg
self.response_code_message = None
def error_dict(self):
return cs_error(self.msg)
class SynapseError(CodeMessageException):
"""A base error which can be caught for all synapse events."""
def __init__(self, code, msg, errcode=""):
def __init__(self, code, msg, errcode=Codes.UNKNOWN):
"""Constructs a synapse error.
Args:
@@ -53,6 +60,11 @@ class SynapseError(CodeMessageException):
super(SynapseError, self).__init__(code, msg)
self.errcode = errcode
def error_dict(self):
return cs_error(
self.msg,
self.errcode,
)
class RoomError(SynapseError):
"""An error raised when a room event fails."""
@@ -91,13 +103,39 @@ class StoreError(SynapseError):
pass
def cs_exception(exception):
if isinstance(exception, SynapseError):
class InvalidCaptchaError(SynapseError):
def __init__(self, code=400, msg="Invalid captcha.", error_url=None,
errcode=Codes.CAPTCHA_INVALID):
super(InvalidCaptchaError, self).__init__(code, msg, errcode)
self.error_url = error_url
def error_dict(self):
return cs_error(
exception.msg,
Codes.UNKNOWN if not exception.errcode else exception.errcode)
elif isinstance(exception, CodeMessageException):
return cs_error(exception.msg)
self.msg,
self.errcode,
error_url=self.error_url,
)
class LimitExceededError(SynapseError):
"""A client has sent too many requests and is being throttled.
"""
def __init__(self, code=429, msg="Too Many Requests", retry_after_ms=None,
errcode=Codes.LIMIT_EXCEEDED):
super(LimitExceededError, self).__init__(code, msg, errcode)
self.retry_after_ms = retry_after_ms
self.response_code_message = "Too Many Requests"
def error_dict(self):
return cs_error(
self.msg,
self.errcode,
retry_after_ms=self.retry_after_ms,
)
def cs_exception(exception):
if isinstance(exception, CodeMessageException):
return exception.error_dict()
else:
logging.error("Unknown exception type: %s", type(exception))

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -42,6 +42,7 @@ class SynapseEvent(JsonEncodedObject):
"user_id", # sender/initiator
"content", # HTTP body, JSON
"state_key",
"required_power_level",
]
internal_keys = [
@@ -52,6 +53,7 @@ class SynapseEvent(JsonEncodedObject):
"destinations",
"origin",
"outlier",
"power_level",
]
required_keys = [
@@ -152,3 +154,15 @@ class SynapseEvent(JsonEncodedObject):
msg = self._check_json(entry, template[key][0])
if msg:
return msg
class SynapseStateEvent(SynapseEvent):
valid_keys = SynapseEvent.valid_keys + [
"prev_content",
]
def __init__(self, **kwargs):
if "state_key" not in kwargs:
kwargs["state_key"] = ""
super(SynapseStateEvent, self).__init__(**kwargs)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,6 +16,8 @@
from synapse.api.events.room import (
RoomTopicEvent, MessageEvent, RoomMemberEvent, FeedbackEvent,
InviteJoinEvent, RoomConfigEvent, RoomNameEvent, GenericEvent,
RoomPowerLevelsEvent, RoomJoinRulesEvent, RoomOpsPowerLevelsEvent,
RoomCreateEvent, RoomAddStateLevelEvent, RoomSendEventLevelEvent
)
from synapse.util.stringutils import random_string
@@ -30,7 +32,13 @@ class EventFactory(object):
RoomMemberEvent,
FeedbackEvent,
InviteJoinEvent,
RoomConfigEvent
RoomConfigEvent,
RoomPowerLevelsEvent,
RoomJoinRulesEvent,
RoomCreateEvent,
RoomAddStateLevelEvent,
RoomSendEventLevelEvent,
RoomOpsPowerLevelsEvent,
]
def __init__(self, hs):
@@ -39,11 +47,14 @@ class EventFactory(object):
self._event_list[event_class.TYPE] = event_class
self.clock = hs.get_clock()
self.hs = hs
def create_event(self, etype=None, **kwargs):
kwargs["type"] = etype
if "event_id" not in kwargs:
kwargs["event_id"] = random_string(10)
kwargs["event_id"] = "%s@%s" % (
random_string(10), self.hs.hostname
)
if "ts" not in kwargs:
kwargs["ts"] = int(self.clock.time_msec())

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
from synapse.api.constants import Feedback, Membership
from synapse.api.errors import SynapseError
from . import SynapseEvent
from . import SynapseEvent, SynapseStateEvent
class GenericEvent(SynapseEvent):
@@ -103,8 +103,7 @@ class FeedbackEvent(SynapseEvent):
def get_content_template(self):
return {
"type": u"string",
"target_event_id": u"string",
"msg_sender_id": u"string"
"target_event_id": u"string"
}
@@ -132,3 +131,52 @@ class RoomConfigEvent(SynapseEvent):
def get_content_template(self):
return {}
class RoomCreateEvent(SynapseStateEvent):
TYPE = "m.room.create"
def get_content_template(self):
return {}
class RoomJoinRulesEvent(SynapseStateEvent):
TYPE = "m.room.join_rules"
def get_content_template(self):
return {}
class RoomPowerLevelsEvent(SynapseStateEvent):
TYPE = "m.room.power_levels"
def get_content_template(self):
return {}
class RoomAddStateLevelEvent(SynapseStateEvent):
TYPE = "m.room.add_state_level"
def get_content_template(self):
return {}
class RoomSendEventLevelEvent(SynapseStateEvent):
TYPE = "m.room.send_event_level"
def get_content_template(self):
return {}
class RoomOpsPowerLevelsEvent(SynapseStateEvent):
TYPE = "m.room.ops_levels"
def get_content_template(self):
return {}
class RoomAliasesEvent(SynapseStateEvent):
TYPE = "m.room.aliases"
def get_content_template(self):
return {}

View File

@@ -0,0 +1,79 @@
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import collections
class Ratelimiter(object):
"""
Ratelimit message sending by user.
"""
def __init__(self):
self.message_counts = collections.OrderedDict()
def send_message(self, user_id, time_now_s, msg_rate_hz, burst_count):
"""Can the user send a message?
Args:
user_id: The user sending a message.
time_now_s: The time now.
msg_rate_hz: The long term number of messages a user can send in a
second.
burst_count: How many messages the user can send before being
limited.
Returns:
A pair of a bool indicating if they can send a message now and a
time in seconds of when they can next send a message.
"""
self.prune_message_counts(time_now_s)
message_count, time_start, _ignored = self.message_counts.pop(
user_id, (0., time_now_s, None),
)
time_delta = time_now_s - time_start
sent_count = message_count - time_delta * msg_rate_hz
if sent_count < 0:
allowed = True
time_start = time_now_s
message_count = 1.
elif sent_count > burst_count - 1.:
allowed = False
else:
allowed = True
message_count += 1
self.message_counts[user_id] = (
message_count, time_start, msg_rate_hz
)
if msg_rate_hz > 0:
time_allowed = (
time_start + (message_count - burst_count + 1) / msg_rate_hz
)
if time_allowed < time_now_s:
time_allowed = time_now_s
else:
time_allowed = -1
return allowed, time_allowed
def prune_message_counts(self, time_now_s):
for user_id in self.message_counts.keys():
message_count, time_start, msg_rate_hz = (
self.message_counts[user_id]
)
time_delta = time_now_s - time_start
if message_count - time_delta * msg_rate_hz > 0:
break
else:
del self.message_counts[user_id]

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,7 +15,7 @@
"""Contains the URL paths to prefix various aspects of the server with. """
CLIENT_PREFIX = "/matrix/client/api/v1"
FEDERATION_PREFIX = "/matrix/federation/v1"
WEB_CLIENT_PREFIX = "/matrix/client"
CONTENT_REPO_PREFIX = "/matrix/content"
CLIENT_PREFIX = "/_matrix/client/api/v1"
FEDERATION_PREFIX = "/_matrix/federation/v1"
WEB_CLIENT_PREFIX = "/_matrix/client"
CONTENT_REPO_PREFIX = "/_matrix/content"

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,25 +20,26 @@ from synapse.server import HomeServer
from twisted.internet import reactor
from twisted.enterprise import adbapi
from twisted.python.log import PythonLoggingObserver
from twisted.web.resource import Resource
from twisted.web.static import File
from twisted.web.server import Site
from synapse.http.server import JsonResource, RootRedirect, ContentRepoResource
from synapse.http.server import JsonResource, RootRedirect
from synapse.http.content_repository import ContentRepoResource
from synapse.http.client import TwistedHttpClient
from synapse.api.urls import (
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX
)
from synapse.config.homeserver import HomeServerConfig
from synapse.crypto import context_factory
from daemonize import Daemonize
import twisted.manhole.telnet
import argparse
import logging
import logging.config
import sqlite3
import os
import re
import sys
logger = logging.getLogger(__name__)
@@ -56,13 +57,13 @@ SCHEMAS = [
# 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
SCHEMA_VERSION = 3
class SynapseHomeServer(HomeServer):
def build_http_client(self):
return TwistedHttpClient()
return TwistedHttpClient(self)
def build_resource_for_client(self):
return JsonResource()
@@ -74,7 +75,9 @@ class SynapseHomeServer(HomeServer):
return File("webclient") # TODO configurable?
def build_resource_for_content_repo(self):
return ContentRepoResource(self, self.upload_dir, self.auth)
return ContentRepoResource(
self, self.upload_dir, self.auth, self.content_addr
)
def build_db_pool(self):
""" Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we
@@ -90,20 +93,28 @@ class SynapseHomeServer(HomeServer):
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)
if user_version > SCHEMA_VERSION:
raise ValueError("Cannot use this database as it is too " +
"new for the server to understand"
)
elif user_version < SCHEMA_VERSION:
logging.info("Upgrading database from version %d",
user_version
)
# Run every version since after the current version.
for v in range(user_version + 1, SCHEMA_VERSION + 1):
sql_script = read_schema("delta/v%d" % (v))
c.executescript(sql_script)
db_conn.commit()
else:
for sql_loc in SCHEMAS:
sql_script = read_schema(sql_loc)
c.executescript(sql_script)
db_conn.commit()
db_conn.commit()
c.execute("PRAGMA user_version = %d" % SCHEMA_VERSION)
c.close()
@@ -206,37 +217,17 @@ class SynapseHomeServer(HomeServer):
"""
return "%s-%s" % (resource, path_seg)
def start_listening(self, port):
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):
""" Sets up logging with verbosity levels.
Args:
verbosity: The verbosity level.
filename: Log to the given file rather than to the console.
config_path: Path to a python logging config file.
"""
if config_path is None:
log_format = (
'%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s'
)
level = logging.INFO
if verbosity:
level = logging.DEBUG
# FIXME: we need a logging.WARN for a -q quiet option
logging.basicConfig(level=level, filename=filename, format=log_format)
else:
logging.config.fileConfig(config_path)
observer = PythonLoggingObserver()
observer.start()
def start_listening(self, secure_port, unsecure_port):
if secure_port is not None:
reactor.listenSSL(
secure_port, Site(self.root_resource), self.tls_context_factory
)
logger.info("Synapse now listening on port %d", secure_port)
if unsecure_port is not None:
reactor.listenTCP(
unsecure_port, Site(self.root_resource)
)
logger.info("Synapse now listening on port %d", unsecure_port)
def run():
@@ -244,78 +235,55 @@ def run():
def setup():
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--port", dest="port", type=int, default=8080,
help="The port to listen on.")
parser.add_argument("-d", "--database", dest="db", default="homeserver.db",
help="The database name.")
parser.add_argument("-H", "--host", dest="host", default="localhost",
help="The hostname of the server.")
parser.add_argument('-v', '--verbose', dest="verbose", action='count',
help="The verbosity level.")
parser.add_argument('-f', '--log-file', dest="log_file", default=None,
help="File to log to.")
parser.add_argument('--log-config', dest="log_config", default=None,
help="Python logging config")
parser.add_argument('-D', '--daemonize', action='store_true',
default=False, help="Daemonize the home server")
parser.add_argument('--pid-file', dest="pid", help="When running as a "
"daemon, the file to store the pid in",
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
# Because if/when we daemonize we change to root dir.
db_name = os.path.abspath(args.db)
log_file = args.log_file
if log_file:
log_file = os.path.abspath(log_file)
setup_logging(
verbosity=verbosity,
filename=log_file,
config_path=args.log_config,
config = HomeServerConfig.load_config(
"Synapse Homeserver",
sys.argv[1:],
generate_section="Homeserver"
)
logger.info("Server hostname: %s", args.host)
config.setup_logging()
if re.search(":[0-9]+$", args.host):
domain_with_port = args.host
logger.info("Server hostname: %s", config.server_name)
if re.search(":[0-9]+$", config.server_name):
domain_with_port = config.server_name
else:
domain_with_port = "%s:%s" % (args.host, args.port)
domain_with_port = "%s:%s" % (config.server_name, config.bind_port)
tls_context_factory = context_factory.ServerContextFactory(config)
hs = SynapseHomeServer(
args.host,
config.server_name,
domain_with_port=domain_with_port,
upload_dir=os.path.abspath("uploads"),
db_name=db_name,
db_name=config.database_path,
tls_context_factory=tls_context_factory,
config=config,
content_addr=config.content_addr,
)
hs.register_servlets()
hs.create_resource_tree(
web_client=args.webclient,
redirect_root_to_web_client=True)
hs.start_listening(args.port)
web_client=config.webclient,
redirect_root_to_web_client=True,
)
hs.start_listening(config.bind_port, config.unsecure_port)
hs.get_db_pool()
if args.manhole:
if config.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')
reactor.listenTCP(config.manhole, f, interface='127.0.0.1')
if args.daemonize:
if config.daemonize:
print config.pid_file
daemon = Daemonize(
app="synapse-homeserver",
pid=args.pid,
pid=config.pid_file,
action=run,
auto_close_fds=False,
verbose=True,

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

131
synapse/config/_base.py Normal file
View File

@@ -0,0 +1,131 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import sys
import os
import yaml
class ConfigError(Exception):
pass
class Config(object):
def __init__(self, args):
pass
@staticmethod
def abspath(file_path):
return os.path.abspath(file_path) if file_path else file_path
@classmethod
def check_file(cls, file_path, config_name):
if file_path is None:
raise ConfigError(
"Missing config for %s."
" Try running again with --generate-config"
% (config_name,)
)
if not os.path.exists(file_path):
raise ConfigError(
"File % config for %s doesn't exist."
" Try running again with --generate-config"
% (config_name,)
)
return cls.abspath(file_path)
@classmethod
def read_file(cls, file_path, config_name):
cls.check_file(file_path, config_name)
with open(file_path) as file_stream:
return file_stream.read()
@staticmethod
def read_config_file(file_path):
with open(file_path) as file_stream:
return yaml.load(file_stream)
@classmethod
def add_arguments(cls, parser):
pass
@classmethod
def generate_config(cls, args, config_dir_path):
pass
@classmethod
def load_config(cls, description, argv, generate_section=None):
config_parser = argparse.ArgumentParser(add_help=False)
config_parser.add_argument(
"-c", "--config-path",
metavar="CONFIG_FILE",
help="Specify config file"
)
config_parser.add_argument(
"--generate-config",
action="store_true",
help="Generate config file"
)
config_args, remaining_args = config_parser.parse_known_args(argv)
if config_args.generate_config:
if not config_args.config_path:
config_parser.error(
"Must specify where to generate the config file"
)
config_dir_path = os.path.dirname(config_args.config_path)
if os.path.exists(config_args.config_path):
defaults = cls.read_config_file(config_args.config_path)
else:
defaults = {}
else:
if config_args.config_path:
defaults = cls.read_config_file(config_args.config_path)
else:
defaults = {}
parser = argparse.ArgumentParser(
parents=[config_parser],
description=description,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
cls.add_arguments(parser)
parser.set_defaults(**defaults)
args = parser.parse_args(remaining_args)
if config_args.generate_config:
config_dir_path = os.path.dirname(config_args.config_path)
config_dir_path = os.path.abspath(config_dir_path)
if not os.path.exists(config_dir_path):
os.makedirs(config_dir_path)
cls.generate_config(args, config_dir_path)
config = {}
for key, value in vars(args).items():
if (key not in set(["config_path", "generate_config"])
and value is not None):
config[key] = value
with open(config_args.config_path, "w") as config_file:
# TODO(paul) it would be lovely if we wrote out vim- and emacs-
# style mode markers into the file, to hint to people that
# this is a YAML file.
yaml.dump(config, config_file, default_flow_style=False)
sys.exit(0)
return cls(args)

42
synapse/config/captcha.py Normal file
View File

@@ -0,0 +1,42 @@
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ._base import Config
class CaptchaConfig(Config):
def __init__(self, args):
super(CaptchaConfig, self).__init__(args)
self.recaptcha_private_key = args.recaptcha_private_key
self.enable_registration_captcha = args.enable_registration_captcha
self.captcha_ip_origin_is_x_forwarded = args.captcha_ip_origin_is_x_forwarded
@classmethod
def add_arguments(cls, parser):
super(CaptchaConfig, cls).add_arguments(parser)
group = parser.add_argument_group("recaptcha")
group.add_argument(
"--recaptcha-private-key", type=str, default="YOUR_PRIVATE_KEY",
help="The matching private key for the web client's public key."
)
group.add_argument(
"--enable-registration-captcha", type=bool, default=False,
help="Enables ReCaptcha checks when registering, preventing signup "+
"unless a captcha is answered. Requires a valid ReCaptcha public/private key."
)
group.add_argument(
"--captcha_ip_origin_is_x_forwarded", type=bool, default=False,
help="When checking captchas, use the X-Forwarded-For (XFF) header as the client IP "+
"and not the actual client IP."
)

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ._base import Config
import os
class DatabaseConfig(Config):
def __init__(self, args):
super(DatabaseConfig, self).__init__(args)
self.database_path = self.abspath(args.database_path)
@classmethod
def add_arguments(cls, parser):
super(DatabaseConfig, cls).add_arguments(parser)
db_group = parser.add_argument_group("database")
db_group.add_argument(
"-d", "--database-path", default="homeserver.db",
help="The database name."
)
@classmethod
def generate_config(cls, args, config_dir_path):
super(DatabaseConfig, cls).generate_config(args, config_dir_path)
args.database_path = os.path.abspath(args.database_path)

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .tls import TlsConfig
from .server import ServerConfig
from .logger import LoggingConfig
from .database import DatabaseConfig
from .ratelimiting import RatelimitConfig
from .repository import ContentRepositoryConfig
from .captcha import CaptchaConfig
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
RatelimitConfig, ContentRepositoryConfig, CaptchaConfig):
pass
if __name__=='__main__':
import sys
HomeServerConfig.load_config("Generate config", sys.argv[1:], "HomeServer")

67
synapse/config/logger.py Normal file
View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ._base import Config
from twisted.python.log import PythonLoggingObserver
import logging
import logging.config
class LoggingConfig(Config):
def __init__(self, args):
super(LoggingConfig, self).__init__(args)
self.verbosity = int(args.verbose) if args.verbose else None
self.log_config = self.abspath(args.log_config)
self.log_file = self.abspath(args.log_file)
@classmethod
def add_arguments(cls, parser):
super(LoggingConfig, cls).add_arguments(parser)
logging_group = parser.add_argument_group("logging")
logging_group.add_argument(
'-v', '--verbose', dest="verbose", action='count',
help="The verbosity level."
)
logging_group.add_argument(
'-f', '--log-file', dest="log_file", default=None,
help="File to log to."
)
logging_group.add_argument(
'--log-config', dest="log_config", default=None,
help="Python logging config file"
)
def setup_logging(self):
log_format = (
'%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s'
)
if self.log_config is None:
level = logging.INFO
if self.verbosity:
level = logging.DEBUG
# FIXME: we need a logging.WARN for a -q quiet option
logging.basicConfig(
level=level,
filename=self.log_file,
format=log_format
)
else:
logging.config.fileConfig(self.log_config)
observer = PythonLoggingObserver()
observer.start()

View File

@@ -0,0 +1,35 @@
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ._base import Config
class RatelimitConfig(Config):
def __init__(self, args):
super(RatelimitConfig, self).__init__(args)
self.rc_messages_per_second = args.rc_messages_per_second
self.rc_message_burst_count = args.rc_message_burst_count
@classmethod
def add_arguments(cls, parser):
super(RatelimitConfig, cls).add_arguments(parser)
rc_group = parser.add_argument_group("ratelimiting")
rc_group.add_argument(
"--rc-messages-per-second", type=float, default=0.2,
help="number of messages a client can send per second"
)
rc_group.add_argument(
"--rc-message-burst-count", type=float, default=10,
help="number of message a client can send before being throttled"
)

View File

@@ -0,0 +1,39 @@
# -*- 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 ._base import Config
import os
class ContentRepositoryConfig(Config):
def __init__(self, args):
super(ContentRepositoryConfig, self).__init__(args)
self.max_upload_size = self.parse_size(args.max_upload_size)
def parse_size(self, string):
sizes = {"K": 1024, "M": 1024 * 1024}
size = 1
suffix = string[-1]
if suffix in sizes:
string = string[:-1]
size = sizes[suffix]
return int(string) * size
@classmethod
def add_arguments(cls, parser):
super(ContentRepositoryConfig, cls).add_arguments(parser)
db_group = parser.add_argument_group("content_repository")
db_group.add_argument(
"--max-upload-size", default="1M"
)

91
synapse/config/server.py Normal file
View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import nacl.signing
import os
from ._base import Config
from syutil.base64util import encode_base64, decode_base64
class ServerConfig(Config):
def __init__(self, args):
super(ServerConfig, self).__init__(args)
self.server_name = args.server_name
self.signing_key = self.read_signing_key(args.signing_key_path)
self.bind_port = args.bind_port
self.bind_host = args.bind_host
self.unsecure_port = args.unsecure_port
self.daemonize = args.daemonize
self.pid_file = self.abspath(args.pid_file)
self.webclient = True
self.manhole = args.manhole
if not args.content_addr:
host = args.server_name
if ':' not in host:
host = "%s:%d" % (host, args.bind_port)
args.content_addr = "https://%s" % (host,)
self.content_addr = args.content_addr
@classmethod
def add_arguments(cls, parser):
super(ServerConfig, cls).add_arguments(parser)
server_group = parser.add_argument_group("server")
server_group.add_argument("-H", "--server-name", default="localhost",
help="The name of the server")
server_group.add_argument("--signing-key-path",
help="The signing key to sign messages with")
server_group.add_argument("-p", "--bind-port", metavar="PORT",
type=int, help="https port to listen on",
default=8448)
server_group.add_argument("--unsecure-port", metavar="PORT",
type=int, help="http port to listen on",
default=8008)
server_group.add_argument("--bind-host", default="",
help="Local interface to listen on")
server_group.add_argument("-D", "--daemonize", action='store_true',
help="Daemonize the home server")
server_group.add_argument('--pid-file', default="homeserver.pid",
help="When running as a daemon, the file to"
" store the pid in")
server_group.add_argument("--manhole", metavar="PORT", dest="manhole",
type=int,
help="Turn on the twisted telnet manhole"
" service on the given port.")
server_group.add_argument("--content-addr", default=None,
help="The host and scheme to use for the "
"content repository")
def read_signing_key(self, signing_key_path):
signing_key_base64 = self.read_file(signing_key_path, "signing_key")
signing_key_bytes = decode_base64(signing_key_base64)
return nacl.signing.SigningKey(signing_key_bytes)
@classmethod
def generate_config(cls, args, config_dir_path):
super(ServerConfig, cls).generate_config(args, config_dir_path)
base_key_name = os.path.join(config_dir_path, args.server_name)
args.pid_file = os.path.abspath(args.pid_file)
if not args.signing_key_path:
args.signing_key_path = base_key_name + ".signing.key"
if not os.path.exists(args.signing_key_path):
with open(args.signing_key_path, "w") as signing_key_file:
key = nacl.signing.SigningKey.generate()
signing_key_file.write(encode_base64(key.encode()))

130
synapse/config/tls.py Normal file
View File

@@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ._base import Config
from OpenSSL import crypto
import subprocess
import os
GENERATE_DH_PARAMS=False
class TlsConfig(Config):
def __init__(self, args):
super(TlsConfig, self).__init__(args)
self.tls_certificate = self.read_tls_certificate(
args.tls_certificate_path
)
self.tls_private_key = self.read_tls_private_key(
args.tls_private_key_path
)
self.tls_dh_params_path = self.check_file(
args.tls_dh_params_path, "tls_dh_params"
)
@classmethod
def add_arguments(cls, parser):
super(TlsConfig, cls).add_arguments(parser)
tls_group = parser.add_argument_group("tls")
tls_group.add_argument("--tls-certificate-path",
help="PEM encoded X509 certificate for TLS")
tls_group.add_argument("--tls-private-key-path",
help="PEM encoded private key for TLS")
tls_group.add_argument("--tls-dh-params-path",
help="PEM dh parameters for ephemeral keys")
def read_tls_certificate(self, cert_path):
cert_pem = self.read_file(cert_path, "tls_certificate")
return crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)
def read_tls_private_key(self, private_key_path):
private_key_pem = self.read_file(private_key_path, "tls_private_key")
return crypto.load_privatekey(crypto.FILETYPE_PEM, private_key_pem)
@classmethod
def generate_config(cls, args, config_dir_path):
super(TlsConfig, cls).generate_config(args, config_dir_path)
base_key_name = os.path.join(config_dir_path, args.server_name)
if args.tls_certificate_path is None:
args.tls_certificate_path = base_key_name + ".tls.crt"
if args.tls_private_key_path is None:
args.tls_private_key_path = base_key_name + ".tls.key"
if args.tls_dh_params_path is None:
args.tls_dh_params_path = base_key_name + ".tls.dh"
if not os.path.exists(args.tls_private_key_path):
with open(args.tls_private_key_path, "w") as private_key_file:
tls_private_key = crypto.PKey()
tls_private_key.generate_key(crypto.TYPE_RSA, 2048)
private_key_pem = crypto.dump_privatekey(
crypto.FILETYPE_PEM, tls_private_key
)
private_key_file.write(private_key_pem)
else:
with open(args.tls_private_key_path) as private_key_file:
private_key_pem = private_key_file.read()
tls_private_key = crypto.load_privatekey(
crypto.FILETYPE_PEM, private_key_pem
)
if not os.path.exists(args.tls_certificate_path):
with open(args.tls_certificate_path, "w") as certifcate_file:
cert = crypto.X509()
subject = cert.get_subject()
subject.CN = args.server_name
cert.set_serial_number(1000)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
cert.set_issuer(cert.get_subject())
cert.set_pubkey(tls_private_key)
cert.sign(tls_private_key, 'sha256')
cert_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
certifcate_file.write(cert_pem)
if not os.path.exists(args.tls_dh_params_path):
if GENERATE_DH_PARAMS:
subprocess.check_call([
"openssl", "dhparam",
"-outform", "PEM",
"-out", args.tls_dh_params_path,
"2048"
])
else:
with open(args.tls_dh_params_path, "w") as dh_params_file:
dh_params_file.write(
"2048-bit DH parameters taken from rfc3526\n"
"-----BEGIN DH PARAMETERS-----\n"
"MIIBCAKCAQEA///////////JD9qiIWjC"
"NMTGYouA3BzRKQJOCIpnzHQCC76mOxOb\n"
"IlFKCHmONATd75UZs806QxswKwpt8l8U"
"N0/hNW1tUcJF5IW1dmJefsb0TELppjft\n"
"awv/XLb0Brft7jhr+1qJn6WunyQRfEsf"
"5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXT\n"
"mmkWP6j9JM9fg2VdI9yjrZYcYvNWIIVS"
"u57VKQdwlpZtZww1Tkq8mATxdGwIyhgh\n"
"fDKQXkYuNs474553LBgOhgObJ4Oi7Aei"
"j7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq\n"
"5RXSJhiY+gUQFXKOWoqsqmj/////////"
"/wIBAg==\n"
"-----END DH PARAMETERS-----\n"
)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,160 +0,0 @@
# -*- 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.
import ConfigParser as configparser
import argparse
import socket
import sys
import os
from OpenSSL import crypto
import nacl.signing
from syutil.base64util import encode_base64
import subprocess
def load_config(description, argv):
config_parser = argparse.ArgumentParser(add_help=False)
config_parser.add_argument("-c", "--config-path", metavar="CONFIG_FILE",
help="Specify config file")
config_args, remaining_args = config_parser.parse_known_args(argv)
if config_args.config_path:
config = configparser.SafeConfigParser()
config.read([config_args.config_path])
defaults = dict(config.items("KeyServer"))
else:
defaults = {}
parser = argparse.ArgumentParser(
parents=[config_parser],
description=description,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.set_defaults(**defaults)
parser.add_argument("--server-name", default=socket.getfqdn(),
help="The name of the server")
parser.add_argument("--signing-key-path",
help="The signing key to sign responses with")
parser.add_argument("--tls-certificate-path",
help="PEM encoded X509 certificate for TLS")
parser.add_argument("--tls-private-key-path",
help="PEM encoded private key for TLS")
parser.add_argument("--tls-dh-params-path",
help="PEM encoded dh parameters for ephemeral keys")
parser.add_argument("--bind-port", type=int,
help="TCP port to listen on")
parser.add_argument("--bind-host", default="",
help="Local interface to listen on")
args = parser.parse_args(remaining_args)
server_config = vars(args)
del server_config["config_path"]
return server_config
def generate_config(argv):
parser = argparse.ArgumentParser()
parser.add_argument("-c", "--config-path", help="Specify config file",
metavar="CONFIG_FILE", required=True)
parser.add_argument("--server-name", default=socket.getfqdn(),
help="The name of the server")
parser.add_argument("--signing-key-path",
help="The signing key to sign responses with")
parser.add_argument("--tls-certificate-path",
help="PEM encoded X509 certificate for TLS")
parser.add_argument("--tls-private-key-path",
help="PEM encoded private key for TLS")
parser.add_argument("--tls-dh-params-path",
help="PEM encoded dh parameters for ephemeral keys")
parser.add_argument("--bind-port", type=int, required=True,
help="TCP port to listen on")
parser.add_argument("--bind-host", default="",
help="Local interface to listen on")
args = parser.parse_args(argv)
dir_name = os.path.dirname(args.config_path)
base_key_name = os.path.join(dir_name, args.server_name)
if args.signing_key_path is None:
args.signing_key_path = base_key_name + ".signing.key"
if args.tls_certificate_path is None:
args.tls_certificate_path = base_key_name + ".tls.crt"
if args.tls_private_key_path is None:
args.tls_private_key_path = base_key_name + ".tls.key"
if args.tls_dh_params_path is None:
args.tls_dh_params_path = base_key_name + ".tls.dh"
if not os.path.exists(args.signing_key_path):
with open(args.signing_key_path, "w") as signing_key_file:
key = nacl.signing.SigningKey.generate()
signing_key_file.write(encode_base64(key.encode()))
if not os.path.exists(args.tls_private_key_path):
with open(args.tls_private_key_path, "w") as private_key_file:
tls_private_key = crypto.PKey()
tls_private_key.generate_key(crypto.TYPE_RSA, 2048)
private_key_pem = crypto.dump_privatekey(
crypto.FILETYPE_PEM, tls_private_key
)
private_key_file.write(private_key_pem)
else:
with open(args.tls_private_key_path) as private_key_file:
private_key_pem = private_key_file.read()
tls_private_key = crypto.load_privatekey(
crypto.FILETYPE_PEM, private_key_pem
)
if not os.path.exists(args.tls_certificate_path):
with open(args.tls_certificate_path, "w") as certifcate_file:
cert = crypto.X509()
subject = cert.get_subject()
subject.CN = args.server_name
cert.set_serial_number(1000)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
cert.set_issuer(cert.get_subject())
cert.set_pubkey(tls_private_key)
cert.sign(tls_private_key, 'sha256')
cert_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)
certifcate_file.write(cert_pem)
if not os.path.exists(args.tls_dh_params_path):
subprocess.check_call([
"openssl", "dhparam",
"-outform", "PEM",
"-out", args.tls_dh_params_path,
"2048"
])
config = configparser.SafeConfigParser()
config.add_section("KeyServer")
for key, value in vars(args).items():
if key != "config_path":
config.set("KeyServer", key, str(value))
with open(args.config_path, "w") as config_file:
config.write(config_file)
if __name__ == "__main__":
generate_config(sys.argv[1:])

View File

@@ -0,0 +1,43 @@
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from twisted.internet import ssl
from OpenSSL import SSL
from twisted.internet._sslverify import _OpenSSLECCurve, _defaultCurveName
class ServerContextFactory(ssl.ContextFactory):
"""Factory for PyOpenSSL SSL contexts that are used to handle incoming
connections and to make connections to remote servers."""
def __init__(self, config):
self._context = SSL.Context(SSL.SSLv23_METHOD)
self.configure_context(self._context, config)
@staticmethod
def configure_context(context, config):
try:
_ecCurve = _OpenSSLECCurve(_defaultCurveName)
_ecCurve.addECKeyToContext(context)
except:
pass
context.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
context.use_certificate(config.tls_certificate)
context.use_privatekey(config.tls_private_key)
context.load_tmp_dh(config.tls_dh_params_path)
context.set_cipher_list("!ADH:HIGH+kEDH:!AECDH:HIGH+kEECDH")
def getContext(self):
return self._context

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -628,7 +628,6 @@ class _TransactionQueue(object):
for deferred in deferreds:
deferred.errback(e)
yield deferred
finally:
# We want to be *very* sure we delete this after we stop processing

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -68,6 +68,7 @@ class Pdu(JsonEncodedObject):
"power_level",
"prev_state_id",
"prev_state_origin",
"required_power_level",
]
internal_keys = [

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
# limitations under the License.
from twisted.internet import defer
from synapse.api.errors import LimitExceededError
class BaseHandler(object):
@@ -25,10 +26,21 @@ class BaseHandler(object):
self.room_lock = hs.get_room_lock_manager()
self.state_handler = hs.get_state_handler()
self.distributor = hs.get_distributor()
self.ratelimiter = hs.get_ratelimiter()
self.clock = hs.get_clock()
self.hs = hs
class BaseRoomHandler(BaseHandler):
def ratelimit(self, user_id):
time_now = self.clock.time()
allowed, time_allowed = self.ratelimiter.send_message(
user_id, time_now,
msg_rate_hz=self.hs.config.rc_messages_per_second,
burst_count=self.hs.config.rc_message_burst_count,
)
if not allowed:
raise LimitExceededError(
retry_after_ms=int(1000*(time_allowed - time_now)),
)
@defer.inlineCallbacks
def _on_new_room_event(self, event, snapshot, extra_destinations=[],

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -18,8 +18,11 @@ from twisted.internet import defer
from ._base import BaseHandler
from synapse.api.errors import SynapseError
from synapse.http.client import HttpClient
from synapse.api.events.room import RoomAliasesEvent
import logging
import sqlite3
logger = logging.getLogger(__name__)
@@ -36,7 +39,8 @@ class DirectoryHandler(BaseHandler):
)
@defer.inlineCallbacks
def create_association(self, room_alias, room_id, servers):
def create_association(self, user_id, room_alias, room_id, servers=None):
# TODO(erikj): Do auth.
if not room_alias.is_mine:
@@ -47,12 +51,43 @@ class DirectoryHandler(BaseHandler):
# TODO(erikj): Check if there is a current association.
yield self.store.create_room_alias_association(
room_alias,
room_id,
servers
if not servers:
servers = yield self.store.get_joined_hosts_for_room(room_id)
if not servers:
raise SynapseError(400, "Failed to get server list")
try:
yield self.store.create_room_alias_association(
room_alias,
room_id,
servers
)
except sqlite3.IntegrityError:
defer.returnValue("Already exists")
# TODO: Send the room event.
aliases = yield self.store.get_aliases_for_room(room_id)
event = self.event_factory.create_event(
etype=RoomAliasesEvent.TYPE,
state_key=self.hs.hostname,
room_id=room_id,
user_id=user_id,
content={"aliases": aliases},
)
snapshot = yield self.store.snapshot_room(
room_id=room_id,
user_id=user_id,
)
yield self.state_handler.handle_new_event(event, snapshot)
yield self._on_new_room_event(event, snapshot, extra_users=[user_id])
@defer.inlineCallbacks
def get_association(self, room_alias):
room_id = None
@@ -68,7 +103,10 @@ class DirectoryHandler(BaseHandler):
result = yield self.federation.make_query(
destination=room_alias.domain,
query_type="directory",
args={"room_alias": room_alias.to_string()},
args={
"room_alias": room_alias.to_string(),
HttpClient.RETRY_DNS_LOOKUP_FAILURES: False
}
)
if result and "room_id" in result and "servers" in result:
@@ -79,6 +117,9 @@ class DirectoryHandler(BaseHandler):
defer.returnValue({})
return
extra_servers = yield self.store.get_joined_hosts_for_room(room_id)
servers = list(set(extra_servers) | set(servers))
defer.returnValue({
"room_id": room_id,
"servers": servers,

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -126,5 +126,7 @@ class EventHandler(BaseHandler):
defer.returnValue(None)
return
yield self.auth.check(event, raises=True)
if hasattr(event, "room_id"):
yield self.auth.check_joined_room(event.room_id, user.to_string())
defer.returnValue(event)

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -21,8 +21,9 @@ from synapse.api.events.room import InviteJoinEvent, RoomMemberEvent
from synapse.api.constants import Membership
from synapse.util.logutils import log_function
from synapse.federation.pdu_codec import PduCodec
from synapse.api.errors import SynapseError
from twisted.internet import defer
from twisted.internet import defer, reactor
import logging
@@ -133,7 +134,7 @@ class FederationHandler(BaseHandler):
yield self.hs.get_handlers().room_member_handler.change_membership(
new_event,
do_auth=True
do_auth=False,
)
else:
@@ -231,7 +232,12 @@ class FederationHandler(BaseHandler):
# TODO (erikj): Time out here.
d = defer.Deferred()
self.waiting_for_join_list.setdefault((joinee, room_id), []).append(d)
yield d
reactor.callLater(10, d.cancel)
try:
yield d
except defer.CancelledError:
raise SynapseError(500, "Unable to join remote room")
try:
yield self.store.store_room(

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ from synapse.api.constants import Membership
from synapse.api.events.room import RoomTopicEvent
from synapse.api.errors import RoomError
from synapse.streams.config import PaginationConfig
from ._base import BaseRoomHandler
from ._base import BaseHandler
import logging
@@ -27,7 +27,7 @@ logger = logging.getLogger(__name__)
class MessageHandler(BaseRoomHandler):
class MessageHandler(BaseHandler):
def __init__(self, hs):
super(MessageHandler, self).__init__(hs)
@@ -76,6 +76,12 @@ class MessageHandler(BaseRoomHandler):
Raises:
SynapseError if something went wrong.
"""
self.ratelimit(event.user_id)
# TODO(paul): Why does 'event' not have a 'user' object?
user = self.hs.parse_userid(event.user_id)
assert user.is_mine, "User must be our own: %s" % (user,)
if stamp_event:
event.content["hsob_ts"] = int(self.clock.time_msec())
@@ -86,6 +92,10 @@ class MessageHandler(BaseRoomHandler):
yield self._on_new_room_event(event, snapshot)
self.hs.get_handlers().presence_handler.bump_presence_active_time(
user
)
@defer.inlineCallbacks
def get_messages(self, user_id=None, room_id=None, pagin_config=None,
feedback=False):
@@ -132,7 +142,12 @@ class MessageHandler(BaseRoomHandler):
SynapseError if something went wrong.
"""
snapshot = yield self.store.snapshot_room(event.room_id, event.user_id)
snapshot = yield self.store.snapshot_room(
event.room_id,
event.user_id,
state_type=event.type,
state_key=event.state_key,
)
yield self.auth.check(event, snapshot, raises=True)
@@ -274,11 +289,11 @@ class MessageHandler(BaseRoomHandler):
messages, token = yield self.store.get_recent_events_for_room(
event.room_id,
limit=limit,
end_token=now_token.events_key,
end_token=now_token.room_key,
)
start_token = now_token.copy_and_replace("events_key", token[0])
end_token = now_token.copy_and_replace("events_key", token[1])
start_token = now_token.copy_and_replace("room_key", token[0])
end_token = now_token.copy_and_replace("room_key", token[1])
d["messages"] = {
"chunk": [m.get_dict() for m in messages],

Some files were not shown because too many files have changed in this diff Show More