Compare commits

..

904 Commits

Author SHA1 Message Date
Erik Johnston
b63691f6e2 Merge branch 'release-v0.4.2' of github.com:matrix-org/synapse 2014-10-31 17:48:05 +00:00
Erik Johnston
13fad06239 Bump version numbers and change log 2014-10-31 17:23:01 +00:00
Kegan Dougal
71ef8f0636 SYWEB-102: Fix desktop notification msg when a user with no display name joins a room. 2014-10-31 11:56:36 +00:00
Kegan Dougal
20cf0b7aeb Factor out notification logic. 2014-10-31 11:54:04 +00:00
Kegan Dougal
ac2a177070 Add notification-service.js to handle binging/notifications. Shift logic to this service. 2014-10-31 11:20:07 +00:00
Kegan Dougal
188de756be SYWEB-45: Display the user_id of a user when hovering over their avatar next to their messages. 2014-10-31 10:06:28 +00:00
Kegan Dougal
baf472f83f SYWEB-63: Fix desktop notification message when notifying for an image. 2014-10-31 10:02:56 +00:00
Kegan Dougal
86d3180666 SYWEB-12: You'll be needing this. 2014-10-30 17:33:14 +00:00
Kegan Dougal
864de6a7a4 SYWEB-12: Minor layout tweaks. 2014-10-30 17:23:11 +00:00
Kegan Dougal
ea6bec96d3 SYWEB-12: UX tweaks. 2014-10-30 17:16:16 +00:00
Kegan Dougal
f618f99ece SYWEB-12: Add ability to add new state events. 2014-10-30 17:01:17 +00:00
Kegan Dougal
0985bfb775 SYWEB-12: Allow edited state events to be submitted. 2014-10-30 16:31:47 +00:00
Kegan Dougal
9de9661baa SYWEB-12: More formatting and tweaking of state event JSON.
Use a proper elastic directive to make the <textarea> resize dynamically.
Use an 'asjson' directive to turn an ngModel of a JSON object into a
formatted JSON string so it can be displayed on the textarea. Also, deep
copy the state events being displayed, else it actually alters the underlying
data structures when playing around with the JSON in the textarea!
2014-10-30 16:21:27 +00:00
Kegan Dougal
6f3f631fd1 SYWEB-12: More formatting. 2014-10-30 13:24:40 +00:00
Kegan Dougal
40342af459 SYWEB-12: Format room info dialog better. 2014-10-30 11:53:28 +00:00
Kegan Dougal
8e8bbb00f5 SYWEB-12: Store unknown state events so they are displayed in the Room Info dialog. 2014-10-30 11:22:47 +00:00
Kegan Dougal
d5aa965522 SYWEB-12: Add a 'Room Info' button which displays all state content.
Content displayed in a modal dialog. Currently only read-only.
2014-10-30 11:15:44 +00:00
Mark Haines
7d709542ca Fix pep8 warnings 2014-10-30 11:10:17 +00:00
Kegan Dougal
b4b492824e SYWEB-112: Use the right user ID when determining invites for display on the recents list. 2014-10-30 10:05:43 +00:00
Kegan Dougal
0f192579ac SYWEB-48: Better regex for binging on usernames.
This uses /\blocalpart\b|\bdisplayname\b/i which is overall a lot
better than before. This specifically gets @localpart references
which the bug was originally for.
2014-10-29 17:44:57 +00:00
Paul "LeoNerd" Evans
beae9acfcc Use floating-point rather than integer division to handle timeouts so that non-zero but sub-second waits don't collapse to zero 2014-10-29 17:03:02 +00:00
Kegan Dougal
0d278f5da8 SYWEB-127: Open event info modal dialog when the bubble is clicked.
This allows images to be clicked by clicking on the edge with the bubble.
This is important since Redactions are only visible on the event info
screen.
2014-10-29 16:35:33 +00:00
Paul "LeoNerd" Evans
b1ee6fd7ed Fix an off-by-one bug in presence event stream pagination; this might be responsible for any number of bug reports 2014-10-29 16:16:01 +00:00
Paul "LeoNerd" Evans
d6bcffa929 Construct a source-specific 'SourcePaginationConfig' to pass into get_pagination_rows; meaning each source doesn't have to care about its own name any more 2014-10-29 16:16:01 +00:00
Paul "LeoNerd" Evans
c5a25f610a Remove redundant (and incorrect) presence pagination fetching code 2014-10-29 16:16:01 +00:00
Matthew Hodgson
194e1e9151 oops - fix css on desktop 2014-10-29 17:02:16 +01:00
Kegan Dougal
c2f2e26ec5 SYWEB-98: Handle incoming m.room.redaction events.
UI for redactions is now complete.
2014-10-29 15:48:41 +00:00
Kegan Dougal
6d4617960d SYWEB-98: Add redactEvent matrix API call. 2014-10-29 15:31:50 +00:00
Kegan Dougal
70137409ed SYWEB-98: Add a 'Redact' button to the event info modal dialog.
I think this is better than overriding the right-click contextual menu.
Currently clicking this button does nothing.
2014-10-29 15:02:30 +00:00
Kegan Dougal
ed241ba032 Implement SYWEB-58: Clicking a notification now takes you to that room. 2014-10-29 11:29:26 +00:00
Kegan Dougal
2a44558fbd Fix SYWEB-128 : Auto-scroll broken if not exactly at bottom of list.
Added a small 10px buffer so if the list isn't quite at the bottom it
still actually scrolls.
2014-10-29 11:05:05 +00:00
Matthew Hodgson
51b81b472d fix mobile CSS layout 2014-10-28 10:03:59 +01:00
Kegan Dougal
4f6acf114c Fix SYWEB-110 : Prevent room ID leaking by looking for an m.room.name 2014-10-27 17:05:13 +00:00
Mark Haines
4841b6d4ba Remove duplicate join_event from create_room 2014-10-27 16:55:51 +00:00
Kegan Dougal
fc121f9785 Fix SYWEB-114 : Error message when trying to invite a user already in the room. 2014-10-27 16:48:43 +00:00
Kegan Dougal
332b2869ef Don't clobber existing css 2014-10-27 16:42:19 +00:00
Kegan Dougal
f4e64ac253 SYWEB-121: Have some bootstrap files. 2014-10-27 16:31:10 +00:00
Kegan Dougal
da87990bd6 Implement SYWEB-121 : Display JSON when clicking messages.
JSON is displayed as a modal dialog via AngularJS' bootstrap module,
"ui.bootstrap".
2014-10-27 16:30:07 +00:00
giomfo
cf1feee21d HandleRoomMember: handle correctly prev_content 2014-10-27 14:17:16 +01:00
Kegan Dougal
6603e39e6a Fix SYWEB-109 : No error if HS rejects the username in registration.
Display all error messages from the server when registering, rather
than just the types of errors the client recognises.
2014-10-27 11:58:23 +00:00
Kegan Dougal
f3bb3943c9 Remove test_pyflakes. 2014-10-27 11:13:04 +00:00
Mark Haines
7bd604e3be Test pyflakes jenikns integration 2014-10-27 10:56:38 +00:00
Mark Haines
d56e389a95 Fix pyflakes warnings 2014-10-27 10:33:17 +00:00
Mark Haines
15be181642 Add log message if we can't enable ECC. Require pyopenssl>=0.14 since 0.13 doesn't seem to have ECC 2014-10-24 19:27:12 +01:00
Mark Haines
db2e350e29 Wrap preparing the database in a transaction. Otherwise it will take many seconds to complete because sqlite will create a transaction per statement 2014-10-24 19:04:26 +01:00
Matthew Hodgson
1342bcedaf switch from the deprecated msg.content.prev to msg.prev_content.membership, and fix the bug where kicks of unjoined users aren't displayed sensibly in the history 2014-10-24 16:14:47 +01:00
Mark Haines
be6d41ffe5 Merge branch 'master' into develop 2014-10-24 10:57:38 +01:00
Kegan Dougal
53f69bf089 Added pylint config file: ignore missing-docstring messages. 2014-10-24 10:22:09 +01:00
David Baker
51edfeb3d0 Coturn's timestamps are in seconds, not milliseconds 2014-10-21 18:57:13 +01:00
manuroe
9e57ed2b1f Added a param (--no-rate-limit) to demo/start.sh to disable the HS rate limit 2014-10-20 18:35:39 +01:00
Erik Johnston
4ae0844ee3 Merge branch 'master' of github.com:matrix-org/synapse into develop 2014-10-20 17:53:18 +01:00
Mark Haines
06a5a40e90 use a tagged version of syutil rather than master 2014-10-20 15:11:01 +01:00
Mark Haines
f0382357ca Use https link to download syutil as not everyone has ssh access to github. 2014-10-20 14:43:37 +01:00
Mark Haines
4be99c2989 Add get_json method to 3pid http client. Better logging for errors in 3pid requests 2014-10-20 14:10:08 +01:00
Mark Haines
9c0826592c Fix auto generating signing_keys 2014-10-18 16:56:44 +01:00
Matthew Hodgson
8f0997d17d improve changelog slightly 2014-10-18 11:46:11 +01:00
Matthew Hodgson
58b1a891ce fix timestamps some more
Merge branch 'develop' of git+ssh://github.com/matrix-org/synapse
2014-10-17 23:54:21 +01:00
Matthew Hodgson
e9abbe89f3 more timestamp fixes 2014-10-17 23:53:24 +01:00
Erik Johnston
b3e6cd59a1 Bump the other version 2014-10-18 00:29:55 +02:00
Erik Johnston
f22d023c4b Bump version 2014-10-18 00:29:15 +02:00
Erik Johnston
4c8111ef98 Bunp to change log. 2014-10-18 00:28:36 +02:00
Erik Johnston
f05dce54a7 Merge pull request #9 from matrix-org/develop
Fix issue with timestamps in webclient
2014-10-18 00:25:57 +02:00
Matthew Hodgson
514e0fd4b6 fix webclient to know about right timestamps 2014-10-17 23:11:55 +01:00
Erik Johnston
cb939ed450 I can't type apparently 2014-10-17 21:29:28 +01:00
Erik Johnston
7ea38a0c9d Update changelong and upgrade 2014-10-17 21:28:32 +01:00
Erik Johnston
f1ddbfaae4 Merge branch 'release-v0.4.0' of github.com:matrix-org/synapse into develop 2014-10-17 21:04:07 +01:00
Erik Johnston
449739e6a3 Merge branch 'release-v0.4.0' of github.com:matrix-org/synapse 2014-10-17 21:02:16 +01:00
Erik Johnston
ac9345b47a Check that we have auth headers and fail nicely 2014-10-17 21:00:58 +01:00
Erik Johnston
cd198dfea8 More log lines. 2014-10-17 20:58:47 +01:00
Mark Haines
3187b5ba2d add log line for checking verifying signatures 2014-10-17 20:56:21 +01:00
Erik Johnston
5356044b77 Bump syutil dependency 2014-10-17 20:35:28 +01:00
Erik Johnston
71e6a94af7 Bump version and changelog 2014-10-17 20:26:26 +01:00
Erik Johnston
5662be894e Bump database version number. 2014-10-17 20:26:18 +01:00
Erik Johnston
a065becea5 Merge branch 'docs-restructuring' of github.com:matrix-org/synapse into develop 2014-10-17 20:18:02 +01:00
Mark Haines
82c5820767 keep 'origin_server_ts' as 'ts' in the database to avoid needlessly updating schema 2014-10-17 17:31:48 +01:00
Mark Haines
f5cf7ac25b SPEC-7: Rename 'ts' to 'origin_server_ts' 2014-10-17 17:12:25 +01:00
Mark Haines
456017e0ae SPEC-7: Don't stamp event contents with 'hsob_ts' 2014-10-17 16:55:55 +01:00
Matthew Hodgson
be2a9a8d1a move gendoc into matrix-doc project 2014-10-17 02:09:07 +01:00
Kegan Dougal
79bd6e77b8 Remove warning since the end result is still $sanitize'd 2014-10-15 14:45:38 +01:00
Kegan Dougal
da19fd0d1a Add unsanitizedLinky filter to fix links in formatted messages.
This filter is identical to ngSanitize's linky but instead of
sanitizing text which isn't linkified in the addText function,
it doesn't.
2014-10-15 14:42:14 +01:00
Kegan Dougal
07890b43ca Remove org.matrix.custom.text.html event type and replace it with 'format' and 'formatted_body' keys on m.text messages 2014-10-15 13:57:19 +01:00
Kegan Dougal
f4667f86af Add support for org.matrix.custom.text.html
This format will remain undocumented as it is not yet suitable for
introduction into the specification.
2014-10-15 09:32:02 +01:00
Paul "LeoNerd" Evans
13b560971e Make sure to return an empty JSON object ({}) from presence PUT/POST requests rather than an empty string ("") because most deserialisers won't like the latter 2014-10-14 16:48:15 +01:00
Mark Haines
9aed791fc3 SYN-103: Ignore the 'origin' key in received EDUs. Instead take the origin from the transaction itself 2014-10-14 16:44:27 +01:00
Mark Haines
f74e850b5c remove debugging logging for signing requests 2014-10-14 11:46:13 +01:00
Erik Johnston
4fe5dfa74c Note that this breaks federation 2014-10-14 10:30:50 +01:00
Mark Haines
636a0dbde7 Merge pull request #8 from matrix-org/server2server_signing
Server2server signing
2014-10-14 10:06:04 +01:00
Matthew Hodgson
c18a6433d4 typoe 2014-10-13 23:24:14 +01:00
Mark Haines
34034af1c9 Better response message when signature is missing or unsupported 2014-10-13 16:47:23 +01:00
Mark Haines
07639c79d9 Respond with more helpful error messages for unsigned requests 2014-10-13 16:39:15 +01:00
Mark Haines
25d80f35f1 Raise a SynapseError if the authorisation header is missing or malformed 2014-10-13 15:53:18 +01:00
Mark Haines
75e517a2da Remove debug logging, raise a proper SynapseError if the auth header is missing 2014-10-13 15:41:20 +01:00
Mark Haines
6684855767 Verify signatures for server2server requests 2014-10-13 14:37:46 +01:00
Mark Haines
10ef8e6e4b SYN-75 sign at the request level rather than the transaction level 2014-10-13 11:49:55 +01:00
Mark Haines
cecda27d73 Merge branch 'develop' into server2server_signing 2014-10-13 11:06:36 +01:00
Mark Haines
984e207b59 Merge branch develop into server2server_signing
Conflicts:
	synapse/app/homeserver.py
2014-10-13 10:58:50 +01:00
Mark Haines
693d0b8f45 Replace on_send_callback with something a bit clearer so that we can sign messages 2014-10-13 10:49:04 +01:00
Matthew Hodgson
66df7f1aaf remove wishlist in favour of jira 2014-10-12 00:00:37 +01:00
Matthew Hodgson
259b5e8451 move swagger JSON from synapse project to matrix-doc project 2014-10-09 20:43:07 +02:00
Matthew Hodgson
e1170d4edb move matrix-generic content to new matrix-doc git project 2014-10-09 20:38:00 +02:00
Kegan Dougal
81b956c70d Add spec-additions.rst with info on recaptcha and common event fields. 2014-10-09 18:08:19 +01:00
Kegan Dougal
868eb478d8 Fixed test. 2014-10-09 15:55:07 +01:00
Kegan Dougal
3db09c4d15 Still broken. 2014-10-09 15:53:40 +01:00
Kegan Dougal
83c53113af Break a test. 2014-10-09 15:51:05 +01:00
Kegan Dougal
d224358e21 Restructure specification sections. 2014-10-09 11:08:06 +01:00
Kegan Dougal
72aef114ab Fix unit test. 2014-10-08 15:18:19 +01:00
Kegan Dougal
6045bd89fb Break unit test. 2014-10-08 15:16:03 +01:00
Erik Johnston
5b096cc3db Merge pull request #7 from matrix-org/paul/doc
Clarify that room alias domain names will be server-scoped; nonlocal edi...
2014-10-07 16:35:32 +01:00
Paul "LeoNerd" Evans
917af4705b Clarify that room alias domain names will be server-scoped; nonlocal edits are unliekly to work but nonlocal lookups will 2014-10-07 16:23:12 +01:00
Erik Johnston
9ac53ef8cf SPEC-3: First hack at defining some of the various event related concepts 2014-10-07 11:38:02 +01:00
Erik Johnston
2fc00508fb Add quick and dirty doc about state resolution 2014-10-06 17:34:44 +01:00
Kegan Dougal
c72074b48e Clarify how-to some more. 2014-10-06 14:57:26 +01:00
Kegan Dougal
3ef2c946d5 Update JSFiddles/how-to to support the new registration format. 2014-10-06 14:52:46 +01:00
Kegan Dougal
aaf1d499bf Add more section headings. 2014-10-06 13:18:52 +01:00
Kegan Dougal
94982392be Clarify room permission / power level information. 2014-10-06 12:41:48 +01:00
Kegan Dougal
51276c60bf Add information about the initialSync API.
Outline and describe the keys from the initial sync API. Hide room-scoped
initial sync API for now as it is not implemented and needs more thought before
it can be specced.
2014-10-06 10:32:04 +01:00
Kegan Dougal
78a3f43d9d swagger: Added DELETE method for directory server. 2014-10-06 09:23:19 +01:00
Kegan Dougal
02a44664b9 More spec work. 2014-10-03 17:38:30 +01:00
Erik Johnston
1fa0454288 Merge pull request #6 from matrix-org/paul/doc
Clarify how m.room.alias event works
2014-10-03 14:50:08 +01:00
Paul "LeoNerd" Evans
ca0e8dedfb Clarify how m.room.alias event works 2014-10-03 14:45:42 +01:00
Kegan Dougal
ba11afafb9 Flesh out room alias section. 2014-10-03 14:39:58 +01:00
Kegan Dougal
7e1437c6b1 Add more information to TODOs. Explain m.room.join_rules. 2014-10-03 10:34:29 +01:00
Paul "LeoNerd" Evans
1aa5cc9178 Federation protocol URLs should have an H2 heading, not H1 2014-10-02 18:11:04 +01:00
Paul "LeoNerd" Evans
bc1d685a8c Remove TODO note about VoIP events as they now have their own entire section 2014-10-02 18:00:31 +01:00
Erik Johnston
f6b9853ad0 Merge pull request #5 from matrix-org/paul/doc
Document the Profile system
2014-10-02 17:37:21 +01:00
Paul "LeoNerd" Evans
de38f54f22 Document the Profile system 2014-10-02 17:18:32 +01:00
Paul Evans
96213f69a2 Merge pull request #4 from matrix-org/erikj-spec-changes
Erikj spec changes
2014-10-02 14:46:40 +01:00
Erik Johnston
036333412d Add todo notes 2014-10-02 14:38:53 +01:00
Erik Johnston
82e278029c Remove incorrect reasons for empty PDU lists. 2014-10-02 14:38:22 +01:00
Mark Haines
b9cdc443d7 Fix pyflakes errors 2014-10-02 14:37:30 +01:00
Erik Johnston
1561ef56ed Remove note about assymetry of having left a room.
Currently, if you leave a room you still appear in the members list.
This is basically a bug with the current implementation/spec, rather
than something that should happen.
2014-10-02 14:35:39 +01:00
Erik Johnston
f368ad946e m.room.ops_levels includes redact_level 2014-10-02 14:33:26 +01:00
Erik Johnston
918e71adb7 Don't use spaces in example room alias 2014-10-02 14:31:21 +01:00
Erik Johnston
cf3188352b Fix default value and key names. 2014-10-02 14:30:25 +01:00
Erik Johnston
6860a18c12 Be less alarmist about not using an ID server. 2014-10-02 14:27:35 +01:00
Erik Johnston
ff553cc9dd Alias lookups return a server list. 2014-10-02 14:26:58 +01:00
Mark Haines
574377636e Add a keyword argument to get_json to avoid retrying on DNS failures. Rather than passing MatrixHttpClient.RETRY_DNS_LOOKUP_FAILURES as a fake query string parameter 2014-10-02 14:26:13 +01:00
Erik Johnston
b2d41b1cd9 All room state is currently shared. 2014-10-02 14:25:47 +01:00
David Baker
9435830351 Merge branch 'master' into develop 2014-10-02 14:11:17 +01:00
David Baker
d694619a95 Fix ncorrect ports in documentation and add notes on how generate-config also generates certs bound to whatever hostname you give with --generate-config.
SYN-87 #resolved
2014-10-02 14:09:27 +01:00
Mark Haines
4f11518934 Split PlainHttpClient into separate clients for talking to Identity servers and talking to Capatcha servers 2014-10-02 14:03:26 +01:00
Mark Haines
2d55d43d40 Merge branch 'master' into develop 2014-10-02 11:03:13 +01:00
Mark Haines
45f7677bdc Trivial formatting fixes for README. 2014-10-02 11:00:21 +01:00
Mark Haines
099083ea6b Merge remote-tracking branch 'origin/master' into develop 2014-10-02 10:46:41 +01:00
David Baker
7a322b6326 Update README setup instructions to be correct. Make synapse spit out explanatory note when generating config to tell people to look at it and customise it. 2014-10-02 10:43:22 +01:00
David Baker
d1adb19b8a Re-apply a0b1b34c71 to master (fixing synctl) 2014-10-02 10:38:11 +01:00
David Baker
a0b1b34c71 Make instructions synctl gives for generateing a config file actuall generate a config file. Also, make synctil run synapse correctly by invoking a module such that the path is correct to pull in other bits from the working directory rather than requiring them to be on the PYTHONPATH (which would lead to people being very confused when they edit source in the working directory and their changes do not take effect). 2014-10-02 09:55:26 +01:00
Paul "LeoNerd" Evans
bf8b9b90cd Added a TODO-doc marker about the presence timing system 2014-10-01 19:37:18 +01:00
Paul "LeoNerd" Evans
c5757a0266 Define the client and server APIs for Presence 2014-10-01 19:35:13 +01:00
Paul "LeoNerd" Evans
ee447abcad Continue moving content out of docs/model/presence into the main spec; delete model docs that are duplicated 2014-10-01 18:34:08 +01:00
Erik Johnston
a940a87ddc SPEC-25: Add details on how to prune redacted events.
SPEC-25 #comment I've added the details of what the server should do on
receipt of a redaction event. In reality it can do whatever it wants,
and its probably a reasonable implementation to flag it up to a server
admin for verification before actually redacting an event.
2014-10-01 18:18:44 +01:00
Paul "LeoNerd" Evans
5813e81dc6 Move documented but-unimplemented 'presence idle times' into a new document to contain such features 2014-10-01 17:59:55 +01:00
Erik Johnston
a6d3be4dbf s/m.room.redacted/m.room.redaction/ 2014-10-01 17:55:31 +01:00
Paul "LeoNerd" Evans
166bec0c08 Nuke the entire 'Typing Notifications' spec section given as they don't exist yet in the implementation 2014-10-01 17:33:18 +01:00
Mark Haines
c8d67beb9c remove "red", "blue" and "green" server_name mappings 2014-10-01 15:52:07 +01:00
Paul "LeoNerd" Evans
392dc8af59 Annotate all the 'TODO' marks as relating to either the specification itself or the documentation thereof 2014-09-30 18:11:24 +01:00
Mark Haines
9605593d11 Merge branch 'develop' into server2server_signing
Conflicts:
	synapse/storage/__init__.py
	tests/rest/test_presence.py
2014-09-30 17:55:06 +01:00
Mark Haines
b95a178584 SYN-75 Verify signatures on server to server transactions 2014-09-30 15:15:10 +01:00
Erik Johnston
fbf6320614 pyflakes cleanup 2014-09-30 12:38:38 +01:00
Erik Johnston
e06adc6d7e SYN-2: Allow server admins to delete room aliases 2014-09-30 11:31:42 +01:00
Paul "LeoNerd" Evans
1f76377a7c Re-wrap content after latest additions 2014-09-29 18:40:15 +01:00
Paul "LeoNerd" Evans
dca75a08ba Merge remote-tracking branch 'origin/develop' into develop 2014-09-29 18:37:28 +01:00
Paul "LeoNerd" Evans
2d61dbc774 Extended docs about the registration/login flows 2014-09-29 18:36:10 +01:00
Paul "LeoNerd" Evans
3ee9a67aa4 Reörder the specification sections, to move 'Registration and Login' first, where it logically belongs 2014-09-29 18:36:10 +01:00
Paul "LeoNerd" Evans
ae953b0884 Huge whitespace hackery - reflow all (content) paragraphs at tw=80 2014-09-29 18:36:10 +01:00
Paul "LeoNerd" Evans
d5bf210998 No longer need the Freenode verification key file 2014-09-29 18:36:10 +01:00
Erik Johnston
389285585d Add a 'Redactions' section. 2014-09-29 17:19:45 +01:00
Erik Johnston
3656eb4740 Add m.room.redacted in events list 2014-09-29 16:39:08 +01:00
Erik Johnston
f1bdf40dda Merge branch 'whois' of github.com:matrix-org/synapse into develop 2014-09-29 15:59:05 +01:00
Erik Johnston
d96cb61f26 Unbreak tests after changing storage API 2014-09-29 15:35:57 +01:00
Erik Johnston
7151615260 Update docstring 2014-09-29 15:35:54 +01:00
Erik Johnston
1550ab9e2f SYN-48: Delete dead code 2014-09-29 15:04:47 +01:00
Erik Johnston
1132663cc7 SYN-48: Fix typo. Get the whois for requested user rather tahan the requester 2014-09-29 15:04:04 +01:00
Erik Johnston
3ccb17ce59 SYN-48: Implement WHOIS rest servlet 2014-09-29 14:59:52 +01:00
Paul "LeoNerd" Evans
472ef19100 No longer need the Freenode verification key file 2014-09-29 14:22:21 +01:00
Erik Johnston
c65306f877 Add auth check to test if a user is an admin or not. 2014-09-29 13:35:38 +01:00
Erik Johnston
f7d80930f2 SYN-48: Track User-Agents as well as IPs for client devices. 2014-09-29 13:35:15 +01:00
Erik Johnston
0fdf308874 Track the IP users connect with. Add an admin column to users table. 2014-09-26 16:36:24 +01:00
Erik Johnston
7a8307fe7c Merge branch 'master' of github.com:matrix-org/synapse into develop 2014-09-25 18:21:42 +01:00
Erik Johnston
697f6714a4 Merge branch 'release-v0.3.4' of github.com:matrix-org/synapse 2014-09-25 18:21:00 +01:00
David Baker
ec5fb77a66 Just use a yaml list for turn servers 2014-09-25 19:18:32 +02:00
Erik Johnston
f1c9ab4e4f More change log lines 2014-09-25 18:10:02 +01:00
Erik Johnston
3b0fb6aae8 Bump version and changelog 2014-09-25 18:05:06 +01:00
David Baker
6e72ee62ae Add realm to coturn options (it needs it). 2014-09-25 17:21:52 +01:00
Erik Johnston
37bfe44046 Merge branch 'deletions' of github.com:matrix-org/synapse into develop 2014-09-25 17:02:53 +01:00
David Baker
48ea055781 fix rst warnings 2014-09-25 17:01:27 +01:00
Erik Johnston
dcadfbbd4a Don't strip out null's in serialized events, as that is not need anymore and it's not in the spec (yet) 2014-09-25 17:00:17 +01:00
David Baker
9bcedf224e add howto for setting up your very own TURN server 2014-09-25 16:58:21 +01:00
Erik Johnston
69ddec6589 Don't strip of False values from events when serializing 2014-09-25 16:49:02 +01:00
Erik Johnston
72e80dbe0e Rename redaction test case to something helpful 2014-09-25 15:52:23 +01:00
Erik Johnston
c818aa13eb Add LIMIT to scalar subquery 2014-09-25 15:51:21 +01:00
Erik Johnston
ba87eb6753 Fix bug where we tried to insert state events with null state key 2014-09-25 14:45:27 +01:00
Emmanuel ROHEE
d170fbdb9f BF: Do a pagination when opening a room from an invitation 2014-09-25 14:46:11 +02:00
David Baker
c58eb0d5a3 Merge branch 'turn' into develop 2014-09-25 13:09:56 +01:00
Erik Johnston
59f2bef187 Fix test where we changed arguments used to call the notifier 2014-09-25 13:04:33 +01:00
Erik Johnston
1ca51c8586 SYN-46: An invite received from fedearation didn't wake up the event stream for the invited user. 2014-09-25 13:01:05 +01:00
David Baker
c0936b103c Add stun server fallback and I-told-you-so message if we get no TURN server and the connection fails. 2014-09-25 11:14:29 +01:00
Emmanuel ROHEE
9d3246ed12 Fixed SYWEB-36: use getUserDisplayName for disambiguating display name in member list and message sender name. This method is robust when disambiguation is no more required 2014-09-25 11:49:43 +02:00
Emmanuel ROHEE
ef99a5d972 getUserDisplayName: Disambiguate users who have the same displayname in the room.
Displayname are then disambiguate where it is necessary
2014-09-25 11:45:01 +02:00
David Baker
a31bf77776 Make turn server endpoint return an empty object if no turn servers to
match the normal response. Don't break if the turn_uris option isn't
present.
2014-09-25 11:24:49 +02:00
Erik Johnston
24e4c48468 More tests. 2014-09-25 10:19:16 +01:00
Erik Johnston
2721f5ccc9 Add test for redactions 2014-09-25 10:02:20 +01:00
David Baker
6806caffc7 Refresh turn server before the ttl runs out. Support firefox. 2014-09-24 17:57:34 +01:00
Mark Haines
52ca867670 Sign federation transactions 2014-09-24 17:25:41 +01:00
Erik Johnston
72eb360f2d Don't set the room name to be the room alias on room creation if the client didn't supply a name 2014-09-24 16:59:57 +01:00
Emmanuel ROHEE
2b4736afcd Fixed getUserDisplayname when the user has a null displayname 2014-09-24 17:42:40 +02:00
David Baker
7dc7c53029 The REST API spec only alows for returning a single server so name the
endpoint appropriately.
2014-09-24 17:28:47 +02:00
Erik Johnston
327dcc98e3 SYN-70: And fix another bug where I can't type 2014-09-24 16:19:29 +01:00
Erik Johnston
87deaf1658 SYN-70: Fix typo 2014-09-24 16:15:58 +01:00
David Baker
7679ee7321 Hopefully implement turn in the web client (probably wrong for Firefox because Firefox is a special snowflake) 2014-09-24 16:08:31 +01:00
David Baker
4553651138 Oops 2014-09-24 17:04:33 +02:00
David Baker
5383ba5587 rename endpoint to better reflect what it is and allow specifying multiple uris 2014-09-24 16:01:36 +01:00
Emmanuel ROHEE
432e8ef2bc Fixed SYWEB-74: Emote desktop notifications sometimes lack a name: "undefined waves" 2014-09-24 16:52:48 +02:00
Erik Johnston
70899d3ab2 Rename deletions to redactions 2014-09-24 15:27:59 +01:00
David Baker
b42b0d3fe5 Use standard base64 encoding with padding to get the same result as
coturn.
2014-09-24 15:29:24 +02:00
Erik Johnston
7d9a84a445 Make deleting deletes not undelete 2014-09-24 14:18:08 +01:00
Erik Johnston
1e6c5b205c Fix bug where we didn't correctly pull out the event_id of the deletion 2014-09-24 13:29:20 +01:00
Emmanuel ROHEE
c7620cca6f SYWEB-27: Public rooms with 2 users must not considered as 1:1 chat room and so, they must no be renamed 2014-09-24 13:17:47 +02:00
Emmanuel ROHEE
b02bb18a70 Fixed SYWEB-28: show displayname changes in recents 2014-09-24 12:48:24 +02:00
Erik Johnston
4e79b09dd9 Fill out the prune_event method. 2014-09-24 11:37:14 +01:00
Emmanuel ROHEE
6f5970a2e1 Added hasOwnProperty tests when required to be robust to random properties added to he Object prototype 2014-09-24 12:22:40 +02:00
Erik Johnston
3d2cca6762 Fix test. 2014-09-24 11:17:43 +01:00
Erik Johnston
4354590a69 Add v4 deltas to current sql. 2014-09-24 11:06:41 +01:00
Emmanuel ROHEE
ef5b39c410 State data now provides up-to-date users displaynames. So use it first.
Continue to use presence data as fallback solution which is required when users do not join the room yet.
Created eventHandlerService.getUserDisplayName() as a single point to compute display name.
2014-09-24 11:04:27 +02:00
Matthew Hodgson
7b8e24a588 close buttons on recents (SYWEB-68) 2014-09-24 01:12:59 +01:00
Matthew Hodgson
53841642a8 close buttons on recents (SYWEB-68) 2014-09-24 01:12:45 +01:00
Matthew Hodgson
b08112f936 on safari at least keypress's event.which returns ASCII rather than keycodes, so 38 & 40 was swallowing ( and & rather than up-arrow and down-arrow(!) 2014-09-23 23:35:17 +01:00
Matthew Hodgson
53ae5bce13 comment-convo with kegan 2014-09-23 23:25:56 +01:00
Matthew Hodgson
e8e80fe6b5 fix yet more room id leak disasters 2014-09-23 20:27:09 +01:00
Matthew Hodgson
0e848d73f9 oops, stupid bug on room/$room/state 2014-09-23 20:01:32 +01:00
Matthew Hodgson
cbea225d97 manu: what's going on here? 2014-09-23 20:01:32 +01:00
Paul "LeoNerd" Evans
a7d53227de Bugfix for older Pythons that lack hmac.compare_digest() 2014-09-23 19:07:16 +01:00
Matthew Hodgson
437969eac9 use all new /rooms/<room id>/state to actually gather the state for rooms whenever join them. a bit ugly, as we don't currently have a nice place to gather housekeeping after joining a room, so horrible code duplication... 2014-09-23 18:50:39 +01:00
Mark Haines
bf4b224fcf Fix a few pyflakes errors in the server_key_resource 2014-09-23 18:43:34 +01:00
Mark Haines
e3117a2a23 Add a _matrix/key/v1 resource with the verification keys of the local server 2014-09-23 18:43:34 +01:00
Mark Haines
c6a8e7d9b9 Read signing keys using methods from syutil. convert keys that are in the wrong format 2014-09-23 18:43:34 +01:00
David Baker
c96ab4fcbb The config is not hierarchical 2014-09-23 19:17:24 +02:00
Erik Johnston
efea61dc50 Rename 'pruned' to 'pruned_because' 2014-09-23 17:40:58 +01:00
Erik Johnston
bc250a6afa SYN-12: Implement auth for deletion by adding a 'delete_level' on the ops levels event
SYN-12 # comment Auth has been added.
2014-09-23 17:36:24 +01:00
Matthew Hodgson
284fac379c patch over another scenario whe we leak room IDs. i have *zero* idea why or where the webclient is overriding message.membership to be "join" though, when it comes down the events pipe as "invite" (which was causing this failure mode) 2014-09-23 17:31:13 +01:00
Matthew Hodgson
5aa13b9084 fix a case of rampaging SYWEB-78 2014-09-23 17:31:13 +01:00
David Baker
14ed6799d7 Add support for TURN servers as per the TURN REST API (http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00) 2014-09-23 17:16:13 +01:00
Kegan Dougal
a7420ff2b5 Fix SYWEB-72 : Improve performance when typing.
Swapped ng-keydown to a directive, which does the same thing (check if up/down
arrow then call history.goUp/goDown). This has *dramatically* improved
performance when typing in rooms which have lots (>100) of messages loaded.
2014-09-23 16:56:54 +01:00
Emmanuel ROHEE
e4e8ad6780 SYWEB-28: Fixed weird members list ordering: sort members on their last activity absolute time 2014-09-23 17:33:16 +02:00
Paul "LeoNerd" Evans
c0673c50e6 Merge branch 'jira/SYN-60' into develop 2014-09-23 16:15:54 +01:00
Matthew Hodgson
7d94913efb remove old commented-out code 2014-09-23 16:12:25 +01:00
Matthew Hodgson
c9f73bd325 fix one cause of SYWEB-53 2014-09-23 16:12:25 +01:00
Paul "LeoNerd" Evans
c03176af59 Send an HMAC(SHA1) protecting the User ID for the ReCAPTCHA bypass, rather than simply the secret itself, so it's useless if that HMAC leaks 2014-09-23 15:58:44 +01:00
Kegan Dougal
2771efb51c Update API docs to include notes on /rooms/$roomid/state 2014-09-23 15:39:04 +01:00
Erik Johnston
932b376b4e Add prune_event method 2014-09-23 15:37:32 +01:00
Kegan Dougal
0c4ae63ad5 Implemented /rooms/$roomid/state API. 2014-09-23 15:35:58 +01:00
Erik Johnston
b99f6eb904 Make sure we don't persist the 'pruned' key 2014-09-23 15:29:27 +01:00
Erik Johnston
78af6bbb98 Add m.room.deletion. If an event is deleted it will be returned to clients 'pruned', i.e. all client specified keys will be removed. 2014-09-23 15:28:32 +01:00
Paul "LeoNerd" Evans
537c7e1137 Config values are almost never 'None', but they might be empty string. Detect their presence by truth 2014-09-23 15:18:59 +01:00
Paul "LeoNerd" Evans
5f16439752 Make sure the config actually /has/ a captcha_bypass_secret set before trying to compare it 2014-09-23 15:16:47 +01:00
Paul "LeoNerd" Evans
3a8a94448a Allow a (hidden undocumented) key to m.login.recaptcha to specify a shared secret to allow bots to bypass the ReCAPTCHA test (SYN-60) 2014-09-23 14:29:08 +01:00
Emmanuel ROHEE
e9c88ae4f4 Partial fix of SYWEB-28: If members do not have last_active_ago, compare their presence state to order them 2014-09-23 15:19:03 +02:00
Matthew Hodgson
4847045259 send messages to users from the home page (SYWEB-19) 2014-09-23 13:36:58 +01:00
Matthew Hodgson
997a016122 fix NPE 2014-09-23 13:01:12 +01:00
Kegan Dougal
512f2cc9c4 Fix SYWEB-8 : Buggy tab-complete.
The first red blink was caused by an uninitialised search index. There is no
caching of entries, since this then wouldn't update if someone joined/left
during the tab. Instead, set to search index to MAX_VALUE then fix it to a
valid index AFTER the search is complete. Also ditched trailing space on ": ".
2014-09-23 12:22:14 +01:00
Matthew Hodgson
6876b1a25b fix grammatics 2014-09-22 21:45:50 +01:00
Mark Haines
107e7d5d91 Add section to explain how to sign events such that we can redact message contents 2014-09-22 19:42:07 +01:00
Mark Haines
09d79b0a9b Merge branch 'develop' into server2server_signing 2014-09-22 18:54:00 +01:00
Kegan Dougal
b5c9d99424 Show display name changes in the message list. 2014-09-22 17:46:53 +01:00
Erik Johnston
176e3fd141 Bump versions and changelog 2014-09-22 17:42:09 +01:00
Kegan Dougal
95acf63ea3 Add working protractor e2e test.
This uses the ignoreSynchronization flag because of the longpoll on the event
stream. It would be better to use $interval, but couldn't get that to
*reliably* work when testing. I suspect that $interval won't help us here,
since there is genuinely an open $http connection, as we're doing a long
poll. https://github.com/angular/protractor/issues/49 for more info.
2014-09-22 16:50:12 +01:00
Kegan Dougal
90f5eb1270 Set required environment variables for e2e testing.
Added an 'id' to the login button so it can be automatically triggered.
Also, added an onPrepare section to protractor.conf to do the login.
2014-09-22 15:00:23 +01:00
Kegan Dougal
7dfcba1649 Updated test README to include a section on environment-protractor.js
The environment file is .gitignored so random selenium servers aren't accidentally pushed.
2014-09-22 14:36:06 +01:00
Kegan Dougal
e3152188ef Added boilerplate for running end-to-end tests.\nThis is done using Protractor, which looks for a .gitignored file environment-protractor.js which contains the selenium endpoint url. 2014-09-22 14:29:12 +01:00
Erik Johnston
231afe464a Add a deletions table 2014-09-22 13:42:52 +01:00
Erik Johnston
e68dc04900 Merge branch 'master' of github.com:matrix-org/synapse into develop 2014-09-22 13:02:47 +01:00
David Baker
4696622b0a Propagate failure reason to the other party. 2014-09-22 11:44:15 +01:00
David Baker
83ea3c96ec Better logging of ICE candidates and fail the call when ICE fails. 2014-09-22 10:55:01 +01:00
Kegan Dougal
333e63156e Fixed unit test; it all actually works. Added a README for running the tests with karma/jasmine. 2014-09-22 10:27:03 +01:00
Matthew Hodgson
a0c3da17b4 go back to the original behaviour of only notifying if we think the app is backgrounded or idle... 2014-09-20 01:40:29 +01:00
Matthew Hodgson
4c7a1abd39 remove insanely busy logging which is killing CPU 2014-09-20 01:14:01 +01:00
Matthew Hodgson
9fda37158a remove the ng-model attribute from mainInput textarea to stop the digest being run every time you press a key (SYWEB-4) 2014-09-20 00:49:45 +01:00
David Baker
648fd2a622 Notify a callee that their browser doesn't support VoIP too.
SYWEB-14 #resolved
2014-09-19 18:22:14 +01:00
David Baker
99b0c9900e Move video background element up as it was causing the page to scroll. 2014-09-19 17:40:00 +01:00
David Baker
f6258221c1 Join rooms if we're not already in them when accepting a call coming from that room.
SYWEB-55 #resolve
2014-09-19 17:23:55 +01:00
Emmanuel ROHEE
68e534777c SYWEB-32: made all input/textearea inherit the font of their parent 2014-09-19 18:00:16 +02:00
David Baker
29686f63ac Fix the "is webrtc supported" titles on buttons and make the video / voice call buttons appear in multi-user rooms but be greyed out with approriate titles. 2014-09-19 16:52:45 +01:00
Erik Johnston
dcc1965bfe Test that prev_content get's added if there is a prev_state key (in the event stream). 2014-09-19 16:44:16 +01:00
David Baker
03ac0c91ae Merge branch 'videocalls' into develop
Conflicts:
	webclient/room/room.html
2014-09-19 16:26:46 +01:00
Emmanuel ROHEE
709b8ac2b7 SYWEB-13 SYWEB-14: disabled "Call" button if the browser does not support all required WebRTC features 2014-09-19 17:20:33 +02:00
Emmanuel ROHEE
e9670fd144 SYWEB-13: disabled "Send image" button if the browser does not support HTML5 file API 2014-09-19 17:20:33 +02:00
Emmanuel ROHEE
f9688d7519 SYWEB-13: Do not start the app if the browser does not support WEBStorage.
Internet Explorer case: Launch the app only for versions 9 and higher.
2014-09-19 17:20:33 +02:00
David Baker
da8b5a5367 First working version of UI chrome for video calls. 2014-09-19 16:18:15 +01:00
Erik Johnston
28bcd01e8d SYN-47: Fix bug where we still returned events for rooms we had left.
SYN-47 #resolve
2014-09-19 14:45:21 +01:00
Kegan Dougal
fba67ef951 Small formatting fixes 2014-09-19 14:19:02 +01:00
Kegan Dougal
3fa01be9e4 formatting 2014-09-19 12:04:26 +01:00
David Baker
270825ab2a Fix undefined variable error 2014-09-19 11:41:49 +01:00
Emmanuel ROHEE
008515c844 A kind of the typo in the fix of SYWEB-44 2014-09-19 09:25:51 +02:00
Emmanuel ROHEE
301ef1bdc6 Room id leaks: log them when then happens. Plus log the conditions that made them happen 2014-09-19 09:17:18 +02:00
Emmanuel ROHEE
cf1e167034 Fixed SYWEB-16: When sending an invite over federation, the remote user sees the name of the resulting invite room as *their* name rather than the inviters 2014-09-19 09:07:16 +02:00
Erik Johnston
beed1ba089 Merge branch 'develop' of github.com:matrix-org/synapse 2014-09-18 18:25:23 +01:00
Matthew Hodgson
2ab7e23790 fix SYWEB-41 (hopefully) 2014-09-18 18:18:30 +01:00
Mark Haines
fceb5f7b22 SYN-39: Add documentation explaining how to check a signature 2014-09-18 18:15:50 +01:00
Emmanuel ROHEE
0dac2f7a8d Fixed missing component dependency which created a crash 2014-09-18 19:12:21 +02:00
Kegan Dougal
6a6a718898 Added test directory, karma conf, and angular-mocks. Expect it to work? Pah, not yet. 2014-09-18 17:59:15 +01:00
Emmanuel ROHEE
faec6f7f31 Oops. Removed dev logs 2014-09-18 17:48:20 +02:00
Emmanuel ROHEE
26dda48e50 SYWEB-14: BF: rooms invitations were not visible in recents after launching/refreshing the web page 2014-09-18 17:34:26 +02:00
Erik Johnston
3108accdee Remove lie from change log. 2014-09-18 16:31:18 +01:00
Erik Johnston
e0f060d89b Merge branch 'master' of github.com:matrix-org/synapse into develop 2014-09-18 16:22:14 +01:00
Erik Johnston
380852b58e Bump Changelog and version 2014-09-18 16:20:53 +01:00
Kegan Dougal
3dea0d2806 undefined is empty. Fixed bug where empty bingWords with old accounts which hadn't logged in didn't send notifications. 2014-09-18 16:17:29 +01:00
David Baker
0505014152 add unprefixed filter css as well 2014-09-18 16:15:48 +01:00
David Baker
3bd8cbc62f Prettier and stabler video with basic support for viewing mode. For now, transition into 'large' mode is disabled. 2014-09-18 15:51:30 +01:00
Matthew Hodgson
d583aaa0c3 fix wordwrap 2014-09-18 15:25:25 +01:00
Matthew Hodgson
3a7375f15e fix binger description 2014-09-18 15:25:11 +01:00
Erik Johnston
79a5fb469b Merge branch 'master' of github.com:matrix-org/synapse into develop 2014-09-18 14:52:19 +01:00
Erik Johnston
9fd0c74e90 Bump changelog and versions 2014-09-18 14:46:23 +01:00
Erik Johnston
335e5d131c Merge branch 'test-sqlite-memory' of github.com:matrix-org/synapse into develop
Conflicts:
	tests/handlers/test_profile.py
2014-09-18 14:31:47 +01:00
Emmanuel ROHEE
b7d42c1e93 SYWEB-40: Only local rooms are shown in the recents list.
Removed an old patch that deduplicated join events. This patch is now useless. Plus it is buggy since it compared event.content and event.prev_content only on the membership field whereas these objects contain more data now like displayname...
2014-09-18 15:28:52 +02:00
Emmanuel ROHEE
0db0528e8e Reverted patches done for SYWEB-40 2014-09-18 15:19:35 +02:00
Erik Johnston
704e7e9f44 Merge branch 'release-v0.3.0' of github.com:matrix-org/synapse 2014-09-18 13:05:07 +01:00
Erik Johnston
c58f7f293d Merge branch 'develop' of github.com:matrix-org/synapse into release-v0.3.0 2014-09-18 12:03:30 +01:00
Erik Johnston
19095552aa Update Change log 2014-09-18 12:03:08 +01:00
Kegan Dougal
a64ff63a41 SYWEB-3 : Boldify if the join_rule is public, rather than visibility so it plays nicer with federation. 2014-09-18 12:02:52 +01:00
Erik Johnston
17db2b27bf Update version in UPGRADE 2014-09-18 12:02:52 +01:00
Kegan Dougal
ac8d73b258 Patch for SYWEB-40 : isStateEvent is not being set correctly, and really shouldn't be a configurable arg in the first place. As a result of being undefined, the events.rooms[rid].members object was not being updated in some cases, which combined with the recents-filter bug (32808e4), caused federated rooms to not appear in the recents list. 2014-09-18 12:02:52 +01:00
Kegan Dougal
a6f5c88b47 Still add the room to the filtered list even if you can't work out the number of users in the room. 2014-09-18 12:02:51 +01:00
David Baker
1c0408de08 unbreak calls in firefox 2014-09-18 11:59:27 +01:00
Kegan Dougal
9cebfd9d90 SYWEB-3 : Boldify if the join_rule is public, rather than visibility so it plays nicer with federation. 2014-09-18 11:35:59 +01:00
David Baker
e932e5237e WIP video chat layout 2014-09-18 11:04:45 +01:00
Kegan Dougal
fbf221ae6d Patch for SYWEB-40 : isStateEvent is not being set correctly, and really shouldn't be a configurable arg in the first place. As a result of being undefined, the events.rooms[rid].members object was not being updated in some cases, which combined with the recents-filter bug (32808e4), caused federated rooms to not appear in the recents list. 2014-09-18 10:35:44 +01:00
Kegan Dougal
32808e4111 Still add the room to the filtered list even if you can't work out the number of users in the room. 2014-09-18 10:05:34 +01:00
Matthew Hodgson
9f94f9de48 freenode verification 2014-09-17 23:53:53 +01:00
Paul "LeoNerd" Evans
4571cf7baa Merge branch 'develop' into test-sqlite-memory 2014-09-17 18:27:47 +01:00
Paul "LeoNerd" Evans
bfae582fa3 Remark on remaining storage modules that still need unit tests 2014-09-17 18:27:30 +01:00
David Baker
575852e6b5 add note to upgrade.rst about web client spec breaking change. 2014-09-17 17:51:22 +01:00
Erik Johnston
10b4291b54 Bump versions 2014-09-17 17:49:01 +01:00
Paul "LeoNerd" Evans
bcf5121937 Neaten more of the storage layer tests with assertObjectHasAttributes; more standardisation on test layout 2014-09-17 16:58:59 +01:00
Kegan Dougal
aeaeceb92c Create room entries for public rooms too so their public state is transferred over correctly when you join it. 2014-09-17 16:38:40 +01:00
Kegan Dougal
16f55d4275 webclient SYWEB-3 : Public rooms are bold. Can't think of a nicer way which doesn't clutter the recents list. 2014-09-17 16:38:40 +01:00
Paul "LeoNerd" Evans
b588ce920d Unit tests for (some) room events via the RoomStore 2014-09-17 16:31:11 +01:00
David Baker
1fb2c831e8 Video calling (in a tiny box at the moment) 2014-09-17 16:26:35 +01:00
Emmanuel ROHEE
246f5d2e20 SYWEB-30: BF: When switching between rooms, pagination flickered between the top of the room before jumping to the bottom of the page 2014-09-17 17:13:07 +02:00
Kegan Dougal
c707b7d128 SYWEB-3 : Added 'visibility' key to rooms returned via /initialSync 2014-09-17 16:09:07 +01:00
Paul "LeoNerd" Evans
ba41ca45fa Use new assertObjectHasAttributes() in tests/storage/test_room.py 2014-09-17 16:04:05 +01:00
Paul "LeoNerd" Evans
7aacd6834a Added a useful unit test primitive for asserting object attributes 2014-09-17 15:56:40 +01:00
Paul "LeoNerd" Evans
de14853237 More RoomStore tests 2014-09-17 15:33:10 +01:00
Paul "LeoNerd" Evans
9973298e2a Print expected-vs-actual data types on typecheck failure from check_json() 2014-09-17 15:27:45 +01:00
Emmanuel ROHEE
65c37cc852 SYWEB-22: Format emote('/me') messages correctly in desktop notification 2014-09-17 16:13:09 +02:00
Erik Johnston
b6818fd4d2 SYN-40: When a user updates their displayname or avatar update all their join events for all the rooms they are currently in. 2014-09-17 15:05:14 +01:00
Emmanuel ROHEE
fe7af80198 BF: edit the actual room name not the displayed room name (which has been computed) 2014-09-17 15:46:12 +02:00
Emmanuel ROHEE
9aed6a06cf SYWEB-15: Always show the room alias as well as its name in the UI 2014-09-17 15:38:20 +02:00
Emmanuel ROHEE
b3a0961c6c SYWEB-7: Use sessionStorage to make per-room history survives when the user navigates through rooms 2014-09-17 14:38:33 +02:00
Emmanuel ROHEE
d9a9a47075 SYWEB-7: Up & down keys let user step through the history as per readline or xchat 2014-09-17 14:18:39 +02:00
Emmanuel ROHEE
f9bb000ccf WEB-35: joins/parts should trigger desktop notifications 2014-09-17 09:41:21 +02:00
Kegan Dougal
d6c0cff3bd Bugfix when content isn't a string. 2014-09-16 16:31:16 +01:00
Kegan Dougal
95e171e19a Don't bing for sent messages. Handle cases where the member is unknown rather than erroring out. 2014-09-16 16:23:20 +01:00
Kegan Dougal
d7b206cc93 Added basic RegExp support. 2014-09-16 16:10:48 +01:00
Emmanuel ROHEE
06dfbdf7c8 WEB-27: We don't need to show the user-count in Recents in the room sidepanel - takes up too much room 2014-09-16 17:07:47 +02:00
Kegan Dougal
3395a3305f Bing on all the things if there are 0 bing words. 2014-09-16 15:47:29 +01:00
Kegan Dougal
5aaa3c09c1 hidden/minimise/focus disaster disclaimer with the TODO 2014-09-16 15:35:23 +01:00
Kegan Dougal
b36a0c71d1 Added utility function containsBingWord and hook up some css to it. 2014-09-16 15:35:23 +01:00
Kegan Dougal
a402e0c5e6 Added bing detection logic. Persist the display name of the user in localstorage for use when binging. 2014-09-16 15:35:23 +01:00
Kegan Dougal
660364d6a7 Move the notification logic out of an individual room controller and into the general event handler, so we can notify for >1 room. 2014-09-16 15:35:23 +01:00
Kegan Dougal
b170fe921e Added a section on bing words if you enable desktop notifications. 2014-09-16 15:35:23 +01:00
David Baker
84372cef4a Time out calls from both ends properly. 2014-09-16 15:26:22 +01:00
Emmanuel ROHEE
890178cf25 Fixed scroll flickering when opening the room 2014-09-16 16:16:11 +02:00
Emmanuel ROHEE
a284de73e6 If an initialSync has been already done on a room, we do not need to paginate back to get more messages 2014-09-16 16:16:11 +02:00
Emmanuel ROHEE
45592ccdfd WEB-29: Improve room page content loading
InitialSync: load the 30 last messages of each room so that a full page of messages can be displayed without additionnal request
2014-09-16 16:16:11 +02:00
David Baker
f4094c5eb3 Update spec with the lifetime field. 2014-09-16 14:54:52 +01:00
David Baker
dd2b933a0d Use event age to recognise which calls are current and which aren't and hence support answering calls that were placed before we loaded the page. 2014-09-16 14:47:10 +01:00
Kegan Dougal
c099b36af3 Comment out password reset for now, until the mechanism is fully discussed (IS token auth vs HS auth) 2014-09-16 13:32:33 +01:00
Kegan Dougal
cc83b06cd1 Added support for the HS to send emails. Use it to send password resets. Added email_smtp_server and email_from_address config args. Added emailutils. 2014-09-16 12:36:39 +01:00
Kegan Dougal
5f30a69a9e Added PasswordResetRestServlet. Hit the IS to confirm the email/user. Need to send email. 2014-09-16 11:22:40 +01:00
Emmanuel ROHEE
faee41c303 Merge remote-tracking branch 'origin/develop' into webclient_data_centralisation 2014-09-16 08:50:53 +02:00
Paul "LeoNerd" Evans
e32cfed1d8 Initial pass at a RoomStore test 2014-09-15 18:41:24 +01:00
Erik Johnston
1e4b971f95 Fix bug where we didn't always get 'prev_content' key 2014-09-15 17:43:46 +01:00
Emmanuel ROHEE
b0483cd47d Filter room where the user has been banned 2014-09-15 18:22:38 +02:00
Erik Johnston
40d2f38abe Fix bug where we incorrectly calculated 'age_ts' from 'age' key rather than the reverse. Don't transmit age_ts to clients for now. 2014-09-15 16:55:39 +01:00
Erik Johnston
59516a8bb1 Correctly handle receiving 'missing' Pdus from federation, rather than just discarding them. 2014-09-15 16:40:44 +01:00
Emmanuel ROHEE
8aa4b7bf7f Recents must not show temporary fake messages 2014-09-15 17:31:07 +02:00
Erik Johnston
e639a3516d Improve logging in federation handler. 2014-09-15 16:24:03 +01:00
Erik Johnston
0897a09f49 Fix unit tests after adding extra argument on put_json 2014-09-15 16:24:03 +01:00
Erik Johnston
6ac0b4ade8 Fix 'age' key to update on retries 2014-09-15 16:24:03 +01:00
Kegan Dougal
34d7896b06 More helpful 400 error messages. 2014-09-15 16:05:51 +01:00
Kegan Dougal
688c37ebf4 Updated CHANGES and UPGRADE to reflect registration API changes. 2014-09-15 15:53:05 +01:00
Kegan Dougal
2c00e1ecd9 Be consistent when associating keys with login types for registration/login. 2014-09-15 15:38:29 +01:00
Emmanuel ROHEE
42f5b0a6b8 Recents uses data directly from $rootscope.events 2014-09-15 16:31:59 +02:00
Kegan Dougal
14bc4ed59f Merge branch 'develop' of github.com:matrix-org/synapse into registration-api-changes in preparation for re-merge to develop. 2014-09-15 15:27:58 +01:00
Kegan Dougal
0b8a3bc3b9 Update spec to include m.login.email.identity 2014-09-15 15:27:17 +01:00
Kegan Dougal
c04caff55c Fix unit tests. 2014-09-15 15:14:19 +01:00
Kegan Dougal
7f23425e59 Updated cmdclient to use new registration logic. 2014-09-15 15:09:21 +01:00
Paul "LeoNerd" Evans
1aaa429081 Also unittest RoomMemberStore's joined_hosts_for_room() 2014-09-15 15:00:14 +01:00
Kegan Dougal
04fbda46dd Make captcha work again with the new registration logic. 2014-09-15 14:52:39 +01:00
Kegan Dougal
d821755b49 Updated webclient to support the new registration logic. 2014-09-15 14:31:53 +01:00
Paul "LeoNerd" Evans
ae7dfeb5b6 Use new 'tests.unittest' in new storage level tests 2014-09-15 14:19:16 +01:00
Paul "LeoNerd" Evans
b0406b9ead Merge remote-tracking branch 'origin/develop' into test-sqlite-memory 2014-09-15 14:15:10 +01:00
Erik Johnston
5bd9369a62 Correctly handle the 'age' key in events and pdus 2014-09-15 13:26:11 +01:00
Kegan Dougal
285ecaacd0 Split out password/captcha/email logic. 2014-09-15 12:42:36 +01:00
Kegan Dougal
34878bc26a Added LoginType constants. Created general structure for processing registrations. 2014-09-15 10:23:20 +01:00
Emmanuel ROHEE
76217890c0 BF: inviter field has moved to the room root object 2014-09-15 11:14:10 +02:00
Kegan Dougal
bf6fa6dd3d Merge branch 'develop' of github.com:matrix-org/synapse into registration-api-changes 2014-09-15 09:46:33 +01:00
Emmanuel ROHEE
a9da2ec895 BF: presence and eventMap were not reset at logout. 2014-09-15 10:39:30 +02:00
Emmanuel ROHEE
f3d3441d02 Use "white-space: pre-wrap" for "Text will wrap when necessary, and on line breaks" 2014-09-15 10:22:57 +02:00
Emmanuel ROHEE
3292f70071 Merge remote-tracking branch 'origin/master' into develop 2014-09-15 10:08:47 +02:00
Matthew Hodgson
49b5dd56b5 unbreak wordwrapping by breaking multiline paste for now 2014-09-13 11:38:45 +01:00
Matthew Hodgson
32acb7e903 always scroll to bottom when entering a room 2014-09-13 11:35:36 +01:00
Matthew Hodgson
276b9f1839 more wishlist 2014-09-13 11:26:16 +01:00
Paul "LeoNerd" Evans
7a77aabb4b Define a CLOS-like 'around' modifier as a decorator, to neaten up the 'orig_*' noise of wrapping the setUp()/tearDown() methods 2014-09-12 19:07:29 +01:00
Paul "LeoNerd" Evans
aeb69c0f8c Add some docstrings 2014-09-12 18:46:13 +01:00
Paul "LeoNerd" Evans
d9f3f322c5 Additionally look first for a 'loglevel' attribute on the running test method, before the TestCase 2014-09-12 18:46:13 +01:00
Paul "LeoNerd" Evans
33c4dd4c2d Define a (class) decorator for easily setting a DEBUG logging level on a TestCase 2014-09-12 18:46:13 +01:00
Paul "LeoNerd" Evans
ca8349a897 Allow a TestCase to set a 'loglevel' attribute, which overrides the logging level while that testcase runs 2014-09-12 18:46:13 +01:00
Paul "LeoNerd" Evans
cd62ee3f29 Have all unit tests import from our own subclass of trial's unittest TestCase; set up logging in ONE PLACE ONLY 2014-09-12 18:46:13 +01:00
Erik Johnston
958b52596c Update CHANGES.rst 2014-09-12 18:36:45 +01:00
Erik Johnston
c7bcd87f37 Merge branch 'master' of github.com:matrix-org/synapse into develop 2014-09-12 18:27:44 +01:00
Erik Johnston
80852d1135 Spellcheck 2014-09-12 18:27:04 +01:00
Erik Johnston
84326e2491 Add note about glare support 2014-09-12 18:26:19 +01:00
Erik Johnston
e3aec9bc81 Merge branch 'release-v0.2.3' of github.com:matrix-org/synapse
Conflicts:
	webclient/room/room-controller.js
2014-09-12 18:19:32 +01:00
David Baker
21b45d2a5b Update the spec document to replace the candidate message with the candidates message. 2014-09-12 18:19:19 +01:00
David Baker
842898df15 Send multiple candidates at once instead of all individually. Changes spec to include multiple candidates in a candidate(s) message. 2014-09-12 18:19:19 +01:00
Erik Johnston
afb7f173cf Bump version and change log 2014-09-12 18:13:05 +01:00
Erik Johnston
14975ce5bc Fix bug where we relied on the current_state_events being updated when we are handling type specific persistence 2014-09-12 17:57:02 +01:00
Erik Johnston
667e747ed1 Fix bug where we no longer stored user_id on Pdus 2014-09-12 17:56:21 +01:00
Paul "LeoNerd" Evans
1c51c8ab7d Merge remote-tracking branch 'origin/develop' into test-sqlite-memory
Conflicts:
	synapse/storage/pdu.py
2014-09-12 17:20:06 +01:00
Erik Johnston
39e3fc69e5 Make the state resolution use actual power levels rather than taking them from a Pdu key. 2014-09-12 17:11:09 +01:00
Erik Johnston
b42fe05c51 Fix bug where we incorrectly removed a remote host from the list of hosts in a room when any user from that host left that room even if they weren't the last user from that host in that room 2014-09-12 17:11:09 +01:00
Erik Johnston
ca1ae7cf9b Fix bug where we didn't return a tuple when expected. 2014-09-12 17:11:09 +01:00
Paul "LeoNerd" Evans
2026942b05 Initial hack at some RoomMemberStore unit tests 2014-09-12 16:44:07 +01:00
Paul "LeoNerd" Evans
aa525e4a63 More accurate docs / clearer paramter names in RoomMemberStore 2014-09-12 16:43:49 +01:00
Emmanuel ROHEE
3ed39ad20e Clean data when user logs out 2014-09-12 17:43:35 +02:00
David Baker
cc2cee4af6 Retry sending events that fail to send. 2014-09-12 16:32:22 +01:00
Emmanuel ROHEE
6c81752e46 Fixed displayname resolution of emote sender 2014-09-12 17:01:49 +02:00
Paul "LeoNerd" Evans
a87eac4308 Revert recent changes to RoomMemberStore 2014-09-12 15:51:51 +01:00
Emmanuel ROHEE
a2cd942a95 Fixed public room name and users count alignement
Put data into a table to ease layout and manage long strings
2014-09-12 16:46:20 +02:00
Paul "LeoNerd" Evans
a840ff8f3f Now don't need the other logger.debug() call in _execute 2014-09-12 14:38:27 +01:00
Paul "LeoNerd" Evans
1c20249884 Logging of all SQL queries via the 'synapse.storage.SQL' logger 2014-09-12 14:37:55 +01:00
Paul "LeoNerd" Evans
e53d77b501 Add a .runInteraction() method on SQLBaseStore itself to wrap the .db_pool 2014-09-12 14:28:07 +01:00
David Baker
09a59ce2d3 Some words about glare 2014-09-12 14:24:56 +01:00
David Baker
8b28f7d14e Always pick the incoming call if we've not yet sent out our invite, otherwise the remorte party will see their call get rejected and our call won't come in until our user clicks allow. 2014-09-12 14:06:35 +01:00
David Baker
a81ec21762 Remove the local AV stream from ourselves when handing it off to a new call or we'll close it when we hang up. 2014-09-12 11:51:57 +01:00
Emmanuel ROHEE
9819b3619e CSS m.room.topic and m.room.name events in the history 2014-09-12 11:56:08 +02:00
Emmanuel ROHEE
311dc61803 Handle NAME_EVENT to get room name update event
(TODO: recents needs to be directly plugged to $rootScope.events.rooms)
2014-09-12 10:51:05 +02:00
Emmanuel ROHEE
d934328904 Added edition of room name 2014-09-12 10:48:06 +02:00
Emmanuel ROHEE
6ea20f3503 Show room name updates in room history and recents.
Update it with the latest value
2014-09-12 10:12:56 +02:00
Emmanuel ROHEE
8b3ce85183 BF: temp workaround while /initialSync on a particular room is not available
initRoom on a new room is not called. Call it for any received events
2014-09-12 08:54:18 +02:00
David Baker
a059ca6915 few fixes for errors in glare conditions. still seem to end up with no audio if both calls are placed at the same time. 2014-09-11 19:16:57 +01:00
David Baker
1e05e30472 Put back the line that adds the stream to the invite, otherwise caller->callee audio won't work... 2014-09-11 18:59:22 +01:00
Paul "LeoNerd" Evans
249e8f2277 Add a better _store_room_member_txn() method that takes separated fields instead of an event object; also add FIXME comment about a big bug in the logic 2014-09-11 18:52:35 +01:00
Paul "LeoNerd" Evans
aaf9ab68c6 Rename _store_room_member_txn to _store_room_member_from_event_txn so we can create another, more sensible function of that name 2014-09-11 18:44:04 +01:00
Paul "LeoNerd" Evans
3d6aee079e Unit-test for RegistrationStore using SQLiteMemoryDbPool 2014-09-11 17:44:00 +01:00
David Baker
81d061e74e Fix bug where web client wold break trying to add the earliest token without having initialised the room if your first page of history contained only events which didn't call initRoom. Just call initRoom in handleMessages since we use it there rather than leaving it to the individual event handling methods. 2014-09-11 17:40:38 +01:00
Paul "LeoNerd" Evans
fb93a4a9e3 Perform PresenceInvitesTestCase against real SQLiteMemoryDbPool 2014-09-11 16:22:44 +01:00
Emmanuel ROHEE
ceec607e7f Clearly show when an user cannot join a room.
In realtime show who kicked or banned him.
2014-09-11 16:54:57 +02:00
David Baker
fb082cf50f start towards glare support (currently not much better but no worse than before) including fixing a lot of self/var self/this fails that caused chaos when we started to have more than one call in play. 2014-09-11 15:24:18 +01:00
Paul "LeoNerd" Evans
493b1e6d3c Need to prepare() the SQLiteMemoryDbPool before passing it to HomeServer constructor, as DataStore's constructor will want it ready 2014-09-11 15:21:15 +01:00
Emmanuel ROHEE
806c49a690 Added support of copy/paste of multi lines content 2014-09-11 15:46:24 +02:00
Emmanuel ROHEE
aa347b52ba Use autofill-event.js to workaround browsers issue: Form model doesn't update on autocomplete
https://github.com/angular/angular.js/issues/1460
2014-09-11 15:07:44 +02:00
Paul "LeoNerd" Evans
4385eadc28 Start of converting PresenceHandler unit tests to use SQLiteMemoryDbPool - just the 'State' test case for now 2014-09-11 13:57:17 +01:00
Emmanuel ROHEE
6b20fef52a Invite: reset the input when the invitation has been done 2014-09-11 13:52:07 +02:00
Emmanuel ROHEE
c92740e8a9 Enable enter key in the invite input 2014-09-11 13:43:55 +02:00
Paul "LeoNerd" Evans
d13d0bba51 Unit-test DirectoryHandler against (real) SQLite memory store, not mocked storage layer 2014-09-11 11:59:48 +01:00
Paul "LeoNerd" Evans
d83202b938 Added unit tests of DirectoryStore 2014-09-11 11:32:46 +01:00
Emmanuel ROHEE
cc049851d0 On member avatar mouseover, show user_id and power level 2014-09-11 12:01:44 +02:00
Emmanuel ROHEE
14a9652324 Room topic: if the request fails, show the error in the feedback 2014-09-11 11:53:37 +02:00
Emmanuel ROHEE
af44e9556d BF: made input autofocus work when opening the room topic input 2014-09-11 11:49:59 +02:00
Emmanuel ROHEE
7e7eb0efc1 Show room topic change in the chat history and in the recents 2014-09-11 11:31:24 +02:00
Emmanuel ROHEE
8dcb6f24b5 getRoomEventIndex: improved speed for what it is used 2014-09-11 09:11:24 +02:00
Paul "LeoNerd" Evans
79fe6083eb Test ProfileHandler against the real datastore layer using SQLite :memory: 2014-09-10 18:11:32 +01:00
Paul "LeoNerd" Evans
dd1a9100c5 Added unit tests for PresenceDataStore too 2014-09-10 17:51:05 +01:00
Emmanuel ROHEE
44998ca450 Merge remote-tracking branch 'origin/develop' into webclient_initialSync 2014-09-10 18:35:05 +02:00
Emmanuel ROHEE
7a153b5c94 Show echoed emote with transparency 2014-09-10 18:29:52 +02:00
Emmanuel ROHEE
5a06f5c5fc Reenabled transparent echo message. It turns to opaque without flickering now. 2014-09-10 18:24:03 +02:00
Paul "LeoNerd" Evans
dc7f39677f Remember to kill now-dead import in test_profile.py 2014-09-10 16:56:52 +01:00
Paul "LeoNerd" Evans
08f5c48fc8 Move SQLiteMemoryDbPool implementation into tests.utils 2014-09-10 16:56:02 +01:00
Paul "LeoNerd" Evans
9774949cc9 It's considered polite to actually wait for DB prepare before running tests 2014-09-10 16:50:09 +01:00
Paul "LeoNerd" Evans
53d0f69dc3 Also test avatar_url profile field 2014-09-10 16:49:34 +01:00
Paul "LeoNerd" Evans
6081f4947e Tiny trivial PoC unit-test using SQLite in :memory: mode 2014-09-10 16:42:31 +01:00
Emmanuel ROHEE
6d18b52931 Clean previous request feedback when doing a new request 2014-09-10 17:40:34 +02:00
Emmanuel ROHEE
81ecaf945d BF: Made /op work when providing no power value. 50 is used as default in this case 2014-09-10 17:37:51 +02:00
Paul "LeoNerd" Evans
55397f6347 prepare_database() on db_conn, not plain name, so we can pass in the connection from outside 2014-09-10 16:23:58 +01:00
Paul "LeoNerd" Evans
2faffc52ee Make sure not to open our TCP ports until /after/ the DB is nicely prepared ready for use 2014-09-10 16:16:24 +01:00
Paul "LeoNerd" Evans
6c1f0055dc No need for a tiny run() function any more, just use reactor.run() directly 2014-09-10 16:07:44 +01:00
Emmanuel ROHEE
811716592c Made users count auto updating. Do show it if the info is not available (ex:user has not joined the room yet) 2014-09-10 16:46:06 +02:00
David Baker
e2d2d63bcd Animation on call end icon. 2014-09-10 15:45:09 +01:00
David Baker
dde7ec8e64 Upgrade angularjs to 1.3.0-rc1 since this is new development 2014-09-10 15:43:27 +01:00
Paul "LeoNerd" Evans
ce55a8cc4b Move database preparing code out of homserver.py into storage where it belongs 2014-09-10 15:42:15 +01:00
Emmanuel ROHEE
30bfa911fc Member event: store use the the latest one 2014-09-10 16:26:11 +02:00
Emmanuel ROHEE
da3f842b8c Removed wrong comments about recents-controller.js: it uses $rootScope.rooms not $rootScope.events.rooms managed by event-handler-service.js and used by other controllers 2014-09-10 14:53:03 +02:00
Emmanuel ROHEE
130cbdd7af dedup events: state events conflict with messages events. Do not consider them in deduplication 2014-09-10 14:45:32 +02:00
Emmanuel ROHEE
b099634ba1 Reenabled handle of room states events in initialSync but do not add them to the displayed messages in the room page.
Show the m.room.member events only when they come from room.messages (from initialSync of pagination) not from room.state.
2014-09-10 14:36:30 +02:00
Emmanuel ROHEE
c2afc6cd0a Presence events do not have event id. Do not discard them 2014-09-10 13:48:33 +02:00
David Baker
80b5470663 Add text for incoming calls 2014-09-10 11:35:14 +01:00
David Baker
7411794fa1 Show mxid in call bar for users with no displayname 2014-09-10 11:21:20 +01:00
David Baker
55fe0d8adc Less buggy rejection of calls when busy 2014-09-10 11:12:02 +01:00
Emmanuel ROHEE
b63dd9506e Improved requests: pagination is done from the data received in initialSync 2014-09-10 12:01:00 +02:00
David Baker
6f256e6380 reject calls if there's already a call in progress 2014-09-10 10:32:05 +01:00
Kegan Dougal
2bd4346075 More rst formatting. 2014-09-09 15:13:50 -07:00
Kegan Dougal
f23e5b17b6 Extra restrictions to make parsing easier. 2014-09-09 15:11:06 -07:00
Kegan Dougal
56a358481e Tyops 2014-09-09 15:00:48 -07:00
Kegan Dougal
d5704cf2a3 Added initial draft for human-readable ID rules. 2014-09-09 14:53:35 -07:00
Kegan Dougal
550e8f32ac Move model to client-server for now. 2014-09-09 13:51:13 -07:00
David Baker
f90ce04a83 Hangup call if user denies media access. 2014-09-09 18:21:03 +01:00
David Baker
ccfb42e4ff Don't try setting up the call if the user has canceled it before allowing permission. 2014-09-09 17:58:26 +01:00
David Baker
25e96f82db Don't break if you press the hangup button before allowing media permission. 2014-09-09 17:52:01 +01:00
David Baker
253c327252 Don't play an engaged tone if we hang up locally. 2014-09-09 17:38:40 +01:00
Erik Johnston
a75f8686ba Fix bug where we used an unbound local variable if we ended up rolling back the persist_event transaction 2014-09-09 16:27:59 +01:00
Emmanuel ROHEE
1ef51e7939 Improved room page loading flow: do pagination only when the members list is available.
Killed an unexpected pagination trigger when the page load: paginateMore
2014-09-09 16:46:30 +02:00
Emmanuel ROHEE
746ed57c0e When the user has been kicked or banned from a room, remove the room from his recents list 2014-09-09 16:31:50 +02:00
Emmanuel ROHEE
5132fcdb8b Made recents list display something when joining a room which we do not have state data yet 2014-09-09 16:10:20 +02:00
Emmanuel ROHEE
332986ba43 BF: prevent joined messages to be displayed twice when joining a room.
Do this by synchronizing the m.room.member joined event from the events stream and the start of the pagination
2014-09-09 16:10:20 +02:00
David Baker
472b4fe48c make calls work in Firefox 2014-09-09 14:54:06 +01:00
Emmanuel ROHEE
fd2d3fcfd7 Removed historical code: recents does not need to manage presences. It is already done by initialSync in eventStreamService 2014-09-09 12:47:42 +02:00
Emmanuel ROHEE
967ac65586 BF: Made the grey background of the current room cover all the cell width 2014-09-09 12:47:42 +02:00
David Baker
16b40cbede Show call invites in the message table 2014-09-09 11:45:36 +01:00
Kegan Dougal
75890d7bdd CSS tweakage 2014-09-08 19:02:23 -07:00
Kegan Dougal
e8f19b4c0d Display a 'Set Topic' button if there is no topic or it's a 0-len string. 2014-09-08 18:59:26 -07:00
Kegan Dougal
6bdb23449a Add ability to set topic by double-clicking on the topic text then hitting enter. 2014-09-08 18:40:34 -07:00
Kegan Dougal
f64cc237fc Fixed bug which displayed an older room topic because it was being returned from /initialSync messages key. Check the ts of the event before clobbering state. 2014-09-08 17:27:51 -07:00
Kegan Dougal
ef2111099a long topic is long. CSS support it 2014-09-08 17:19:04 -07:00
Kegan Dougal
df50a6823f Display public room topics if they exist on the public room list. 2014-09-08 17:14:58 -07:00
Kegan Dougal
324020d5fe Display the room topic in the room, underneath the name of the room. 2014-09-08 15:36:52 -07:00
Kegan Dougal
544691ab05 Update jsfiddles to have more helpful error messages when there is no connection when logging in. 2014-09-08 14:54:10 -07:00
Erik Johnston
5236de5b03 Add slightly helpful advice on how to generate config if you don'y already have one 2014-09-08 22:52:50 +01:00
Erik Johnston
91b370650a Don't autogen config in synctl for the same reasons we don't turn of --generate-config by default on the homeserver - it is liable to confuse people who have moved the config file or have chosen a non standard location.
Also, don't override log file location.
2014-09-08 22:48:46 +01:00
Erik Johnston
e062f2dfa8 Apparently we can't do txn.rollback(), so raise and catch an exception instead. 2014-09-08 22:37:19 +01:00
Kegan Dougal
c1a25756c2 Added demo.details 2014-09-08 14:24:28 -07:00
Kegan Dougal
d692994ea4 Updated jsfiddle links to point to github 2014-09-08 14:16:22 -07:00
Kegan Dougal
a3590dfa26 Bodge to default to '1 users' when you create a room, which is better than blindly assuming a recents controller is writing to rootScope.rooms and setting numUsersInRoom there. 2014-09-08 14:01:34 -07:00
Kegan Dougal
da9b7b0368 Added big massive TODOs on a huge design problem with initial sync 2014-09-08 13:54:09 -07:00
Kegan Dougal
054fad5360 Float right the num users, apply room highlight to user count. 2014-09-08 13:28:55 -07:00
Kegan Dougal
e0954f3b36 Better checks are better. 2014-09-08 12:15:29 -07:00
Kegan Dougal
76fe7d4eba Added num_joined_users key to /publicRooms for each room. Show this information in the webclient. 2014-09-08 12:15:29 -07:00
Erik Johnston
942d8412c4 Handle the case where we don't have a common ancestor 2014-09-08 20:13:27 +01:00
Kegan Dougal
2eaa199e6a Added number of users in recent rooms. 2014-09-08 11:55:29 -07:00
Erik Johnston
83ce57302d Fix bug in state handling where we incorrectly identified a missing pdu. Update tests to catch this case. 2014-09-08 19:50:59 +01:00
Kegan Dougal
de727f854a Make #matrix public rooms bold to make them stand out from the other public rooms. Ideally this would be metadata in /publicRooms to say something like 'featured channel', but for now, just make it a client side check. 2014-09-08 11:33:12 -07:00
Kegan Dougal
0627366b2f Sort the public room list by display name. 2014-09-08 11:17:44 -07:00
Kegan Dougal
586e0df62d Updated spec and api docs to desired new format. 2014-09-08 11:07:52 -07:00
Erik Johnston
c0577ea87a Rollback if we try and insert duplicate events 2014-09-08 18:34:18 +01:00
Emmanuel ROHEE
d81e7dc00e Added /join description 2014-09-08 18:25:56 +02:00
Emmanuel ROHEE
9a5f224931 matrixService.rooms must be renamed matrixService.initialSync now 2014-09-08 18:21:41 +02:00
Emmanuel ROHEE
21d6ce2380 App startup improvements:
- do one and only one initialSync when the app starts. (recents-controller does not do its own anymore)
 - initialSync: get only the last message per room instead of default number of messages (10)

Prevent recents-controller from loosing its data each time the page URL changes
2014-09-08 18:14:35 +02:00
David Baker
972f664b6b add sounds to the calling interface 2014-09-08 16:10:36 +01:00
Emmanuel ROHEE
1dc4ad1efa Merge branch 'origin/release-v0.2.2' into develop 2014-09-08 11:29:47 +02:00
Matthew Hodgson
a0a609e8af fix embarassing bug where in-progress messages get vaped when the previous one gets delivered 2014-09-08 11:28:51 +02:00
Matthew Hodgson
dc1f202eca fix desktop notifs, which were broken in eab463fd 2014-09-08 11:28:51 +02:00
Kegan Dougal
ce5cd2202f Center recaptcha dialog. 2014-09-08 11:28:51 +02:00
Erik Johnston
2df5cb114d Remove disabled change from CHANGES 2014-09-08 11:28:50 +02:00
Matthew Hodgson
ef0304beff disable broken event dup suppression, and fix echo for /me 2014-09-08 11:28:50 +02:00
Kegan Dougal
dd2ae64120 Set the room_alias field when we encounter a new one, rather than only from local storage. 2014-09-08 11:28:50 +02:00
Kegan Dougal
cde6bdfa77 Use the room_display_name when presenting on the home page, and not the room_alias which may not be set. 2014-09-08 11:28:50 +02:00
Kegan Dougal
f397b2264c https when loading recaptcha js 2014-09-08 11:28:50 +02:00
Erik Johnston
768ff1a850 Fix race in presence handler where we evicted things from cache while handling a key therein 2014-09-08 11:28:50 +02:00
Erik Johnston
7735aad9d6 Bump version and changelog 2014-09-08 11:28:50 +02:00
Kegan Dougal
7bff9b6269 Minor spec tweaks. 2014-09-08 11:28:50 +02:00
Emmanuel ROHEE
24f0bb4af5 Revert "BF: Made notification work again (forgot to renamed "offline" to "unavailable")"
This reverts commit c3f9d8e41b.
2014-09-08 11:09:14 +02:00
Emmanuel ROHEE
c3f9d8e41b BF: Made notification work again (forgot to renamed "offline" to "unavailable") 2014-09-08 10:28:07 +02:00
Matthew Hodgson
64b6f09b0d fix embarassing bug where in-progress messages get vaped when the previous one gets delivered 2014-09-06 17:48:16 -07:00
Erik Johnston
a73104b566 Merge branch 'release-v0.2.2' of github.com:matrix-org/synapse 2014-09-06 18:28:24 +01:00
Matthew Hodgson
41907209bb fix desktop notifs, which were broken in eab463fd 2014-09-06 10:26:41 -07:00
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
Mark Haines
e0fa4cf874 Spelling 2014-09-05 18:22:24 +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
Mark Haines
9243f0c5e3 Add docs on how to sign json 2014-09-05 17:42:54 +01: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
7b79c0f08f v0.1.2 2014-08-29 18:13:34 +01:00
David Baker
8c36179d35 Give basic feedback on the state of VoIP calls in the UI (manually adding to master having added to develop) 2014-08-29 18:11:20 +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
288 changed files with 42934 additions and 10060 deletions

4
.gitignore vendored
View File

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

View File

@@ -1,3 +1,225 @@
Changes in synapse 0.4.2 (2014-10-31)
=====================================
Homeserver:
* Fix bugs where we did not notify users of correct presence updates.
* Fix bug where we did not handle sub second event stream timeouts.
Webclient:
* Add ability to click on messages to see JSON.
* Add ability to redact messages.
* Add ability to view and edit all room state JSON.
* Handle incoming redactions.
* Improve feedback on errors.
* Fix bugs in mobile CSS.
* Fix bugs with desktop notifications.
Changes in synapse 0.4.1 (2014-10-17)
=====================================
Webclient:
* Fix bug with display of timestamps.
Changes in synpase 0.4.0 (2014-10-17)
=====================================
This release includes changes to the federation protocol and client-server API
that is not backwards compatible.
The Matrix specification has been moved to a separate git repository:
http://github.com/matrix-org/matrix-doc
You will also need an updated syutil and config. See UPGRADES.rst.
Homeserver:
* Sign federation transactions to assert strong identity over federation.
* Rename timestamp keys in PDUs and events from 'ts' and 'hsob_ts' to 'origin_server_ts'.
Changes in synapse 0.3.4 (2014-09-25)
=====================================
This version adds support for using a TURN server. See docs/turn-howto.rst on
how to set one up.
Homeserver:
* Add support for redaction of messages.
* Fix bug where inviting a user on a remote home server could take up to
20-30s.
* Implement a get current room state API.
* Add support specifying and retrieving turn server configuration.
Webclient:
* Add button to send messages to users from the home page.
* Add support for using TURN for VoIP calls.
* Show display name change messages.
* Fix bug where the client didn't get the state of a newly joined room
until after it has been refreshed.
* Fix bugs with tab complete.
* Fix bug where holding down the down arrow caused chrome to chew 100% CPU.
* Fix bug where desktop notifications occasionally used "Undefined" as the
display name.
* Fix more places where we sometimes saw room IDs incorrectly.
* Fix bug which caused lag when entering text in the text box.
Changes in synapse 0.3.3 (2014-09-22)
=====================================
Homeserver:
* Fix bug where you continued to get events for rooms you had left.
Webclient:
* Add support for video calls with basic UI.
* Fix bug where one to one chats were named after your display name rather
than the other person's.
* Fix bug which caused lag when typing in the textarea.
* Refuse to run on browsers we know won't work.
* Trigger pagination when joining new rooms.
* Fix bug where we sometimes didn't display invitations in recents.
* Automatically join room when accepting a VoIP call.
* Disable outgoing and reject incoming calls on browsers we don't support
VoIP in.
* Don't display desktop notifications for messages in the room you are
non-idle and speaking in.
Changes in synapse 0.3.2 (2014-09-18)
=====================================
Webclient:
* Fix bug where an empty "bing words" list in old accounts didn't send
notifications when it should have done.
Changes in synapse 0.3.1 (2014-09-18)
=====================================
This is a release to hotfix v0.3.0 to fix two regressions.
Webclient:
* Fix a regression where we sometimes displayed duplicate events.
* Fix a regression where we didn't immediately remove rooms you were
banned in from the recents list.
Changes in synapse 0.3.0 (2014-09-18)
=====================================
See UPGRADE for information about changes to the client server API, including
breaking backwards compatibility with VoIP calls and registration API.
Homeserver:
* When a user changes their displayname or avatar the server will now update
all their join states to reflect this.
* The server now adds "age" key to events to indicate how old they are. This
is clock independent, so at no point does any server or webclient have to
assume their clock is in sync with everyone else.
* Fix bug where we didn't correctly pull in missing PDUs.
* Fix bug where prev_content key wasn't always returned.
* Add support for password resets.
Webclient:
* Improve page content loading.
* Join/parts now trigger desktop notifications.
* Always show room aliases in the UI if one is present.
* No longer show user-count in the recents side panel.
* Add up & down arrow support to the text box for message sending to step
through your sent history.
* Don't display notifications for our own messages.
* Emotes are now formatted correctly in desktop notifications.
* The recents list now differentiates between public & private rooms.
* Fix bug where when switching between rooms the pagination flickered before
the view jumped to the bottom of the screen.
* Add bing word support.
Registration API:
* The registration API has been overhauled to function like the login API. In
practice, this means registration requests must now include the following:
'type':'m.login.password'. See UPGRADE for more information on this.
* The 'user_id' key has been renamed to 'user' to better match the login API.
* There is an additional login type: 'm.login.email.identity'.
* The command client and web client have been updated to reflect these changes.
Changes in synapse 0.2.3 (2014-09-12)
=====================================
Homeserver:
* Fix bug where we stopped sending events to remote home servers if a
user from that home server left, even if there were some still in the
room.
* Fix bugs in the state conflict resolution where it was incorrectly
rejecting events.
Webclient:
* Display room names and topics.
* Allow setting/editing of room names and topics.
* Display information about rooms on the main page.
* Handle ban and kick events in real time.
* VoIP UI and reliability improvements.
* Add glare support for VoIP.
* Improvements to initial startup speed.
* Don't display duplicate join events.
* Local echo of messages.
* Differentiate sending and sent of local echo.
* Various minor bug fixes.
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)
=====================================
Webclient:
* Add basic call state UI for VoIP calls.
Changes in synapse 0.1.1 (2014-08-29)
=====================================

View File

@@ -1,65 +1,94 @@
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.
- Matrix user IDs look like ``@matthew:matrix.org`` (although in the future
you will normally refer to yourself and others using a 3PID: email
address, phone number, etc rather than manipulating Matrix user IDs)
- Chatrooms are distributed and do not exist on any single server. Rooms
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
address, phone number, etc rather than manipulating Matrix user IDs)
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 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 simply play with an **existing** homeserver you can
just go straight to http://matrix.org/alpha.
- To run your own **private** homeserver on localhost:8008, generate a basic
config file: ``./synctl start`` will give you instructions on how to do this.
For this purpose, you can use 'localhost' or your hostname as a server name.
Once you've done so, running ``./synctl start`` again will start your private
home sserver. You will find a webclient running at http://localhost:8008.
Please use a recent Chrome or Firefox for now (or Safari if you don't need
VoIP support).
- 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
============
Matrix specifies a set of pragmatic RESTful HTTP JSON APIs as an open standard,
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
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]
- 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)
- Creating and managing fully distributed chat rooms with no
single points of control or failure
- 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[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
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
the entirely open Matrix ecosystem rather than using closed or proprietary
and clients, letting developers build messaging and VoIP functionality on top
of the entirely open Matrix ecosystem rather than using closed or proprietary
solutions. The hope is for Matrix to act as the building blocks for a new
generation of fully open and interoperable messaging and VoIP apps for the
internet.
@@ -74,38 +103,37 @@ In Matrix, every user runs one or more Matrix clients, which connect through to
a Matrix homeserver which stores all their personal chat history and user
account information - much as a mail client connects through to an IMAP/SMTP
server. Just like email, you can either run your own Matrix homeserver and
control and own your own communications and history or use one hosted by someone
else (e.g. matrix.org) - there is no single point of control or mandatory
service provider in Matrix, unlike WhatsApp, Facebook, Hangouts, etc.
control and own your own communications and history or use one hosted by
someone else (e.g. matrix.org) - there is no single point of control or
mandatory service provider in Matrix, unlike WhatsApp, Facebook, Hangouts, etc.
Synapse ships with two basic demo Matrix clients: webclient (a basic group chat
web client demo implemented in AngularJS) and cmdclient (a basic Python
commandline utility which lets you easily see what the JSON APIs are up to).
command line utility which lets you easily see what the JSON APIs are up to).
We'd like to invite you to take a look at the Matrix spec, try to run a
homeserver, and join the existing Matrix chatrooms already out there, experiment
with the APIs and the demo clients, and let us know your thoughts at
homeserver, and join the existing Matrix chatrooms already out there,
experiment with the APIs and the demo clients, and let us know your thoughts at
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
=======================
First, the dependencies need to be installed. Start by installing
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::
Installing prerequisites on Ubuntu::
$ sudo apt-get install build-essential python2.7-dev libffi-dev
Installing prerequisites on Mac OS X::
Installing prerequisites on Mac OS X::
$ xcode-select --install
@@ -113,22 +141,25 @@ The homeserver has a number of external dependencies, that are easiest
to install by making setup.py do so, in --user mode::
$ python setup.py develop --user
You'll need a version of setuptools new enough to know about git, so you
may need to also run:
may need to also run::
$ sudo apt-get install python-pip
$ sudo pip install --upgrade setuptools
If you don't have access to github, then you may need to install ``syutil``
manually by checking it out and running ``python setup.py develop --user`` on it
too.
manually by checking it out and running ``python setup.py develop --user`` on
it too.
If you get errors about ``sodium.h`` being missing, you may also need to
manually install a newer PyNaCl via pip as setuptools installs an old one. Or
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).
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
@@ -151,7 +182,7 @@ Upgrading an existing homeserver
Before upgrading an existing homeserver to a new version, please refer to
UPGRADE.rst for any additional instructions.
Setting up Federation
=====================
@@ -161,19 +192,28 @@ be publicly visible on the internet, and they will need to know its host name.
You have two choices here, which will influence the form of your Matrix user
IDs:
1) Use the machine's own hostname as available on public DNS in the form of its
A or AAAA records. This is easier to set up initially, perhaps for testing,
but lacks the flexibility of SRV.
1) Use the machine's own hostname as available on public DNS in the form of
its A or AAAA records. This is easier to set up initially, perhaps for
testing, but lacks the flexibility of SRV.
2) Set up a SRV record for your domain name. This requires you create a SRV
record in DNS, but gives the flexibility to run the server on your own
choice of TCP port, on a machine that might not be the same name as the
domain name.
2) Set up a SRV record for your domain name. This requires you create a SRV
record in DNS, but gives the flexibility to run the server on your own
choice of TCP port, on a machine that might not be the same name as the
domain name.
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 +226,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.
@@ -208,7 +254,7 @@ http://localhost:8080. Simply run::
Running The Demo Web Client
===========================
The homeserver runs a web client by default at http://localhost:8080.
The homeserver runs a web client by default at https://localhost:8448/.
If this is the first time you have used the client from that browser (it uses
HTML5 local storage to remember its config), you will need to log in to your
@@ -228,8 +274,8 @@ account. Your name will take the form of::
Specify your desired localpart in the topmost box of the "Register for an
account" form, and click the "Register" button. Hostnames can contain ports if
required due to lack of SRV records (e.g. @matthew:localhost:8080 on an internal
synapse sandbox running on localhost)
required due to lack of SRV records (e.g. @matthew:localhost:8448 on an
internal synapse sandbox running on localhost)
Logging In To An Existing Account
@@ -244,9 +290,9 @@ Identity Servers
The job of authenticating 3PIDs and tracking which 3PIDs are associated with a
given Matrix user is very security-sensitive, as there is obvious risk of spam
if it is too easy to sign up for Matrix accounts or harvest 3PID data. Meanwhile
the job of publishing the end-to-end encryption public keys for Matrix users is
also very security-sensitive for similar reasons.
if it is too easy to sign up for Matrix accounts or harvest 3PID data.
Meanwhile the job of publishing the end-to-end encryption public keys for
Matrix users is also very security-sensitive for similar reasons.
Therefore the role of managing trusted identity in the Matrix ecosystem is
farmed out to a cluster of known trusted ecosystem partners, who run 'Matrix
@@ -255,7 +301,8 @@ 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,73 @@
Upgrading to v0.4.0
===================
This release needs an updated syutil version. Run::
python setup.py develop
You will also need to upgrade your configuration as the signing key format has
changed. Run::
python -m synapse.app.homeserver --config-path <CONFIG> --generate-config
Upgrading to v0.3.0
===================
This registration API now closely matches the login API. This introduces a bit
more backwards and forwards between the HS and the client, but this improves
the overall flexibility of the API. You can now GET on /register to retrieve a list
of valid registration flows. Upon choosing one, they are submitted in the same
way as login, e.g::
{
type: m.login.password,
user: foo,
password: bar
}
The default HS supports 2 flows, with and without Identity Server email
authentication. Enabling captcha on the HS will add in an extra step to all
flows: ``m.login.recaptcha`` which must be completed before you can transition
to the next stage. There is a new login type: ``m.login.email.identity`` which
contains the ``threepidCreds`` key which were previously sent in the original
register request. For more information on this, see the specification.
Web Client
----------
The VoIP specification has changed between v0.2.0 and v0.3.0. Users should
refresh any browser tabs to get the latest web client code. Users on
v0.2.0 of the web client will not be able to call those on v0.3.0 and
vice versa.
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.1
0.4.2

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):
@@ -143,35 +145,50 @@ class SynapseCmd(cmd.Cmd):
<noupdate> : Do not automatically clobber config values.
"""
args = self._parse(line, ["userid", "noupdate"])
path = "/register"
password = None
pwd = None
pwd2 = "_"
while pwd != pwd2:
pwd = getpass.getpass("(Optional) Type a password for this user: ")
if len(pwd) == 0:
print "Not using a password for this user."
break
pwd = getpass.getpass("Type a password for this user: ")
pwd2 = getpass.getpass("Retype the password: ")
if pwd != pwd2:
if pwd != pwd2 or len(pwd) == 0:
print "Password mismatch."
pwd = None
else:
password = pwd
body = {}
body = {
"type": "m.login.password"
}
if "userid" in args:
body["user_id"] = args["userid"]
body["user"] = args["userid"]
if password:
body["password"] = password
reactor.callFromThread(self._do_register, "POST", path, body,
reactor.callFromThread(self._do_register, body,
"noupdate" not in args)
@defer.inlineCallbacks
def _do_register(self, method, path, data, update_config):
url = self._url() + path
json_res = yield self.http_client.do_request(method, url, data=data)
def _do_register(self, data, update_config):
# check the registration flows
url = self._url() + "/register"
json_res = yield self.http_client.do_request("GET", url)
print json.dumps(json_res, indent=4)
passwordFlow = None
for flow in json_res["flows"]:
if flow["type"] == "m.login.recaptcha" or ("stages" in flow and "m.login.recaptcha" in flow["stages"]):
print "Unable to register: Home server requires captcha."
return
if flow["type"] == "m.login.password" and "stages" not in flow:
passwordFlow = flow
break
if not passwordFlow:
return
json_res = yield self.http_client.do_request("POST", url, data=data)
print json.dumps(json_res, indent=4)
if update_config and "user_id" in json_res:
self.config["user"] = json_res["user_id"]
@@ -191,10 +208,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 +271,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 +293,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 +313,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 +331,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 +379,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 +719,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.

View File

@@ -14,3 +14,4 @@ fi
find "$DIR" -name "*.log" -delete
find "$DIR" -name "*.db" -delete
rm -rf $DIR/etc

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,38 @@ CWD=$(pwd)
cd "$DIR/.."
mkdir -p demo/etc
# Check the --no-rate-limit param
PARAMS=""
if [ $# -eq 1 ]; then
if [ $1 = "--no-rate-limit" ]; then
PARAMS="--rc-messages-per-second 1000 --rc-message-burst-count 1000"
fi
fi
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" \
$PARAMS
python -m synapse.app.homeserver \
--config-path "demo/etc/$port.config" \
-vv \
done
echo "Starting webclient on port 8000..."

6
docs/README.rst Normal file
View File

@@ -0,0 +1,6 @@
All matrix-generic documentation now lives in its own project at
github.com/matrix-org/matrix-doc.git
Only Synapse implementation-specific documentation lives here now
(together with some older stuff will be shortly migrated over to matrix-doc)

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,303 +0,0 @@
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".
How to use the client-server API
================================
This guide focuses on how the client-server APIs *provided by the reference
home server* can be used. Since this is specific to a home server
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``.
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/**
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"
{
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.AqdSzFmFYrLrTmteXc",
"home_server": "localhost",
"user_id": "@example:localhost"
}
NB: If a ``user_id`` is not specified, one will be randomly generated for you.
If you do not specify a ``password``, you will be unable to login to the account
if you forget the ``access_token``.
Implementation note: The matrix specification does not enforce how users
register with a server. It just specifies the URL path and absolute minimum
keys. The reference home server uses a username/password to authenticate user,
but other home servers may use different methods.
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"
{
"type": "m.login.password"
}
curl -XPOST -d '{"type":"m.login.password", "user":"example", "password":"wordpass"}' "http://localhost:8080/matrix/client/api/v1/login"
{
"access_token": "QGV4YW1wbGU6bG9jYWxob3N0.vRDLTgxefmKWQEtgGd",
"home_server": "localhost",
"user_id": "@example:localhost"
}
Implementation note: Different home servers may implement different methods for
logging in to an existing account. In order to check that you know how to login
to this home server, you must perform a ``GET`` first and make sure you
recognise the login type. If you do not know how to login, you can
``GET /login/fallback`` which will return a basic webpage which you can use to
login. The reference home server implementation support username/password login,
but other home servers may support different login methods (e.g. OAuth2).
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/**
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"
{
"room_alias": "#tutorial:localhost",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
}
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.
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"
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.
Users and rooms
===============
Each room can be configured to allow or disallow certain rules. In particular,
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/**
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"
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::
curl -XPUT -d '{"membership":"join"}' "http://localhost:8080/matrix/client/api/v1/rooms/%21CvcvRuDYDzTOzfKKgh:localhost/members/%40myfriend%3Alocalhost/state?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK"
NB: Only the person invited (``@myfriend:localhost``) can change the membership
state to ``"join"``.
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"
{
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost"
}
You will need to use the room ID when sending messages, not the room alias.
NB: If the room is configured to be an invite-only room, you will still require
an invite in order to join the room even though you know the room alias. As a
result, it is more common to see a room alias in relation to a public room,
which do not require invitations.
Getting events
==============
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/**
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"
[
{
"membership": "join",
"messages": {
"chunk": [
{
"content": {
"body": "@example:localhost joined the room.",
"hsob_ts": 1408444664249,
"membership": "join",
"membership_source": "@example:localhost",
"membership_target": "@example:localhost",
"msgtype": "m.text"
},
"event_id": "lZjmmlrEvo",
"msg_id": "m1408444664249",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"user_id": "_homeserver_"
},
{
"content": {
"body": "hello",
"hsob_ts": 1408445405672,
"msgtype": "m.text"
},
"event_id": "BiBJqamISg",
"msg_id": "msgid1",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"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"
},
"event_id": "IMmXbOzFAa",
"msg_id": "m1408446501661",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"user_id": "_homeserver_"
}
],
"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::
curl -XGET "http://localhost:8080/matrix/client/api/v1/im/sync?access_token=QG15ZnJpZW5kOmxvY2FsaG9zdA...XKuGdVsovHmwMyDDvK&from=END&to=START&limit=1"
[
{
"membership": "join",
"messages": {
"chunk": [
{
"content": {
"body": "@myfriend:localhost joined the room.",
"hsob_ts": 1408446501661,
"membership": "join",
"membership_source": "@myfriend:localhost",
"membership_target": "@myfriend:localhost",
"msgtype": "m.text"
},
"event_id": "IMmXbOzFAa",
"msg_id": "m1408446501661",
"room_id": "!CvcvRuDYDzTOzfKKgh:localhost",
"type": "m.room.message",
"user_id": "_homeserver_"
}
],
"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"
{
"chunk": [],
"end": "215",
"start": "215"
}
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.
NB: The timeout can be changed by adding a ``timeout`` query parameter, which is
in milliseconds. A timeout of 0 will not block.
Example application
-------------------
The following example demonstrates registration and login, live event streaming,
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/**

View File

@@ -1,103 +1,3 @@
========
Presence
========
A description of presence information and visibility between users.
Overview
========
Each user has the concept of Presence information. This encodes a sense of the
"availability" of that user, suitable for display on other user's clients.
Presence Information
====================
The basic piece of presence information is an enumeration of a small set of
state; such as "free to chat", "online", "busy", or "offline". The default state
unless the user changes it is "online". Lower states suggest some amount of
decreased availability from normal, which might have some client-side effect
like muting notification sounds and suggests to other users not to bother them
unless it is urgent. Equally, the "free to chat" state exists to let the user
announce their general willingness to receive messages moreso than default.
Home servers should also allow a user to set their state as "hidden" - a state
which behaves as offline, but allows the user to see the client state anyway and
generally interact with client features such as reading message history or
accessing contacts in the address book.
This basic state field applies to the user as a whole, regardless of how many
client devices they have connected. The home server should synchronise this
status choice among multiple devices to ensure the user gets a consistent
experience.
Idle Time
---------
As well as the basic state field, the presence information can also show a sense
of an "idle timer". This should be maintained individually by the user's
clients, and the homeserver can take the highest reported time as that to
report. Likely this should be presented in fairly coarse granularity; possibly
being limited to letting the home server automatically switch from a "free to
chat" or "online" mode into "idle".
When a user is offline, the Home Server can still report when the user was last
seen online, again perhaps in a somewhat coarse manner.
Device Type
-----------
Client devices that may limit the user experience somewhat (such as "mobile"
devices with limited ability to type on a real keyboard or read large amounts of
text) should report this to the home server, as this is also useful information
to report as "presence" if the user cannot be expected to provide a good typed
response to messages.
Presence List
=============
Each user's home server stores a "presence list" for that user. This stores a
list of other user IDs the user has chosen to add to it (remembering any ACL
Pointer if appropriate).
To be added to a contact list, the user being added must grant permission. Once
granted, both user's HS(es) store this information, as it allows the user who
has added the contact some more abilities; see below. Since such subscriptions
are likely to be bidirectional, HSes may wish to automatically accept requests
when a reverse subscription already exists.
As a convenience, presence lists should support the ability to collect users
into groups, which could allow things like inviting the entire group to a new
("ad-hoc") chat room, or easy interaction with the profile information ACL
implementation of the HS.
Presence and Permissions
========================
For a viewing user to be allowed to see the presence information of a target
user, either
* The target user has allowed the viewing user to add them to their presence
list, or
* The two users share at least one room in common
In the latter case, this allows for clients to display some minimal sense of
presence information in a user list for a room.
Home servers can also use the user's choice of presence state as a signal for
how to handle new private one-to-one chat message requests. For example, it
might decide:
"free to chat": accept anything
"online": accept from anyone in my addres book list
"busy": accept from anyone in this "important people" group in my address
book list
API Efficiency
==============

View File

@@ -1,46 +0,0 @@
{
"apiVersion": "1.0.0",
"swaggerVersion": "1.2",
"apis": [
{
"path": "/login",
"description": "Login operations"
},
{
"path": "/registration",
"description": "Registration operations"
},
{
"path": "/rooms",
"description": "Room operations"
},
{
"path": "/profile",
"description": "Profile operations"
},
{
"path": "/presence",
"description": "Presence operations"
},
{
"path": "/events",
"description": "Event operations"
},
{
"path": "/directory",
"description": "Directory operations"
}
],
"authorizations": {
"token": {
"scopes": []
}
},
"info": {
"title": "Matrix Client-Server API Reference",
"description": "This contains the client-server API for the reference implementation of the home server",
"termsOfServiceUrl": "http://matrix.org",
"license": "Apache 2.0",
"licenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
}

View File

@@ -1,83 +0,0 @@
{
"apiVersion": "1.0.0",
"swaggerVersion": "1.2",
"basePath": "http://localhost:8080/matrix/client/api/v1",
"resourcePath": "/directory",
"produces": [
"application/json"
],
"apis": [
{
"path": "/directory/room/{roomAlias}",
"operations": [
{
"method": "GET",
"summary": "Get the room ID corresponding to this room alias.",
"type": "DirectoryResponse",
"nickname": "get_room_id_for_alias",
"parameters": [
{
"name": "roomAlias",
"description": "The room alias.",
"required": true,
"type": "string",
"paramType": "path"
}
]
},
{
"method": "PUT",
"summary": "Create a new mapping from room alias to room ID.",
"type": "void",
"nickname": "add_room_alias",
"parameters": [
{
"name": "roomAlias",
"description": "The room alias to set.",
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "body",
"description": "The room ID to set.",
"required": true,
"type": "RoomAliasRequest",
"paramType": "body"
}
]
}
]
}
],
"models": {
"DirectoryResponse": {
"id": "DirectoryResponse",
"properties": {
"room_id": {
"type": "string",
"description": "The fully-qualified room ID.",
"required": true
},
"servers": {
"type": "array",
"items": {
"$ref": "string"
},
"description": "A list of servers that know about this room.",
"required": true
}
}
},
"RoomAliasRequest": {
"id": "RoomAliasRequest",
"properties": {
"room_id": {
"type": "string",
"description": "The room ID to map the alias to.",
"required": true
}
}
}
}
}

View File

@@ -1,246 +0,0 @@
{
"apiVersion": "1.0.0",
"swaggerVersion": "1.2",
"basePath": "http://localhost:8080/matrix/client/api/v1",
"resourcePath": "/events",
"produces": [
"application/json"
],
"apis": [
{
"path": "/events",
"operations": [
{
"method": "GET",
"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"
}
],
"responseMessages": [
{
"code": 400,
"message": "Bad pagination token."
}
]
},
{
"path": "/events/{eventId}",
"operations": [
{
"method": "GET",
"summary": "Get information about a single event.",
"notes": "Get information about a single event.",
"type": "Event",
"nickname": "get_event",
"parameters": [
{
"name": "eventId",
"description": "The event ID to get.",
"required": true,
"type": "string",
"paramType": "path"
}
],
"responseMessages": [
{
"code": 404,
"message": "Event not found."
}
]
}
]
},
{
"path": "/initialSync",
"operations": [
{
"method": "GET",
"summary": "Get this user's current state.",
"notes": "Get this user's current state.",
"type": "InitialSyncResponse",
"nickname": "initial_sync",
"parameters": [
{
"name": "limit",
"description": "The maximum number of messages to return for each room.",
"type": "integer",
"paramType": "query",
"required": false
}
]
}
]
},
{
"path": "/publicRooms",
"operations": [
{
"method": "GET",
"summary": "Get a list of publicly visible rooms.",
"type": "PublicRoomsPaginationChunk",
"nickname": "get_public_room_list"
}
]
}
],
"models": {
"PaginationChunk": {
"id": "PaginationChunk",
"properties": {
"start": {
"type": "string",
"description": "A token which correlates to the first value in \"chunk\" for paginating.",
"required": true
},
"end": {
"type": "string",
"description": "A token which correlates to the last value in \"chunk\" for paginating.",
"required": true
},
"chunk": {
"type": "array",
"description": "An array of events.",
"required": true,
"items": {
"$ref": "Event"
}
}
}
},
"Event": {
"id": "Event",
"properties": {
"event_id": {
"type": "string",
"description": "An ID which uniquely identifies this event.",
"required": true
},
"room_id": {
"type": "string",
"description": "The room in which this event occurred.",
"required": true
}
}
},
"PublicRoomInfo": {
"id": "PublicRoomInfo",
"properties": {
"aliases": {
"type": "array",
"description": "A list of room aliases for this room.",
"items": {
"$ref": "string"
}
},
"name": {
"type": "string",
"description": "The name of the room, as given by the m.room.name state event."
},
"room_id": {
"type": "string",
"description": "The room ID for this public room.",
"required": true
},
"topic": {
"type": "string",
"description": "The topic of this room, as given by the m.room.topic state event."
}
}
},
"PublicRoomsPaginationChunk": {
"id": "PublicRoomsPaginationChunk",
"properties": {
"start": {
"type": "string",
"description": "A token which correlates to the first value in \"chunk\" for paginating.",
"required": true
},
"end": {
"type": "string",
"description": "A token which correlates to the last value in \"chunk\" for paginating.",
"required": true
},
"chunk": {
"type": "array",
"description": "A list of public room data.",
"required": true,
"items": {
"$ref": "PublicRoomInfo"
}
}
}
},
"InitialSyncResponse": {
"id": "InitialSyncResponse",
"properties": {
"end": {
"type": "string",
"description": "A streaming token which can be used with /events to continue from this snapshot of data.",
"required": true
},
"presence": {
"type": "array",
"description": "A list of presence events.",
"items": {
"$ref": "Event"
},
"required": false
},
"rooms": {
"type": "array",
"description": "A list of initial sync room data.",
"required": false,
"items": {
"$ref": "InitialSyncRoomData"
}
}
}
},
"InitialSyncRoomData": {
"id": "InitialSyncRoomData",
"properties": {
"membership": {
"type": "string",
"description": "This user's membership state in this room.",
"required": true
},
"room_id": {
"type": "string",
"description": "The ID of this room.",
"required": true
},
"messages": {
"type": "PaginationChunk",
"description": "The most recent messages for this room, governed by the limit parameter.",
"required": false
},
"state": {
"type": "array",
"description": "A list of state events representing the current state of the room.",
"required": false,
"items": {
"$ref": "Event"
}
}
}
}
}
}

View File

@@ -1,102 +0,0 @@
{
"apiVersion": "1.0.0",
"apis": [
{
"operations": [
{
"method": "GET",
"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"
},
{
"method": "POST",
"nickname": "submit_login",
"notes": "If this is part of a multi-stage login, there MUST be a 'session' key.",
"parameters": [
{
"description": "A login submission",
"name": "body",
"paramType": "body",
"required": true,
"type": "LoginSubmission"
}
],
"responseMessages": [
{
"code": 400,
"message": "Bad login type"
},
{
"code": 400,
"message": "Missing JSON keys"
}
],
"summary": "Submit a login action.",
"type": "LoginResult"
}
],
"path": "/login"
}
],
"basePath": "http://localhost:8080/matrix/client/api/v1",
"consumes": [
"application/json"
],
"models": {
"LoginInfo": {
"id": "LoginInfo",
"properties": {
"stages": {
"description": "Multi-stage login only: An array of all the login types required to login.",
"format": "string",
"type": "array"
},
"type": {
"description": "The login type that must be used when logging in.",
"type": "string"
}
}
},
"LoginResult": {
"id": "LoginResult",
"properties": {
"access_token": {
"description": "The access token for this user's login if this is the final stage of the login process.",
"type": "string"
},
"next": {
"description": "Multi-stage login only: The next login type to submit.",
"type": "string"
},
"session": {
"description": "Multi-stage login only: The session token to send when submitting the next login type.",
"type": "string"
}
}
},
"LoginSubmission": {
"id": "LoginSubmission",
"properties": {
"type": {
"description": "The type of login being submitted.",
"type": "string"
},
"session": {
"description": "Multi-stage login only: The session token from an earlier login stage.",
"type": "string"
},
"_login_type_defined_keys_": {
"description": "Keys as defined by the specified login type, e.g. \"user\", \"password\""
}
}
}
},
"produces": [
"application/json"
],
"resourcePath": "/login",
"swaggerVersion": "1.2"
}

View File

@@ -1,164 +0,0 @@
{
"apiVersion": "1.0.0",
"swaggerVersion": "1.2",
"basePath": "http://localhost:8080/matrix/client/api/v1",
"resourcePath": "/presence",
"produces": [
"application/json"
],
"consumes": [
"application/json"
],
"apis": [
{
"path": "/presence/{userId}/status",
"operations": [
{
"method": "PUT",
"summary": "Update this user's presence state.",
"notes": "This can only be done by the logged in user.",
"type": "void",
"nickname": "update_presence",
"parameters": [
{
"name": "body",
"description": "The new presence state",
"required": true,
"type": "PresenceUpdate",
"paramType": "body"
},
{
"name": "userId",
"description": "The user whose presence to set.",
"required": true,
"type": "string",
"paramType": "path"
}
]
},
{
"method": "GET",
"summary": "Get this user's presence state.",
"notes": "Get this user's presence state.",
"type": "PresenceUpdate",
"nickname": "get_presence",
"parameters": [
{
"name": "userId",
"description": "The user whose presence to get.",
"required": true,
"type": "string",
"paramType": "path"
}
]
}
]
},
{
"path": "/presence/list/{userId}",
"operations": [
{
"method": "GET",
"summary": "Retrieve a list of presences for all of this user's friends.",
"notes": "",
"type": "array",
"items": {
"$ref": "Presence"
},
"nickname": "get_presence_list",
"parameters": [
{
"name": "userId",
"description": "The user whose presence list to get.",
"required": true,
"type": "string",
"paramType": "path"
}
]
},
{
"method": "POST",
"summary": "Add or remove users from this presence list.",
"notes": "Add or remove users from this presence list.",
"type": "void",
"nickname": "modify_presence_list",
"parameters": [
{
"name": "userId",
"description": "The user whose presence list is being modified.",
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "body",
"description": "The modifications to make to this presence list.",
"required": true,
"type": "PresenceListModifications",
"paramType": "body"
}
]
}
]
}
],
"models": {
"PresenceUpdate": {
"id": "PresenceUpdate",
"properties": {
"state": {
"type": "string",
"description": "Enum: The presence state.",
"enum": [
"offline",
"unavailable",
"online",
"free_for_chat"
]
},
"status_msg": {
"type": "string",
"description": "The user-defined message associated with this presence state."
}
},
"subTypes": [
"Presence"
]
},
"Presence": {
"id": "Presence",
"properties": {
"mtime_age": {
"type": "integer",
"format": "int64",
"description": "The last time this user's presence state changed, in milliseconds."
},
"user_id": {
"type": "string",
"description": "The fully qualified user ID"
}
}
},
"PresenceListModifications": {
"id": "PresenceListModifications",
"properties": {
"invite": {
"type": "array",
"description": "A list of user IDs to add to the list.",
"items": {
"type": "string",
"description": "A fully qualified user ID."
}
},
"drop": {
"type": "array",
"description": "A list of user IDs to remove from the list.",
"items": {
"type": "string",
"description": "A fully qualified user ID."
}
}
}
}
}
}

View File

@@ -1,122 +0,0 @@
{
"apiVersion": "1.0.0",
"swaggerVersion": "1.2",
"basePath": "http://localhost:8080/matrix/client/api/v1",
"resourcePath": "/profile",
"produces": [
"application/json"
],
"consumes": [
"application/json"
],
"apis": [
{
"path": "/profile/{userId}/displayname",
"operations": [
{
"method": "PUT",
"summary": "Set a display name.",
"notes": "This can only be done by the logged in user.",
"type": "void",
"nickname": "set_display_name",
"parameters": [
{
"name": "body",
"description": "The new display name for this user.",
"required": true,
"type": "DisplayName",
"paramType": "body"
},
{
"name": "userId",
"description": "The user whose display name to set.",
"required": true,
"type": "string",
"paramType": "path"
}
]
},
{
"method": "GET",
"summary": "Get a display name.",
"notes": "This can be done by anyone.",
"type": "DisplayName",
"nickname": "get_display_name",
"parameters": [
{
"name": "userId",
"description": "The user whose display name to get.",
"required": true,
"type": "string",
"paramType": "path"
}
]
}
]
},
{
"path": "/profile/{userId}/avatar_url",
"operations": [
{
"method": "PUT",
"summary": "Set an avatar URL.",
"notes": "This can only be done by the logged in user.",
"type": "void",
"nickname": "set_avatar_url",
"parameters": [
{
"name": "body",
"description": "The new avatar url for this user.",
"required": true,
"type": "AvatarUrl",
"paramType": "body"
},
{
"name": "userId",
"description": "The user whose avatar url to set.",
"required": true,
"type": "string",
"paramType": "path"
}
]
},
{
"method": "GET",
"summary": "Get an avatar url.",
"notes": "This can be done by anyone.",
"type": "AvatarUrl",
"nickname": "get_avatar_url",
"parameters": [
{
"name": "userId",
"description": "The user whose avatar url to get.",
"required": true,
"type": "string",
"paramType": "path"
}
]
}
]
}
],
"models": {
"DisplayName": {
"id": "DisplayName",
"properties": {
"displayname": {
"type": "string",
"description": "The textual display name"
}
}
},
"AvatarUrl": {
"id": "AvatarUrl",
"properties": {
"avatar_url": {
"type": "string",
"description": "A url to an image representing an avatar."
}
}
}
}
}

View File

@@ -1,75 +0,0 @@
{
"apiVersion": "1.0.0",
"apis": [
{
"operations": [
{
"method": "POST",
"nickname": "register",
"notes": "Volatile: This API is likely to change.",
"parameters": [
{
"description": "A registration request",
"name": "body",
"paramType": "body",
"required": true,
"type": "RegistrationRequest"
}
],
"responseMessages": [
{
"code": 400,
"message": "No JSON object."
},
{
"code": 400,
"message": "User ID must only contain characters which do not require url encoding."
},
{
"code": 400,
"message": "User ID already taken."
}
],
"summary": "Register with the home server.",
"type": "RegistrationResponse"
}
],
"path": "/register"
}
],
"basePath": "http://localhost:8080/matrix/client/api/v1",
"consumes": [
"application/json"
],
"models": {
"RegistrationResponse": {
"id": "RegistrationResponse",
"properties": {
"access_token": {
"description": "The access token for this user.",
"type": "string"
},
"user_id": {
"description": "The fully-qualified user ID.",
"type": "string"
}
}
},
"RegistrationRequest": {
"id": "RegistrationRequest",
"properties": {
"user_id": {
"description": "The desired user ID. If not specified, a random user ID will be allocated.",
"type": "string",
"required": false
}
}
}
},
"produces": [
"application/json"
],
"resourcePath": "/register",
"swaggerVersion": "1.2"
}

View File

@@ -1,863 +0,0 @@
{
"apiVersion": "1.0.0",
"swaggerVersion": "1.2",
"basePath": "http://localhost:8080/matrix/client/api/v1",
"resourcePath": "/rooms",
"produces": [
"application/json"
],
"consumes": [
"application/json"
],
"authorizations": {
"token": []
},
"apis": [
{
"path": "/rooms/{roomId}/send/{eventType}/{txnId}",
"operations": [
{
"method": "PUT",
"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}",
"type": "EventId",
"nickname": "send_non_state_event",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "body",
"description": "The event contents",
"required": true,
"type": "EventContent",
"paramType": "body"
},
{
"name": "roomId",
"description": "The room to send the message in.",
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "eventType",
"description": "The type of event to send.",
"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"
}
]
}
]
},
{
"path": "/rooms/{roomId}/state/{eventType}/{stateKey}",
"operations": [
{
"method": "PUT",
"summary": "Send a generic state event to this room.",
"notes": "The state key can be omitted, such that you can PUT to /rooms/{roomId}/state/{eventType}. The state key defaults to a 0 length string in this case.",
"type": "void",
"nickname": "send_state_event",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "body",
"description": "The event contents",
"required": true,
"type": "EventContent",
"paramType": "body"
},
{
"name": "roomId",
"description": "The room to send the message in.",
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "eventType",
"description": "The type of event to send.",
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "stateKey",
"description": "An identifier used to specify clobbering semantics. State events with the same (roomId, eventType, stateKey) will be replaced.",
"required": true,
"type": "string",
"paramType": "path"
}
]
}
]
},
{
"path": "/rooms/{roomId}/send/m.room.message/{txnId}",
"operations": [
{
"method": "PUT",
"summary": "Send a message in this room.",
"notes": "This operation can also be done as a POST to /rooms/{roomId}/send/m.room.message",
"type": "EventId",
"nickname": "send_message",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "body",
"description": "The message contents",
"required": true,
"type": "Message",
"paramType": "body"
},
{
"name": "roomId",
"description": "The room to send the message in.",
"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"
}
]
}
]
},
{
"path": "/rooms/{roomId}/state/m.room.topic",
"operations": [
{
"method": "PUT",
"summary": "Set the topic for this room.",
"notes": "Set the topic for this room.",
"type": "void",
"nickname": "set_topic",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "body",
"description": "The topic contents",
"required": true,
"type": "Topic",
"paramType": "body"
},
{
"name": "roomId",
"description": "The room to set the topic in.",
"required": true,
"type": "string",
"paramType": "path"
}
]
},
{
"method": "GET",
"summary": "Get the topic for this room.",
"notes": "Get the topic for this room.",
"type": "Topic",
"nickname": "get_topic",
"parameters": [
{
"name": "roomId",
"description": "The room to get topic in.",
"required": true,
"type": "string",
"paramType": "path"
}
],
"responseMessages": [
{
"code": 404,
"message": "Topic not found."
}
]
}
]
},
{
"path": "/rooms/{roomId}/send/m.room.message.feedback/{txnId}",
"operations": [
{
"method": "PUT",
"summary": "Send feedback to a message.",
"notes": "This operation can also be done as a POST to /rooms/{roomId}/send/m.room.message.feedback",
"type": "EventId",
"nickname": "send_feedback",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "body",
"description": "The feedback contents",
"required": true,
"type": "Feedback",
"paramType": "body"
},
{
"name": "roomId",
"description": "The room to send the feedback in.",
"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": [
{
"code": 400,
"message": "Bad feedback type."
}
]
}
]
},
{
"path": "/rooms/{roomId}/invite/{txnId}",
"operations": [
{
"method": "PUT",
"summary": "Invite a user to this room.",
"notes": "This operation can also be done as a POST to /rooms/{roomId}/invite",
"type": "void",
"nickname": "invite",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "roomId",
"description": "The room which has this user.",
"required": true,
"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.",
"required": true,
"type": "InviteRequest",
"paramType": "body"
}
]
}
]
},
{
"path": "/rooms/{roomId}/join/{txnId}",
"operations": [
{
"method": "PUT",
"summary": "Join this room.",
"notes": "This operation can also be done as a POST to /rooms/{roomId}/join",
"type": "void",
"nickname": "join_room",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "roomId",
"description": "The room to join.",
"required": true,
"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"
}
]
}
]
},
{
"path": "/rooms/{roomId}/leave/{txnId}",
"operations": [
{
"method": "PUT",
"summary": "Leave this room.",
"notes": "This operation can also be done as a POST to /rooms/{roomId}/leave",
"type": "void",
"nickname": "leave",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "roomId",
"description": "The room to leave.",
"required": true,
"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"
}
]
}
]
},
{
"path": "/rooms/{roomId}/state/m.room.member/{userId}",
"operations": [
{
"method": "PUT",
"summary": "Change the membership state for a user in a room.",
"notes": "Change the membership state for a user in a room.",
"type": "void",
"nickname": "set_membership",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "body",
"description": "The new membership state",
"required": true,
"type": "Member",
"paramType": "body"
},
{
"name": "userId",
"description": "The user whose membership is being changed.",
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "roomId",
"description": "The room which has this user.",
"required": true,
"type": "string",
"paramType": "path"
}
],
"responseMessages": [
{
"code": 400,
"message": "No membership key."
},
{
"code": 400,
"message": "Bad membership value."
},
{
"code": 403,
"message": "When inviting: You are not in the room."
},
{
"code": 403,
"message": "When inviting: <target> is already in the room."
},
{
"code": 403,
"message": "When joining: Cannot force another user to join."
},
{
"code": 403,
"message": "When joining: You are not invited to this room."
}
]
},
{
"method": "GET",
"summary": "Get the membership state of a user in a room.",
"notes": "Get the membership state of a user in a room.",
"type": "Member",
"nickname": "get_membership",
"parameters": [
{
"name": "userId",
"description": "The user whose membership state you want to get.",
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "roomId",
"description": "The room which has this user.",
"required": true,
"type": "string",
"paramType": "path"
}
],
"responseMessages": [
{
"code": 404,
"message": "Member not found."
}
]
}
]
},
{
"path": "/join/{roomAliasOrId}",
"operations": [
{
"method": "PUT",
"summary": "Join a room via a room alias or room ID.",
"notes": "Join a room via a room alias or room ID.",
"type": "RoomInfo",
"nickname": "join",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "roomAliasOrId",
"description": "The room alias or room ID to join.",
"required": true,
"type": "string",
"paramType": "path"
}
],
"responseMessages": [
{
"code": 400,
"message": "Bad room alias."
}
]
}
]
},
{
"path": "/createRoom",
"operations": [
{
"method": "POST",
"summary": "Create a room.",
"notes": "Create a room.",
"type": "RoomInfo",
"nickname": "create_room",
"consumes": [
"application/json"
],
"parameters": [
{
"name": "body",
"description": "The desired configuration for the room.",
"required": true,
"type": "RoomConfig",
"paramType": "body"
}
],
"responseMessages": [
{
"code": 400,
"message": "Body must be JSON."
},
{
"code": 400,
"message": "Room alias already taken."
}
]
}
]
},
{
"path": "/rooms/{roomId}/messages",
"operations": [
{
"method": "GET",
"summary": "Get a list of messages for this room.",
"notes": "Get a list of messages for this room.",
"type": "MessagePaginationChunk",
"nickname": "get_messages",
"parameters": [
{
"name": "roomId",
"description": "The room to get messages in.",
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "from",
"description": "The token to start getting results from.",
"required": false,
"type": "string",
"paramType": "query"
},
{
"name": "to",
"description": "The token to stop getting results at.",
"required": false,
"type": "string",
"paramType": "query"
},
{
"name": "limit",
"description": "The maximum number of messages to return.",
"required": false,
"type": "integer",
"paramType": "query"
}
]
}
]
},
{
"path": "/rooms/{roomId}/members",
"operations": [
{
"method": "GET",
"summary": "Get a list of members for this room.",
"notes": "Get a list of members for this room.",
"type": "MemberPaginationChunk",
"nickname": "get_members",
"parameters": [
{
"name": "roomId",
"description": "The room to get a list of members from.",
"required": true,
"type": "string",
"paramType": "path"
},
{
"name": "from",
"description": "The token to start getting results from.",
"required": false,
"type": "string",
"paramType": "query"
},
{
"name": "to",
"description": "The token to stop getting results at.",
"required": false,
"type": "string",
"paramType": "query"
},
{
"name": "limit",
"description": "The maximum number of members to return.",
"required": false,
"type": "integer",
"paramType": "query"
}
]
}
]
},
{
"path": "/rooms/{roomId}/state",
"operations": [
{
"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.",
"type": "array",
"items": {
"$ref": "Event"
},
"nickname": "get_state_events",
"parameters": [
{
"name": "roomId",
"description": "The room to get a list of current state events from.",
"required": true,
"type": "string",
"paramType": "path"
}
]
}
]
},
{
"path": "/rooms/{roomId}/initialSync",
"operations": [
{
"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.",
"type": "InitialSyncRoomData",
"nickname": "get_room_sync_data",
"parameters": [
{
"name": "roomId",
"description": "The room to get information for.",
"required": true,
"type": "string",
"paramType": "path"
}
]
}
]
}
],
"models": {
"Topic": {
"id": "Topic",
"properties": {
"topic": {
"type": "string",
"description": "The topic text"
}
}
},
"Message": {
"id": "Message",
"properties": {
"msgtype": {
"type": "string",
"description": "The type of message being sent, e.g. \"m.text\"",
"required": true
},
"_msgtype_defined_keys_": {
"description": "Additional keys as defined by the msgtype, e.g. \"body\""
}
}
},
"Feedback": {
"id": "Feedback",
"properties": {
}
},
"Member": {
"id": "Member",
"properties": {
"membership": {
"type": "string",
"description": "Enum: The membership state of this member.",
"enum": [
"invite",
"join",
"leave",
"knock"
]
}
}
},
"RoomInfo": {
"id": "RoomInfo",
"properties": {
"room_id": {
"type": "string",
"description": "The allocated room ID.",
"required": true
},
"room_alias": {
"type": "string",
"description": "The alias for the room.",
"required": false
}
}
},
"RoomConfig": {
"id": "RoomConfig",
"properties": {
"visibility": {
"type": "string",
"description": "Enum: The room visibility.",
"required": false,
"enum": [
"public",
"private"
]
},
"room_alias_name": {
"type": "string",
"description": "The alias to give the new room.",
"required": false
}
}
},
"PaginationRequest": {
"id": "PaginationRequest",
"properties": {
"from": {
"type": "string",
"description": "The token to start getting results from."
},
"to": {
"type": "string",
"description": "The token to stop getting results at."
},
"limit": {
"type": "integer",
"description": "The maximum number of entries to return."
}
}
},
"PaginationChunk": {
"id": "PaginationChunk",
"properties": {
"start": {
"type": "string",
"description": "A token which correlates to the first value in \"chunk\" for paginating.",
"required": true
},
"end": {
"type": "string",
"description": "A token which correlates to the last value in \"chunk\" for paginating.",
"required": true
}
},
"subTypes": [
"MessagePaginationChunk"
]
},
"MessagePaginationChunk": {
"id": "MessagePaginationChunk",
"properties": {
"chunk": {
"type": "array",
"description": "A list of message events.",
"items": {
"$ref": "MessageEvent"
},
"required": true
}
}
},
"MemberPaginationChunk": {
"id": "MemberPaginationChunk",
"properties": {
"chunk": {
"type": "array",
"description": "A list of member events.",
"items": {
"$ref": "MemberEvent"
},
"required": true
}
}
},
"Event": {
"id": "Event",
"properties": {
"event_id": {
"type": "string",
"description": "An ID which uniquely identifies this event. This is automatically set by the server.",
"required": true
},
"room_id": {
"type": "string",
"description": "The room in which this event occurred. This is automatically set by the server.",
"required": true
},
"type": {
"type": "string",
"description": "The event type.",
"required": true
}
},
"subTypes": [
"MessageEvent"
]
},
"EventId": {
"id": "EventId",
"properties": {
"event_id": {
"type": "string",
"description": "The allocated event ID for this event.",
"required": true
}
}
},
"EventContent": {
"id": "EventContent",
"properties": {
"__event_content_keys__": {
"type": "string",
"description": "Event-specific content keys and values.",
"required": false
}
}
},
"MessageEvent": {
"id": "MessageEvent",
"properties": {
"content": {
"type": "Message"
}
}
},
"MemberEvent": {
"id": "MemberEvent",
"properties": {
"content": {
"type": "Member"
}
}
},
"InviteRequest": {
"id": "InviteRequest",
"properties": {
"user_id": {
"type": "string",
"description": "The fully-qualified user ID."
}
}
},
"InitialSyncRoomData": {
"id": "InitialSyncRoomData",
"properties": {
"membership": {
"type": "string",
"description": "This user's membership state in this room.",
"required": true
},
"room_id": {
"type": "string",
"description": "The ID of this room.",
"required": true
},
"messages": {
"type": "MessagePaginationChunk",
"description": "The most recent messages for this room, governed by the limit parameter.",
"required": false
},
"state": {
"type": "array",
"description": "A list of state events representing the current state of the room.",
"required": false,
"items": {
"$ref": "Event"
}
}
}
}
}
}

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,43 +0,0 @@
===================
Documentation Style
===================
A brief single sentence to describe what this file contains; in this case a
description of the style to write documentation in.
Sections
========
Each section should be separated from the others by two blank lines. Headings
should be underlined using a row of equals signs (===). Paragraphs should be
separated by a single blank line, and wrap to no further than 80 columns.
[[TODO(username): if you want to leave some unanswered questions, notes for
further consideration, or other kinds of comment, use a TODO section. Make sure
to notate it with your name so we know who to ask about it!]]
Subsections
-----------
If required, subsections can use a row of dashes to underline their header. A
single blank line between subsections of a single section.
Bullet Lists
============
* Bullet lists can use asterisks with a single space either side.
* Another blank line between list elements.
Definition Lists
================
Terms:
Start in the first column, ending with a colon
Definitions:
Take a two space indent, following immediately from the term without a blank
line before it, but having a blank line afterwards.

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

@@ -0,0 +1,151 @@
Signing JSON
============
JSON is signed by encoding the JSON object without ``signatures`` or ``meta``
keys using a canonical encoding. The JSON bytes are then signed using the
signature algorithm and the signature encoded using base64 with the padding
stripped. The resulting base64 signature is added to an object under the
*signing key identifier* which is added to the ``signatures`` object under the
name of the server signing it which is added back to the original JSON object
along with the ``meta`` object.
The *signing key identifier* is the concatenation of the *signing algorithm*
and a *key version*. The *signing algorithm* identifies the algorithm used to
sign the JSON. The currently support value for *signing algorithm* is
``ed25519`` as implemented by NACL (http://nacl.cr.yp.to/). The *key version*
is used to distinguish between different signing keys used by the same entity.
The ``meta`` object and the ``signatures`` object are not covered by the
signature. Therefore intermediate servers can add metadata such as time stamps
and additional signatures.
::
{
"name": "example.org",
"signing_keys": {
"ed25519:1": "XSl0kuyvrXNj6A+7/tkrB9sxSbRi08Of5uRhxOqZtEQ"
},
"meta": {
"retrieved_ts_ms": 922834800000
},
"signatures": {
"example.org": {
"ed25519:1": "s76RUgajp8w172am0zQb/iPTHsRnb4SkrzGoeCOSFfcBY2V/1c8QfrmdXHpvnc2jK5BD1WiJIxiMW95fMjK7Bw"
}
}
}
::
def sign_json(json_object, signing_key, signing_name):
signatures = json_object.pop("signatures", {})
meta = json_object.pop("meta", None)
signed = signing_key.sign(encode_canonical_json(json_object))
signature_base64 = encode_base64(signed.signature)
key_id = "%s:%s" % (signing_key.alg, signing_key.version)
signatures.setdefault(sigature_name, {})[key_id] = signature_base64
json_object["signatures"] = signatures
if meta is not None:
json_object["meta"] = meta
return json_object
Checking for a Signature
------------------------
To check if an entity has signed a JSON object a server does the following
1. Checks if the ``signatures`` object contains an entry with the name of the
entity. If the entry is missing then the check fails.
2. Removes any *signing key identifiers* from the entry with algorithms it
doesn't understand. If there are no *signing key identifiers* left then the
check fails.
3. Looks up *verification keys* for the remaining *signing key identifiers*
either from a local cache or by consulting a trusted key server. If it
cannot find a *verification key* then the check fails.
4. Decodes the base64 encoded signature bytes. If base64 decoding fails then
the check fails.
5. Checks the signature bytes using the *verification key*. If this fails then
the check fails. Otherwise the check succeeds.
Canonical JSON
--------------
The canonical JSON encoding for a value is the shortest UTF-8 JSON encoding
with dictionary keys lexicographically sorted by unicode codepoint. Numbers in
the JSON value must be integers in the range [-(2**53)+1, (2**53)-1].
::
import json
def canonical_json(value):
return json.dumps(
value,
ensure_ascii=False,
separators=(',',':'),
sort_keys=True,
).encode("UTF-8")
Grammar
+++++++
Adapted from the grammar in http://tools.ietf.org/html/rfc7159 removing
insignificant whitespace, fractions, exponents and redundant character escapes
::
value = false / null / true / object / array / number / string
false = %x66.61.6c.73.65
null = %x6e.75.6c.6c
true = %x74.72.75.65
object = %x7B [ member *( %x2C member ) ] %7D
member = string %x3A value
array = %x5B [ value *( %x2C value ) ] %5B
number = [ %x2D ] int
int = %x30 / ( %x31-39 *digit )
digit = %x30-39
string = %x22 *char %x22
char = unescaped / %x5C escaped
unescaped = %x20-21 / %x23-5B / %x5D-10FFFF
escaped = %x22 ; " quotation mark U+0022
/ %x5C ; \ reverse solidus U+005C
/ %x62 ; b backspace U+0008
/ %x66 ; f form feed U+000C
/ %x6E ; n line feed U+000A
/ %x72 ; r carriage return U+000D
/ %x74 ; t tab U+0009
/ %x75.30.30.30 (%x30-37 / %x62 / %x65-66) ; u000X
/ %x75.30.30.31 (%x30-39 / %x61-66) ; u001X
Signing Events
==============
Signing events is a more complicated process since servers can choose to redact
non-essential event contents. Before signing the event it is encoded as
Canonical JSON and hashed using SHA-256. The resulting hash is then stored
in the event JSON in a ``hash`` object under a ``sha256`` key. Then all
non-essential keys are stripped from the event object, and the resulting object
which included the ``hash`` key is signed using the JSON signing algorithm.
Servers can then transmit the entire event or the event with the non-essential
keys removed. Receiving servers can then check the entire event if it is
present by computing the SHA-256 of the event excluding the ``hash`` object, or
by using the ``hash`` object included in the event if keys have been redacted.
New hash functions can be introduced by adding additional keys to the ``hash``
object. Since the ``hash`` object cannot be redacted a server shouldn't allow
too many hashes to be listed, otherwise a server might embed illict data within
the ``hash`` object. For similar reasons a server shouldn't allow hash values
that are too long.
[[TODO(markjh): We might want to specify a maximum number of keys for the
``hash`` and we might want to specify the maximum output size of a hash]]
[[TODO(markjh) We might want to allow the server to omit the output of well
known hash functions like SHA-256 when none of the keys have been redacted]]

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":

View File

@@ -1,839 +0,0 @@
Matrix Specification
====================
TODO(Introduction) : Matthew
- Similar to intro paragraph from README.
- Explaining the overall mission, what this spec describes...
- "What is Matrix?"
- Draw parallels with email?
Architecture
============
Clients transmit data to other clients through home servers (HSes). Clients do not communicate with each
other directly.
::
How data flows between clients
==============================
{ Matrix client A } { Matrix client B }
^ | ^ |
| events | | events |
| V | V
+------------------+ +------------------+
| |---------( HTTP )---------->| |
| Home Server | | Home Server |
| |<--------( HTTP )-----------| |
+------------------+ Federation +------------------+
A "Client" is an end-user, typically a human using a web application or mobile app. Clients use the
"Client-to-Server" (C-S) API to communicate with their home server. A single Client is usually
responsible for a single user account. A user account is represented by their "User ID". This ID is
namespaced to the home server which allocated the account and looks like::
@localpart:domain
The ``localpart`` of a user ID may be a user name, or an opaque ID identifying this user.
A "Home Server" is a server which provides C-S APIs and has the ability to federate with other HSes.
It is typically responsible for multiple clients. "Federation" is the term used to describe the
sharing of data between two or more home servers.
Data in Matrix is encapsulated in an "Event". An event is an action within the system. Typically each
action (e.g. sending a message) correlates with exactly one event. Each event has a ``type`` which is
used to differentiate different kinds of data. ``type`` values SHOULD be namespaced according to standard
Java package naming conventions, e.g. ``com.example.myapp.event``. Events are usually sent in the context
of a "Room".
Room structure
--------------
A room is a conceptual place where users can send and receive events. Rooms
can be created, joined and left. Events are sent to a room, and all
participants in that room will receive the event. Rooms are uniquely
identified via a "Room ID", which look like::
!opaque_id:domain
There is exactly one room ID for each room. Whilst the room ID does contain a
domain, it is simply for namespacing room IDs. The room does NOT reside on the
domain specified. Room IDs are not meant to be human readable.
The following diagram shows an ``m.room.message`` event being sent in the room
``!qporfwt:matrix.org``::
{ @alice:matrix.org } { @bob:domain.com }
| ^
| |
Room ID: !qporfwt:matrix.org Room ID: !qporfwt:matrix.org
Event type: m.room.message Event type: m.room.message
Content: { JSON object } Content: { JSON object }
| |
V |
+------------------+ +------------------+
| Home Server | | Home Server |
| matrix.org |<-------Federation------->| domain.com |
+------------------+ +------------------+
| ................................. |
|______| Partially Shared State |_______|
| Room ID: !qporfwt:matrix.org |
| Servers: matrix.org, domain.com |
| Members: |
| - @alice:matrix.org |
| - @bob:domain.com |
|.................................|
Federation maintains shared state between multiple home servers, such that when an event is
sent to a room, the home server knows where to forward the event on to, and how to process
the event. Home servers do not need to have completely shared state in order to participate
in a room. State is scoped to a single room, and federation ensures that all home servers
have the information they need, even if that means the home server has to request more
information from another home server before processing the event.
Room Aliases
------------
Each room can also have multiple "Room Aliases", which looks like::
#room_alias:domain
A room alias "points" to a room ID. The room ID the alias is pointing to can be obtained
by visiting the domain specified. Room aliases are designed to be human readable strings
which can be used to publicise rooms. Note that the mapping from a room alias to a
room ID is not fixed, and may change over time to point to a different room ID. For this
reason, Clients SHOULD resolve the room alias to a room ID once and then use that ID on
subsequent requests.
::
GET
#matrix:domain.com !aaabaa:matrix.org
| ^
| |
_______V____________________|____
| domain.com |
| Mappings: |
| #matrix >> !aaabaa:matrix.org |
| #golf >> !wfeiofh:sport.com |
| #bike >> !4rguxf:matrix.org |
|________________________________|
Identity
--------
- Identity in relation to 3PIDs. Discovery of users based on 3PIDs.
- Identity servers; trusted clique of servers which replicate content.
- They govern the mapping of 3PIDs to user IDs and the creation of said mappings.
- Not strictly required in order to communicate.
API Standards
-------------
- All HTTP[S]
- Uses JSON as HTTP bodies
- Standard error response format { errcode: M_WHATEVER, error: "some message" }
- C-S API provides POST for operations, or PUT with txn IDs. Explain txn IDs.
Receiving live updates on a client
----------------------------------
- C-S longpoll event stream
- Concept of start/end tokens.
- Mention /initialSync to get token.
Rooms
=====
- How are they created? PDU anchor point: "root of the tree".
- Adding / removing aliases.
- Invite/join dance
- State and non-state data (+extensibility)
TODO : Room permissions / config / power levels.
Messages
========
This specification outlines several standard event types, all of which are
prefixed with ``m.``
State messages
--------------
- m.room.name
- m.room.topic
- m.room.member
- m.room.config
- m.room.invite_join
What are they, when are they used, what do they contain, how should they be used
Non-state messages
------------------
- m.room.message
- m.room.message.feedback (and compressed format)
What are they, when are they used, what do they contain, how should they be used
m.room.message msgtypes
-----------------------
Each ``m.room.message`` MUST have a ``msgtype`` key which identifies the type of
message being sent. Each type has their own required and optional keys, as outlined
below:
``m.text``
Required keys:
- ``body`` : "string" - The body of the message.
Optional keys:
None.
Example:
``{ "msgtype": "m.text", "body": "I am a fish" }``
``m.emote``
Required keys:
- ``body`` : "string" - The emote action to perform.
Optional keys:
None.
Example:
``{ "msgtype": "m.emote", "body": "tries to come up with a witty explanation" }``
``m.image``
Required keys:
- ``url`` : "string" - The URL to the image.
Optional keys:
- ``info`` : "string" - info : JSON object (ImageInfo) - The image info for image
referred to in ``url``.
- ``thumbnail_url`` : "string" - The URL to the thumbnail.
- ``thumbnail_info`` : JSON object (ImageInfo) - The image info for the image
referred to in ``thumbnail_url``.
- ``body`` : "string" - The alt text of the image, or some kind of content
description for accessibility e.g. "image attachment".
ImageInfo:
Information about an image::
{
"size" : integer (size of image in bytes),
"w" : integer (width of image in pixels),
"h" : integer (height of image in pixels),
"mimetype" : "string (e.g. image/jpeg)",
}
``m.audio``
Required keys:
- ``url`` : "string" - The URL to the audio.
Optional keys:
- ``info`` : JSON object (AudioInfo) - The audio info for the audio referred to in
``url``.
- ``body`` : "string" - A description of the audio e.g. "Bee Gees -
Stayin' Alive", or some kind of content description for accessibility e.g.
"audio attachment".
AudioInfo:
Information about a piece of audio::
{
"mimetype" : "string (e.g. audio/aac)",
"size" : integer (size of audio in bytes),
"duration" : integer (duration of audio in milliseconds),
}
``m.video``
Required keys:
- ``url`` : "string" - The URL to the video.
Optional keys:
- ``info`` : JSON object (VideoInfo) - The video info for the video referred to in
``url``.
- ``body`` : "string" - A description of the video e.g. "Gangnam style",
or some kind of content description for accessibility e.g. "video attachment".
VideoInfo:
Information about a video::
{
"mimetype" : "string (e.g. video/mp4)",
"size" : integer (size of video in bytes),
"duration" : integer (duration of video in milliseconds),
"w" : integer (width of video in pixels),
"h" : integer (height of video in pixels),
"thumbnail_url" : "string (URL to image)",
"thumbanil_info" : JSON object (ImageInfo)
}
``m.location``
Required keys:
- ``geo_uri`` : "string" - The geo URI representing the location.
Optional keys:
- ``thumbnail_url`` : "string" - The URL to a thumnail of the location being
represented.
- ``thumbnail_info`` : JSON object (ImageInfo) - The image info for the image
referred to in ``thumbnail_url``.
- ``body`` : "string" - A description of the location e.g. "Big Ben,
London, UK", or some kind of content description for accessibility e.g.
"location attachment".
The following keys can be attached to any ``m.room.message``:
Optional keys:
- ``sender_ts`` : integer - A timestamp (ms resolution) representing the
wall-clock time when the message was sent from the client.
Presence
========
Each user has the concept of presence information. This encodes the
"availability" of that user, suitable for display on other user's clients. This
is transmitted as an ``m.presence`` event and is one of the few events which
are sent *outside the context of a room*. The basic piece of presence information
is represented by the ``state`` key, which is an enum of one of the following:
- ``online`` : The default state when the user is connected to an event stream.
- ``unavailable`` : The user is not reachable at this time.
- ``offline`` : The user is not connected to an event stream.
- ``free_for_chat`` : The user is generally willing to receive messages
moreso than default.
- ``hidden`` : TODO. Behaves as offline, but allows the user to see the client
state anyway and generally interact with client features.
This basic ``state`` field applies to the user as a whole, regardless of how many
client devices they have connected. The home server should synchronise this
status choice among multiple devices to ensure the user gets a consistent
experience.
Idle Time
---------
As well as the basic ``state`` field, the presence information can also show a sense
of an "idle timer". This should be maintained individually by the user's
clients, and the home server can take the highest reported time as that to
report. When a user is offline, the home server can still report when the user was last
seen online.
Transmission
------------
- Transmitted as an EDU.
- Presence lists determine who to send to.
Presence List
-------------
Each user's home server stores a "presence list" for that user. This stores a
list of other user IDs the user has chosen to add to it. To be added to this
list, the user being added must receive permission from the list owner. Once
granted, both user's HS(es) store this information. Since such subscriptions
are likely to be bidirectional, HSes may wish to automatically accept requests
when a reverse subscription already exists.
Presence and Permissions
------------------------
For a viewing user to be allowed to see the presence information of a target
user, either:
- The target user has allowed the viewing user to add them to their presence
list, or
- The two users share at least one room in common
In the latter case, this allows for clients to display some minimal sense of
presence information in a user list for a room.
Typing notifications
====================
TODO : Leo
Voice over IP
=============
TODO : Dave
Profiles
========
Internally within Matrix users are referred to by their user ID, which is not a
human-friendly string. Profiles grant users the ability to see human-readable
names for other users that are in some way meaningful to them. Additionally,
profiles can publish additional information, such as the user's age or location.
A Profile consists of a display name, an avatar picture, and a set of other
metadata fields that the user may wish to publish (email address, phone
numbers, website URLs, etc...). This specification puts no requirements on the
display name other than it being a valid unicode string.
- Metadata extensibility
- Bundled with which events? e.g. m.room.member
- Generate own events? What type?
Registration and login
======================
Clients must register with a home server in order to use Matrix. After
registering, the client will be given an access token which must be used in ALL
requests to that home server as a query parameter 'access_token'.
- TODO Kegan : Make registration like login (just omit the "user" key on the
initial request?)
If the client has already registered, they need to be able to login to their
account. The home server may provide many different ways of logging in, such
as user/password auth, login via a social network (OAuth2), login by confirming
a token sent to their email address, etc. This specification does not define how
home servers should authorise their users who want to login to their existing
accounts, but instead defines the standard interface which implementations
should follow so that ANY client can login to ANY home server.
The login process breaks down into the following:
1. Determine the requirements for logging in.
2. Submit the login stage credentials.
3. Get credentials or be told the next stage in the login process and repeat
step 2.
As each home server may have different ways of logging in, the client needs to know how
they should login. All distinct login stages MUST have a corresponding ``type``.
A ``type`` is a namespaced string which details the mechanism for logging in.
A client may be able to login via multiple valid login flows, and should choose a single
flow when logging in. A flow is a series of login stages. The home server MUST respond
with all the valid login flows when requested::
The client can login via 3 paths: 1a and 1b, 2a and 2b, or 3. The client should
select one of these paths.
{
"flows": [
{
"type": "<login type1a>",
"stages": [ "<login type 1a>", "<login type 1b>" ]
},
{
"type": "<login type2a>",
"stages": [ "<login type 2a>", "<login type 2b>" ]
},
{
"type": "<login type3>"
}
]
}
After the login is completed, the client's fully-qualified user ID and a new access
token MUST be returned::
{
"user_id": "@user:matrix.org",
"access_token": "abcdef0123456789"
}
The ``user_id`` key is particularly useful if the home server wishes to support
localpart entry of usernames (e.g. "user" rather than "@user:matrix.org"), as the
client may not be able to determine its ``user_id`` in this case.
If a login has multiple requests, the home server may wish to create a session. If
a home server responds with a 'session' key to a request, clients MUST submit it in
subsequent requests until the login is completed::
{
"session": "<session id>"
}
This specification defines the following login types:
- ``m.login.password``
- ``m.login.oauth2``
- ``m.login.email.code``
- ``m.login.email.url``
Password-based
--------------
:Type:
m.login.password
:Description:
Login is supported via a username and password.
To respond to this type, reply with::
{
"type": "m.login.password",
"user": "<user_id or user localpart>",
"password": "<password>"
}
The home server MUST respond with either new credentials, the next stage of the login
process, or a standard error response.
OAuth2-based
------------
:Type:
m.login.oauth2
:Description:
Login is supported via OAuth2 URLs. This login consists of multiple requests.
To respond to this type, reply with::
{
"type": "m.login.oauth2",
"user": "<user_id or user localpart>"
}
The server MUST respond with::
{
"uri": <Authorization Request URI OR service selection URI>
}
The home server acts as a 'confidential' client for the purposes of OAuth2.
If the uri is a ``sevice selection URI``, it MUST point to a webpage which prompts the
user to choose which service to authorize with. On selection of a service, this
MUST link through to an ``Authorization Request URI``. If there is only 1 service which the
home server accepts when logging in, this indirection can be skipped and the
"uri" key can be the ``Authorization Request URI``.
The client then visits the ``Authorization Request URI``, which then shows the OAuth2
Allow/Deny prompt. Hitting 'Allow' returns the ``redirect URI`` with the auth code.
Home servers can choose any path for the ``redirect URI``. The client should visit
the ``redirect URI``, which will then finish the OAuth2 login process, granting the
home server an access token for the chosen service. When the home server gets
this access token, it verifies that the cilent has authorised with the 3rd party, and
can now complete the login. The OAuth2 ``redirect URI`` (with auth code) MUST respond
with either new credentials, the next stage of the login process, or a standard error
response.
For example, if a home server accepts OAuth2 from Google, it would return the
Authorization Request URI for Google::
{
"uri": "https://accounts.google.com/o/oauth2/auth?response_type=code&
client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=photos"
}
The client then visits this URI and authorizes the home server. The client then
visits the REDIRECT_URI with the auth code= query parameter which returns::
{
"user_id": "@user:matrix.org",
"access_token": "0123456789abcdef"
}
Email-based (code)
------------------
:Type:
m.login.email.code
:Description:
Login is supported by typing in a code which is sent in an email. This login
consists of multiple requests.
To respond to this type, reply with::
{
"type": "m.login.email.code",
"user": "<user_id or user localpart>",
"email": "<email address>"
}
After validating the email address, the home server MUST send an email containing
an authentication code and return::
{
"type": "m.login.email.code",
"session": "<session id>"
}
The second request in this login stage involves sending this authentication code::
{
"type": "m.login.email.code",
"session": "<session id>",
"code": "<code in email sent>"
}
The home server MUST respond to this with either new credentials, the next stage of
the login process, or a standard error response.
Email-based (url)
-----------------
:Type:
m.login.email.url
:Description:
Login is supported by clicking on a URL in an email. This login consists of
multiple requests.
To respond to this type, reply with::
{
"type": "m.login.email.url",
"user": "<user_id or user localpart>",
"email": "<email address>"
}
After validating the email address, the home server MUST send an email containing
an authentication URL and return::
{
"type": "m.login.email.url",
"session": "<session id>"
}
The email contains a URL which must be clicked. After it has been clicked, the
client should perform another request::
{
"type": "m.login.email.url",
"session": "<session id>"
}
The home server MUST respond to this with either new credentials, the next stage of
the login process, or a standard error response.
A common client implementation will be to periodically poll until the link is clicked.
If the link has not been visited yet, a standard error response with an errcode of
``M_LOGIN_EMAIL_URL_NOT_YET`` should be returned.
N-Factor Authentication
-----------------------
Multiple login stages can be combined to create N-factor authentication during login.
This can be achieved by responding with the ``next`` login type on completion of a
previous login stage::
{
"next": "<next login type>"
}
If a home server implements N-factor authentication, it MUST respond with all
``stages`` when initially queried for their login requirements::
{
"type": "<1st login type>",
"stages": [ <1st login type>, <2nd login type>, ... , <Nth login type> ]
}
This can be represented conceptually as::
_______________________
| Login Stage 1 |
| type: "<login type1>" |
| ___________________ |
| |_Request_1_________| | <-- Returns "session" key which is used throughout.
| ___________________ |
| |_Request_2_________| | <-- Returns a "next" value of "login type2"
|_______________________|
|
|
_________V_____________
| Login Stage 2 |
| type: "<login type2>" |
| ___________________ |
| |_Request_1_________| |
| ___________________ |
| |_Request_2_________| |
| ___________________ |
| |_Request_3_________| | <-- Returns a "next" value of "login type3"
|_______________________|
|
|
_________V_____________
| Login Stage 3 |
| type: "<login type3>" |
| ___________________ |
| |_Request_1_________| | <-- Returns user credentials
|_______________________|
Fallback
--------
Clients cannot be expected to be able to know how to process every single
login type. If a client determines it does not know how to handle a given
login type, it should request a login fallback page::
GET matrix/client/api/v1/login/fallback
This MUST return an HTML page which can perform the entire login process.
Identity
========
TODO : Dave
- 3PIDs and identity server, functions
Federation
==========
Federation is the term used to describe how to communicate between Matrix home
servers. Federation is a mechanism by which two home servers can exchange
Matrix event messages, both as a real-time push of current events, and as a
historic fetching mechanism to synchronise past history for clients to view. It
uses HTTP connections between each pair of servers involved as the underlying
transport. Messages are exchanged between servers in real-time by active pushing
from each server's HTTP client into the server of the other. Queries to fetch
historic data for the purpose of back-filling scrollback buffers and the like
can also be performed.
There are three main kinds of communication that occur between home servers:
:Queries:
These are single request/response interactions between a given pair of
servers, initiated by one side sending an HTTP GET request to obtain some
information, and responded by the other. They are not persisted and contain
no long-term significant history. They simply request a snapshot state at the
instant the query is made.
:Ephemeral Data Units (EDUs):
These are notifications of events that are pushed from one home server to
another. They are not persisted and contain no long-term significant history,
nor does the receiving home server have to reply to them.
:Persisted Data Units (PDUs):
These are notifications of events that are broadcast from one home server to
any others that are interested in the same "context" (namely, a Room ID).
They are persisted to long-term storage and form the record of history for
that context.
EDUs and PDUs are further wrapped in an envelope called a Transaction, which is
transferred from the origin to the destination home server using an HTTP PUT request.
Transactions
------------
The transfer of EDUs and PDUs between home servers is performed by an exchange
of Transaction messages, which are encoded as JSON objects, passed over an
HTTP PUT request. A Transaction is meaningful only to the pair of home servers that
exchanged it; they are not globally-meaningful.
Each transaction has:
- An opaque transaction ID.
- A timestamp (UNIX epoch time in milliseconds) generated by its origin server.
- An origin and destination server name.
- A list of "previous IDs".
- A list of PDUs and EDUs - the actual message payload that the Transaction carries.
::
{
"transaction_id":"916d630ea616342b42e98a3be0b74113",
"ts":1404835423000,
"origin":"red",
"destination":"blue",
"prev_ids":["e1da392e61898be4d2009b9fecce5325"],
"pdus":[...],
"edus":[...]
}
The ``prev_ids`` field contains a list of previous transaction IDs that
the ``origin`` server has sent to this ``destination``. Its purpose is to act as a
sequence checking mechanism - the destination server can check whether it has
successfully received that Transaction, or ask for a retransmission if not.
The ``pdus`` field of a transaction is a list, containing zero or more PDUs.[*]
Each PDU is itself a JSON object containing a number of keys, the exact details of
which will vary depending on the type of PDU. Similarly, the ``edus`` field is
another list containing the EDUs. This key may be entirely absent if there are
no EDUs to transfer.
(* Normally the PDU list will be non-empty, but the server should cope with
receiving an "empty" transaction, as this is useful for informing peers of other
transaction IDs they should be aware of. This effectively acts as a push
mechanism to encourage peers to continue to replicate content.)
PDUs and EDUs
-------------
All PDUs have:
- An ID
- A context
- A declaration of their type
- A list of other PDU IDs that have been seen recently on that context (regardless of which origin
sent them)
[[TODO(paul): Update this structure so that 'pdu_id' is a two-element
[origin,ref] pair like the prev_pdus are]]
::
{
"pdu_id":"a4ecee13e2accdadf56c1025af232176",
"context":"#example.green",
"origin":"green",
"ts":1404838188000,
"pdu_type":"m.text",
"prev_pdus":[["blue","99d16afbc857975916f1d73e49e52b65"]],
"content":...
"is_state":false
}
In contrast to Transactions, it is important to note that the ``prev_pdus``
field of a PDU refers to PDUs that any origin server has sent, rather than
previous IDs that this ``origin`` has sent. This list may refer to other PDUs sent
by the same origin as the current one, or other origins.
Because of the distributed nature of participants in a Matrix conversation, it
is impossible to establish a globally-consistent total ordering on the events.
However, by annotating each outbound PDU at its origin with IDs of other PDUs it
has received, a partial ordering can be constructed allowing causallity
relationships to be preserved. A client can then display these messages to the
end-user in some order consistent with their content and ensure that no message
that is semantically in reply of an earlier one is ever displayed before it.
PDUs fall into two main categories: those that deliver Events, and those that
synchronise State. For PDUs that relate to State synchronisation, additional
keys exist to support this:
::
{...,
"is_state":true,
"state_key":TODO
"power_level":TODO
"prev_state_id":TODO
"prev_state_origin":TODO}
[[TODO(paul): At this point we should probably have a long description of how
State management works, with descriptions of clobbering rules, power levels, etc
etc... But some of that detail is rather up-in-the-air, on the whiteboard, and
so on. This part needs refining. And writing in its own document as the details
relate to the server/system as a whole, not specifically to server-server
federation.]]
EDUs, by comparison to PDUs, do not have an ID, a context, or a list of
"previous" IDs. The only mandatory fields for these are the type, origin and
destination home server names, and the actual nested content.
::
{"edu_type":"m.presence",
"origin":"blue",
"destination":"orange",
"content":...}
Backfilling
-----------
- What it is, when is it used, how is it done
SRV Records
-----------
- Why it is needed
Security
========
- rate limiting
- crypto (s-s auth)
- E2E
- Lawful intercept + Key Escrow
TODO Mark
Policy Servers
==============
TODO
Content repository
==================
- thumbnail paths
Address book repository
=======================
- format
Glossary
========
- domain specific words/acronyms with definitions
User ID:
An opaque ID which identifies an end-user, which consists of some opaque
localpart combined with the domain name of their home server.

93
docs/turn-howto.rst Normal file
View File

@@ -0,0 +1,93 @@
How to enable VoIP relaying on your Home Server with TURN
Overview
--------
The synapse Matrix Home Server supports integration with TURN server via the
TURN server REST API
(http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00). This allows
the Home Server to generate credentials that are valid for use on the TURN
server through the use of a secret shared between the Home Server and the
TURN server.
This document described how to install coturn
(https://code.google.com/p/coturn/) which also supports the TURN REST API,
and integrate it with synapse.
coturn Setup
============
1. Check out coturn::
svn checkout http://coturn.googlecode.com/svn/trunk/ coturn
cd coturn
2. Configure it::
./configure
You may need to install libevent2: if so, you should do so
in the way recommended by your operating system.
You can ignore warnings about lack of database support: a
database is unnecessary for this purpose.
3. Build and install it::
make
make install
4. Make a config file in /etc/turnserver.conf. You can customise
a config file from turnserver.conf.default. The relevant
lines, with example values, are::
lt-cred-mech
use-auth-secret
static-auth-secret=[your secret key here]
realm=turn.myserver.org
See turnserver.conf.default for explanations of the options.
One way to generate the static-auth-secret is with pwgen::
pwgen -s 64 1
5. Ensure youe firewall allows traffic into the TURN server on
the ports you've configured it to listen on (remember to allow
both TCP and UDP if you've enabled both).
6. If you've configured coturn to support TLS/DTLS, generate or
import your private key and certificate.
7. Start the turn server::
bin/turnserver -o
synapse Setup
=============
Your home server configuration file needs the following extra keys:
1. "turn_uris": This needs to be a yaml list
of public-facing URIs for your TURN server to be given out
to your clients. Add separate entries for each transport your
TURN server supports.
2. "turn_shared_secret": This is the secret shared between your Home
server and your TURN server, so you should set it to the same
string you used in turnserver.conf.
3. "turn_user_lifetime": This is the amount of time credentials
generated by your Home Server are valid for (in milliseconds).
Shorter times offer less potential for abuse at the expense
of increased traffic between web clients and your home server
to refresh credentials. The TURN REST API specification recommends
one day (86400000).
As an example, here is the relevant section of the config file for
matrix.org::
turn_uris: turn:turn.matrix.org:3478?transport=udp,turn:turn.matrix.org:3478?transport=tcp
turn_shared_secret: n0t4ctuAllymatr1Xd0TorgSshar3d5ecret4obvIousreAsons
turn_user_lifetime: 86400000
Now, restart synapse::
cd /where/you/run/synapse
./synctl restart
...and your Home Server now supports VoIP relaying!

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" }),
@@ -19,17 +19,23 @@ $('.login').live('click', function() {
showLoggedIn(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});
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 +49,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 +84,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 +95,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" }),
@@ -58,21 +58,27 @@ $('.login').live('click', function() {
showLoggedIn(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});
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 +98,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 +114,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

@@ -0,0 +1,7 @@
name: Example Matrix Client
description: Includes login, live event streaming, creating rooms, sending messages and viewing member lists.
authors:
- matrix.org
resources:
- http://matrix.org
normalize_css: no

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,10 +107,10 @@ $('.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 }),
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
dataType: "json",
success: function(data) {
onLoggedIn(data);
@@ -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,23 +11,7 @@ $('.register').live('click', function() {
var user = $("#user").val();
var password = $("#password").val();
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/register",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user_id: user, password: password }),
dataType: "json",
success: function(data) {
showLoggedIn(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
}
});
});
var login = function(user, password) {
$.ajax({
url: "http://localhost:8080/matrix/client/api/v1/login",
url: "http://localhost:8008/_matrix/client/api/v1/register",
type: "POST",
contentType: "application/json; charset=utf-8",
data: JSON.stringify({ user: user, password: password, type: "m.login.password" }),
@@ -36,7 +20,33 @@ var login = function(user, password) {
showLoggedIn(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});
var login = function(user, password) {
$.ajax({
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" }),
dataType: "json",
success: function(data) {
showLoggedIn(data);
},
error: function(err) {
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
};
@@ -44,8 +54,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 +70,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" }),
@@ -20,7 +28,12 @@ $('.login').live('click', function() {
showLoggedIn(data);
},
error: function(err) {
alert(JSON.stringify($.parseJSON(err.responseText)));
var errMsg = "To try this, you need a home server running!";
var errJson = $.parseJSON(err.responseText);
if (errJson) {
errMsg = JSON.stringify(errJson);
}
alert(errMsg);
}
});
});
@@ -31,10 +44,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 +58,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 +92,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",

280
pylint.cfg Normal file
View File

@@ -0,0 +1,280 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=
[MESSAGES CONTROL]
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=missing-docstring
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the beginning of the name of dummy variables
# (i.e. not used).
dummy-variables-rgx=_$|dummy
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input
# Regular expression which should only match correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression which should only match correct module level names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression which should only match correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Regular expression which should only match correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct instance attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match correct attribute names in class
# bodies
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression which should only match correct list comprehension /
# generator expression variable names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
[DESIGN]
# Maximum number of arguments for function / method
max-args=5
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception

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.

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.
@@ -31,20 +31,23 @@ setup(
packages=find_packages(exclude=["tests"]),
description="Reference Synapse Home Server",
install_requires=[
"syutil==0.0.1",
"syutil==0.0.2",
"Twisted>=14.0.0",
"service_identity>=1.0.0",
"pyopenssl>=0.14",
"pyyaml",
"pyasn1",
"pynacl",
"daemonize",
"py-bcrypt",
],
dependency_links=[
"git+ssh://git@github.com/matrix-org/syutil.git#egg=syutil-0.0.1",
"https://github.com/matrix-org/syutil/tarball/v0.0.2#egg=syutil-0.0.2",
],
setup_requires=[
"setuptools_trial",
"setuptools>=1.0.0", # Needs setuptools that supports git+ssh. It's not obvious when support for this was introduced.
"setuptools>=1.0.0", # Needs setuptools that supports git+ssh.
# TODO: Do we need this now? we don't use git+ssh.
"mock"
],
include_package_data=True,

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.1"
__version__ = "0.4.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.
@@ -12,4 +12,3 @@
# 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.

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,12 @@
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, RoomRedactionEvent,
)
from synapse.util.logutils import log_function
import logging
@@ -44,16 +47,35 @@ 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)
if event.type == RoomRedactionEvent.TYPE:
yield self._check_redaction(event)
defer.returnValue(True)
else:
raise AuthError(500, "Unknown event: %s" % event)
except AuthError as e:
@@ -111,7 +133,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,23 +153,60 @@ 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)
defer.returnValue(True)
@defer.inlineCallbacks
def get_user_by_req(self, request):
""" Get a registered user's ID.
@@ -153,7 +219,25 @@ class Auth(object):
"""
# Can optionally look elsewhere in the request (e.g. headers)
try:
return self.get_user_by_token(request.args["access_token"][0])
access_token = request.args["access_token"][0]
user_info = yield self.get_user_by_token(access_token)
user = user_info["user"]
ip_addr = self.hs.get_ip_from_request(request)
user_agent = request.requestHeaders.getRawHeaders(
"User-Agent",
default=[""]
)[0]
if user and access_token and ip_addr:
self.store.insert_client_ip(
user=user,
access_token=access_token,
device_id=user_info["device_id"],
ip=ip_addr,
user_agent=user_agent
)
defer.returnValue(user)
except KeyError:
raise AuthError(403, "Missing access token.")
@@ -162,17 +246,233 @@ class Auth(object):
""" Get a registered user's ID.
Args:
token (str)- The access token to get the user by.
token (str): The access token to get the user by.
Returns:
UserID : User ID object of the user who has that access token.
dict : dict that includes the user, device_id, and whether the
user is a server admin.
Raises:
AuthError if no user by that token exists or the token is invalid.
"""
try:
user_id = yield self.store.get_user_by_token(token=token)
if not user_id:
ret = yield self.store.get_user_by_token(token=token)
if not ret:
raise StoreError()
defer.returnValue(self.hs.parse_userid(user_id))
user_info = {
"admin": bool(ret.get("admin", False)),
"device_id": ret.get("device_id"),
"user": self.hs.parse_userid(ret.get("name")),
}
defer.returnValue(user_info)
except StoreError:
raise AuthError(403, "Unrecognised access token.",
errcode=Codes.UNKNOWN_TOKEN)
def is_server_admin(self, user):
return self.store.is_server_admin(user)
@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_redaction(self, event):
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
_, _, redact_level = yield self.store.get_ops_levels(event.room_id)
if not redact_level:
redact_level = 50
if user_level < redact_level:
raise AuthError(
403,
"You don't have permission to redact events"
)
@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(new_people.keys()) - set(old_people.keys())
same = set(old_people.keys()) & set(new_people.keys())
for r in removed:
if int(old_list[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,19 @@ 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"
class LoginType(object):
PASSWORD = u"m.login.password"
OAUTH = u"m.login.oauth2"
EMAIL_CODE = u"m.login.email.code"
EMAIL_URL = u"m.login.email.url"
EMAIL_IDENTITY = u"m.login.email.identity"
RECAPTCHA = u"m.login.recaptcha"

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,6 +19,7 @@ import logging
class Codes(object):
UNAUTHORIZED = "M_UNAUTHORIZED"
FORBIDDEN = "M_FORBIDDEN"
BAD_JSON = "M_BAD_JSON"
NOT_JSON = "M_NOT_JSON"
@@ -28,6 +29,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,21 +42,31 @@ 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:
code (int): The integer error code (typically an HTTP response code)
code (int): The integer error code (an HTTP response code)
msg (str): The human-readable error message.
err (str): The error code e.g 'M_FORBIDDEN'
"""
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 +105,40 @@ 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.
@@ -17,6 +17,20 @@ from synapse.api.errors import SynapseError, Codes
from synapse.util.jsonobject import JsonEncodedObject
def serialize_event(hs, e):
# FIXME(erikj): To handle the case of presence events and the like
if not isinstance(e, SynapseEvent):
return e
# Should this strip out None's?
d = {k: v for k, v in e.get_dict().items()}
if "age_ts" in d:
d["age"] = int(hs.get_clock().time_msec()) - d["age_ts"]
del d["age_ts"]
return d
class SynapseEvent(JsonEncodedObject):
"""Base class for Synapse events. These are JSON objects which must abide
@@ -42,16 +56,22 @@ class SynapseEvent(JsonEncodedObject):
"user_id", # sender/initiator
"content", # HTTP body, JSON
"state_key",
"required_power_level",
"age_ts",
"prev_content",
"prev_state",
"redacted_because",
]
internal_keys = [
"is_state",
"prev_events",
"prev_state",
"depth",
"destinations",
"origin",
"outlier",
"power_level",
"redacted",
]
required_keys = [
@@ -139,7 +159,8 @@ class SynapseEvent(JsonEncodedObject):
return "Missing %s key" % key
if type(content[key]) != type(template[key]):
return "Key %s is of the wrong type." % key
return "Key %s is of the wrong type (got %s, want %s)" % (
key, type(content[key]), type(template[key]))
if type(content[key]) == dict:
# we must go deeper
@@ -152,3 +173,11 @@ class SynapseEvent(JsonEncodedObject):
msg = self._check_json(entry, template[key][0])
if msg:
return msg
class SynapseStateEvent(SynapseEvent):
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,9 @@
from synapse.api.events.room import (
RoomTopicEvent, MessageEvent, RoomMemberEvent, FeedbackEvent,
InviteJoinEvent, RoomConfigEvent, RoomNameEvent, GenericEvent,
RoomPowerLevelsEvent, RoomJoinRulesEvent, RoomOpsPowerLevelsEvent,
RoomCreateEvent, RoomAddStateLevelEvent, RoomSendEventLevelEvent,
RoomRedactionEvent,
)
from synapse.util.stringutils import random_string
@@ -30,7 +33,14 @@ class EventFactory(object):
RoomMemberEvent,
FeedbackEvent,
InviteJoinEvent,
RoomConfigEvent
RoomConfigEvent,
RoomPowerLevelsEvent,
RoomJoinRulesEvent,
RoomCreateEvent,
RoomAddStateLevelEvent,
RoomSendEventLevelEvent,
RoomOpsPowerLevelsEvent,
RoomRedactionEvent,
]
def __init__(self, hs):
@@ -39,14 +49,25 @@ 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())
if "origin_server_ts" not in kwargs:
kwargs["origin_server_ts"] = int(self.clock.time_msec())
# The "age" key is a delta timestamp that should be converted into an
# absolute timestamp the minute we see it.
if "age" in kwargs:
kwargs["age_ts"] = int(self.clock.time_msec()) - int(kwargs["age"])
del kwargs["age"]
elif "age_ts" not in kwargs:
kwargs["age_ts"] = int(self.clock.time_msec())
if etype in self._event_list:
handler = self._event_list[etype]

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,61 @@ 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 {}
class RoomRedactionEvent(SynapseEvent):
TYPE = "m.room.redaction"
valid_keys = SynapseEvent.valid_keys + ["redacts"]
def get_content_template(self):
return {}

View File

@@ -0,0 +1,64 @@
# -*- 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 .room import (
RoomMemberEvent, RoomJoinRulesEvent, RoomPowerLevelsEvent,
RoomAddStateLevelEvent, RoomSendEventLevelEvent, RoomOpsPowerLevelsEvent,
RoomAliasesEvent, RoomCreateEvent,
)
def prune_event(event):
""" Prunes the given event of all keys we don't know about or think could
potentially be dodgy.
This is used when we "redact" an event. We want to remove all fields that
the user has specified, but we do want to keep necessary information like
type, state_key etc.
"""
# Remove all extraneous fields.
event.unrecognized_keys = {}
new_content = {}
def add_fields(*fields):
for field in fields:
if field in event.content:
new_content[field] = event.content[field]
if event.type == RoomMemberEvent.TYPE:
add_fields("membership")
elif event.type == RoomCreateEvent.TYPE:
add_fields("creator")
elif event.type == RoomJoinRulesEvent.TYPE:
add_fields("join_rule")
elif event.type == RoomPowerLevelsEvent.TYPE:
# TODO: Actually check these are valid user_ids etc.
add_fields("default")
for k, v in event.content.items():
if k.startswith("@") and isinstance(v, (int, long)):
new_content[k] = v
elif event.type == RoomAddStateLevelEvent.TYPE:
add_fields("level")
elif event.type == RoomSendEventLevelEvent.TYPE:
add_fields("level")
elif event.type == RoomOpsPowerLevelsEvent.TYPE:
add_fields("kick_level", "ban_level", "redact_level")
elif event.type == RoomAliasesEvent.TYPE:
add_fields("aliases")
event.content = new_content
return event

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,8 @@
"""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"
SERVER_KEY_PREFIX = "/_matrix/key/v1"

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.
@@ -12,4 +12,3 @@
# 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.

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.
@@ -14,55 +14,42 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from synapse.storage import read_schema
from synapse.storage import prepare_database
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.client import TwistedHttpClient
from synapse.http.server import JsonResource, RootRedirect
from synapse.http.content_repository import ContentRepoResource
from synapse.http.server_key_resource import LocalKey
from synapse.http.client import MatrixHttpClient
from synapse.api.urls import (
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX
CLIENT_PREFIX, FEDERATION_PREFIX, WEB_CLIENT_PREFIX, CONTENT_REPO_PREFIX,
SERVER_KEY_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
import sqlite3
logger = logging.getLogger(__name__)
SCHEMAS = [
"transactions",
"pdu",
"users",
"profiles",
"presence",
"im",
"room_aliases",
]
# Remember to update this number every time an incompatible change is made to
# database schema files, so the users will be informed on server restarts.
SCHEMA_VERSION = 1
class SynapseHomeServer(HomeServer):
def build_http_client(self):
return TwistedHttpClient()
return MatrixHttpClient(self)
def build_resource_for_client(self):
return JsonResource()
@@ -74,47 +61,20 @@ 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_resource_for_server_key(self):
return LocalKey(self)
def build_db_pool(self):
""" Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we
don't have to worry about overwriting existing content.
"""
logging.info("Preparing database: %s...", self.db_name)
with sqlite3.connect(self.db_name) as db_conn:
c = db_conn.cursor()
c.execute("PRAGMA user_version")
row = c.fetchone()
if row and row[0]:
user_version = row[0]
if user_version < SCHEMA_VERSION:
# TODO(paul): add some kind of intelligent fixup here
raise ValueError("Cannot use this database as the " +
"schema version (%d) does not match (%d)" %
(user_version, SCHEMA_VERSION)
)
else:
for sql_loc in SCHEMAS:
sql_script = read_schema(sql_loc)
c.executescript(sql_script)
db_conn.commit()
c.execute("PRAGMA user_version = %d" % SCHEMA_VERSION)
c.close()
logging.info("Database prepared in %s.", self.db_name)
pool = adbapi.ConnectionPool(
'sqlite3', self.db_name, check_same_thread=False,
cp_min=1, cp_max=1)
return pool
return adbapi.ConnectionPool(
"sqlite3", self.get_db_name(),
check_same_thread=False,
cp_min=1,
cp_max=1
)
def create_resource_tree(self, web_client, redirect_root_to_web_client):
"""Create the resource tree for this Home Server.
@@ -133,7 +93,8 @@ class SynapseHomeServer(HomeServer):
desired_tree = [
(CLIENT_PREFIX, self.get_resource_for_client()),
(FEDERATION_PREFIX, self.get_resource_for_federation()),
(CONTENT_REPO_PREFIX, self.get_resource_for_content_repo())
(CONTENT_REPO_PREFIX, self.get_resource_for_content_repo()),
(SERVER_KEY_PREFIX, self.get_resource_for_server_key()),
]
if web_client:
logger.info("Adding the web client.")
@@ -206,117 +167,80 @@ 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 run():
reactor.run()
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 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,
)
db_name = hs.get_db_name()
logging.info("Preparing database: %s...", db_name)
with sqlite3.connect(db_name) as db_conn:
prepare_database(db_conn)
logging.info("Database prepared in %s.", db_name)
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:
hs.start_listening(config.bind_port, config.unsecure_port)
if config.daemonize:
print config.pid_file
daemon = Daemonize(
app="synapse-homeserver",
pid=args.pid,
action=run,
pid=config.pid_file,
action=reactor.run,
auto_close_fds=False,
verbose=True,
logger=logger,
@@ -324,7 +248,7 @@ def setup():
daemon.start()
else:
run()
reactor.run()
if __name__ == '__main__':

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.
@@ -12,4 +12,3 @@
# 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.

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

@@ -0,0 +1,140 @@
# -*- 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)
print (
"A config file has been generated in %s for server name"
" '%s' with corresponding SSL keys and self-signed"
" certificates. Please review this file and customise it to"
" your needs."
) % (
config_args.config_path, config['server_name']
)
print (
"If this server name is incorrect, you will need to regenerate"
" the SSL certificates"
)
sys.exit(0)
return cls(args)

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

@@ -0,0 +1,51 @@
# 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
)
self.captcha_bypass_secret = args.captcha_bypass_secret
@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."
)
group.add_argument(
"--captcha_bypass_secret", type=str,
help="A secret key used to bypass the captcha test entirely."
)

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)

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

@@ -0,0 +1,42 @@
# -*- 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
class EmailConfig(Config):
def __init__(self, args):
super(EmailConfig, self).__init__(args)
self.email_from_address = args.email_from_address
self.email_smtp_server = args.email_smtp_server
@classmethod
def add_arguments(cls, parser):
super(EmailConfig, cls).add_arguments(parser)
email_group = parser.add_argument_group("email")
email_group.add_argument(
"--email-from-address",
default="FROM@EXAMPLE.COM",
help="The address to send emails from (e.g. for password resets)."
)
email_group.add_argument(
"--email-smtp-server",
default="",
help=(
"The SMTP server to send emails from (e.g. for password"
" resets)."
)
)

View File

@@ -0,0 +1,35 @@
# -*- 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
from .email import EmailConfig
from .voip import VoipConfig
class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
RatelimitConfig, ContentRepositoryConfig, CaptchaConfig,
EmailConfig, VoipConfig):
pass
if __name__ == '__main__':
import sys
HomeServerConfig.load_config("Generate config", sys.argv[1:], "HomeServer")

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

@@ -0,0 +1,68 @@
# -*- 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,36 @@
# 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
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"
)

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

@@ -0,0 +1,112 @@
# -*- 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 os
from ._base import Config, ConfigError
import syutil.crypto.signing_key
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_keys = self.read_file(signing_key_path, "signing_key")
try:
return syutil.crypto.signing_key.read_signing_keys(
signing_keys.splitlines(True)
)
except Exception:
raise ConfigError(
"Error reading signing_key."
" Try running again with --generate-config"
)
@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:
syutil.crypto.signing_key.write_signing_keys(
signing_key_file,
(syutil.crypto.signing_key.generate_singing_key("auto"),),
)
else:
signing_keys = cls.read_file(args.signing_key_path, "signing_key")
if len(signing_keys.split("\n")[0].split()) == 1:
# handle keys in the old format.
key = syutil.crypto.signing_key.decode_signing_key_base64(
syutil.crypto.signing_key.NACL_ED25519,
"auto",
signing_keys.split("\n")[0]
)
with open(args.signing_key_path, "w") as signing_key_file:
syutil.crypto.signing_key.write_signing_keys(
signing_key_file,
(key,),
)

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

44
synapse/config/voip.py Normal file
View File

@@ -0,0 +1,44 @@
# 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 VoipConfig(Config):
def __init__(self, args):
super(VoipConfig, self).__init__(args)
self.turn_uris = args.turn_uris
self.turn_shared_secret = args.turn_shared_secret
self.turn_user_lifetime = args.turn_user_lifetime
@classmethod
def add_arguments(cls, parser):
super(VoipConfig, cls).add_arguments(parser)
group = parser.add_argument_group("voip")
group.add_argument(
"--turn-uris", type=str, default=None,
help="The public URIs of the TURN server to give to clients"
)
group.add_argument(
"--turn-shared-secret", type=str, default=None,
help=(
"The shared secret used to compute passwords for the TURN"
" server"
)
)
group.add_argument(
"--turn-user-lifetime", type=int, default=(1000 * 60 * 60),
help="How long generated TURN credentials last, in ms"
)

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.
@@ -12,4 +12,3 @@
# 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.

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,46 @@
# 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
import logging
logger = logging.getLogger(__name__)
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:
logger.exception("Failed to enable eliptic curve for TLS")
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.
@@ -15,9 +15,9 @@
from twisted.web.http import HTTPClient
from twisted.internet.protocol import Factory
from twisted.internet import defer, reactor
from twisted.internet.protocol import ClientFactory
from twisted.names.srvconnect import SRVConnector
from synapse.http.endpoint import matrix_endpoint
import json
import logging
@@ -30,15 +30,19 @@ def fetch_server_key(server_name, ssl_context_factory):
"""Fetch the keys for a remote server."""
factory = SynapseKeyClientFactory()
endpoint = matrix_endpoint(
reactor, server_name, ssl_context_factory, timeout=30
)
SRVConnector(
reactor, "matrix", server_name, factory,
protocol="tcp", connectFuncName="connectSSL", defaultPort=443,
connectFuncKwArgs=dict(contextFactory=ssl_context_factory)).connect()
server_key, server_certificate = yield factory.remote_key
defer.returnValue((server_key, server_certificate))
for i in range(5):
try:
protocol = yield endpoint.connect(factory)
server_response, server_certificate = yield protocol.remote_key
defer.returnValue((server_response, server_certificate))
return
except Exception as e:
logger.exception(e)
raise IOError("Cannot get key for %s" % server_name)
class SynapseKeyClientError(Exception):
@@ -51,69 +55,46 @@ class SynapseKeyClientProtocol(HTTPClient):
the server and extracts the X.509 certificate for the remote peer from the
SSL connection."""
timeout = 30
def __init__(self):
self.remote_key = defer.Deferred()
def connectionMade(self):
logger.debug("Connected to %s", self.transport.getHost())
self.sendCommand(b"GET", b"/key")
self.sendCommand(b"GET", b"/_matrix/key/v1/")
self.endHeaders()
self.timer = reactor.callLater(
self.factory.timeout_seconds,
self.timeout,
self.on_timeout
)
def handleStatus(self, version, status, message):
if status != b"200":
logger.info("Non-200 response from %s: %s %s",
self.transport.getHost(), status, message)
#logger.info("Non-200 response from %s: %s %s",
# self.transport.getHost(), status, message)
self.transport.abortConnection()
def handleResponse(self, response_body_bytes):
try:
json_response = json.loads(response_body_bytes)
except ValueError:
logger.info("Invalid JSON response from %s",
self.transport.getHost())
#logger.info("Invalid JSON response from %s",
# self.transport.getHost())
self.transport.abortConnection()
return
certificate = self.transport.getPeerCertificate()
self.factory.on_remote_key((json_response, certificate))
self.remote_key.callback((json_response, certificate))
self.transport.abortConnection()
self.timer.cancel()
def on_timeout(self):
logger.debug("Timeout waiting for response from %s",
self.transport.getHost())
self.remote_key.errback(IOError("Timeout waiting for response"))
self.transport.abortConnection()
class SynapseKeyClientFactory(ClientFactory):
class SynapseKeyClientFactory(Factory):
protocol = SynapseKeyClientProtocol
max_retries = 5
timeout_seconds = 30
def __init__(self):
self.succeeded = False
self.retries = 0
self.remote_key = defer.Deferred()
def on_remote_key(self, key):
self.succeeded = True
self.remote_key.callback(key)
def retry_connection(self, connector):
self.retries += 1
if self.retries < self.max_retries:
connector.connector = None
connector.connect()
else:
self.remote_key.errback(
SynapseKeyClientError("Max retries exceeded"))
def clientConnectionFailed(self, connector, reason):
logger.info("Connection failed %s", reason)
self.retry_connection(connector)
def clientConnectionLost(self, connector, reason):
logger.info("Connection lost %s", reason)
if not self.succeeded:
self.retry_connection(connector)

155
synapse/crypto/keyring.py Normal file
View File

@@ -0,0 +1,155 @@
# -*- 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 synapse.crypto.keyclient import fetch_server_key
from twisted.internet import defer
from syutil.crypto.jsonsign import verify_signed_json, signature_ids
from syutil.crypto.signing_key import (
is_signing_algorithm_supported, decode_verify_key_bytes
)
from syutil.base64util import decode_base64, encode_base64
from synapse.api.errors import SynapseError, Codes
from OpenSSL import crypto
import logging
logger = logging.getLogger(__name__)
class Keyring(object):
def __init__(self, hs):
self.store = hs.get_datastore()
self.clock = hs.get_clock()
self.hs = hs
@defer.inlineCallbacks
def verify_json_for_server(self, server_name, json_object):
logger.debug("Verifying for %s", server_name)
key_ids = signature_ids(json_object, server_name)
if not key_ids:
raise SynapseError(
400,
"Not signed with a supported algorithm",
Codes.UNAUTHORIZED,
)
try:
verify_key = yield self.get_server_verify_key(server_name, key_ids)
except IOError:
raise SynapseError(
502,
"Error downloading keys for %s" % (server_name,),
Codes.UNAUTHORIZED,
)
except:
raise SynapseError(
401,
"No key for %s with id %s" % (server_name, key_ids),
Codes.UNAUTHORIZED,
)
try:
verify_signed_json(json_object, server_name, verify_key)
except:
raise SynapseError(
401,
"Invalid signature for server %s with key %s:%s" % (
server_name, verify_key.alg, verify_key.version
),
Codes.UNAUTHORIZED,
)
@defer.inlineCallbacks
def get_server_verify_key(self, server_name, key_ids):
"""Finds a verification key for the server with one of the key ids.
Args:
server_name (str): The name of the server to fetch a key for.
keys_ids (list of str): The key_ids to check for.
"""
# Check the datastore to see if we have one cached.
cached = yield self.store.get_server_verify_keys(server_name, key_ids)
if cached:
defer.returnValue(cached[0])
return
# Try to fetch the key from the remote server.
# TODO(markjh): Ratelimit requests to a given server.
(response, tls_certificate) = yield fetch_server_key(
server_name, self.hs.tls_context_factory
)
# Check the response.
x509_certificate_bytes = crypto.dump_certificate(
crypto.FILETYPE_ASN1, tls_certificate
)
if ("signatures" not in response
or server_name not in response["signatures"]):
raise ValueError("Key response not signed by remote server")
if "tls_certificate" not in response:
raise ValueError("Key response missing TLS certificate")
tls_certificate_b64 = response["tls_certificate"]
if encode_base64(x509_certificate_bytes) != tls_certificate_b64:
raise ValueError("TLS certificate doesn't match")
verify_keys = {}
for key_id, key_base64 in response["verify_keys"].items():
if is_signing_algorithm_supported(key_id):
key_bytes = decode_base64(key_base64)
verify_key = decode_verify_key_bytes(key_id, key_bytes)
verify_keys[key_id] = verify_key
for key_id in response["signatures"][server_name]:
if key_id not in response["verify_keys"]:
raise ValueError(
"Key response must include verification keys for all"
" signatures"
)
if key_id in verify_keys:
verify_signed_json(
response,
server_name,
verify_keys[key_id]
)
# Cache the result in the datastore.
time_now_ms = self.clock.time_msec()
self.store.store_server_certificate(
server_name,
server_name,
time_now_ms,
tls_certificate,
)
for key_id, key in verify_keys.items():
self.store.store_server_verify_key(
server_name, server_name, time_now_ms, key
)
for key_id in key_ids:
if key_id in verify_keys:
defer.returnValue(verify_keys[key_id])
return
raise ValueError("No verification key found for given key ids")

View File

@@ -1,111 +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.
from twisted.internet import reactor, ssl
from twisted.web import server
from twisted.web.resource import Resource
from twisted.python.log import PythonLoggingObserver
from synapse.crypto.resource.key import LocalKey
from synapse.crypto.config import load_config
from syutil.base64util import decode_base64
from OpenSSL import crypto, SSL
import logging
import nacl.signing
import sys
class KeyServerSSLContextFactory(ssl.ContextFactory):
"""Factory for PyOpenSSL SSL contexts that are used to handle incoming
connections and to make connections to remote servers."""
def __init__(self, key_server):
self._context = SSL.Context(SSL.SSLv23_METHOD)
self.configure_context(self._context, key_server)
@staticmethod
def configure_context(context, key_server):
context.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
context.use_certificate(key_server.tls_certificate)
context.use_privatekey(key_server.tls_private_key)
context.load_tmp_dh(key_server.tls_dh_params_path)
context.set_cipher_list("!ADH:HIGH+kEDH:!AECDH:HIGH+kEECDH")
def getContext(self):
return self._context
class KeyServer(object):
"""An HTTPS server serving LocalKey and RemoteKey resources."""
def __init__(self, server_name, tls_certificate_path, tls_private_key_path,
tls_dh_params_path, signing_key_path, bind_host, bind_port):
self.server_name = server_name
self.tls_certificate = self.read_tls_certificate(tls_certificate_path)
self.tls_private_key = self.read_tls_private_key(tls_private_key_path)
self.tls_dh_params_path = tls_dh_params_path
self.signing_key = self.read_signing_key(signing_key_path)
self.bind_host = bind_host
self.bind_port = int(bind_port)
self.ssl_context_factory = KeyServerSSLContextFactory(self)
@staticmethod
def read_tls_certificate(cert_path):
with open(cert_path) as cert_file:
cert_pem = cert_file.read()
return crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)
@staticmethod
def read_tls_private_key(private_key_path):
with open(private_key_path) as private_key_file:
private_key_pem = private_key_file.read()
return crypto.load_privatekey(crypto.FILETYPE_PEM, private_key_pem)
@staticmethod
def read_signing_key(signing_key_path):
with open(signing_key_path) as signing_key_file:
signing_key_b64 = signing_key_file.read()
signing_key_bytes = decode_base64(signing_key_b64)
return nacl.signing.SigningKey(signing_key_bytes)
def run(self):
root = Resource()
root.putChild("key", LocalKey(self))
site = server.Site(root)
reactor.listenSSL(
self.bind_port,
site,
self.ssl_context_factory,
interface=self.bind_host
)
logging.basicConfig(level=logging.DEBUG)
observer = PythonLoggingObserver()
observer.start()
reactor.run()
def main():
key_server = KeyServer(**load_config(__doc__, sys.argv[1:]))
key_server.run()
if __name__ == "__main__":
main()

View File

@@ -1,161 +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.
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from twisted.internet import defer
from synapse.http.server import respond_with_json_bytes
from synapse.crypto.keyclient import fetch_server_key
from syutil.crypto.jsonsign import sign_json, verify_signed_json
from syutil.base64util import encode_base64, decode_base64
from syutil.jsonutil import encode_canonical_json
from OpenSSL import crypto
from nacl.signing import VerifyKey
import logging
logger = logging.getLogger(__name__)
class LocalKey(Resource):
"""HTTP resource containing encoding the TLS X.509 certificate and NACL
signature verification keys for this server::
GET /key HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"server_name": "this.server.example.com"
"signature_verify_key": # base64 encoded NACL verification key.
"tls_certificate": # base64 ASN.1 DER encoded X.509 tls cert.
"signatures": {
"this.server.example.com": # NACL signature for this server.
}
}
"""
def __init__(self, key_server):
self.key_server = key_server
self.response_body = encode_canonical_json(
self.response_json_object(key_server)
)
Resource.__init__(self)
@staticmethod
def response_json_object(key_server):
verify_key_bytes = key_server.signing_key.verify_key.encode()
x509_certificate_bytes = crypto.dump_certificate(
crypto.FILETYPE_ASN1,
key_server.tls_certificate
)
json_object = {
u"server_name": key_server.server_name,
u"signature_verify_key": encode_base64(verify_key_bytes),
u"tls_certificate": encode_base64(x509_certificate_bytes)
}
signed_json = sign_json(
json_object,
key_server.server_name,
key_server.signing_key
)
return signed_json
def getChild(self, name, request):
logger.info("getChild %s %s", name, request)
if name == '':
return self
else:
return RemoteKey(name, self.key_server)
def render_GET(self, request):
return respond_with_json_bytes(request, 200, self.response_body)
class RemoteKey(Resource):
"""HTTP resource for retreiving the TLS certificate and NACL signature
verification keys for a another server. Checks that the reported X.509 TLS
certificate matches the one used in the HTTPS connection. Checks that the
NACL signature for the remote server is valid. Returns JSON signed by both
the remote server and by this server.
GET /key/remote.server.example.com HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"server_name": "remote.server.example.com"
"signature_verify_key": # base64 encoded NACL verification key.
"tls_certificate": # base64 ASN.1 DER encoded X.509 tls cert.
"signatures": {
"remote.server.example.com": # NACL signature for remote server.
"this.server.example.com": # NACL signature for this server.
}
}
"""
isLeaf = True
def __init__(self, server_name, key_server):
self.server_name = server_name
self.key_server = key_server
Resource.__init__(self)
def render_GET(self, request):
self._async_render_GET(request)
return NOT_DONE_YET
@defer.inlineCallbacks
def _async_render_GET(self, request):
try:
server_keys, certificate = yield fetch_server_key(
self.server_name,
self.key_server.ssl_context_factory
)
resp_server_name = server_keys[u"server_name"]
verify_key_b64 = server_keys[u"signature_verify_key"]
tls_certificate_b64 = server_keys[u"tls_certificate"]
verify_key = VerifyKey(decode_base64(verify_key_b64))
if resp_server_name != self.server_name:
raise ValueError("Wrong server name '%s' != '%s'" %
(resp_server_name, self.server_name))
x509_certificate_bytes = crypto.dump_certificate(
crypto.FILETYPE_ASN1,
certificate
)
if encode_base64(x509_certificate_bytes) != tls_certificate_b64:
raise ValueError("TLS certificate doesn't match")
verify_signed_json(server_keys, self.server_name, verify_key)
signed_json = sign_json(
server_keys,
self.key_server.server_name,
self.key_server.signing_key
)
json_bytes = encode_canonical_json(signed_json)
respond_with_json_bytes(request, 200, json_bytes)
except Exception as e:
json_bytes = encode_canonical_json({
u"error": {u"code": 502, u"message": e.message}
})
respond_with_json_bytes(request, 502, json_bytes)

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