Compare commits

..

208 Commits

Author SHA1 Message Date
Maxime Dor
3d39ee97dd Add 3PIDs at registration 2018-04-13 03:48:32 +02:00
Maxime Dor
94d60b6fc9 Auto-generate username 2018-04-13 03:48:32 +02:00
Maxime Dor
ac9881fa4b Fix login 2018-04-13 03:48:32 +02:00
Maxime Dor
547cafdd13 PoC of registration with Google 3PID 2018-04-13 03:48:32 +02:00
Maxime Dor
0da1af9869 registration handling skeleton 2018-04-13 03:48:32 +02:00
Maxime Dor
268df533f5 Research with Google auth integration 2018-04-13 03:48:32 +02:00
Max Dor
ef80f4aa30 Documentation enhancements (#73) 2018-04-13 03:26:33 +02:00
Max Dor
1e413af019 Fix crypto
A recent change in synapse shown that the various classes handling crypto were broken.
All the crypto code has been refactored in the SDK and the local code has been adapted.
2018-04-11 23:37:33 +02:00
Max Dor
a0f8af820e Fix minor regression with Auth feature and REST/Memory backend
See https://matrix.to/#/!NPRUEisLjcaMtHIzDr:kamax.io/$1523216730848820dFUZX:matrix.org
2018-04-08 22:05:36 +02:00
Max Dor
5ef145212a Support access tokens in headers (Fix #65) (#70) 2018-04-02 17:26:03 +02:00
Max Dor
91ccb75fa1 Properly handle invalid characters in identifiers for Wordpress 2018-04-02 14:36:23 +02:00
Max Dor
ac6f549618 Support 3PID in memory identity store profile 2018-03-30 18:31:22 +02:00
Max Dor
7f9c7aa76d Fix Synapse SQL directory provider class name 2018-03-25 23:19:45 +02:00
Max Dor
02688942fd Enforce host present in DNS override config to avoid request loop 2018-03-25 19:31:52 +02:00
Max Dor
48668bcd92 Support of Directory for in-memory Identity store 2018-03-25 19:30:42 +02:00
Max Dor
a9627121fa Enchanced profile management (#68)
* Proof of concept of adding 3PIDs data to user profile
* Document reverse proxy apache config
* Support for Matrix Gateway project roles' endpoint
* Fix conflicting ThreePid object defined in SDK and mxisd projects
2018-03-25 01:20:59 +01:00
Max Dor
3fc86465f8 Wordpress identity store (#67) 2018-03-23 17:14:59 +01:00
Max Dor
d93b546e3c Improved Travis-CI config
- Use default JDK
- Cache management for faster builds
2018-03-21 18:21:49 +01:00
Maxime Dor
ea15f24d41 Be clear about which LDAP config/backend is picked up 2018-03-21 02:32:00 +01:00
Max Dor
290a32d640 Merge pull request #66 from kamax-io/max/federation-discovery-enhancement
Better federation auto-discovery
2018-03-15 00:14:20 +01:00
Maxime Dor
10f9126cb6 Better federation auto-discovery
- Use the new status check endpoint at /_matrix/identity/api/v1
- Enforce DNS SRV existence before asking remote server for data
2018-03-11 18:28:48 +01:00
Maxime Dor
c3385b38dc Update to latest SDK 2018-03-09 23:58:16 +01:00
adrnam
61fec4aec7 3PID authentication (#60)
Fix for #49
2018-03-08 18:29:03 +01:00
Max Dor
1db76139a9 Invite resolution enhancements (#63)
* Make invite resolution process configurable

* Add warning in logs if invite resolution is not recursive
2018-03-05 10:00:09 +01:00
Max Dor
a27858082c Add support for NetIQ as a LDAP backend (#61) 2018-03-03 00:28:15 +01:00
Maxime Dor
ea08a80504 Respect 3PID session policy for remote sending with phone numbers 2018-03-02 23:27:19 +01:00
Maxime Dor
cb3130d365 Send phone number in response body when creating 3PID session
If missing, Riot will show "undefined" instead of the number.
2018-03-02 23:26:33 +01:00
Maxime Dor
7189a4b100 Prepare for kamax-io/matrix-java-sdk#23 2018-02-27 18:30:36 +01:00
adrnam
f71cdbf83e Clarified configuration comments (#54) 2018-02-23 19:40:08 +01:00
Maxime Dor
665a284f4b Clarify wording 2018-02-21 17:22:11 +01:00
Maxime Dor
5e142eb41d Add some more LDAP debug entries 2018-01-28 18:02:03 +01:00
Maxime Dor
9fede41904 Add documentation for nginx config 2018-01-21 14:36:44 +01:00
Maxime Dor
5871bb6609 Set proper backend for Synapse SQL 2018-01-18 23:40:46 +01:00
Maxime Dor
5dbaca643a Fix #42 2017-12-31 13:02:41 +01:00
Maxime Dor
bf9576f9c3 Optimize Dockerfile statements order for caching 2017-12-25 08:54:38 +01:00
Maxime Dor
773f38d349 Properly mark REST Directory provider as component (Fix #48) 2017-12-23 17:43:03 +01:00
kiorky
6a5a4b3c1c Fix Docker build (#44) 2017-12-22 02:48:31 +01:00
Maxime Dor
7fff2448a1 Improve the docker experience
- Only one env variable to configure on first usage
- Auto-generate a default config
- Improve doc
2017-12-16 19:58:11 +01:00
Maxime Dor
6571ff76b1 Take LDAP filter into account when doing 3PID lookups 2017-12-16 19:17:22 +01:00
Maxime Dor
16690a0329 Enforce baseDn for LDAP provider 2017-12-06 20:31:06 +01:00
Maxime Dor
6ac593f0fa Fix typo 2017-12-02 20:13:25 +01:00
Maxime Dor
1581ab9e07 Better logic (cosmetic) for default 3PID notification providers 2017-11-30 02:44:21 +01:00
Maxime Dor
a1adca72e8 Properly select raw notification handler by default 2017-11-26 16:30:42 +01:00
Maxime Dor
e2b3920840 Directory: document HS exclusion config option 2017-11-22 19:40:16 +01:00
Maxime Dor
aaa742f6d2 LDAP: Properly handle multi-value attributes 2017-11-17 16:51:16 +01:00
Maxime Dor
959feb686c Improve auth doc 2017-11-16 22:35:16 +01:00
Maxime Dor
d9c5c5056a Improve getting started wording 2017-11-15 21:01:33 +01:00
Maxime Dor
83fafdcfeb Add config option to disable HS lookup for directory searches 2017-11-06 11:16:22 +01:00
Maxime Dor
e916ecd08b Properly handle Synapse as an Identity provider 2017-10-30 17:43:22 +01:00
Maxime Dor
1461d8ef6c Add fixme to prevent using mxisd DB as synapse identity store 2017-10-30 16:42:00 +01:00
Maxime Dor
19c1214e4a Fix release version extraction from git tags 2017-10-25 00:40:43 +02:00
Maxime Dor
b976f69c39 Fix systemd log format 2017-10-17 15:59:16 +02:00
Maxime Dor
3675da4a0f Specify that LDAP now supports profile auto-fill 2017-10-11 21:08:10 +02:00
Max Dor
077955d538 Set theme jekyll-theme-cayman 2017-10-09 19:18:37 +02:00
Maxime Dor
9af0cd3615 Support for profile auto-fill with LDAP 2017-10-08 04:22:38 +02:00
Maxime Dor
2bf68538c3 Fix typo, broken links 2017-10-08 03:41:45 +02:00
Maxime Dor
6c02e478d9 Add donate link 2017-10-07 18:50:17 +02:00
Maxime Dor
284da779f9 Fix typo 2017-10-07 18:39:13 +02:00
Maxime Dor
af161296b3 Be clear about profile auto-fill in LDAP auth 2017-10-07 18:27:47 +02:00
Maxime Dor
6317acd7fc Add missing links 2017-10-07 18:02:47 +02:00
Maxime Dor
30260af1f2 More documentation 2017-10-07 17:59:55 +02:00
Maxime Dor
3b697e86ac Various error handling improvements and user feedback 2017-10-07 06:15:57 +02:00
Maxime Dor
b4f0645257 Handle Homeservers not implementing the directory endpoint 2017-10-06 14:12:43 +02:00
Maxime Dor
0e48edf86e Properly handle session next url 2017-10-06 14:10:08 +02:00
Maxime Dor
7e92bfa474 Add warning not to use built-in configuration file 2017-10-05 21:42:02 +02:00
Maxime Dor
851e0c9d94 Properly build remote 3PID token request 2017-10-05 16:04:29 +02:00
Maxime Dor
ac1cbc4265 Fix redirect with thymleaf 2017-10-05 16:03:59 +02:00
Maxime Dor
62711ee12e Improve README 2017-10-02 20:07:22 +02:00
Maxime Dor
3954be2f08 Fix recursive logic 2017-10-02 18:03:57 +02:00
Maxime Dor
640512eb27 Line wrap 2017-10-02 16:14:26 +02:00
Maxime Dor
40705b5d47 Improved documentation 2017-10-02 16:10:22 +02:00
Maxime Dor
642d560ba9 Fix spelling 2017-10-02 03:46:30 +02:00
Maxime Dor
b6e86f5b2e Proper order of sections 2017-10-02 03:44:32 +02:00
Maxime Dor
4a99ec5531 Lots of new awesome documentation 2017-10-02 03:42:23 +02:00
Max Dor
9079bb25cc Merge pull request #34 from kamax-io/directory-integration
Directory integration
2017-10-01 22:04:58 +02:00
Maxime Dor
88e86cd0d5 Improve Directory documentation 2017-10-01 21:09:18 +02:00
Maxime Dor
8662b3f39f Stable implementation of Directory integration
- Documentation
- Allow to specific other attributes in LDAP to include in the search
2017-10-01 19:36:11 +02:00
Maxime Dor
d0aac5ac52 User Directory support in REST Backend 2017-10-01 18:13:01 +02:00
Maxime Dor
c702a34aab Fix regression due to bad replace 2017-10-01 16:10:05 +02:00
Maxime Dor
786e4a8f91 Prepare REST backend for directory flow 2017-10-01 02:21:48 +02:00
Maxime Dor
8d0b0edad2 Clarify some items thanks to users feedback 2017-10-01 00:06:03 +02:00
Maxime Dor
88a37c52c0 Skeleton for User directory setup instructions 2017-09-30 00:56:16 +02:00
Maxime Dor
52e4a65c3c Fix query generation 2017-09-30 00:27:36 +02:00
Maxime Dor
69ecef0155 Refactored directory package to include API version 2017-09-29 22:13:51 +02:00
Maxime Dor
f7984bd36e LDAP Directory search support 2017-09-29 20:54:08 +02:00
Maxime Dor
f735b3b730 Merge branch 'master' into directory-integration 2017-09-29 05:43:52 +02:00
Maxime Dor
b6008a41f2 Be consistent with DNS overwrite (always a URL) 2017-09-29 05:38:58 +02:00
Maxime Dor
ed2d13decf Don't mix up configs 2017-09-29 05:34:21 +02:00
Maxime Dor
4f3ecc19f3 Directory integration prototype using Google Firebase auth + Synapse SQL 2017-09-29 02:52:05 +02:00
Maxime Dor
c816217b22 Send new invite notification to same user if rooms are different 2017-09-28 02:45:01 +02:00
Maxime Dor
182f3c4bc3 Skeleton for HS User directory integration 2017-09-27 04:37:45 +02:00
Maxime Dor
2e7b5d2a87 Refactor packages (cosmetic) 2017-09-27 03:59:45 +02:00
Max Dor
09208d55d7 Merge pull request #33 from kamax-io/email-connector-sendgrid
Email connector sendgrid
2017-09-27 03:33:13 +02:00
Maxime Dor
05c76a657e Fix extra placeholders in smtp sender 2017-09-27 03:30:53 +02:00
Maxime Dor
f3bbc7c7c6 Add support for SendGrid as Email notification handler 2017-09-27 01:55:37 +02:00
Maxime Dor
61addd297a Use the correct formatting for MSISDN 2017-09-26 04:26:39 +02:00
Maxime Dor
1de0951733 Support 3PID listing during auth with Google Firebase 2017-09-26 03:11:15 +02:00
Maxime Dor
d348ebd813 Improved README to point to dedicated documents 2017-09-25 18:25:58 +02:00
Maxime Dor
0499c10a2c Better sample config, better README 2017-09-25 18:20:18 +02:00
Maxime Dor
13e248c71e Do not enforce Twilio config by default 2017-09-25 18:04:21 +02:00
Maxime Dor
d221b2c5de Fix groovy rollback issue 2017-09-25 17:56:06 +02:00
Maxime Dor
3a1900cbb2 Add link to 3PID session documentation 2017-09-25 17:15:37 +02:00
Max Dor
9f1867a030 Merge pull request #32 from kamax-io/phone_numbers-validation
Phone numbers validation
2017-09-25 17:12:59 +02:00
Maxime Dor
a061241291 Use relative links 2017-09-25 17:11:58 +02:00
Maxime Dor
fefa81e935 Add link to phone numbers in TOC 2017-09-25 17:06:39 +02:00
Maxime Dor
1e77bf43c6 Add documentation to validate phone numbers 2017-09-25 17:03:50 +02:00
Maxime Dor
c73bbf675e First prototype to validate phone numbers 2017-09-25 05:53:07 +02:00
Maxime Dor
6c2e65ace5 Code formatting, cosmetic 2017-09-25 02:35:16 +02:00
Maxime Dor
33263d3cff Bye bye Groovy, you won't be missed :( 2017-09-25 02:31:31 +02:00
Maxime Dor
af19fed6e7 More links to specific config docs 2017-09-25 00:15:30 +02:00
Maxime Dor
246dc4f8d1 Add web views link 2017-09-25 00:10:25 +02:00
Maxime Dor
31efa3e33f More 3PID sessions configuration documentation 2017-09-25 00:08:58 +02:00
Maxime Dor
bee2a5129b Fix inconsistencies into DNS library 2017-09-24 21:29:14 +02:00
Maxime Dor
f1e78af80b Fix empty JSON object on empty lookup results 2017-09-24 21:12:49 +02:00
Max Dor
e0022e549e Merge pull request #31 from kamax-io/binding-validation
3PID sessions support (email only)
2017-09-24 05:26:37 +02:00
Maxime Dor
b46d047411 Add session disabled config 2017-09-24 04:54:48 +02:00
Maxime Dor
542c549e4e Fix config for remote-only sessions mode 2017-09-24 04:53:37 +02:00
Maxime Dor
ebb9a6daa0 Fix links and better formatting 2017-09-24 04:52:24 +02:00
Maxime Dor
f93a94ddf1 First draft of 3PID sessions/binds manual 2017-09-24 04:47:16 +02:00
Maxime Dor
597fc95cef Clean-up 2017-09-24 01:49:56 +02:00
Maxime Dor
df81dda22d First working prototype to proxy 3PID binds to central Matrix.org IS 2017-09-23 04:27:14 +02:00
Maxime Dor
5836965a1e Saving current work 2017-09-22 01:49:27 +02:00
Maxime Dor
58d80b8eb3 Notification for proxying 3PID, remote 3PID are proxied by default 2017-09-22 00:00:25 +02:00
Maxime Dor
a4b4a3f24c Polishing, prepare for proxying 3PID sessions 2017-09-21 07:26:33 +02:00
Maxime Dor
ace6019197 Refactor after first tests against synapse 2017-09-21 04:07:13 +02:00
Maxime Dor
88cefeabbf Fix refactored calls 2017-09-20 17:24:21 +02:00
Maxime Dor
bf2afd8739 Further work 2017-09-20 17:22:51 +02:00
Maxime Dor
0b087ee08c Prepare structure to handle 3PID sessions and bindings validation/proxy 2017-09-20 04:35:34 +02:00
Maxime Dor
c1746697b9 Split template creation and 3PID connector to integrate bindings verification 2017-09-19 03:46:31 +02:00
Maxime Dor
5179c4dbb5 Proper check 2017-09-19 01:08:13 +02:00
Maxime Dor
64973f57cf Better defaults for logging 2017-09-19 01:03:44 +02:00
Maxime Dor
00a00be692 LDAP backend: protect against empty username 2017-09-18 12:51:36 +02:00
Maxime Dor
9e8dade238 Clarify README and REST backend doc 2017-09-18 10:58:27 +02:00
Maxime Dor
9babad6b33 Add push of latest dev docker image tag to push target 2017-09-18 02:29:58 +02:00
Maxime Dor
00896ab280 Add PostgreSQL support for SQL Backend 2017-09-18 02:24:46 +02:00
Max Dor
f03cd76f52 Merge pull request #30 from kamax-io/rest-backend
REST backend
2017-09-18 01:05:31 +02:00
Maxime Dor
0453c1db30 Add tests for REST backend implementation 2017-09-18 01:00:17 +02:00
Maxime Dor
013be139c9 Fix bad copy/paste and bad method scope 2017-09-17 23:26:22 +02:00
Maxime Dor
317fc367f8 Identity lookup implementation for REST backend 2017-09-17 22:34:48 +02:00
Maxime Dor
efc54e73f2 Streamline Backend auth mechanism/return values 2017-09-17 21:19:29 +02:00
Maxime Dor
0182ec7251 Streamline JSON requests/answers 2017-09-17 15:48:33 +02:00
Maxime Dor
640ccb7ef8 Improve docker build process and doc 2017-09-17 15:12:39 +02:00
Maxime Dor
2e694349c9 Add default type for SQL Backend 2017-09-17 14:47:48 +02:00
Maxime Dor
9328fa1eb4 Update link for REST auth module in sample config 2017-09-17 14:17:50 +02:00
Maxime Dor
221d823f3b Update LDAP library to fix auth filter bug 2017-09-17 14:01:38 +02:00
Maxime Dor
8b6eadb9ab Auth endpoint implementation 2017-09-17 05:17:00 +02:00
Maxime Dor
22d8380bce Configuration management
- Default values
- Compute values
2017-09-17 02:03:45 +02:00
Maxime Dor
0cbf1a83a5 First skeleton for REST backend 2017-09-17 01:06:43 +02:00
Maxime Dor
23f717579e Refactor backend packages 2017-09-16 22:56:53 +02:00
Maxime Dor
4eb8c95c3a Handle anonymous bind in LDAP backend (Fix #27) 2017-09-16 18:11:39 +02:00
Maxime Dor
0fa04e4a54 Better wording 2017-09-16 14:19:26 +02:00
Maxime Dor
f97aa8cbef Cosmetic refactoring 2017-09-16 13:46:15 +02:00
Maxime Dor
37b0f29184 Cosmetic refactoring 2017-09-16 13:01:37 +02:00
Maxime Dor
2befdbb54f Improve network discovery explanation 2017-09-16 04:34:16 +02:00
Maxime Dor
d1a6c84e6b Properly split authoritative domain and public IS host 2017-09-16 04:29:01 +02:00
Maxime Dor
e8229b867a Add docker build targets 2017-09-16 02:07:10 +02:00
Maxime Dor
6fb18d5827 Remove problematic handling of multiple validation requests for same 3PID 2017-09-16 01:34:31 +02:00
Maxime Dor
a8488a0745 Add ability to overwrite DNS when trying to contact the related homeserver 2017-09-14 23:10:23 +02:00
Maxime Dor
89a7416367 Add prototype support for SQL auth/directory backends 2017-09-14 20:59:35 +02:00
Maxime Dor
068c6b8555 Use proper object for key validity json answer 2017-09-14 18:34:02 +02:00
Maxime Dor
9a90d846e6 Improve README
- List current features
- List features to come
- Improve wording at various places
- Centralise configuration directions
- Improve layout to make it easier for new users (hopefully)
2017-09-14 04:54:01 +02:00
Maxime Dor
16efe93920 Use proper ID when clearing invites from memory 2017-09-14 03:56:21 +02:00
Maxime Dor
8fbb45037c Create SQLite DB file parent directory if necessary 2017-09-14 03:38:14 +02:00
Maxime Dor
f1196d5b72 Revamp example config for better handling by users and build tools 2017-09-14 03:31:56 +02:00
Max Dor
3b4736b00f Merge pull request #28 from kamax-io/invite-support
Invite support
2017-09-14 02:49:24 +02:00
Maxime Dor
5796982f2d Add persistence storage for invites 2017-09-14 02:36:08 +02:00
Maxime Dor
9e6d3ab5dd Build error json object properly 2017-09-13 21:27:29 +02:00
Maxime Dor
571506d1d2 Add new placeholders for e-mail to access invited address and medium type 2017-09-13 19:15:39 +02:00
Maxime Dor
c00adcf575 Be consistent with annotations 2017-09-13 16:25:21 +02:00
Maxime Dor
808aed2bc3 log user agent for lookup requests 2017-09-13 16:21:09 +02:00
Maxime Dor
ec0a9c7b80 log user agent for lookup requests 2017-09-13 16:20:46 +02:00
Maxime Dor
84afb86b77 Properly handle default config file when running as systemd/sysv daemon 2017-09-13 02:47:25 +02:00
Maxime Dor
02c5523d6d Handle bundled and external e-mail template properly 2017-09-13 02:29:58 +02:00
Maxime Dor
d7cf31fb9a Remove debug log statement 2017-09-13 02:28:37 +02:00
Maxime Dor
d7258cd3c6 Fix DNS lookup 2017-09-13 02:28:22 +02:00
Maxime Dor
222f7f104a Add regular pending invite mapping checks 2017-09-13 01:35:11 +02:00
Maxime Dor
548dace78c Properly handle invites with LDAP backend 2017-09-13 01:16:02 +02:00
Maxime Dor
001de470ca Fix invalid add of @Override 2017-09-12 19:48:39 +02:00
Maxime Dor
09b789dfc2 Refactor logic, preparing to generalize post-login publish of mappings 2017-09-12 19:47:01 +02:00
Maxime Dor
55b759a31c Enhance e-mail invitations
- Built-in e-mail template
- More template placeholders
2017-09-12 02:24:58 +02:00
Maxime Dor
cb0ffe0575 Remove debug log 2017-09-07 17:45:48 +02:00
Maxime Dor
3cde0b9c23 Fix typo 2017-09-07 17:44:37 +02:00
Maxime Dor
2ce3bab3b6 Properly handle force recursive lookup during invite 2017-09-07 02:04:48 +02:00
Maxime Dor
ba723f8523 Don't fail on non-existing config key 2017-09-07 01:53:42 +02:00
Maxime Dor
d2d5f80b44 Don't fail if template is not found and hope for the best 2017-09-07 01:21:49 +02:00
Maxime Dor
e2a097b2d0 Avoid header that could be considered as a profile 2017-09-07 01:16:43 +02:00
Maxime Dor
45a81e5979 Add sane defaults 2017-09-07 00:07:49 +02:00
Maxime Dor
158060a3b0 Add some placeholders handling for e-mail template 2017-09-06 23:08:47 +02:00
Maxime Dor
1e3b832186 Fix example data 2017-09-06 15:56:45 +02:00
Maxime Dor
8294990b1f Cosmetic changes
- Add missing license headers
- Remove unused class
2017-09-06 15:04:17 +02:00
Maxime Dor
a704ba2e6c Working prototype 2017-09-06 15:00:43 +02:00
Maxime Dor
a7303fef15 Add icons 2017-09-06 10:52:57 +02:00
Maxime Dor
cd6960fa80 Attempt to support invites, working in progress 2017-09-06 04:17:46 +02:00
Maxime Dor
c05ca68de4 Keep LDAP disabled by default 2017-09-05 21:37:43 +02:00
Maxime Dor
a8df386d58 Auth support with synapse REST auth module 2017-09-05 21:36:33 +02:00
Maxime Dor
4b0d549dd6 Add LDAP Auth support with synapse REST Auth module 2017-09-05 21:31:36 +02:00
Maxime Dor
85236793e1 Skeleton to support LDAP Auth 2017-09-04 03:08:19 +02:00
Maxime Dor
954dcf3a5c Improve startup failures user experience
- Be clear about config errors instead of difficult stack traces
- Fix default values which should not cause startup failures
2017-09-03 23:26:32 +02:00
Max Dor
466a3d270e Merge pull request #26 from kamax-io/recursive-enhanced
Add properties for enhanced recursive lookup queries
2017-09-03 20:36:30 +02:00
Maxime Dor
a046f9f327 Add Docker public repo 2017-09-03 13:49:00 +02:00
Maxime Dor
c03573b24f Be clear about building mxisd before building Docker img 2017-09-03 03:18:44 +02:00
Maxime Dor
694e62edee Firebase UID is case sensitive, must not alter 2017-09-02 03:23:46 +02:00
Maxime Dor
61399c7705 Add status endpoint placeholder 2017-09-01 18:28:40 +02:00
Maxime Dor
05d1594ac2 Fix race condition and add more log statements 2017-08-31 18:52:06 +02:00
Maxime Dor
361596e773 Support 3PID lookups 2017-08-31 16:33:07 +02:00
Maxime Dor
d57aef36ea Wait for async calls 2017-08-31 03:34:08 +02:00
Maxime Dor
0033d0dc1d Experimental support for synapse REST auth module
- See https://github.com/maxidor/matrix-synapse-rest-auth
- Include Google Firebase backend using UID as login and user token as password
2017-08-31 02:10:36 +02:00
Maxime Dor
1c43ca7666 Add properties for enhanced recursive lookup queries 2017-08-29 01:44:33 +02:00
268 changed files with 18278 additions and 2264 deletions

5
.gitignore vendored
View File

@@ -7,4 +7,7 @@ out/
.idea/
# Local dev config
application.yaml
/application.yaml
# Local dev storage
/mxisd.db

View File

@@ -1,4 +1,8 @@
language: groovy
jdk:
- oraclejdk8
language: java
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/

View File

@@ -1,11 +1,17 @@
FROM openjdk:8-jre-alpine
RUN apk update && apk add bash && rm -rf /var/lib/apk/* /var/cache/apk/*
VOLUME /etc/mxisd
VOLUME /var/mxisd
EXPOSE 8090
ADD build/libs/mxisd.jar /mxisd.jar
ADD src/docker/start.sh /start.sh
ENV JAVA_OPTS=""
ENV CONF_FILE_PATH="/etc/mxisd/mxisd.yaml"
ENV SIGN_KEY_PATH="/var/mxisd/sign.key"
ENV SQLITE_DATABASE_PATH="/var/mxisd/mxisd.db"
CMD [ "/start.sh" ]
ADD src/docker/start.sh /start.sh
ADD build/libs/mxisd.jar /mxisd.jar

243
README.md
View File

@@ -1,168 +1,105 @@
mxisd
-----
mxisd - Federated Matrix Identity Server
----------------------------------------
![Travis-CI build status](https://travis-ci.org/kamax-io/mxisd.svg?branch=master)
[Overview](#overview) | [Lookup process](#lookup-process) | [Packages](#packages) | [From source](#from-source) | [Network Discovery](#network-discovery) | [Integration](#integration) | [Support](#support)
- [Overview](#overview)
- [Features](#features)
- [Use cases](#use-cases)
- [Getting Started](#getting-started)
- [Support](#support)
- [Contribute](#contribute)
- [FAQ](#faq)
- [Contact](#contact)
# Overview
mxisd is an implementation of the [Matrix Identity Service specification](https://matrix.org/docs/spec/identity_service/unstable.html)
which provides an alternative to the vector.im and matrix.org Identity servers.
mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures with [enhanced features](#features).
As an enhanced Identity service, it implements the [Matrix Identity service API](https://kamax.io/matrix/api/identity_service/unstable.html)
and several [extra features](#features) that greatly enhance user experience within Matrix.
It is the one stop shop for anything regarding Authentication, Directory and Identity management in Matrix built in a
single coherent product.
mxisd is federated and uses a cascading lookup model which performs lookup from a more authoritative to a less authoritative source, usually doing:
- Local identity stores: LDAP, etc.
- Federated identity stores: another Identity Server in charge of a specific domain, if applicable
- Configured identity stores: another Identiy Server specifically configured, if part of some sort of group trust
- Root identity store: vector.im/matrix.org central Identity Server
mxisd is specifically designed to connect to an existing on-premise Identity store (AD/Samba/LDAP, SQL Database,
Web services/app, etc.) and ease the integration of a Matrix infrastructure within an existing one.
mxisd only aims to support workflow that does NOT break federation or basic lookup process of the Matrix ecosystem.
The core principle of mxisd is to map between Matrix IDs and 3PIDs (Third-Party IDentifiers) for the Homeserver and its
users. 3PIDs can be anything that uniquely and globally identify a user, like:
- Email address
- Phone number
- Skype/Live ID
- Twitter handle
- Facebook ID
**NOTE:** mxisd is **NOT** an authenticator module for homeservers. Identity servers are opaque public directories for those who publish their data on it.
For more details, please read the [Identity Service specification](https://matrix.org/docs/spec/identity_service/unstable.html).
If you are unfamiliar with the Identity vocabulary and concepts in Matrix, **please read this [introduction](docs/concepts.md)**.
# Lookup Process
Default Lookup strategy will use a priority order and a configurable recursive/local type of request.
# Features
[Identity](docs/features/identity.md): As a [regular Matrix Identity service](https://kamax.io/matrix/api/identity_service/unstable.html#general-principles):
- Search for people by 3PID using its own Identity stores
([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#association-lookup))
- Invite people to rooms by 3PID using its own Identity stores, with notifications to the invitee (Email, SMS, etc.)
([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#post-matrix-identity-api-v1-store-invite))
- Allow users to add 3PIDs to their settings/profile
([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#establishing-associations))
- Register accounts on your Homeserver with 3PIDs
([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#establishing-associations))
## E-mail
Given the 3PID `john.doe@example.org`, the following will be performed until a mapping is found:
- LDAP: lookup the Matrix ID (partial or complete) from a configurable attribute using a dedicated query.
- DNS: lookup another Identity Server using the domain part of an e-mail and:
- Look for a SRV record under `_matrix-identity._tcp.example.org`
- Lookup using the base domain name `example.org`
- Forwarder: Proxy the request to other configurable identity servers.
As an enhanced Identity service:
- [Federation](docs/features/federation.md): Use a recursive lookup mechanism when searching and inviting people by 3PID,
allowing to fetch data from:
- Own Identity store(s)
- Federated Identity servers, if applicable to the 3PID
- Arbitrary Identity servers
- Central Matrix Identity servers
- [Session Control](docs/threepids/session/session.md): Extensive control of where 3PIDs are transmitted so they are not
leaked publicly by users
- [Authentication](docs/features/authentication.md): Use your Identity stores to perform authentication in [synapse](https://github.com/matrix-org/synapse)
via the [REST password provider](https://github.com/kamax-io/matrix-synapse-rest-auth)
- [Directory search](docs/features/directory.md) which allows you to search for users within your organisation,
even without prior contact within Matrix using arbitrary search terms
- [Auto-fill of user profile](docs/features/authentication.md#profile-auto-fill) (Display name, 3PIDs)
- [Bridge Integration](docs/features/bridge-integration.md): Automatically bridge users without a published Matrix ID
## Phone number
Given the phone number `+123456789`, the following lookup logic will be performed:
- LDAP: lookup the Matrix ID (partial or complete) from a configurable attribute using a dedicated query.
- Forwarder: Proxy the request to other configurable identity servers.
# Use cases
- Use your existing Identity stores, do not duplicate your users information
- Auto-fill user profiles with relevant information
- As an organisation, stay in control of your data so it is not published to other servers by default where they
currently **cannot be removed**
- Users can directly find each other using whatever attribute is relevant within your Identity store
- Federate your Identity server so you can discover others and/or others can discover you
# Packages
See [releases]((https://github.com/kamax-io/mxisd/releases)) for native installers of supported systems.
If none is available, please use other packages or build from source.
## Docker
### From source
Build the image:
```
docker build -t your-org/mxisd:$(git describe --tags --always --dirty) .
```
You can run a container of the given image and test it with the following command (adapt volumes host paths):
```
docker run -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t your-org/mxisd:$(git describe --tags --always --dirty)
```
## Debian
### Download
See the [releases section](https://github.com/kamax-io/mxisd/releases).
### From source
Requirements:
- fakeroot
- dpkg-deb
Run:
```
./gradlew buildDeb
```
You will find the debian package in `build/dist`
# From Source
## Requirements
- JDK 1.8
## Build
```
git clone https://github.com/kamax-io/mxisd.git
cd mxisd
./gradlew build
```
## Configure
1. Create a new local config: `cp application.example.yaml application.yaml`
2. Set the `server.name` value to the domain value used in your Home Server configuration
3. Set an absolute location for the signing keys using `key.path`
4. Provide the LDAP attributes you want to use for lookup, if you want to use one
5. Edit an entity in your LDAP database and set the configure attribute with a Matrix ID (e.g. `@john.doe:example.org`)
## Test build and configuration
Start the server in foreground to validate the build:
```
java -jar build/libs/mxisd.jar
```
Ensure the signing key is available:
```
curl http://localhost:8090/_matrix/identity/api/v1/pubkey/ed25519:0
```
Validate your LDAP config and binding info (replace the e-mail):
```
curl "http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=john.doe@example.org"
```
If you plan on testing the integration with a homeserver, you will need to run an HTTPS reverse proxy in front of it
as the reference Home Server implementation [synapse](https://github.com/matrix-org/synapse) requires a HTTPS connection
to an ID server.
See the [Integration section](https://github.com/kamax-io/mxisd#integration) for more details.
## Install
After [building](#build) the software, run all the following commands as `root` or using `sudo`
1. Prepare files and directories:
```
# Create a dedicated user
useradd -r mxisd
# Create bin directory
mkdir /opt/mxisd
# Create config directory and set ownership
mkdir /etc/mxisd
chown mxisd /etc/mxisd
# Create data directory and set ownership
mkdir /var/opt/mxisd
chown mxisd /var/opt/mxisd
# Copy <repo root>/build/libs/mxisd.jar to bin directory
cp ./build/libs/mxisd.jar /opt/mxisd/
chown mxisd /opt/mxisd/mxisd.jar
chmod a+x /opt/mxisd/mxisd.jar
# Create symlink for easy exec
ln -s /opt/mxisd/mxisd.jar /usr/bin/mxisd
```
2. Copy the config file created earlier `./application.yaml` to `/etc/mxisd/mxisd.yaml`
3. Configure `/etc/mxisd/mxisd.yaml` with production value, `key.path` being the most important - `/var/opt/mxisd/signing.key` is recommended
4. Copy `<repo root>/src/systemd/mxisd.service` to `/etc/systemd/system/` and edit if needed
5. Manage service for auto-startup
```
# Enable service
systemctl enable mxisd
# Start service
systemctl start mxisd
```
# Network Discovery
To allow other federated Identity Server to reach yours, configure the following DNS SRV entry (adapt to your values):
```
_matrix-identity._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com.
```
This would only apply for 3PID that are DNS-based, like e-mails. For anything else, like phone numbers, no federation is currently possible.
The port must be HTTPS capable. Typically, the port `8090` of mxisd should be behind a reverse proxy which does HTTPS.
See the [integration section](#integration) for more details.
# Integration
- [HTTPS and Reverse proxy](https://github.com/kamax-io/mxisd/wiki/HTTPS)
- [synapse](https://github.com/kamax-io/mxisd/wiki/Homeserver-Integration)
# Getting started
See the [dedicated document](docs/getting-started.md)
# Support
## Community
If you need help, want to report a bug or just say hi, you can reach us on Matrix at [#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io)
For more high-level discussion about the Identity Server architecture/API, go to [#matrix-identity:matrix.org](https://matrix.to/#/#matrix-identity:matrix.org)
Over Matrix: [#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io) ([Preview](https://view.matrix.org/room/!NPRUEisLjcaMtHIzDr:kamax.io/))
## Professional
If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open source technologies/products,
please visit [our website](https://www.kamax.io/) to get in touch with us and get a quote.
For more high-level discussion about the Identity Server architecture/API, go to [#matrix-identity:kamax.io](https://matrix.to/#/#matrix-identity:kamax.io)
## Commercial
If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open
source technologies/products:
- Visit our [website](https://www.kamax.io/) to get in touch with us and get a quote.
- Come in our general Matrix room: [#kamax-matrix:kamax.io](https://matrix.to/#/#kamax-matrix:kamax.io)
# Contribute
You can contribute as a community member by:
- Giving us feedback about your usage of mxisd, even if it seems unimportant or if all is working well!
- Opening issues for any weird behaviour or bug. mxisd should feel natural, let us know if it does not!
- Helping us improve the documentation: tell us what is good or not good (in an issue or in Matrix), or make a PR with
changes you feel improve the doc.
- Contribute code directly: we love contributors! All your contributions will be licensed under AGPLv3.
- [Donate!](https://liberapay.com/maximusdor/) Any donation is welcome, regardless how small or big, and will directly
be used for the fixed costs and developer time of mxisd.
You can contribute as an organisation/corporation by:
- Get a [support contract](#commercial). This is the best way you can help us as it ensures mxisd is
maintained regularly and you get direct access to the support team.
- Sponsoring new features or bug fixes. [Get in touch](#contact) so we can discuss it further.
# FAQ
See the [dedicated document](docs/faq.md)
# Contact
Get in touch via:
- Matrix: [#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io)
- Email: see our website: [Kamax.io](https://www.kamax.io)

View File

@@ -1,148 +1,99 @@
server:
# Sample configuration file explaining the minimum required keys to be set to run mxisd
#
# For a complete list of options, see https://github.com/kamax-io/mxisd
# Indicate on which port the Identity Server will listen.
#
# This is be default an unencrypted port.
# HTTPS can be configured using Tomcat configuration properties.
port: 8090
# Realm under which this Identity Server is authoritative, required.
#
# This is used to avoid unnecessary connections and endless recursive lookup.
# e.g. domain name in e-mails.
name: 'example.org'
#######################
# Matrix config items #
#######################
# Matrix domain, same as the domain configure in your Homeserver configuration.
# (note: in Synapse Homeserver, the Matrix domain may be defined as 'server_name' in configuration file).
#
# This is used to build the various identifiers for identity, auth and directory.
matrix.domain: ''
key:
# Absolute path for the Identity Server signing key, required.
# During testing, /var/tmp/mxisd.key is a possible value
#
# For production, use a stable location like:
# - /var/opt/mxisd/sign.key
# - /var/local/mxisd/sign.key
# - /var/lib/mxisd/sign.key
path: '%SIGNING_KEYS_PATH%'
################
# Signing keys #
################
# Absolute path for the Identity Server signing key.
# During testing, /var/tmp/mxisd.key is a possible value
#
# For production, recommended location shall be one of the following:
# - /var/opt/mxisd/sign.key
# - /var/local/mxisd/sign.key
# - /var/lib/mxisd/sign.key
#
# The signing key is auto-generated during execution time if not present.
key.path: ''
############################
# Persistence config items #
############################
# This element contains all the configuration item for lookup strategies
lookup:
# Configure the storage backend, usually a DB
# Possible built-in values:
# sqlite SQLite backend, default
#
#storage.backend: 'sqlite'
# Configuration items for recursion-type of lookup
#
# Lookup access are divided into two types:
# - Local
# - Remote
#
# This is similar to DNS lookup and recursion and is therefore prone to the same vulnerabilities.
# By default, only non-public hosts are allowed to perform recursive lookup.
#
# This will also prevent very basic endless loops where host A ask host B, which in turn is configured to ask host A,
# which would then ask host B again, etc.
recursive:
# Enable recursive lookup globally
enabled: true
# Whitelist of CIDR that will trigger a recursive lookup.
# The default list includes all private IPv4 address and the IPv6 loopback.
allowedCidr:
- '127.0.0.0/8'
- '10.0.0.0/8'
- '172.16.0.0/12'
- '192.168.0.0/16'
- '::1/128'
# In case no binding is found, query an application server which implements the single lookup end-point
# to return bridge virtual user that would allow the user to be contacted directly by the said bridge.
#
# If a binding is returned, the application server is not expected to sign the message as it is not meant to be
# reachable from the outside.
# If a signature is provided, it will be discarded/replaced by this IS implementation (to be implemented).
#
# IMPORTANT: This bypass the regular Invite system of the Homeserver. It will be up to the Application Server
# to handle such invite. Also, if the bridged user were to actually join Matrix later, or if a 3PID binding is found
# room rights and history would not be transferred, as it would appear as a regular Matrix user to the Homeserver.
#
# This configuration is only helpful for Application Services that want to overwrite bridging for 3PID that are
# handled by the Homeserver. Do not enable unless the Application Server specifically supports it!
bridge:
# Enable unknown 3PID bridging globally
enabled: false
# Enable unknown 3PID bridging for hosts that are allowed to perform recursive lookups.
# Leaving this setting to true is highly recommended in a standard setup, unless this Identity Server
# is meant to always return a virtual user MXID even for the outside world.
recursiveOnly: true
# This mechanism can handle the following scenarios:
#
# - Single Application Server for all 3PID types: only configure the server value, comment out the rest.
#
# - Specific Application Server for some 3PID types, default server for the rest: configure the server value and
# each specific 3PID type.
#
# - Only specific 3PID types: do not configure the server value or leave it empty/blank, configure each specific
# 3PID type.
# Default application server to use for all 3PID types. Remove config item or leave empty/blank to disable.
server: ''
# Configure each 3PID type with a specific application server. Remove config item or leave empty/blank to disable.
mappings:
email: 'http://localhost:8091'
msisdn: ''
# Path to the SQLite DB file
#
# Examples:
# - /var/opt/mxisd/mxisd.db
# - /var/local/mxisd/mxisd.db
# - /var/lib/mxisd/mxisd.db
#
storage.provider.sqlite.database: '/path/to/mxisd.db'
################
# LDAP Backend #
################
# If you would like to integrate with your AD/Samba/LDAP server,
# see https://github.com/kamax-io/mxisd/blob/master/docs/backends/ldap.md
ldap:
enabled: false
tls: false
host: 'localhost'
port: 389
bindDn: 'CN=Matrix Identity Server,CN=Users,DC=example,DC=org'
bindPassword: 'password'
baseDn: 'CN=Users,DC=example,DC=org'
###############
# SQL Backend #
###############
# If you would like to integrate with a MySQL/MariaDB/PostgreQL/SQLite DB,
# see https://github.com/kamax-io/mxisd/blob/master/docs/backends/sql.md
# How should we resolve the Matrix ID in case of a match using the attribute.
#
# The following type are supported:
# - uid : the attribute only contains the UID part of the Matrix ID. e.g. 'john.doe' in @john.doe:example.org
# - mxid : the attribute contains the full Matrix ID - e.g. '@john.doe:example.org'
type: 'uid'
# The attribute containing the binding itself. This value will be used differently depending on the type.
#
# /!\ This should match the synapse LDAP Authenticator 'uid' configuration /!\
#
# Typical values:
# - For type 'uid': 'userPrincipalName' or 'uid' or 'saMAccountName'
# - For type 'mxid', regardless of the directory type, we recommend using 'pager' as it is a standard attribute and
# is typically not used.
attribute: 'userPrincipalName'
# Configure each 3PID type with a dedicated query.
mappings:
email: "(|(mailPrimaryAddress=%3pid)(mail=%3pid)(otherMailbox=%3pid))"
# Phone numbers query.
#
# Phone numbers use the MSISDN format: https://en.wikipedia.org/wiki/MSISDN
# This format does not include international prefix (+ or 00) and therefore has to be put in the query.
# Adapt this to your needs for each attribute.
msisdn: "(|(telephoneNumber=+%3pid)(mobile=+%3pid)(homePhone=+%3pid)(otherTelephone=+%3pid)(otherMobile=+%3pid)(otherHomePhone=+%3pid))"
################
# REST Backend #
################
# If you would like to integrate with an existing web service/webapp,
# see https://github.com/kamax-io/mxisd/blob/master/docs/backends/rest.md
#################################################
# Notifications for invites/addition to profile #
#################################################
# If you would like to change the content,
# see https://github.com/kamax-io/mxisd/blob/master/docs/threepids/notifications/template-generator.md
#
#### E-mail invite sender
#
# SMTP host
threepid.medium.email.connectors.smtp.host: "smtp.example.org"
forward:
# SMTP port
threepid.medium.email.connectors.smtp.port: 587
# List of forwarders to use to try to match a 3PID.
#
# Each server will be tried in the given order, going to the next if no binding was found or an error occurred.
# These are the current root Identity Servers of the Matrix network.
servers:
- "https://matrix.org"
- "https://vector.im"
# TLS mode for the connection.
#
# Possible values:
# 0 Disable TLS entirely
# 1 Enable TLS if supported by server (default)
# 2 Force TLS and fail if not available
#
#threepid.medium.email.connectors.smtp.tls: 1
# Login for SMTP
threepid.medium.email.connectors.smtp.login: "matrix-identity@example.org"
# Password for the account
threepid.medium.email.connectors.smtp.password: "ThePassword"
# The e-mail to send as.
threepid.medium.email.identity.from: "matrix-identity@example.org"

View File

@@ -1,5 +1,3 @@
import java.util.regex.Pattern
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
@@ -20,7 +18,9 @@ import java.util.regex.Pattern
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
apply plugin: 'groovy'
import java.util.regex.Pattern
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
def confFileName = "application.example.yaml"
@@ -40,11 +40,14 @@ def debBuildConfPath = "${debBuildBasePath}${debConfPath}"
def debBuildDataPath = "${debBuildBasePath}${debDataPath}"
def debBuildSystemdPath = "${debBuildBasePath}${debSystemdPath}"
def dockerImageName = "kamax/mxisd"
def dockerImageTag = "${dockerImageName}:${gitVersion()}"
String gitVersion() {
def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?")
ByteArrayOutputStream out = new ByteArrayOutputStream()
exec {
commandLine = [ 'git', 'describe', '--always', '--dirty' ]
commandLine = ['git', 'describe', '--tags', '--always', '--dirty']
standardOutput = out
}
def v = out.toString().replace(System.lineSeparator(), '')
@@ -62,24 +65,29 @@ buildscript {
}
repositories {
maven { url "https://kamax.io/maven/releases/" }
maven { url "https://kamax.io/maven/snapshots/" }
mavenCentral()
}
dependencies {
// We are a groovy project
compile 'org.codehaus.groovy:groovy-all:2.4.7'
// Easy file management
compile 'commons-io:commons-io:2.5'
// Spring Boot - standalone app
compile 'org.springframework.boot:spring-boot-starter-web:1.5.3.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-web:1.5.10.RELEASE'
// Thymeleaf for HTML templates
compile "org.springframework.boot:spring-boot-starter-thymeleaf:1.5.10.RELEASE"
// Matrix Java SDK
compile 'io.kamax:matrix-java-sdk:0.0.11'
// ed25519 handling
compile 'net.i2p.crypto:eddsa:0.1.0'
// LDAP connector
compile 'org.apache.directory.api:api-all:1.0.0-RC2'
compile 'org.apache.directory.api:api-all:1.0.0'
// DNS lookups
compile 'dnsjava:dnsjava:2.1.8'
@@ -87,13 +95,42 @@ dependencies {
// HTTP connections
compile 'org.apache.httpcomponents:httpclient:4.5.3'
// JSON
compile 'com.google.code.gson:gson:2.8.1'
// Phone numbers validation
compile 'com.googlecode.libphonenumber:libphonenumber:8.7.1'
// E-mail sending
compile 'com.sun.mail:javax.mail:1.5.6'
compile 'javax.mail:javax.mail-api:1.5.6'
// Google Client APIs
compile 'com.google.api-client:google-api-client:1.23.0'
// Google Firebase Authentication backend
compile 'com.google.firebase:firebase-admin:5.3.0'
// ORMLite
compile 'com.j256.ormlite:ormlite-jdbc:5.0'
// Connection Pool
compile 'com.mchange:c3p0:0.9.5.2'
// SQLite
compile 'org.xerial:sqlite-jdbc:3.20.0'
// PostgreSQL
compile 'org.postgresql:postgresql:42.1.4'
// MariaDB/MySQL
compile 'org.mariadb.jdbc:mariadb-java-client:2.1.2'
// Twilio SDK for SMS
compile 'com.twilio.sdk:twilio:7.14.5'
// SendGrid SDK to send emails from GCE
compile 'com.sendgrid:sendgrid-java:2.2.2'
testCompile 'junit:junit:4.12'
testCompile 'com.github.tomakehurst:wiremock:2.8.0'
}
springBoot {
@@ -104,6 +141,17 @@ springBoot {
]
}
processResources {
doLast {
copy {
from('build/resources/main/application.yaml') {
rename 'application.yaml', 'mxisd.yaml'
}
into 'build/resources/main'
}
}
}
task buildDeb(dependsOn: build) {
doLast {
def v = gitVersion()
@@ -133,10 +181,16 @@ task buildDeb(dependsOn: build) {
into debBuildConfPath
}
ant.replace(
ant.replaceregexp(
file: "${debBuildConfPath}/${debConfFileName}",
token: '%SIGNING_KEYS_PATH%',
value: "${debDataPath}/signing.key"
match: "key.path:(.*)",
replace: "key.path: '${debDataPath}/signing.key'"
)
ant.replaceregexp(
file: "${debBuildConfPath}/${debConfFileName}",
match: "storage.provider.sqlite.database:(.*)",
replace: "storage.provider.sqlite.database: '${debDataPath}/mxisd.db'"
)
copy {
@@ -182,3 +236,23 @@ task buildDeb(dependsOn: build) {
}
}
}
task dockerBuild(type: Exec, dependsOn: build) {
commandLine 'docker', 'build', '-t', dockerImageTag, project.rootDir
doLast {
exec {
commandLine 'docker', 'tag', dockerImageTag, "${dockerImageName}:latest-dev"
}
}
}
task dockerPush(type: Exec) {
commandLine 'docker', 'push', dockerImageTag
doLast {
exec {
commandLine 'docker', 'push', "${dockerImageName}:latest-dev"
}
}
}

22
docs/README.md Normal file
View File

@@ -0,0 +1,22 @@
# Table of Contents
- [Build from sources](build.md) (Optional)
- Installation
- [Debian package](install/debian.md)
- [ArchLinux](install/archlinux.md)
- [Docker](install/docker.md)
- [From source](install/source.md)
- [Architecture overview](architecture.md)
- [Configuration](configure.md)
- Features
- [Authentication](features/authentication.md)
- [Directory search](features/directory.md)
- [Identity](features/identity.md)
- [Federation](features/federation.md)
- [Bridge integration](features/bridge-integration.md)
- [Identity Stores](stores/README.md)
- Notifications
- Handlers
- [Basic](threepids/notification/basic-handler.md)
- [SendGrid](threepids/notification/sendgrid-handler.md)
- [Sessions](threepids/session/session.md)
- [Views](threepids/session/session-views.md)

1
docs/_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

43
docs/architecture.md Normal file
View File

@@ -0,0 +1,43 @@
# Architecture
## Overview
### Basic setup with default settings
```
Client
|
TCP 443
| +---------------------+ +---------------------------+
+-> | Reverse proxy | | Homeserver |
| | TCP 8008 | |
| /_matrix/* -------------------> | - 3PID invite from client |
| | | | |
| /_matrix/identity/ | | | |
+--|------------------+ +---|-----------------------+
| |
+<---------------------------------<+
|
| +-------------------+
TCP 8090 +-> | mxisd |
| |
| - Profile's 3PIDs >----+
| - 3PID Invites | | +--------------------------+
+-|-----------------+ +>----------> | Central Identity service |
| | TCP 443 | Matrix.org / Vector.im |
| | +--------------------------+
+>-------------------->+
|
TCP 443
| +------------------------+
| | Remote Federated |
| | mxisd servers |
| | |
+--> - 3PID Invites |
+------------------------+
```
### With Authentication
See the [dedicated document](features/authentication.md).
### With Directory
See the [dedicated document](features/directory.md).
### With Federation
See the [dedicated document](features/federation.md).

73
docs/build.md Normal file
View File

@@ -0,0 +1,73 @@
# From source
- [Binaries](#binaries)
- [Requirements](#requirements)
- [Build](#build)
- [Debian package](#debian-package)
- [Docker image](#docker-image)
- [Next steps](#next-steps)
## Binaries
### Requirements
- JDK 1.8
### Build
```bash
git clone https://github.com/kamax-io/mxisd.git
cd mxisd
./gradlew build
```
Create a new configuration file by coping `application.example.yaml` to `application.yaml` and edit to your needs.
For advanced configuration, see the [Configure section](configure.md).
**NOTE**: `application.yaml` is also called `mxisd.yaml` in some specific installations.
Start the server in foreground to validate the build and configuration:
```bash
java -jar build/libs/mxisd.jar
```
Ensure the signing key is available:
```bash
$ curl 'http://localhost:8090/_matrix/identity/api/v1/pubkey/ed25519:0'
{"public_key":"..."}
```
Test basic recursive lookup (requires Internet connection with access to TCP 443):
```bash
$ curl 'http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=mxisd-federation-test@kamax.io'
{"address":"mxisd-federation-test@kamax.io","medium":"email","mxid":"@mxisd-lookup-test:kamax.io",...}
```
If you enabled LDAP, you can also validate your config with a similar request after replacing the `address` value with
something present within your LDAP
```bash
curl 'http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=john.doe@example.org'
```
If you plan on testing the integration with a homeserver, you will need to run an HTTPS reverse proxy in front of it
as the reference Home Server implementation [synapse](https://github.com/matrix-org/synapse) requires a HTTPS connection
to an ID server.
Next step: [Install your compiled binaries](install/source.md)
## Debian package
Requirements:
- fakeroot
- dpkg-deb
[Build mxisd](#build) then:
```bash
./gradlew buildDeb
```
You will find the debian package in `build/dist`.
Then follow the instruction in the [Debian package](install/debian.md) document.
## Docker image
[Build mxisd](#build) then:
```bash
./gradlew dockerBuild
```
Then follow the instructions in the [Docker install](install/docker.md#configure) document.
## Next steps
- [Integrate with your infrastructure](getting-started.md#integrate)

43
docs/concepts.md Normal file
View File

@@ -0,0 +1,43 @@
# Concepts
- [Matrix](#matrix)
- [mxisd](#mxisd)
## Matrix
The following concepts are part of the Matrix ecosystem and specification.
### 3PID
`3PID` stands for Third-Party Identifier.
It is also commonly written:
- `3pid`
- `tpid`
A 3PID is a globally unique canonical identifier which is made of:
- Medium, which describes what network it belongs to (Email, Phone, Twitter, Discord, etc.)
- Address, the actual value people typically use on a daily basis.
mxisd core mission is to map those identifiers to Matrix User IDs.
### Homeserver
Where a user **account and data** are stored.
### Identity server
An Identity server:
- Does lookup of 3PIDs to User Matrix IDs.
- Does validate 3PIDs ownership, typically by sending a code that the user has to enter in an application/on a website.
- Does send notifications about room invites where no Matrix User ID could be found for the invitee.
An Identity server:
- **DOES NOT** store user accounts.
- **DOES NOT** store user data.
- **DOES NOT** allow migration of user account and/or data between homeservers.
### 3PID session
The fact to validate a 3PID (email, phone number, etc.) via the introduction of a token which was sent to the 3PID address.
## mxisd
The following concepts are specific to mxisd.
### Identity store
Where your user accounts and 3PID mappings are stored.
mxisd itself **DOES NOT STORE** user accounts or 3PID mappings.

102
docs/configure.md Normal file
View File

@@ -0,0 +1,102 @@
# Configuration
- [Concepts](#concepts)
- [Syntax](#syntax)
- [Variables](#variables)
- [Matrix](#matrix)
- [Server](#server)
- [Storage](#storage)
- [Identity stores](#identity-stores)
- [3PID Validation sessions](#3pid-validation-sessions)
- [Notifications](#notifications)
## Concepts
### Syntax
The configuration file is [YAML](http://yaml.org/) based, allowing two types of syntax.
Properties-like:
```yaml
my.config.item: 'value'
```
Object-like:
```yaml
my:
config:
item: 'value'
```
These can also be combined within the same file.
Both syntax will be used interchangeably in these documents.
### Variables
It is possible to copy the value of a configuration item into another using the syntax `${config.key.item}`.
Example that will copy the value of `matrix.domain` into `server.name`:
```yaml
matrix:
domain: 'example.org'
server:
name: '${matrix.domain}'
```
**WARNING:** mxisd might overwrite/adapt some values during launch. Those changes will not be reflected into copied keys.
## Matrix
`matrix.domain`
Matrix domain name, same as the Homeserver, used to build appropriate Matrix IDs |
---
`matrix.identity.servers`
Namespace to create arbitrary list of Identity servers, usable in other parts of the configuration |
Example:
```yaml
matrix.identity.servers:
myOtherServers:
- 'https://other1.example.org'
- 'https://other2.example.org'
```
Create a list under the label `root` containing a single Identity server, `https://matrix.org`
## Server
- `server.name`: Public hostname of mxisd, if different from the Matrix domain.
- `server.port`: HTTP port to listen on (unencrypted)
- `server.publicUrl`: Defaults to `https://${server.name}`
## Storage
### SQLite
`storage.provider.sqlite.database`: Absolute location of the SQLite database
## Identity stores
See the [Identity stores](stores/README.md) for specific configuration
## 3PID Validation sessions
See the dedicated documents:
- [Flow](threepids/session/session.md)
- [Branding](threepids/session/session-views.md)
## Notifications
- `notification.handler.<3PID medium>`: Handler to use for the given 3PID medium. Repeatable.
Example:
```yaml
notification.handler.email: 'sendgrid'
notification.handler.msisdn: 'raw'
```
- Emails notifications would use the `sendgrid` handler, which define its own configuration under `notification.handlers.sendgrid`
- Phone notification would use the `raw` handler, basic default built-in handler of mxisd
### Handlers
- `notification.handers.<handler ID>`: Handler-specific configuration for the given handler ID. Repeatable.
Example:
```yaml
notification.handlers.raw: ...
notification.handlers.sendgrid: ...
```
Built-in:
- [Raw](threepids/notification/basic-handler.md)
- [SendGrid](threepids/notification/sendgrid-handler.md)

89
docs/faq.md Normal file
View File

@@ -0,0 +1,89 @@
# Frequently Asked Questions
### This is all very complicated and I'm getting confused with all the words, concepts and diagrams - Help!
Matrix is still a very young protocol and there are a whole lot of rough edges.
Identity in Matrix is one of the most difficult topic, mainly as it has not received much love in the past years.
We have tried our best to put together documentation that requires almost no knowledge of Matrix inner workings to get a
first basic setup running which relies on you reading the documentation in the right order:
- [The Concepts](concepts.md) in few words.
- [Getting Started](getting-started.md) step-by-step to a minimal working install.
- [Identity stores](stores/README.md) you wish to fetch data from.
- [Features](features) you are interested in that will use your Identity store(s) data.
**IMPORTANT**: Be aware that mxisd tries to fit within the current protocol and existing products and basic understanding
of the Matrix protocol is required for some advanced features.
If all fails, come over to [the project room](https://matrix.to/#/#mxisd:kamax.io) and we'll do our best to get you
started and answer questions you might have.
### Do I need to use mxisd if I run a Homeserver?
No, but it is strongly recommended, even if you don't use any Identity store or integration.
In its default configuration, mxisd will talk to the central Matrix Identity servers and use other federated public
servers when performing queries, giving you access to at least the same information as if you were not running it.
It will also give your users a choice to make their 3PIDs available publicly, ensuring they are made aware of the
privacy consequences, which is not the case with the central Matrix.org servers.
So mxisd is like your gatekeeper and guardian angel. It does not change what you already know, just adds some nice
simple features on top of it.
### I'm not sure I understand what an "Identity server" is supposed to be or do...
The current Identity service API is more a placeholder, as the Matrix devs did not have time so far to really work on
what they want to do with that part of the ecosystem. Therefore, "Identity" is currently a misleading word and concept.
Given the scope of the current Identity Service API, it would be best called "Invitation service".
Because the current scope is so limited and no integration is done with the Homeserver, there was a big lack of features
for groups/corporations/organisation. This is where mxisd comes in.
mxisd implements the Identity Service API and also a set of features which are expected by regular users, truly living
up to its "Identity server" name.
### Can I migrate my existing account on another Matrix server with mxisd?
No.
Accounts cannot currently migrate/move from one server to another.
See a [brief explanation document](concepts.md) about Matrix and mxisd concepts and vocabulary.
### I already use the synapse LDAP3 auth provider. Why should I care about mxisd?
The [synapse LDAP3 auth provider](https://github.com/matrix-org/matrix-synapse-ldap3) is not longer maintained and
only handles on specific flow: validate credentials at login.
It does not:
- Auto-provision user profiles
- Integrate with Identity management
- Integrate with Directory searches
mxisd is a replacement and enhancement of it, offering coherent results in all areas, which the LDAP3 auth provider
does not.
### Sydent is the official Identity server implementation of the Matrix team. Why not use that?
You can, but [sydent](https://github.com/matrix-org/sydent):
- [should not be used and/or self-hosted](https://github.com/matrix-org/sydent/issues/22)
- is not meant to be linked to a specific Homeserver / domain
- cannot handle federation or proxy lookups, effectively isolating your users from the rest of the network
- forces you to duplicate all your identity data, so people can be found by 3PIDs
- forces users to enter all their emails and phone numbers manually in their profile
So really, you should go with mxisd.
### Will I loose access to the central Matrix.org/Vector.im Identity data if I use mxisd?
No.
In its default configuration, mxisd act as a proxy to Matrix.org/Vector.im. You will have access to the same data and
behaviour than if you were using them directly. There is no downside in using mxisd with the default configuration.
mxisd can also be configured not to talk to the central Identity servers if you wish.
### So mxisd is just a big hack! I don't want to use non-official features!
mxisd primary concern is to always be compatible with the Matrix ecosystem and the Identity service API.
Whenever the API will be updated and/or enhanced, mxisd will follow, remaining 100% compatible with the ecosystem.
Therefore, using mxisd is a safe choice. It will be like using the central Matrix.org Identity servers, yet not closing
the door to a growing list of enhancements and integrations.
### Should I use mxisd if I don't host my own Homeserver?
No.
It is possible, but it is not supported and the scope of features will be extremely limited.
Please consider hosting your own Homeserver and using mxisd alongside it.

View File

@@ -0,0 +1,154 @@
# Authentication
- [Description](#description)
- [Basic](#basic)
- [Overview](#overview)
- [synapse](#synapse)
- [mxisd](#mxisd)
- [Validate](#validate)
- [Next steps](#next-steps)
- [Profile auto-fil](#profile-auto-fill)
- [Advanced](#advanced)
- [Overview](#overview-1)
- [Requirements](#requirements)
- [Configuration](#configuration)
- [Reverse Proxy](#reverse-proxy)
- [Apache2](#apache2)
- [DNS Overwrite](#dns-overwrite)
## Description
Authentication is an enhanced feature of mxisd to ensure coherent and centralized identity management.
It allows to use Identity stores configured in mxisd to authenticate users on your Homeserver.
Authentication is divided into two parts:
- [Basic](#basic): authenticate with a regular username.
- [Advanced](#advanced): same as basic with extra ability to authenticate using a 3PID.
## Basic
Authentication by username is possible by linking synapse and mxisd together using a specific module for synapse, also
known as password provider.
### Overview
An overview of the Basic Authentication process:
```
Identity stores
Client +------+
| +-------------------------+ +--> | LDAP |
| +---------------+ /_matrix/identity | mxisd | | +------+
+-> | Reverse proxy | >------------------+ | | |
+--|------------+ | | | | +--------+
| +-----> Check ID stores >------+--> | SQL DB |
Login request | | | | +--------+
| | | | | |
| +--------------------------+ | +-----|-------------------+ +--> ...
+-> | Homeserver | | |
| | | |
| - Validate credentials >----+ |
| Using REST auth module | |
| | |
| - Auto-provision <-------------------<+
| user profiles | If valid credentials and supported by Identity store(s)
+--------------------------+
```
Performed on [synapse with REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth/blob/master/README.md)
### Synapse
- Install the [password provider](https://github.com/kamax-io/matrix-synapse-rest-auth)
- Edit your **synapse** configuration:
- As described by the auth module documentation
- Set `endpoint` to `http://mxisdAddress:8090` - Replace `mxisdAddress` by an IP/host name that provides a direct
connection to mxisd.
This **MUST NOT** be a public address, and SHOULD NOT go through a reverse proxy.
- Restart synapse
### mxisd
- Configure and enable at least one [Identity store](../stores/README.md)
- Restart mxisd
### Validate
Login on the Homeserver using credentials present in one of your Identity stores.
## Next steps
### Profile auto-fill
Auto-filling user profile depends on its support by your configured Identity stores.
See your Identity store [documentation](../stores/README.md) on how to enable the feature.
## Advanced
The Authentication feature allows users to login to their Homeserver by using their 3PIDs in a configured Identity store.
### Overview
This is performed by intercepting the Homeserver endpoint `/_matrix/client/r0/login` as depicted below:
```
+----------------------------+
| Reverse Proxy |
| |
| | Step 1 +---------------------------+ Step 2
| | | |
Client+---->| /_matrix/client/r0/login +---------------->| | Look up address +---------+
| ^ | | mxisd - Identity server +----------------->| Backend |
| | | | | +---------+
| /_matrix/* +--+ +---------------------+ |
| | | +---------------+-----------+
| | | Step 4 |
| | | | Step 3
+---------------|------------+ |
| | /_matrix/client/r0/login
| +--------------+ |
| | | |
+---------------------->| Homeserver |<----+
| |
+--------------+
```
Steps of user authentication using a 3PID:
1. The intercepted login request is directly sent to mxisd instead of the Homeserver.
2. Identity stores are queried for a matching user identity in order to modify the request to use the user name.
3. The Homeserver, from which the request was intercepted, is queried using the request at previous step.
Its address is resolved using the DNS Overwrite feature to reach its internal address on a non-encrypted port.
4. The response from the Homeserver is sent back to the client, believing it was the HS which directly answered.
### Requirements
- [Basic Authentication configured and working](#basic)
- Reverse proxy setup
- Homeserver
- Compatible [Identity store](../stores/README.md)
### Configuration
#### Reverse Proxy
##### Apache2
The specific configuration to put under the relevant `VirtualHost`:
```apache
ProxyPass /_matrix/client/r0/login http://localhost:8090/_matrix/client/r0/login
```
`ProxyPreserveHost` or equivalent **must** be enabled to detect to which Homeserver mxisd should talk to when building results.
Your VirtualHost should now look similar to:
```apache
<VirtualHost *:443>
ServerName example.org
...
ProxyPreserveHost on
ProxyPass /_matrix/client/r0/login http://localhost:8090/_matrix/client/r0/login
ProxyPass /_matrix/identity http://localhost:8090/_matrix/identity
ProxyPass /_matrix http://localhost:8008/_matrix
</VirtualHost>
```
#### DNS Overwrite
Just like you need to configure a reverse proxy to send client requests to mxisd, you also need to configure mxisd with
the internal IP of the Homeserver so it can talk to it directly to integrate its directory search.
To do so, put the following configuration in your mxisd configuration:
```yaml
dns.overwrite.homeserver.client:
- name: 'example.org'
value: 'http://localhost:8008'
```
`name` must be the hostname of the URL that clients use when connecting to the Homeserver.
In case the hostname is the same as your Matrix domain, you can use `${matrix.domain}` to auto-populate the `value`
using the `matrix.domain` configuration option and avoid duplicating it.
`value` is the base internal URL of the Homeserver, without any `/_matrix/..` or trailing `/`.

View File

@@ -0,0 +1,28 @@
# Bridge Integration
To help natural bridge integration into the regular usage of a Matrix client, mxisd provides a way for bridge to reply
to 3PID queries if no mapping was found, allowing seamless bridging to a network.
This is performed by implementing a specific endpoint on the bridge to map a 3PID lookup to a virtual user.
**NOTE**: This document is incomplete and might be misleading. In doubt, come in our Matrix room.
You can also look at our [Email Bridge README](https://github.com/kamax-io/matrix-appservice-email#mxisd) for an example
of working configuration.
## Configuration
```yaml
lookup.recursive.bridge.enabled: <boolean>
lookup.recursive.bridge.recursiveOnly: <boolean>
lookup.recursive.bridge.server: <URL to the bridge endpoint for all 3PID medium>
lookup.recursive.bridge.mappings:
<3PID MEDIUM HERE>: <URL to dedicated bridge for that medium>
```
## Integration
Implement a simplified version of the [Identity service single lookup endpoint](https://kamax.io/matrix/api/identity_service/unstable.html#get-matrix-identity-api-v1-lookup)
with only the following parameters needed:
- `address`
- `medium`
- `mxid`
Or an empty object if no resolution exists or desired.

142
docs/features/directory.md Normal file
View File

@@ -0,0 +1,142 @@
# User Directory
- [Description](#description)
- [Overview](#overview)
- [Requirements](#requirements)
- [Configuration](#configuration)
- [Reverse Proxy](#reverse-proxy)
- [Apache2](#apache2)
- [nginx](#nginx)
- [DNS Overwrite](#dns-overwrite)
- [Next steps](#next-steps)
## Description
This feature allows you to search for existing and/or potential users that are already present in your Identity backend
or that already share a room with you on the Homeserver.
Without any integration, synapse:
- Only search within the users **already** known to you or in public rooms
- Only search on the Display Name and the Matrix ID
By enabling this feature, you can by default:
- Search on Matrix ID, Display name and 3PIDs (Email, phone numbers) of any users already in your configured backend
- Search for users which you are not in contact with yet. Super useful for corporations who want to give Matrix access
internally, so users can just find themselves **prior** to having any common room(s)
- Add extra attributes of your backend to extend the search
- Include your homeserver search results to those found by mxisd
By integrating mxisd, you get the default behaviour and a bunch of extras, ensuring your users will always find each other.
## Overview
This is performed by intercepting the Homeserver endpoint `/_matrix/client/r0/user_directory/search` like so:
```
+----------------------------------------------+
Client --> | Reverse proxy Step 2
| Step 1 +-------------------------+
| /_matrix/client/r0/user_directory/search ----------> | | Search in +---------+
| /\ | mxisd - Identity server | -----------> | Backend |
| /_matrix/* \----------------------------- | | all users +---------+
| | Step 4: Send back merged results +-------------------------+
+ | |
| Step 3
| |
| +------------+ Search in known users
\--> | Homeserver | <----------------------------------------/
+------------+ /_matrix/client/r0/user_directory/search
```
Steps:
1. The intercepted request is directly sent to mxisd instead of the Homeserver.
2. Identity stores are queried for any match on the search value sent by the client.
3. The Homeserver, from which the request was intercepted, is queried using the same request as the client.
Its address is resolved using the DNS Overwrite feature to reach its internal address on a non-encrypted port.
4. Results from Identity stores and the Homeserver are merged together and sent back to the client, believing it was the HS
which directly answered the request.
## Requirements
- Reverse proxy setup, which you should already have in place if you use mxisd
- At least one compatible [Identity store](../stores/README.md) enabled
## Configuration
### Reverse Proxy
#### Apache2
The specific configuration to put under the relevant `VirtualHost`:
```apache
ProxyPass /_matrix/client/r0/user_directory/ http://0.0.0.0:8090/_matrix/client/r0/user_directory/
```
`ProxyPreserveHost` or equivalent must be enabled to detect to which Homeserver mxisd should talk to when building
results.
Your `VirtualHost` should now look like this:
```apache
<VirtualHost *:443>
ServerName example.org
...
ProxyPreserveHost on
ProxyPass /_matrix/client/r0/user_directory/ http://localhost:8090/_matrix/client/r0/user_directory/
ProxyPass /_matrix/identity http://localhost:8090/_matrix/identity
ProxyPass /_matrix http://localhost:8008/_matrix
</VirtualHost>
```
#### nginx
The specific configuration to add under your `server` section is:
```nginx
location /_matrix/client/r0/user_directory {
proxy_pass http://0.0.0.0:8090/_matrix/client/r0/user_directory;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
```
Your `server` section should now look like this:
```nginx
server {
listen 443 ssl;
server_name example.org;
...
location /_matrix/client/r0/user_directory {
proxy_pass http://localhost:8090/_matrix/client/r0/user_directory;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
location /_matrix/identity {
proxy_pass http://localhost:8090/_matrix/identity;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
location /_matrix {
proxy_pass http://localhost:8008/_matrix;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
```
### DNS Overwrite
Just like you need to configure a reverse proxy to send client requests to mxisd, you also need to configure mxisd with
the internal IP of the Homeserver so it can talk to it directly to integrate its directory search.
To do so, use the following configuration:
```yaml
dns.overwrite.homeserver.client:
- name: 'example.org'
value: 'http://localhost:8008'
```
`name` must be the hostname of the URL that clients use when connecting to the Homeserver.
In case the hostname is the same as your Matrix domain, you can use `${matrix.domain}` to auto-populate the value using
the `matrix.domain` configuration option and avoid duplicating it.
`value` is the base internal URL of the Homeserver, without any `/_matrix/..` or trailing `/`.
## Next steps
### Homeserver results
You can configure if the Homeserver should be queried at all when doing a directory search.
To disable Homeserver results, set the following in mxisd configuration file:
```yaml
directory.exclude.homeserever: true
```

View File

@@ -0,0 +1,51 @@
# Federation
Federation is the process by which domain owners can make compatible 3PIDs mapping auto-discoverable by looking for another
Federated Identity server using the DNS domain part of the 3PID.
Emails are the best candidate for this kind of resolution which are DNS domain based already.
On the other hand, Phone numbers cannot be resolved this way.
For 3PIDs which are not compatible with the DNS system, mxisd will talk to the central Identity server of matrix.org by
default.
Outbound federation is enabled by default while inbound federation is opt-in and require a specific DNS record.
## Overview
```
+-------------------+ +-------------> +----------+
| mxisd | | | Backends |
| | | +------> +----------+
| | | |
| Invites / Lookups | | |
Federated | +--------+ | | | +-------------------+
Identity ---->| Remote |>-----------+ +------> | Remote Federated |
Server | +--------+ | | | mxisd servers |
| | | +-------------------+
| +--------+ | |
Homeserver --->| Local |>------------------+
and clients | +--------+ | | +--------------------------+
+-------------------+ +------> | Central Identity service |
| Matrix.org / Vector.im |
+--------------------------+
```
## Inbound
If you would like to be reachable for lookups over federation, create the following DNS SRV entry and replace
`matrix.example.com` by your Identity server public hostname:
```
_matrix-identity._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com.
```
The port must be HTTPS capable which is what you get in a regular setup with a reverse proxy from 443 to TCP 8090 of mxisd.
## Outbound
If you would like to disable outbound federation and isolate your identity server from the rest of the Matrix network,
use the following mxisd configuration options:
```yaml
lookup.recursive.enabled: false
invite.resolution.recursive: false
session.policy.validation.forLocal.toRemote.enabled: false
session.policy.validation.forRemote.toRemote.enabled: false
```
There is currently no way to selectively disable federation towards specific servers, but this feature is planned.

21
docs/features/identity.md Normal file
View File

@@ -0,0 +1,21 @@
# Identity
**WARNING**: This document is incomplete and can be missleading.
Implementation of the [Unofficial Matrix Identity Service API](https://kamax.io/matrix/api/identity_service/unstable.html).
## Room Invitations
Resolution can be customized using the following configuration:
`invite.resolution.recursive`
- Default value: `true`
- Description: Control if the pending invite resolution should be done recursively or not.
**DANGER ZONE:** This setting has the potential to create "an isolated island", which can have unexpected side effects
and break invites in rooms. This will most likely not have the effect you think it does. Only change the value if you
understand the consequences.
`invite.resolution.timer`
- Default value: `1`
- Description: How often, in minutes, mxisd should try to resolve pending invites.
## 3PID addition to user profile
See the [3PID session documents](../threepids/session)

9
docs/features/profile.md Normal file
View File

@@ -0,0 +1,9 @@
# Profile enhancement
**WARNING**: Alpha feature not officially supported. Do not use.
## Configuration
### Reverse proxy
#### Apache
```apache
ProxyPassMatch "^/_matrix/client/r0/profile/([^/]+)$" "http://127.0.0.1:8090/_matrix/client/r0/profile/$1"
```

135
docs/getting-started.md Normal file
View File

@@ -0,0 +1,135 @@
# Getting started
1. [Preparation](#preparation)
2. [Install](#install)
3. [Configure](#configure)
4. [Integrate](#integrate)
5. [Validate](#validate)
6. [Next steps](#next-steps)
Following these quick start instructions, you will have a basic setup that can perform recursive/federated lookups and
talk to the central Matrix.org Identity server.
This will be a good ground work for further integration with features and your existing Identity stores.
## Preparation
You will need:
- Homeserver
- Reverse proxy with regular TLS/SSL certificate (Let's encrypt) for your mxisd domain
As synapse requires an HTTPS connection when talking to an Identity service, **a reverse proxy is required** as mxisd does
not support HTTPS listener at this time.
For maximum integration, it is best to have your Homeserver and mxisd reachable via the same hostname.
Be aware of a [NAT/Reverse proxy gotcha](https://github.com/kamax-io/mxisd/wiki/Gotchas#nating) if you use the same
hostname.
The following Quick Start guide assumes you will host the Homeserver and mxisd under the same hostname.
If you would like a high-level view of the infrastructure and how each feature is integrated, see the
[dedicated document](architecture.md)
## Install
Install via:
- [Debian package](install/debian.md)
- [Docker image](install/docker.md)
- [Sources](build.md)
See the [Latest release](https://github.com/kamax-io/mxisd/releases/latest) for links to each.
## Configure
**NOTE**: please view the install instruction for your platform, as this step might be optional or already handled for you.
Create/edit a minimal configuration (see installer doc for the location):
```yaml
matrix.domain: 'example.org'
key.path: '/path/to/signing.key.file'
storage.provider.sqlite.database: '/path/to/mxisd.db'
```
- `matrix.domain` should be set to your Homeserver domain (`server_name` in synapse configuration)
- `key.path` will store the signing keys, which must be kept safe! If the file does not exist, keys will be generated for you.
- `storage.provider.sqlite.database` is the location of the SQLite Database file which will hold state (invites, etc.)
If your HS/mxisd hostname is not the same as your Matrix domain, configure `server.name`.
Complete configuration guide is available [here](configure.md).
## Integrate
For an overview of a typical mxisd infrastructure, see the [dedicated document](architecture.md)
### Reverse proxy
#### Apache2
In the `VirtualHost` section handling the domain with SSL, add the following and replace `0.0.0.0` by the internal
hostname/IP pointing to mxisd.
**This line MUST be present before the one for the homeserver!**
```apache
ProxyPass /_matrix/identity http://0.0.0.0:8090/_matrix/identity
```
Typical configuration would look like:
```apache
<VirtualHost *:443>
ServerName example.org
...
ProxyPreserveHost on
ProxyPass /_matrix/identity http://localhost:8090/_matrix/identity
ProxyPass /_matrix http://localhost:8008/_matrix
</VirtualHost>
```
#### nginx
In the `server` section handling the domain with SSL, add the following and replace `0.0.0.0` with the internal
hostname/IP pointing to mxisd.
**This line MUST be present before the one for the homeserver!**
```nginx
location /_matrix/identity {
proxy_pass http://0.0.0.0:8090/_matrix/identity;
}
```
Typical configuration would look like:
```nginx
server {
listen 443 ssl;
server_name example.org;
...
location /_matrix/identity {
proxy_pass http://localhost:8090/_matrix/identity;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
location /_matrix {
proxy_pass http://localhost:8008/_matrix;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
}
}
```
### Synapse
Add your mxisd domain into the `homeserver.yaml` at `trusted_third_party_id_servers` and restart synapse.
In a typical configuration, you would end up with something similar to:
```yaml
trusted_third_party_id_servers:
- example.org
```
It is recommended to remove `matrix.org` and `vector.im` (or any other default entry) from your configuration so only
your own Identity server is authoritative for your HS.
## Validate
Log in using your Matrix client and set `https://example.org` as your Identity server URL, replacing `example.org` by
the relevant hostname which you configured in your reverse proxy.
Invite `mxisd-federation-test@kamax.io` to a room, which should be turned into a Matrix invite to `@mxisd-lookup-test:kamax.io`.
At this point, the test user will join the room, send a congratulation message and leave.
**NOTE:** You might not see a suggestion for the e-mail address, which is normal. Still proceed with the invite.
If it worked, it means you are up and running and can enjoy mxisd in its basic mode! Congratulations!
If it did not work, [get in touch](../README.md#support) and we'll do our best to get you started.
## Next steps
Once your mxisd server is up and running, there are several ways you can enhance and integrate further with your
infrastructure:
- [Enable extra features](features/)
- [Use your own Identity stores](stores/README.md)

View File

@@ -0,0 +1,3 @@
# Arch Linux package
An Arch Linux package in the AUR repos is maintained by [r3pek](https://matrix.to/#/@r3pek:r3pek.org), a community member.
See https://aur.archlinux.org/packages/mxisd/

39
docs/install/debian.md Normal file
View File

@@ -0,0 +1,39 @@
# Debian package
## Install
1. Download the [latest release](https://github.com/kamax-io/mxisd/releases/latest)
2. Run:
```bash
dpkg -i /path/to/downloaded/mxisd.deb
```
## Files
| Location | Purpose |
|-------------------------------------|----------------------------------------------|
| `/etc/mxisd` | Configuration directory |
| `/etc/mxisd/mxisd.yaml` | Main configuration file |
| `/etc/systemd/system/mxisd.service` | Systemd configuration file for mxisd service |
| `/usr/lib/mxisd` | Binaries |
| `/var/lib/mxisd` | Data |
| `/var/lib/mxisd/signing.key` | Default location for mxisd signing keys |
## Control
Start mxisd using:
```bash
sudo systemctl start mxisd
```
Stop mxisd using:
```bash
sudo systemctl stop mxisd
```
## Troubleshoot
All logs are sent to `STDOUT` which are saved in `/var/log/syslog` by default.
You can:
- grep & tail using `mxisd`:
```
tail -n 99 -f /var/log/syslog | grep mxisd
```
- use Systemd's journal:
```
journalctl -f -n 99 -u mxisd
```

22
docs/install/docker.md Normal file
View File

@@ -0,0 +1,22 @@
# Docker
## Fetch
Pull the latest stable image:
```bash
docker pull kamax/mxisd
```
## Configure
On first run, simply using `MATRIX_DOMAIN` as an environment variable will create a default config for you.
You can also provide a configuration file named `mxisd.yaml` in the volume mapped to `/etc/mxisd` before starting your
container.
## Run
Use the following command after adapting to your needs:
- The `MATRIX_DOMAIN` environment variable to yours
- The volumes host paths
```bash
docker run --rm -e MATRIX_DOMAIN=example.org -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t kamax/mxisd
```
For more info, including the list of possible tags, see [the public repository](https://hub.docker.com/r/kamax/mxisd/)

38
docs/install/source.md Normal file
View File

@@ -0,0 +1,38 @@
# Install from sources
## Instructions
Follow the [build instructions](../build.md) then:
1. Prepare files and directories:
```bash
# Create a dedicated user
useradd -r mxisd
# Create bin directory
mkdir /opt/mxisd
# Create config directory and set ownership
mkdir -p /etc/opt/mxisd
chown -R mxisd /etc/opt/mxisd
# Create data directory and set ownership
mkdir -p /var/opt/mxisd
chown -R mxisd /var/opt/mxisd
# Copy <repo root>/build/libs/mxisd.jar to bin directory
cp ./build/libs/mxisd.jar /opt/mxisd/
chown mxisd /opt/mxisd/mxisd.jar
chmod a+x /opt/mxisd/mxisd.jar
# Create symlink for easy exec
ln -s /opt/mxisd/mxisd.jar /usr/bin/mxisd
```
2. Copy the sample config file `./application.example.yaml` to `/etc/opt/mxisd/mxisd.yaml`, edit to your needs
3. Copy `src/systemd/mxisd.service` to `/etc/systemd/system/` and edit if needed
4. Enable service for auto-startup
```bash
systemctl enable mxisd
```
5. Start mxisd
```bash
systemctl start mxisd
```

7
docs/stores/README.md Normal file
View File

@@ -0,0 +1,7 @@
# Identity Stores
- [Synapse](synapse.md)
- [LDAP-based](ldap.md)
- [SQL Databases](sql.md)
- [Website / Web service / Web app](rest.md)
- [Google Firebase](firebase.md)
- [Wordpress](wordpress.md)

52
docs/stores/firebase.md Normal file
View File

@@ -0,0 +1,52 @@
# Google Firebase Identity store
https://firebase.google.com/
## Features
| Name | Supported? |
|----------------|------------|
| Authentication | Yes |
| Directory | No |
| Identity | Yes |
## Requirements
This backend requires a suitable Matrix client capable of performing Firebase authentication and passing the following
information:
- Firebase User ID as Matrix username
- Firebase token as Matrix password
If your client is Riot, you will need a custom version.
## Configuration
```yaml
firebase.enabled: <boolean>
```
Enable/disable this identity store.
Example:
```yaml
firebase.enabled: <boolean>
```
---
```yaml
firebase.credentials: <string>
```
Path to the credentials file provided by Google Firebase to use with an external app.
Example:
```yaml
firebase.credentials: '/path/to/firebase/credentials.json'
```
---
```yaml
firebase.database: <string>
```
URL to your Firebase database.
Example:
```yaml
firebase.database: 'https://my-project.firebaseio.com/'
```

120
docs/stores/ldap.md Normal file
View File

@@ -0,0 +1,120 @@
# LDAP Identity store
## Supported products:
- Samba
- Active Directory
- OpenLDAP
- NetIQ eDirectory
For NetIQ, replace all the `ldap` prefix in the configuration by `netiq`.
## Features
| Name | Supported? |
|----------------|------------|
| Authentication | Yes |
| Directory | Yes |
| Identity | Yes |
## Getting started
### Base
To use your LDAP backend, add the bare minimum configuration in mxisd config file:
```yaml
ldap.enabled: true
ldap.connection.host: 'ldapHostnameOrIp'
ldap.connection.port: 389
ldap.connection.bindDn: 'CN=My Mxisd User,OU=Users,DC=example,DC=org'
ldap.connection.bindPassword: 'TheUserPassword'
ldap.connection.baseDn: 'OU=Users,DC=example,DC=org'
```
These are standard LDAP connection configuration. mxisd will try to connect on port default port 389 without encryption.
### TLS/SSL connection
If you would like to use a TLS/SSL connection, use the following configuration options (STARTLS not supported):
```yaml
ldap.connection.tls: true
ldap.connection.port: 12345
```
### Filter results
You can also set a default global filter on any LDAP queries:
```yaml
ldap.filter: '(memberOf=CN=My Matrix Users,OU=Groups,DC=example,DC=org)'
```
This example would only return users part of the group called `My Matrix Users`.
This can be overwritten or append in each specific flow describe below.
For supported syntax, see the [LDAP library documentation](http://directory.apache.org/api/user-guide/2.3-searching.html#filter).
### Attribute mapping
LDAP features are based on mapping LDAP attributes to Matrix concepts, like a Matrix ID, its localpart, the user display
name, their email(s) and/or phone number(s).
Default attributes are well suited for Active Directory/Samba. In case you are using a native LDAP backend, you will
most certainly configure those mappings.
#### User ID
`ldap.attribute.uid.type`: How to process the User ID (UID) attribute:
- `uid` will consider the value as the [Localpart](https://matrix.org/docs/spec/intro.html#user-identifiers)
- `mxid` will consider the value as a complete [Matrix ID](https://matrix.org/docs/spec/intro.html#user-identifiers)
`ldap.attribute.uid.value`: Attribute to use to set the User ID value.
The following example would set the `sAMAccountName` attribute as a Matrix User ID localpart:
```yaml
ldap.attribute.uid.type: 'uid'
ldap.attribute.uid.value: 'sAMAccountName'
```
#### Display name
Use `ldap.attribute.name`.
The following example would set the display name to the value of the `cn` attribute:
```yaml
ldap.attribute.name: 'cn'
```
#### 3PIDs
You can also change the attribute lists for 3PID, like email or phone numbers.
The following example would overwrite the [default list of attributes](../../src/main/resources/application.yaml#L67)
for emails and phone number:
```yaml
ldap.attribute.threepid.email:
- 'mail'
- 'otherMailAttribute'
ldap.attribute.threepid.msisdn:
- 'phone'
- 'otherPhoneAttribute'
```
## Features
### Identity
Identity features (related to 3PID invites or searches) are enabled and configured using default values and no specific
configuration item is needed to get started.
#### Configuration
- `ldap.identity.filter`: Specific user filter applied during identity search. Global filter is used if blank/not set.
- `ldap.identity.medium`: Namespace to overwrite generated queries from the list of attributes for each 3PID medium.
### Authentication
No further configuration is needed to use the Authentication feature with LDAP once globally enabled and configured.
Profile auto-fill is enabled by default. It will use the `ldap.attribute.name` and `ldap.attribute.threepid` configuration
options to get a lit of attributes to be used to build the user profile to pass on to synapse during authentication.
#### Configuration
- `ldap.auth.filter`: Specific user filter applied during identity search. Global filter is used if blank/not set.
### Directory
No further configuration is needed to use the Directory feature with LDAP once globally enabled and configured.
#### Configuration
To set a specific filter applied during directory search, use `ldap.directory.filter`
If you would like to use extra attributes in search that are not 3PIDs, like nicknames, group names, employee number:
```yaml
ldap.directory.attribute.other:
- 'myNicknameAttribute'
- 'memberOf'
- 'employeeNumberAttribute'
```

216
docs/stores/rest.md Normal file
View File

@@ -0,0 +1,216 @@
# REST Identity store
The REST backend allows you to query identity data in existing webapps, like:
- Forums (phpBB, Discourse, etc.)
- Custom Identity stores (Keycloak, ...)
- CRMs (Wordpress, ...)
- self-hosted clouds (Nextcloud, ownCloud, ...)
To integrate this backend with your webapp, you will need to implement three specific REST endpoints detailed below.
## Features
| Name | Supported? |
|----------------|------------|
| Authentication | Yes |
| Directory | Yes |
| Identity | Yes |
## Configuration
| Key | Default | Description |
|----------------------------------|------------------------------------------------|------------------------------------------------------|
| `rest.enabled` | `false` | Globally enable/disable the REST backend |
| `rest.host` | *None* | Default base URL to use for the different endpoints. |
| `rest.endpoints.auth` | `/_mxisd/backend/api/v1/auth/login` | Validate credentials and get user profile |
| `rest.endpoints.directory` | `/_mxisd/backend/api/v1/directory/user/search` | Search for users by arbitrary input |
| `rest.endpoints.identity.single` | `/_mxisd/backend/api/v1/identity/single` | Endpoint to query a single 3PID |
| `rest.endpoints.identity.bulk` | `/_mxisd/backend/api/v1/identity/bulk` | Endpoint to query a list of 3PID |
Endpoint values can handle two formats:
- URL Path starting with `/` that gets happened to the `rest.host`
- Full URL, if you want each endpoint to go to a specific server/protocol/port
`rest.host` is mandatory if at least one endpoint is not a full URL.
## Endpoints
### Authentication
HTTP method: `POST`
Content-type: JSON UTF-8
#### Request Body
```json
{
"auth": {
"mxid": "@john.doe:example.org",
"localpart": "john.doe",
"domain": "example.org",
"password": "passwordOfTheUser"
}
}
```
#### Response Body
If the authentication fails:
```json
{
"auth": {
"success": false
}
}
```
If the authentication succeed:
- `auth.id` supported values: `localpart`, `mxid`
- `auth.profile` and any sub-member are all optional
```json
{
"auth": {
"success": true,
"id": {
"type": "localpart",
"value": "john"
},
"profile": {
"display_name": "John Doe",
"three_pids": [
{
"medium": "email",
"address": "john.doe@example.org"
},
{
"medium": "msisdn",
"address": "123456789"
}
]
}
}
}
```
### Directory
HTTP method: `POST`
Content-type: JSON UTF-8
#### Request Body
```json
{
"by": "<search type>",
"search_term": "doe"
}
```
`by` can be:
- `name`
- `threepid`
#### Response Body:
If users found:
```json
{
"limited": false,
"results": [
{
"avatar_url": "http://domain.tld/path/to/avatar.png",
"display_name": "John Doe",
"user_id": "UserIdLocalpart"
},
{
...
}
]
}
```
If no user found:
```json
{
"limited": false,
"results": []
}
```
### Identity
#### Single 3PID lookup
HTTP method: `POST`
Content-type: JSON UTF-8
#### Request Body
```json
{
"lookup": {
"medium": "email",
"address": "john.doe@example.org"
}
}
```
#### Response Body
If a match was found:
- `lookup.id.type` supported values: `localpart`, `mxid`
```json
{
"lookup": {
"medium": "email",
"address": "john.doe@example.org",
"id": {
"type": "mxid",
"value": "@john:example.org"
}
}
}
```
If no match was found:
```json
{}
```
#### Bulk 3PID lookup
HTTP method: `POST`
Content-type: JSON UTF-8
#### Request Body
```json
{
"lookup": [
{
"medium": "email",
"address": "john.doe@example.org"
},
{
"medium": "msisdn",
"address": "123456789"
}
]
}
```
#### Response Body
For all entries where a match was found:
- `lookup[].id.type` supported values: `localpart`, `mxid`
```json
{
"lookup": [
{
"medium": "email",
"address": "john.doe@example.org",
"id": {
"type": "localpart",
"value": "john"
}
},
{
"medium": "msisdn",
"address": "123456789",
"id": {
"type": "mxid",
"value": "@jane:example.org"
}
}
]
}
```
If no match was found:
```json
{
"lookup": []
}
```

98
docs/stores/sql.md Normal file
View File

@@ -0,0 +1,98 @@
# SQL Identity store
## Supported Databases
- PostgreSQL
- MariaDB
- MySQL
- SQLite
## Features
| Name | Supported? |
|----------------|------------|
| Authentication | No |
| Directory | Yes |
| Identity | Yes |
Due to the implementation complexity of supporting arbitrary hashing/encoding mechanisms or auth flow, Authentication
will be out of scope of SQL Identity stores and should be done via one of the other identity stores, typically
the [REST Identity store](rest.md).
## Configuration
### Basic
```yaml
sql.enabled: <boolean>
```
Enable/disable the identity store
---
```yaml
sql.type: <string>
```
Set the SQL backend to use:
- `sqlite`
- `postgresql`
- `mariadb`
- `mysql`
### Connection
#### SQLite
```yaml
sql.connection: <string>
```
Set the value to the absolute path to the Synapse SQLite DB file.
Example: `/path/to/sqlite/file.db`
#### Others
```yaml
sql.connection: //<HOST[:PORT]/DB?username=USER&password=PASS
```
Set the connection info for the database by replacing the following values:
- `HOST`: Hostname of the SQL server
- `PORT`: Optional port value, if not default
- `DB`: Database name
- `USER`: Username for the connection
- `PASS`: Password for the connection
This follow the JDBC URI syntax. See [official website](https://docs.oracle.com/javase/tutorial/jdbc/basics/connecting.html#db_connection_url).
### Directory
```yaml
sql.directory.enabled: false
```
---
```yaml
sql.directory.query:
name:
type: <string>
value: <string>
threepid:
type: <string>
value: <string>
```
For each query, `type` can be used to tell mxisd how to process the ID column:
- `localpart` will append the `matrix.domain` to it
- `mxid` will use the ID as-is. If it is not a valid Matrix ID, the search will fail.
`value` is the SQL query and must return two columns:
- The first being the User ID
- The second being its display name
Example:
```yaml
sql.directory.query:
name:
type: 'localpart'
value: 'SELECT idColumn, displayNameColumn FROM table WHERE displayNameColumn LIKE ?'
threepid:
type: 'localpart'
value: 'SELECT idColumn, displayNameColumn FROM table WHERE threepidColumn LIKE ?'
```
### Identity
```yaml
sql.identity.type: <string>
sql.identity.query: <string>
```

48
docs/stores/synapse.md Normal file
View File

@@ -0,0 +1,48 @@
# Synapse Identity Store
Synapse's Database itself can be used as an Identity store.
## Features
| Name | Supported? |
|----------------|------------|
| Authentication | No |
| Directory | Yes |
| Identity | Yes |
Authentication is done by Synapse itself.
## Configuration
### Basic
```yaml
synapseSql.enabled: <boolean>
```
Enable/disable the identity store
---
```yaml
synapseSql.type: <string>
```
Set the SQL backend to use which is configured in synapse:
- `sqlite`
- `postgresql`
### SQLite
```yaml
synapseSql.connection: <string>
```
Set the value to the absolute path to the Synapse SQLite DB file.
Example: `/path/to/synapse/sqliteFile.db`
### PostgreSQL
```yaml
synapseSql.connection: //<HOST[:PORT]/DB?username=USER&password=PASS
```
Set the connection info for the database by replacing the following values:
- `HOST`: Hostname of the SQL server
- `PORT`: Optional port value, if not default
- `DB`: Database name
- `USER`: Username for the connection
- `PASS`: Password for the connection
### Query customization
See the [SQL Identity store](sql.md)

57
docs/stores/wordpress.md Normal file
View File

@@ -0,0 +1,57 @@
# Wordpress Identity store
This Identity store allows you to use user accounts registered on your Wordpress setup.
Two types of connections are required for full support:
- [REST API](https://developer.wordpress.org/rest-api/) with JWT authentication
- Direct SQL access
## Features
| Name | Supported? |
|----------------|------------|
| Authentication | Yes |
| Directory | Yes |
| Identity | Yes |
## Requirements
- [Wordpress](https://wordpress.org/download/) >= 4.4
- Permalink structure set to `Post Name`
- [JWT Auth plugin for REST API](https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/)
- SQL Credentials to the Wordpress Database
## Configuration
### Wordpress
#### JWT Auth
Set a JWT secret into `wp-config.php` like so:
```php
define('JWT_AUTH_SECRET_KEY', 'your-top-secret-key');
```
`your-top-secret-key` should be set to a randomly generated value which is kept secret.
#### Rewrite of `index.php`
Wordpress is normally configured with rewrite of `index.php` so it does not appear in URLs.
If this is not the case for your installation, the mxisd URL will need to be appended with `/index.php`
### mxisd
Enable in the configuration:
```yaml
wordpress.enabled: true
```
Configure the URL to your Wordpress installation - see above about added `/index.php`:
```yaml
wordpress.rest.base: 'http://localhost:8080'
```
Configure the SQL connection to your Wordpress database:
```yaml
wordpress.sql.connection: '//127.0.0.1/wordpress?user=root&password=example'
```
---
By default, MySQL database is expected. If you use another database, use:
```yaml
wordpress.sql.type: <string>
```
With possible values:
- `mysql`
- `mariadb`
- `postgresql`
- `sqlite`

View File

@@ -0,0 +1,19 @@
# Email notifications - SMTP connector
Enabled by default.
Connector ID: `smtp`
## Configuration
```yaml
threepid.medium.email:
identity:
from: 'identityServerEmail@example.org'
name: 'My Identity Server'
connectors:
smtp:
host: 'smtpHostname'
port: 587
tls: 1 # 0 = no STARTLS, 1 = try, 2 = force
login: 'smtpLogin'
password: 'smtpPassword'
```

View File

@@ -0,0 +1,11 @@
# SMS notifications - Twilio connector
Enabled by default.
Connector ID: `twilio`
## Configuration
```yaml
threepid.medium.msisdn.connectors.twilio.accountSid: 'myAccountSid'
threepid.medium.msisdn.connectors.twilio.authToken: 'myAuthToken'
threepid.medium.msisdn.connectors.twilio.number: '+123456789'
```

View File

@@ -0,0 +1,37 @@
# Basic Notification handler
Basic notification handler which uses two components:
- Content generator, to produce the notifications
- Connectors to send the notification content
This handler can be used with the 3PID types:
- `email`
- `msisdn` (Phone numbers)
## Generators
- [Template](template-generator.md)
## Connectors
- Email
- [SMTP](../medium/email/smtp-connector.md)
- SMS
- [Twilio](../medium/msisdn/twilio-connector.md)
## Configuration
Enabled by default or with:
```yaml
notification.handler.email: 'raw'
```
**WARNING:** Will be consolidated soon, prone to breaking changes.
Structure and default values:
```yaml
threepid.medium:
email:
identity:
from: ''
name: ''
connector: 'smtp'
generator: 'template'
msisdn:
connector: 'twilio'
generator: 'template'
```

View File

@@ -0,0 +1,7 @@
# SendGrid Notification handler
To be completed. See [raw possible configuration items](https://github.com/kamax-io/mxisd/blob/master/src/main/resources/application.yaml#L172).
Enabled with:
```yaml
notification.handler.email: 'sendgrid'
```

View File

@@ -0,0 +1,71 @@
# Notifications: Generate from templates
To create notification content, you can use the `template` generator if supported for the 3PID medium which will read
content from configured files.
Placeholders can be integrated into the templates to dynamically populate such content with relevant information like
the 3PID that was requested, the domain of your Identity server, etc.
Templates can be configured for each event that would send a notification to the end user. Events share a set of common
placeholders and also have their own individual set of placeholders.
## Configuration
To configure paths to the various templates:
```yaml
threepid.medium.<YOUR 3PID MEDIUM HERE>:
generators:
template:
invite: '/path/to/invite-template.eml'
session:
validation:
local: '/path/to/validate-local-template.eml'
remote: 'path/to/validate-remote-template.eml'
```
The `template` generator is usually the default, so no further configuration is needed.
## Global placeholders
| Placeholder | Purpose |
|-----------------------|------------------------------------------------------------------------------|
| `%DOMAIN%` | Identity server authoritative domain, as configured in `matrix.domain` |
| `%DOMAIN_PRETTY%` | Same as `%DOMAIN%` with the first letter upper case and all other lower case |
| `%FROM_EMAIL%` | Email address configured in `threepid.medium.<3PID medium>.identity.from` |
| `%FROM_NAME%` | Name configured in `threepid.medium.<3PID medium>.identity.name` |
| `%RECIPIENT_MEDIUM%` | The 3PID medium, like `email` or `msisdn` |
| `%RECIPIENT_ADDRESS%` | The address to which the notification is sent |
## Events
### Room invitation
This template is used when someone is invited into a room using an email address which has no known bind to a Matrix ID.
#### Placeholders
| Placeholder | Purpose |
|-----------------------|------------------------------------------------------------------------------------------|
| `%SENDER_ID%` | Matrix ID of the user who made the invite |
| `%SENDER_NAME%` | Display name of the user who made the invite, if not available/set, empty |
| `%SENDER_NAME_OR_ID%` | Display name of the user who made the invite. If not available/set, its Matrix ID |
| `%INVITE_MEDIUM%` | The 3PID medium for the invite. |
| `%INVITE_ADDRESS%` | The 3PID address for the invite. |
| `%ROOM_ID%` | The Matrix ID of the Room in which the invite took place |
| `%ROOM_NAME%` | The Name of the room in which the invite took place. If not available/set, empty |
| `%ROOM_NAME_OR_ID%` | The Name of the room in which the invite took place. If not available/set, its Matrix ID |
### Local validation of 3PID Session
This template is used when to user which added their 3PID address to their profile/settings and the session policy
allows at least local sessions.
#### Placeholders
| Placeholder | Purpose |
|----------------------|--------------------------------------------------------------------------------------|
| `%VALIDATION_LINK%` | URL, including token, to validate the 3PID session. |
| `%VALIDATION_TOKEN%` | The token needed to validate the local session, in case the user cannot use the link |
### Remote validation of 3PID Session
This template is used when to user which added their 3PID address to their profile/settings and the session policy only
allows remote sessions.
**NOTE:** 3PID session always require local validation of a token, even if a remote session is enforced.
One cannot bind a Matrix ID to the session until both local and remote sessions have been validated.
#### Placeholders
| Placeholder | Purpose |
|----------------------|--------------------------------------------------------|
| `%VALIDATION_TOKEN%` | The token needed to validate the session |
| `%NEXT_URL%` | URL to continue with remote validation of the session. |

View File

@@ -0,0 +1,86 @@
# Web pages for the 3PID sessions
You can customize the various pages used during a 3PID validation using [Thymeleaf templates](http://www.thymeleaf.org/).
## Configuration
Pseudo-configuration to illustrate the structure:
```yaml
# CONFIGURATION EXAMPLE
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
view.session.local:
onTokenSubmit:
success: '/path/to/session/local/tokenSubmitSuccess-page.html'
failure: '/path/to/session/local/tokenSubmitFailure-page.html'
view.session.localRemote:
onTokenSubmit:
success: '/path/to/session/localRemote/tokenSubmitSuccess-page.html'
failure: '/path/to/session/local/tokenSubmitFailure-page.html'
view.session.remote:
onRequest:
success: '/path/to/session/remote/requestSuccess-page.html'
failure: '/path/to/session/remote/requestFailure-page.html'
onCheck:
success: '/path/to/session/remote/checkSuccess-page.html'
failure: '/path/to/session/remote/checkFailure-page.html'
# CONFIGURATION EXAMPLE
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
```
3PID session are divided into three config sections:
- `local` for local-only 3PID sessions
- `localRemote` for local 3PID sessions that can also be turned into remote sessions, if the user so desires
- `remote` for remote-only 3PID sessions
Each section contains a sub-key per support event. Finally, a `success` and `failure` key is available depending on the
outcome of the request.
## Local
### onTokenSubmit
This is triggered when a user submit a validation token for a 3PID session. It is typically visited when clicking the
link in a validation email.
The template should typically inform the user that the validation was successful and to go back in their Matrix client
to finish the validation process.
#### Placeholders
No object/placeholder are currently available.
## Local & Remote
### onTokenSubmit
This is triggered when a user submit a validation token for a 3PID session. It is typically visited when clicking the
link in a validation email.
The template should typically inform the user that their 3PID address will not yet be publicly/globally usable. In case
they want to make it, they should start a Remote 3PID session with a given link or that they can go back to their Matrix
client if they do not wish to proceed any further.
#### Placeholders
##### Success
`<a th:href="${remoteSessionLink}">text</a>` can be used to display the link to start a Remote 3PID session.
##### Failure
No object/placeholder are currently available.
## Remote
### onRequest
This is triggered when a user starts a Remote 3PID session, usually from a link produced in the `local.onTokenSubmit`
view or in a remote-only 3PID notification.
The template should typically inform the user that the remote creation was successful, followed the instructions sent by
the remote Identity server and, once that is done, click a link to validate the session.
#### Placeholders
##### Success
`<a th:href="${checkLink}">text</a>` can be used to display the link to validate the Remote 3PID session.
##### Failure
No object/placeholder are currently available.
### onCheck
This is triggered when a user attempts to inform the Identity server that the Remote 3PID session has been validated
with the remote Identity server.
The template should typically inform the user that the validation was successful and to go back in their Matrix client
to finish the validation process.
#### Placeholders
No object/placeholder are currently available.

View File

@@ -0,0 +1,260 @@
# 3PID Sessions
- [Overview](#overview)
- [Purpose](#purpose)
- [Federation](#federation)
- [3PID scope](#3pid-scope)
- [Session scope](#session-scope)
- [Notifications](#notifications)
- [Email](#email)
- [Phone numbers](#msisdn-(phone-numbers))
- [Usage](#usage)
- [Configuration](#configuration)
- [Web views](#web-views)
- [Scenarios](#scenarios)
- [Default](#default)
- [Local sessions only](#local-sessions-only)
- [Remote sessions only](#remote-sessions-only)
- [Sessions disabled](#sessions-disabled)
## Overview
When adding an email, a phone number or any other kind of 3PID (Third-Party Identifier) in a Matrix client,
the identity server is called to validate the 3PID.
Once this 3PID is validated, the Homeserver will publish the user Matrix ID on the Identity Server and
add this 3PID to the Matrix account which initiated the request.
## Purpose
This serves two purposes:
- Add the 3PID as an administrative/login info for the Homeserver directly
- Publish, or *Bind*, the 3PID so it can be queried from Homeservers and clients when inviting someone in a room
by a 3PID, allowing it to be resolved to a Matrix ID.
## Federation
Federation is based on the principle that one can get a domain name and serve services and information within that
domain namespace in a way which can be discovered following a specific protocol or specification.
In the Matrix eco-system, some 3PID can be federated (e.g. emails) while some others cannot (phone numbers).
Also, Matrix users might add 3PIDs that would not point to the Identity server that actually holds the 3PID binding.
Example: a user from Homeserver `example.org` adds an email `john@gmail.com`.
If a federated lookup was performed, Identity servers would try to find the 3PID bind at the `gmail.com` server, and
not `example.org`.
To allow global publishing of 3PID bindings to be found anywhere within the current protocol specification, one would
perform a *Remote session* and *Remote bind*, effectively starting a new 3PID session with another Identity server on
behalf of the user.
To ensure lookup works consistency within the current Matrix network, the central Matrix.org Identity Server should be
used to store *remote* sessions and binds.
On the flip side, at the time of writing, the Matrix specification and the central Matrix.org servers do not allow to
remote a 3PID bind. This means that once a 3PID is published (email, phone number, etc.), it cannot be easily removed
and would require contacting the Matrix.org administrators for each bind individually.
This poses a privacy, control and security concern, especially for groups/corporations that want to keep a tight control
on where such identifiers can be made publicly visible.
To ensure full control, validation management rely on two concepts:
- The scope of 3PID being validated
- The scope of 3PID sessions that should be possible/offered
### 3PID scope
3PID can either be scoped as local or remote.
Local means that they can looked up using federation and that such federation call would end up on the local
Identity Server.
Remote means that they cannot be lookup using federation or that a federation call would not end up on the local
Identity Server.
Email addresses can either be local or remote 3PID, depending on the domain. If the address is one from the configured
domain in the Identity server, it will be scoped as local. If it is from another domain, it will be as remote.
Phone number can only be scoped as remote, since there is currently no way to perform DNS queries that would lead back
to the Identity server who validated the phone number.
### Session scope
Sessions can be scoped as:
- Local only - validate 3PIDs directly, do not allow the creation of 3PID sessions on a remote Identity server.
- Local and Remote - validate 3PIDs directly, offer users to option to also validate and bind 3PID on another server.
- Remote only - validate and bind 3PIDs on another server, no validation or bind done locally.
---
**IMPORTANT NOTE:** mxisd does not store bindings directly. While a user can see its email, phone number or any other
3PID in its settings/profile, it does **NOT** mean it is published anywhere and can be used to invite/search the user.
Identity stores are the ones holding such data.
If you still want added arbitrary 3PIDs to be discoverable on a synapse Homeserver, use the corresponding [Identity store](../../stores/synapse.md).
See the [Scenarios](#scenarios) for more info on how and why.
## Notifications
3PIDs are validated by sending a pre-formatted message containing a token to that 3PID address, which must be given to the
Identity server that received the request. This is usually done by means of a URL to visit for email or a short number
received by SMS for phone numbers.
mxisd use two components for this:
- Generator which produces the message to be sent with the necessary information the user needs to validate their session.
- Connector which actually send the notification (e.g. SMTP for email).
Built-in generators and connectors for supported 3PID types:
### Email
Generators:
- [Template](../notification/template-generator.md)
Connectors:
- [SMTP](../medium/email/smtp-connector.md)
#### MSISDN (Phone numbers)
Generators:
- [Template](../notification/template-generator.md)
Connectors:
- [Twilio](../medium/msisdn/twilio-connector.md) with SMS
## Usage
### Configuration
The following example of configuration (incomplete extract) shows which items are relevant for 3PID sessions.
**IMPORTANT:** Most configuration items shown have default values and should not be included in your own configuration
file unless you want to specifically overwrite them.
```yaml
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
session.policy.validation.enabled: true
session.policy.validation.forLocal:
enabled: true
toLocal: true
toRemote:
enabled: true
server: 'configExample' # Not to be included in config! Already present in default config!
session.policy.validation.forRemote:
enabled: true
toLocal: true
toRemote:
enabled: true
server: 'configExample' # Not to be included in config! Already present in default config!
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
```
`session.policy.validation` is the core configuration to control what users configured to use your Identity server
are allowed to do in terms of 3PID sessions.
The policy is divided contains a global on/off switch for 3PID sessions using `.enabled`
It is also divided into two sections: `forLocal` and `forRemote` which refers to the 3PID scopes.
Each scope is divided into three parts:
- global on/off switch for 3PID sessions using `.enabled`
- `toLocal` allowing or not local 3PID session validations
- `toRemote` allowing or not remote 3PID session validations and to which server such sessions should be sent.
`.server` takes a Matrix Identity server list label. Only the first server in the list is currently used.
If both `toLocal` and `toRemote` are enabled, the user will be offered to initiate a remote session once their 3PID
locally validated.
### Web views
Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submitted.
If the session or token is invalid, an error page is displayed.
Workflow pages are also available for the remote 3PID session process.
See [the dedicated document](session-views.md)
on how to configure/customize/brand those pages to your liking.
### Scenarios
It is important to keep in mind that mxisd does not create bindings, irrelevant if a user added a 3PID to their profile.
Instead, when queried for bindings, mxisd will query Identity stores which are responsible to store this kind of information.
This has the side effect that any 3PID added to a user profile which is NOT within a configured and enabled Identity backend
will simply not be usable for search or invites, **even on the same Homeserver!**
mxisd does not store binds on purpose, as one of its primary goal is to ensure maximum compatibility with federation
and the rest of the Matrix ecosystem is preserved.
Nonetheless, because mxisd also aims at offering support for tight control over identity data, it is possible to have
such 3PID bindings available for search and invite queries on synapse with the corresponding [Identity store](../../stores/synapse.md).
See the [Local sessions only](#local-sessions-only) use case for more information on how to configure.
#### Default
By default, mxisd allows the following:
| | Local Session | Remote Session |
|-----------------|-------------------|----------------|
| **Local 3PID** | Yes | Yes, offered |
| **Remote 3PID** | No, Remote forced | Yes |
This is usually what people expect and will feel natural to users and does not involve further integration.
This allows to stay in control for e-mail addresses which domain matches your Matrix environment, still making them
discoverable with federation but not recorded in a 3rd party Identity server which is not under your control.
Users still get the possibility to publish globally their address if needed.
Other e-mail addresses and phone number will be redirected to remote sessions to ensure full compatibility with the Matrix
ecosystem and other federated servers.
#### Local sessions only
**NOTE:** This does not affect 3PID lookups (queries to find Matrix IDs). See [Federation](../../features/federation.md)
to disable remote lookup for those.
This configuration ensures maximum confidentiality and privacy.
Typical use cases:
- Private Homeserver, not federated
- Internal Homeserver without direct Internet access
- Custom product based on Matrix which does not federate
No 3PID will be sent to a remote Identity server and all validation will be performed locally.
On the flip side, people with *Remote* 3PID scopes will not be found from other servers.
Use the following values:
```yaml
session.policy.validation.enabled: true
session.policy.validation.forLocal:
enabled: true
toLocal: true
toRemote:
enabled: false
session.policy.validation.forRemote:
enabled: true
toLocal: true
toRemote:
enabled: false
```
**IMPORTANT**: When using local-only mode and if you are using synapse, you will also need to enable its dedicated Identity
store if you want user searches and invites to work. To do so, see the [dedicated document](../../stores/synapse.md).
#### Remote sessions only
This configuration ensures all 3PID are made public for maximum compatibility and reach within the Matrix ecosystem, at
the cost of confidentiality and privacy.
Typical use cases:
- Public Homeserver
- Homeserver with registration enabled
Use the following values:
```yaml
session.policy.validation.enabled: true
session.policy.validation.forLocal:
enabled: true
toLocal: false
toRemote:
enabled: true
session.policy.validation.forRemote:
enabled: true
toLocal: false
toRemote:
enabled: true
```
#### Sessions disabled
This configuration would disable 3PID session altogether, preventing users from adding emails and/or phone numbers to
their profiles.
This would be used if mxisd is also performing authentication for the Homeserver, typically with synapse and the
[REST password provider](https://github.com/kamax-io/matrix-synapse-rest-auth).
**This mode comes with several important restrictions:**
- This does not prevent users from removing 3PID from their profile. They would be unable to add them back!
- This prevents users from initiating remote session to make their 3PID binds globally visible
It is therefore recommended to not fully disable sessions but instead restrict specific set of 3PID and Session scopes.
Use the following values to enable this mode:
```yaml
session.policy.validation.enabled: false
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
media/mx-id-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -1,2 +1,25 @@
#!/bin/sh
#!/usr/bin/env bash
if [[ -n "$CONF_FILE_PATH" ]] && [ ! -f "$CONF_FILE_PATH" ]; then
echo "Generating config file $CONF_FILE_PATH"
touch "CONF_FILE_PATH"
if [[ -n "$MATRIX_DOMAIN" ]]; then
echo "Setting matrix domain to $MATRIX_DOMAIN"
echo "matrix.domain: $MATRIX_DOMAIN" >> "$CONF_FILE_PATH"
fi
if [[ -n "$SIGN_KEY_PATH" ]]; then
echo "Setting signing key path to $SIGN_KEY_PATH"
echo "key.path: $SIGN_KEY_PATH" >> "$CONF_FILE_PATH"
fi
if [[ -n "$SQLITE_DATABASE_PATH" ]]; then
echo "Setting SQLite DB path to $SQLITE_DATABASE_PATH"
echo "storage.provider.sqlite.database: $SQLITE_DATABASE_PATH" >> "$CONF_FILE_PATH"
fi
echo "Starting mxisd..."
echo
fi
exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dspring.config.location=/etc/mxisd/ -Dspring.config.name=mxisd -jar /mxisd.jar

View File

@@ -1,156 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.InitializingBean
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
@Configuration
@ConfigurationProperties(prefix = "ldap")
class LdapConfig implements InitializingBean {
private Logger log = LoggerFactory.getLogger(LdapConfig.class)
private boolean enabled
private boolean tls
private String host
private int port
private String baseDn
private String type
private String attribute
private String bindDn
private String bindPassword
private Map<String, String> mappings
boolean getEnabled() {
return enabled
}
void setEnabled(boolean enabled) {
this.enabled = enabled
}
boolean getTls() {
return tls
}
void setTls(boolean tls) {
this.tls = tls
}
String getHost() {
return host
}
void setHost(String host) {
this.host = host
}
int getPort() {
return port
}
void setPort(int port) {
this.port = port
}
String getBaseDn() {
return baseDn
}
void setBaseDn(String baseDn) {
this.baseDn = baseDn
}
String getType() {
return type
}
void setType(String type) {
this.type = type
}
String getAttribute() {
return attribute
}
void setAttribute(String attribute) {
this.attribute = attribute
}
String getBindDn() {
return bindDn
}
void setBindDn(String bindDn) {
this.bindDn = bindDn
}
String getBindPassword() {
return bindPassword
}
void setBindPassword(String bindPassword) {
this.bindPassword = bindPassword
}
Map<String, String> getMappings() {
return mappings
}
void setMappings(Map<String, String> mappings) {
this.mappings = mappings
}
Optional<String> getMapping(String type) {
if (mappings == null) {
return Optional.empty()
}
return Optional.ofNullable(mappings.get(type))
}
@Override
void afterPropertiesSet() throws Exception {
log.info("LDAP enabled: {}", getEnabled())
if (!getEnabled()) {
return
}
log.info("Matrix ID type: {}", getType())
log.info("LDAP Host: {}", getHost())
log.info("LDAP Bind DN: {}", getBindDn())
log.info("LDAP Attribute: {}", getAttribute())
if (StringUtils.isBlank(getHost())) {
throw new IllegalStateException("LDAP Host must be configured!")
}
if (StringUtils.isBlank(getAttribute())) {
throw new IllegalStateException("LDAP attribute must be configured!")
}
}
}

View File

@@ -1,60 +0,0 @@
package io.kamax.mxisd.config
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.InitializingBean
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
@Configuration
@ConfigurationProperties(prefix = "lookup.recursive.bridge")
class RecursiveLookupBridgeConfig implements InitializingBean {
private Logger log = LoggerFactory.getLogger(RecursiveLookupBridgeConfig.class)
private boolean enabled
private boolean recursiveOnly
private String server
private Map<String, String> mappings
boolean getEnabled() {
return enabled
}
void setEnabled(boolean enabled) {
this.enabled = enabled
}
boolean getRecursiveOnly() {
return recursiveOnly
}
void setRecursiveOnly(boolean recursiveOnly) {
this.recursiveOnly = recursiveOnly
}
String getServer() {
return server
}
void setServer(String server) {
this.server = server
}
Map<String, String> getMappings() {
return mappings
}
void setMappings(Map<String, String> mappings) {
this.mappings = mappings
}
@Override
void afterPropertiesSet() throws Exception {
log.info("Enabled: {}", getEnabled())
log.info("Recursive only: {}", getRecursiveOnly())
log.info("Server: {}", getServer())
log.info("Mappings: {}", mappings.size())
}
}

View File

@@ -1,71 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.controller.v1
import groovy.json.JsonOutput
import io.kamax.mxisd.exception.BadRequestException
import io.kamax.mxisd.exception.NotImplementedException
import io.kamax.mxisd.key.KeyManager
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpServletRequest
import static org.springframework.web.bind.annotation.RequestMethod.GET
@RestController
class KeyController {
private Logger log = LoggerFactory.getLogger(KeyController.class)
@Autowired
private KeyManager keyMgr
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/{keyType}:{keyId}", method = GET)
String getKey(@PathVariable String keyType, @PathVariable int keyId) {
if (!"ed25519".contentEquals(keyType)) {
throw new BadRequestException("Invalid algorithm: " + keyType)
}
return JsonOutput.toJson([
public_key: keyMgr.getPublicKeyBase64(keyId)
])
}
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/ephemeral/isvalid", method = GET)
String checkEphemeralKeyValidity(HttpServletRequest request) {
log.error("{} was requested but not implemented", request.getRequestURL())
throw new NotImplementedException()
}
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/isvalid", method = GET)
String checkKeyValidity(HttpServletRequest request) {
log.error("{} was requested but not implemented", request.getRequestURL())
throw new NotImplementedException()
}
}

View File

@@ -1,103 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.controller.v1
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import io.kamax.mxisd.lookup.BulkLookupRequest
import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.strategy.LookupStrategy
import io.kamax.mxisd.signature.SignatureManager
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpServletRequest
import static org.springframework.web.bind.annotation.RequestMethod.GET
import static org.springframework.web.bind.annotation.RequestMethod.POST
@RestController
class MappingController {
private Logger log = LoggerFactory.getLogger(MappingController.class)
private JsonSlurper json = new JsonSlurper()
@Autowired
private LookupStrategy strategy
@Autowired
private SignatureManager signMgr
@RequestMapping(value = "/_matrix/identity/api/v1/lookup", method = GET)
String lookup(HttpServletRequest request, @RequestParam String medium, @RequestParam String address) {
String remote = StringUtils.defaultIfBlank(request.getHeader("X-FORWARDED-FOR"), request.getRemoteAddr())
log.info("Got request from {}", remote)
SingleLookupRequest lookupRequest = new SingleLookupRequest()
lookupRequest.setRequester(remote)
lookupRequest.setType(medium)
lookupRequest.setThreePid(address)
Optional<?> lookupOpt = strategy.find(lookupRequest)
if (!lookupOpt.isPresent()) {
log.info("No mapping was found, return empty JSON object")
return JsonOutput.toJson([])
}
def lookup = lookupOpt.get()
if (lookup['signatures'] == null) {
log.info("lookup is not signed yet, we sign it")
lookup['signatures'] = signMgr.signMessage(JsonOutput.toJson(lookup))
}
return JsonOutput.toJson(lookup)
}
@RequestMapping(value = "/_matrix/identity/api/v1/bulk_lookup", method = POST)
String bulkLookup(HttpServletRequest request) {
String remote = StringUtils.defaultIfBlank(request.getHeader("X-FORWARDED-FOR"), request.getRemoteAddr())
log.info("Got request from {}", remote)
BulkLookupRequest lookupRequest = new BulkLookupRequest()
lookupRequest.setRequester(remote)
ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(request.getInputStream().getText())
List<ThreePidMapping> mappings = new ArrayList<>()
for (List<String> mappingRaw : input.getThreepids()) {
ThreePidMapping mapping = new ThreePidMapping()
mapping.setMedium(mappingRaw.get(0))
mapping.setValue(mappingRaw.get(1))
mappings.add(mapping)
}
lookupRequest.setMappings(mappings)
ClientBulkLookupAnswer answer = new ClientBulkLookupAnswer()
answer.addAll(strategy.find(lookupRequest))
return JsonOutput.toJson(answer)
}
}

View File

@@ -1,154 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.controller.v1
import com.google.gson.Gson
import com.google.gson.JsonObject
import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson
import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson
import io.kamax.mxisd.exception.BadRequestException
import io.kamax.mxisd.lookup.ThreePid
import io.kamax.mxisd.mapping.MappingManager
import org.apache.commons.io.IOUtils
import org.apache.commons.lang.StringUtils
import org.apache.http.HttpStatus
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import java.nio.charset.StandardCharsets
@RestController
class SessionController {
@Autowired
private MappingManager mgr
private Gson gson = new Gson()
private Logger log = LoggerFactory.getLogger(SessionController.class)
private <T> T fromJson(HttpServletRequest req, Class<T> obj) {
gson.fromJson(new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8), obj)
}
@RequestMapping(value = "/_matrix/identity/api/v1/validate/{medium}/requestToken")
String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) {
log.info("Requested: {}", request.getRequestURL(), request.getQueryString())
if (StringUtils.equals("email", medium)) {
SessionEmailTokenRequestJson req = fromJson(request, SessionEmailTokenRequestJson.class)
return gson.toJson(new Sid(mgr.create(req)))
}
if (StringUtils.equals("msisdn", medium)) {
SessionPhoneTokenRequestJson req = fromJson(request, SessionPhoneTokenRequestJson.class)
return gson.toJson(new Sid(mgr.create(req)))
}
JsonObject obj = new JsonObject();
obj.addProperty("errcode", "M_INVALID_3PID_TYPE")
obj.addProperty("error", medium + " is not supported as a 3PID type")
response.setStatus(HttpStatus.SC_BAD_REQUEST)
return gson.toJson(obj)
}
@RequestMapping(value = "/_matrix/identity/api/v1/validate/{medium}/submitToken")
String validate(HttpServletRequest request,
@RequestParam String sid,
@RequestParam("client_secret") String secret, @RequestParam String token) {
log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString())
mgr.validate(sid, secret, token)
return "{}"
}
@RequestMapping(value = "/_matrix/identity/api/v1/3pid/getValidated3pid")
String check(HttpServletRequest request, HttpServletResponse response,
@RequestParam String sid, @RequestParam("client_secret") String secret) {
log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString())
Optional<ThreePid> result = mgr.getValidated(sid, secret)
if (result.isPresent()) {
log.info("requested session was validated")
ThreePid pid = result.get()
JsonObject obj = new JsonObject()
obj.addProperty("medium", pid.getMedium())
obj.addProperty("address", pid.getAddress())
obj.addProperty("validated_at", pid.getValidation().toEpochMilli())
return gson.toJson(obj);
} else {
log.info("requested session was not validated")
JsonObject obj = new JsonObject()
obj.addProperty("errcode", "M_SESSION_NOT_VALIDATED")
obj.addProperty("error", "sid, secret or session not valid")
response.setStatus(HttpStatus.SC_BAD_REQUEST)
return gson.toJson(obj)
}
}
@RequestMapping(value = "/_matrix/identity/api/v1/3pid/bind")
String bind(HttpServletRequest request, HttpServletResponse response,
@RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String mxid) {
String data = IOUtils.toString(request.getReader())
log.info("Requested: {}", request.getRequestURL(), request.getQueryString())
try {
mgr.bind(sid, secret, mxid)
return "{}"
} catch (BadRequestException e) {
log.info("requested session was not validated")
JsonObject obj = new JsonObject()
obj.addProperty("errcode", "M_SESSION_NOT_VALIDATED")
obj.addProperty("error", e.getMessage())
response.setStatus(HttpStatus.SC_BAD_REQUEST)
return gson.toJson(obj)
}
}
private class Sid {
private String sid;
public Sid(String sid) {
setSid(sid);
}
String getSid() {
return sid
}
void setSid(String sid) {
this.sid = sid
}
}
}

View File

@@ -1,23 +0,0 @@
package io.kamax.mxisd.controller.v1.io;
import io.kamax.mxisd.mapping.MappingSession;
public abstract class GenericTokenRequestJson implements MappingSession {
private String client_secret;
private int send_attempt;
private String id_server;
public String getSecret() {
return client_secret;
}
public int getAttempt() {
return send_attempt;
}
public String getServer() {
return id_server;
}
}

View File

@@ -1,17 +0,0 @@
package io.kamax.mxisd.controller.v1.io;
public class SessionEmailTokenRequestJson extends GenericTokenRequestJson {
private String email;
@Override
public String getMedium() {
return "email";
}
@Override
public String getValue() {
return email;
}
}

View File

@@ -1,29 +0,0 @@
package io.kamax.mxisd.controller.v1.io;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
public class SessionPhoneTokenRequestJson extends GenericTokenRequestJson {
private static PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
private String country;
private String phone_number;
@Override
public String getMedium() {
return "msisdn";
}
@Override
public String getValue() {
try {
Phonenumber.PhoneNumber num = phoneUtil.parse(phone_number, country);
return phoneUtil.format(num, PhoneNumberUtil.PhoneNumberFormat.E164).replace("+", "");
} catch (NumberParseException e) {
throw new IllegalArgumentException("Invalid phone number");
}
}
}

View File

@@ -1,106 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.key
import io.kamax.mxisd.config.KeyConfig
import net.i2p.crypto.eddsa.EdDSAEngine
import net.i2p.crypto.eddsa.EdDSAPrivateKey
import net.i2p.crypto.eddsa.EdDSAPublicKey
import net.i2p.crypto.eddsa.KeyPairGenerator
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.apache.commons.io.FileUtils
import org.springframework.beans.factory.InitializingBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.security.KeyPair
import java.security.MessageDigest
import java.security.PrivateKey
@Component
class KeyManager implements InitializingBean {
@Autowired
private KeyConfig keyCfg
private EdDSAParameterSpec keySpecs
private EdDSAEngine signEngine
private List<KeyPair> keys
@Override
void afterPropertiesSet() throws Exception {
keySpecs = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512)
signEngine = new EdDSAEngine(MessageDigest.getInstance(keySpecs.getHashAlgorithm()))
keys = new ArrayList<>()
Path privKey = Paths.get(keyCfg.getPath())
if (!Files.exists(privKey)) {
KeyPair pair = (new KeyPairGenerator()).generateKeyPair()
String keyEncoded = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded())
FileUtils.writeStringToFile(privKey.toFile(), keyEncoded, StandardCharsets.ISO_8859_1)
keys.add(pair)
} else {
if (Files.isDirectory(privKey)) {
throw new RuntimeException("Invalid path for private key: ${privKey.toString()}")
}
if (Files.isReadable(privKey)) {
byte[] seed = Base64.getDecoder().decode(FileUtils.readFileToString(privKey.toFile(), StandardCharsets.ISO_8859_1))
EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(seed, keySpecs)
EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKeySpec.getA(), keySpecs)
keys.add(new KeyPair(new EdDSAPublicKey(pubKeySpec), new EdDSAPrivateKey(privKeySpec)))
}
}
}
int getCurrentIndex() {
return 0
}
KeyPair getKeys(int index) {
return keys.get(index)
}
PrivateKey getPrivateKey(int index) {
return getKeys(index).getPrivate()
}
EdDSAPublicKey getPublicKey(int index) {
return (EdDSAPublicKey) getKeys(index).getPublic()
}
EdDSAParameterSpec getSpecs() {
return keySpecs
}
String getPublicKeyBase64(int index) {
return Base64.getEncoder().encodeToString(getPublicKey(index).getAbyte())
}
}

View File

@@ -1,29 +0,0 @@
package io.kamax.mxisd.lookup;
import java.time.Instant;
public class ThreePid {
private String medium;
private String address;
private Instant validation;
public ThreePid(String medium, String address, Instant validation) {
this.medium = medium;
this.address = address;
this.validation = validation;
}
public String getMedium() {
return medium;
}
public String getAddress() {
return address;
}
public Instant getValidation() {
return validation;
}
}

View File

@@ -1,230 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.lookup.provider
import io.kamax.mxisd.config.ServerConfig
import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import org.xbill.DNS.Lookup
import org.xbill.DNS.SRVRecord
import org.xbill.DNS.Type
import java.util.concurrent.ForkJoinPool
import java.util.concurrent.RecursiveTask
import java.util.function.Function
@Component
class DnsLookupProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(DnsLookupProvider.class)
@Autowired
private ServerConfig srvCfg
@Autowired
private IRemoteIdentityServerFetcher fetcher
@Override
boolean isEnabled() {
return true
}
@Override
boolean isLocal() {
return false
}
@Override
int getPriority() {
return 10
}
String getSrvRecordName(String domain) {
return "_matrix-identity._tcp." + domain
}
Optional<String> getDomain(String email) {
int atIndex = email.lastIndexOf("@")
if (atIndex == -1) {
return Optional.empty()
}
return Optional.of(email.substring(atIndex + 1))
}
// TODO use caching mechanism
Optional<String> findIdentityServerForDomain(String domain) {
if (StringUtils.equals(srvCfg.getName(), domain)) {
log.info("We are authoritative for {}, no remote lookup", domain)
return Optional.empty()
}
log.info("Performing SRV lookup")
String lookupDns = getSrvRecordName(domain)
log.info("Lookup name: {}", lookupDns)
SRVRecord[] records = (SRVRecord[]) new Lookup(lookupDns, Type.SRV).run()
if (records != null) {
Arrays.sort(records, new Comparator<SRVRecord>() {
@Override
int compare(SRVRecord o1, SRVRecord o2) {
return Integer.compare(o1.getPriority(), o2.getPriority())
}
})
for (SRVRecord record : records) {
log.info("Found SRV record: {}", record.toString())
String baseUrl = "https://${record.getTarget().toString(true)}:${record.getPort()}"
if (fetcher.isUsable(baseUrl)) {
log.info("Found Identity Server for domain {} at {}", domain, baseUrl)
return Optional.of(baseUrl)
} else {
log.info("{} is not a usable Identity Server", baseUrl)
}
}
} else {
log.info("No SRV record for {}", lookupDns)
}
log.info("Performing basic lookup using domain name {}", domain)
String baseUrl = "https://" + domain
if (fetcher.isUsable(baseUrl)) {
log.info("Found Identity Server for domain {} at {}", domain, baseUrl)
return Optional.of(baseUrl)
} else {
log.info("{} is not a usable Identity Server", baseUrl)
return Optional.empty()
}
}
@Override
Optional<?> find(SingleLookupRequest request) {
if (!StringUtils.equals("email", request.getType())) { // TODO use enum
log.info("Skipping unsupported type {} for {}", request.getType(), request.getThreePid())
return Optional.empty()
}
log.info("Performing DNS lookup for {}", request.getThreePid())
String domain = request.getThreePid().substring(request.getThreePid().lastIndexOf("@") + 1)
log.info("Domain name for {}: {}", request.getThreePid(), domain)
Optional<String> baseUrl = findIdentityServerForDomain(domain)
if (baseUrl.isPresent()) {
return fetcher.find(baseUrl.get(), request.getType().toString(), request.getThreePid())
}
return Optional.empty()
}
@Override
List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
Map<String, List<ThreePidMapping>> domains = new HashMap<>()
for (ThreePidMapping mapping : mappings) {
if (!StringUtils.equals("email", mapping.getMedium())) {
log.info("Skipping unsupported type {} for {}", mapping.getMedium(), mapping.getValue())
continue
}
Optional<String> domainOpt = getDomain(mapping.getValue())
if (!domainOpt.isPresent()) {
log.warn("No domain for 3PID {}", mapping.getValue())
continue
}
String domain = domainOpt.get()
List<ThreePidMapping> domainMappings = domains.computeIfAbsent(domain, new Function<String, List<ThreePidMapping>>() {
@Override
List<ThreePidMapping> apply(String s) {
return new ArrayList<>()
}
})
domainMappings.add(mapping)
}
log.info("Looking mappings across {} domains", domains.keySet().size())
ForkJoinPool pool = new ForkJoinPool()
RecursiveTask<List<ThreePidMapping>> task = new RecursiveTask<List<ThreePidMapping>>() {
@Override
protected List<ThreePidMapping> compute() {
List<ThreePidMapping> mappingsFound = new ArrayList<>()
List<DomainBulkLookupTask> tasks = new ArrayList<>()
for (String domain : domains.keySet()) {
DomainBulkLookupTask domainTask = new DomainBulkLookupTask(domain, domains.get(domain))
domainTask.fork()
tasks.add(domainTask)
}
for (DomainBulkLookupTask task : tasks) {
mappingsFound.addAll(task.join())
}
return mappingsFound
}
}
pool.submit(task)
pool.shutdown()
List<ThreePidMapping> mappingsFound = task.join()
log.info("Found {} mappings overall", mappingsFound.size())
return mappingsFound
}
private class DomainBulkLookupTask extends RecursiveTask<List<ThreePidMapping>> {
private String domain
private List<ThreePidMapping> mappings
DomainBulkLookupTask(String domain, List<ThreePidMapping> mappings) {
this.domain = domain
this.mappings = mappings
}
@Override
protected List<ThreePidMapping> compute() {
List<ThreePidMapping> domainMappings = new ArrayList<>()
Optional<String> baseUrl = findIdentityServerForDomain(domain)
if (!baseUrl.isPresent()) {
log.info("No usable Identity server for domain {}", domain)
} else {
domainMappings.addAll(fetcher.find(baseUrl.get(), mappings))
log.info("Found {} mappings in domain {}", domainMappings.size(), domain)
}
return domainMappings
}
}
}

View File

@@ -1,172 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.lookup.provider
import io.kamax.mxisd.config.LdapConfig
import io.kamax.mxisd.config.ServerConfig
import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping
import org.apache.commons.lang.StringUtils
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException
import org.apache.directory.api.ldap.model.cursor.EntryCursor
import org.apache.directory.api.ldap.model.entry.Attribute
import org.apache.directory.api.ldap.model.entry.Entry
import org.apache.directory.api.ldap.model.message.SearchScope
import org.apache.directory.ldap.client.api.LdapConnection
import org.apache.directory.ldap.client.api.LdapNetworkConnection
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
@Component
class LdapProvider implements IThreePidProvider {
public static final String UID = "uid"
public static final String MATRIX_ID = "mxid"
private Logger log = LoggerFactory.getLogger(LdapProvider.class)
@Autowired
private ServerConfig srvCfg
@Autowired
private LdapConfig ldapCfg
@Override
boolean isEnabled() {
return ldapCfg.getEnabled()
}
@Override
boolean isLocal() {
return true
}
@Override
int getPriority() {
return 20
}
Optional<String> lookup(LdapConnection conn, String medium, String value) {
Optional<String> queryOpt = ldapCfg.getMapping(medium)
if (!queryOpt.isPresent()) {
log.warn("{} is not a configured 3PID type for LDAP lookup", medium)
return Optional.empty()
}
String searchQuery = queryOpt.get().replaceAll("%3pid", value)
EntryCursor cursor = conn.search(ldapCfg.getBaseDn(), searchQuery, SearchScope.SUBTREE, ldapCfg.getAttribute())
try {
while (cursor.next()) {
Entry entry = cursor.get()
log.info("Found possible match, DN: {}", entry.getDn().getName())
Attribute attribute = entry.get(ldapCfg.getAttribute())
if (attribute == null) {
log.info("DN {}: no attribute {}, skpping", entry.getDn(), ldapCfg.getAttribute())
continue
}
String data = attribute.get().toString()
if (data.length() < 1) {
log.info("DN {}: empty attribute {}, skipping", ldapCfg.getAttribute())
continue
}
StringBuilder matrixId = new StringBuilder()
// TODO Should we turn this block into a map of functions?
if (StringUtils.equals(UID, ldapCfg.getType())) {
matrixId.append("@").append(data).append(":").append(srvCfg.getName())
} else if (StringUtils.equals(MATRIX_ID, ldapCfg.getType())) {
matrixId.append(data)
} else {
log.warn("Bind was found but type {} is not supported", ldapCfg.getType())
continue
}
log.info("DN {} is a valid match", entry.getDn().getName())
return Optional.of(matrixId.toString())
}
} catch (CursorLdapReferralException e) {
log.warn("3PID {} is only available via referral, skipping", value)
} finally {
cursor.close()
}
return Optional.empty()
}
@Override
Optional<?> find(SingleLookupRequest request) {
log.info("Performing LDAP lookup ${request.getThreePid()} of type ${request.getType()}")
LdapConnection conn = new LdapNetworkConnection(ldapCfg.getHost(), ldapCfg.getPort(), ldapCfg.getTls())
try {
conn.bind(ldapCfg.getBindDn(), ldapCfg.getBindPassword())
Optional<String> mxid = lookup(conn, request.getType(), request.getThreePid())
if (mxid.isPresent()) {
return Optional.of([
address : request.getThreePid(),
medium : request.getType(),
mxid : mxid.get(),
not_before: 0,
not_after : 9223372036854775807,
ts : 0
])
}
} finally {
conn.close()
}
log.info("No match found")
return Optional.empty()
}
@Override
List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
log.info("Looking up {} mappings", mappings.size())
List<ThreePidMapping> mappingsFound = new ArrayList<>()
LdapConnection conn = new LdapNetworkConnection(ldapCfg.getHost(), ldapCfg.getPort())
try {
conn.bind(ldapCfg.getBindDn(), ldapCfg.getBindPassword())
for (ThreePidMapping mapping : mappings) {
try {
Optional<String> mxid = lookup(conn, mapping.getMedium(), mapping.getValue())
if (mxid.isPresent()) {
mapping.setMxid(mxid.get())
mappingsFound.add(mapping)
}
} catch (IllegalArgumentException e) {
log.warn("{} is not a supported 3PID type for LDAP lookup", mapping.getMedium())
}
}
} finally {
conn.close()
}
return mappingsFound
}
}

View File

@@ -1,153 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.lookup.provider
import groovy.json.JsonException
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import io.kamax.mxisd.controller.v1.ClientBulkLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher
import org.apache.http.HttpEntity
import org.apache.http.HttpResponse
import org.apache.http.client.HttpClient
import org.apache.http.client.entity.EntityBuilder
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.ContentType
import org.apache.http.impl.client.HttpClients
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.annotation.Lazy
import org.springframework.context.annotation.Scope
import org.springframework.stereotype.Component
@Component
@Scope("prototype")
@Lazy
public class RemoteIdentityServerFetcher implements IRemoteIdentityServerFetcher {
public static final String THREEPID_TEST_MEDIUM = "email"
public static final String THREEPID_TEST_ADDRESS = "john.doe@example.org"
private Logger log = LoggerFactory.getLogger(RemoteIdentityServerFetcher.class)
private JsonSlurper json = new JsonSlurper()
@Override
boolean isUsable(String remote) {
try {
HttpURLConnection rootSrvConn = (HttpURLConnection) new URL(
"${remote}/_matrix/identity/api/v1/lookup?medium=${THREEPID_TEST_MEDIUM}&address=${THREEPID_TEST_ADDRESS}"
).openConnection()
// TODO turn this into a configuration property
rootSrvConn.setConnectTimeout(2000)
if (rootSrvConn.getResponseCode() != 200) {
return false
}
def output = json.parseText(rootSrvConn.getInputStream().getText())
if (output['address']) {
return false
}
return true
} catch (IOException | JsonException e) {
log.info("{} is not a usable Identity Server: {}", remote, e.getMessage())
return false
}
}
@Override
Optional<?> find(String remote, String type, String threePid) {
log.info("Looking up {} 3PID {} using {}", type, threePid, remote)
HttpURLConnection rootSrvConn = (HttpURLConnection) new URL(
"${remote}/_matrix/identity/api/v1/lookup?medium=${type}&address=${threePid}"
).openConnection()
try {
def output = json.parseText(rootSrvConn.getInputStream().getText())
if (output['address']) {
log.info("Found 3PID mapping: {}", output)
return Optional.of(output)
}
log.info("Empty 3PID mapping from {}", remote)
return Optional.empty()
} catch (IOException e) {
log.warn("Error looking up 3PID mapping {}: {}", threePid, e.getMessage())
return Optional.empty()
} catch (JsonException e) {
log.warn("Invalid JSON answer from {}", remote)
return Optional.empty()
}
}
@Override
List<ThreePidMapping> find(String remote, List<ThreePidMapping> mappings) {
List<ThreePidMapping> mappingsFound = new ArrayList<>()
ClientBulkLookupRequest mappingRequest = new ClientBulkLookupRequest()
mappingRequest.setMappings(mappings)
String url = "${remote}/_matrix/identity/api/v1/bulk_lookup"
HttpClient client = HttpClients.createDefault()
try {
HttpPost request = new HttpPost(url)
request.setEntity(
EntityBuilder.create()
.setText(JsonOutput.toJson(mappingRequest))
.setContentType(ContentType.APPLICATION_JSON)
.build()
)
HttpResponse response = client.execute(request)
try {
if (response.getStatusLine().getStatusCode() != 200) {
log.info("Could not perform lookup at {} due to HTTP return code: {}", url, response.getStatusLine().getStatusCode())
return mappingsFound
}
HttpEntity entity = response.getEntity()
if (entity != null) {
ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(entity.getContent().getText())
for (List<String> mappingRaw : input.getThreepids()) {
ThreePidMapping mapping = new ThreePidMapping()
mapping.setMedium(mappingRaw.get(0))
mapping.setValue(mappingRaw.get(1))
mapping.setMxid(mappingRaw.get(2))
mappingsFound.add(mapping)
}
} else {
log.info("HTTP response from {} was empty", remote)
}
return mappingsFound
} finally {
response.close()
}
} finally {
client.close()
}
}
}

View File

@@ -1,36 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.lookup.strategy
import io.kamax.mxisd.lookup.BulkLookupRequest
import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.provider.IThreePidProvider
interface LookupStrategy {
List<IThreePidProvider> getLocalProviders()
Optional<?> find(SingleLookupRequest request)
List<ThreePidMapping> find(BulkLookupRequest requests)
}

View File

@@ -1,160 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.lookup.strategy
import edazdarevic.commons.net.CIDRUtils
import io.kamax.mxisd.config.RecursiveLookupConfig
import io.kamax.mxisd.lookup.ALookupRequest
import io.kamax.mxisd.lookup.BulkLookupRequest
import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher
import io.kamax.mxisd.lookup.provider.IThreePidProvider
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.InitializingBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.util.function.Predicate
import java.util.stream.Collectors
@Component
class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBean {
private Logger log = LoggerFactory.getLogger(RecursivePriorityLookupStrategy.class)
@Autowired
private RecursiveLookupConfig recursiveCfg
@Autowired
private List<IThreePidProvider> providers
@Autowired
private IBridgeFetcher bridge
private List<CIDRUtils> allowedCidr = new ArrayList<>()
@Override
void afterPropertiesSet() throws Exception {
log.info("Found ${providers.size()} providers")
providers.sort(new Comparator<IThreePidProvider>() {
@Override
int compare(IThreePidProvider o1, IThreePidProvider o2) {
return Integer.compare(o2.getPriority(), o1.getPriority())
}
})
log.info("Recursive lookup enabled: {}", recursiveCfg.isEnabled())
for (String cidr : recursiveCfg.getAllowedCidr()) {
log.info("{} is allowed for recursion", cidr)
allowedCidr.add(new CIDRUtils(cidr))
}
}
boolean isAllowedForRecursive(String source) {
boolean canRecurse = false
if (recursiveCfg.isEnabled()) {
log.debug("Checking {} CIDRs for recursion", allowedCidr.size())
for (CIDRUtils cidr : allowedCidr) {
if (cidr.isInRange(source)) {
log.debug("{} is in range {}, allowing recursion", source, cidr.getNetworkAddress())
canRecurse = true
break
} else {
log.debug("{} is not in range {}", source, cidr.getNetworkAddress())
}
}
}
return canRecurse
}
List<IThreePidProvider> listUsableProviders(ALookupRequest request) {
List<IThreePidProvider> usableProviders = new ArrayList<>()
boolean canRecurse = isAllowedForRecursive(request.getRequester())
log.info("Host {} allowed for recursion: {}", request.getRequester(), canRecurse)
for (IThreePidProvider provider : providers) {
if (provider.isEnabled() && (provider.isLocal() || canRecurse)) {
usableProviders.add(provider)
}
}
return usableProviders
}
@Override
List<IThreePidProvider> getLocalProviders() {
return providers.stream().filter(new Predicate<IThreePidProvider>() {
@Override
boolean test(IThreePidProvider iThreePidProvider) {
return iThreePidProvider.isEnabled() && iThreePidProvider.isLocal()
}
}).collect(Collectors.toList())
}
@Override
Optional<?> find(SingleLookupRequest request) {
for (IThreePidProvider provider : listUsableProviders(request)) {
Optional<?> lookupDataOpt = provider.find(request)
if (lookupDataOpt.isPresent()) {
return lookupDataOpt
}
}
if (recursiveCfg.getBridge().getEnabled() && (!recursiveCfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))) {
log.info("Using bridge failover for lookup")
return bridge.find(request)
}
return Optional.empty()
}
@Override
List<ThreePidMapping> find(BulkLookupRequest request) {
List<ThreePidMapping> mapToDo = new ArrayList<>(request.getMappings())
List<ThreePidMapping> mapFoundAll = new ArrayList<>()
for (IThreePidProvider provider : listUsableProviders(request)) {
if (mapToDo.isEmpty()) {
log.info("No more mappings to lookup")
break
} else {
log.info("{} mappings remaining overall", mapToDo.size())
}
log.info("Using provider {} for remaining mappings", provider.getClass().getSimpleName())
List<ThreePidMapping> mapFound = provider.populate(mapToDo)
log.info("Provider {} returned {} mappings", provider.getClass().getSimpleName(), mapFound.size())
mapFoundAll.addAll(mapFound)
mapToDo.removeAll(mapFound)
}
return mapFoundAll
}
}

View File

@@ -1,164 +0,0 @@
package io.kamax.mxisd.mapping;
import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.lookup.ThreePid;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.*;
@Component
public class MappingManager {
private Logger log = LoggerFactory.getLogger(MappingManager.class);
private Map<String, Session> threePidLookups = new WeakHashMap<>();
private Map<String, Session> sessions = new HashMap<>();
private Timer cleaner;
MappingManager() {
cleaner = new Timer();
cleaner.schedule(new TimerTask() {
@Override
public void run() {
List<Session> sList = new ArrayList<>(sessions.values());
for (Session s : sList) {
if (s.timestamp.plus(24, ChronoUnit.HOURS).isBefore(Instant.now())) { // TODO config timeout
log.info("Session {} is obsolete, removing", s.sid);
sessions.remove(s.sid);
threePidLookups.remove(s.hash);
}
}
}
}, 0, 10 * 1000); // TODO config delay
}
public String create(MappingSession data) {
String sid;
do {
sid = Long.toString(System.currentTimeMillis());
} while (sessions.containsKey(sid));
String threePidHash = data.getMedium() + data.getValue();
Session session = threePidLookups.get(threePidHash);
if (session != null) {
sid = session.sid;
} else {
// TODO perform some kind of validation
session = new Session(sid, threePidHash, data);
sessions.put(sid, session);
threePidLookups.put(threePidHash, session);
}
log.info("Created new session {} to validate {} {}", sid, session.medium, session.address);
return sid;
}
public void validate(String sid, String secret, String token) {
Session s = sessions.get(sid);
if (s == null || !StringUtils.equals(s.secret, secret)) {
throw new BadRequestException("sid or secret are not valid");
}
// TODO actually check token
s.isValidated = true;
s.validationTimestamp = Instant.now();
}
public Optional<ThreePid> getValidated(String sid, String secret) {
Session s = sessions.get(sid);
if (s != null && StringUtils.equals(s.secret, secret)) {
return Optional.of(new ThreePid(s.medium, s.address, s.validationTimestamp));
}
return Optional.empty();
}
public void bind(String sid, String secret, String mxid) {
Session s = sessions.get(sid);
if (s == null || !StringUtils.equals(s.secret, secret)) {
throw new BadRequestException("sid or secret are not valid");
}
log.info("Performed bind for mxid {}", mxid);
// TODO perform bind, whatever it is
}
private class Session {
private String sid;
private String hash;
private Instant timestamp;
private Instant validationTimestamp;
private boolean isValidated;
private String secret;
private String medium;
private String address;
public Session(String sid, String hash, MappingSession data) {
this.sid = sid;
this.hash = hash;
timestamp = Instant.now();
validationTimestamp = Instant.now();
secret = data.getSecret();
medium = data.getMedium();
address = data.getValue();
}
public Instant getTimestamp() {
return timestamp;
}
public void setTimestamp(Instant timestamp) {
this.timestamp = timestamp;
}
public Instant getValidationTimestamp() {
return validationTimestamp;
}
public void setValidationTimestamp(Instant validationTimestamp) {
this.validationTimestamp = validationTimestamp;
}
public boolean isValidated() {
return isValidated;
}
public void setValidated(boolean validated) {
isValidated = validated;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public String getMedium() {
return medium;
}
public void setMedium(String medium) {
this.medium = medium;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
}

View File

@@ -1,15 +0,0 @@
package io.kamax.mxisd.mapping;
public interface MappingSession {
String getServer();
String getSecret();
int getAttempt();
String getMedium();
String getValue();
}

View File

@@ -1,59 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.signature
import io.kamax.mxisd.config.ServerConfig
import io.kamax.mxisd.key.KeyManager
import net.i2p.crypto.eddsa.EdDSAEngine
import org.springframework.beans.factory.InitializingBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.security.MessageDigest
@Component
class SignatureManager implements InitializingBean {
@Autowired
private KeyManager keyMgr
@Autowired
private ServerConfig srvCfg
private EdDSAEngine signEngine
Map<?, ?> signMessage(String message) {
byte[] signRaw = signEngine.signOneShot(message.getBytes())
String sign = Base64.getEncoder().encodeToString(signRaw)
return [
"${srvCfg.getName()}": [
"ed25519:${keyMgr.getCurrentIndex()}": sign
]
]
}
@Override
void afterPropertiesSet() throws Exception {
signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getSpecs().getHashAlgorithm()))
signEngine.initSign(keyMgr.getPrivateKey(keyMgr.getCurrentIndex()))
}
}

View File

@@ -18,16 +18,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd
package io.kamax.mxisd;
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
class MatrixIdentityServerApplication {
public class MatrixIdentityServerApplication {
static void main(String[] args) throws Exception {
SpringApplication.run(MatrixIdentityServerApplication.class, args)
public static void main(String[] args) {
SpringApplication.run(MatrixIdentityServerApplication.class, args);
}
}

View File

@@ -0,0 +1,46 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd;
// FIXME consider integrating in matrix-java-sdk?
public class UserID {
private String type;
private String value;
protected UserID() {
// stub for (de)serialization
}
public UserID(String type, String value) {
this.type = type;
this.value = value;
}
public String getType() {
return type;
}
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,47 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd;
import org.apache.commons.lang.StringUtils;
// FIXME consider integrating in matrix-java-sdk?
public enum UserIdType {
Localpart("localpart"),
MatrixID("mxid"),
EmailLocalpart("email_localpart"),
Email("email");
private String id;
UserIdType(String id) {
this.id = id;
}
public String getId() {
return id;
}
public boolean is(String id) {
return StringUtils.equalsIgnoreCase(this.id, id);
}
}

View File

@@ -0,0 +1,98 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.auth;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix.ThreePid;
import io.kamax.matrix._MatrixID;
import io.kamax.matrix._ThreePid;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.invitation.InvitationManager;
import io.kamax.mxisd.lookup.ThreePidMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class AuthManager {
private Logger log = LoggerFactory.getLogger(AuthManager.class);
private List<AuthenticatorProvider> providers;
@Autowired
private MatrixConfig mxCfg;
@Autowired
private InvitationManager invMgr;
public AuthManager(List<AuthenticatorProvider> providers) {
this.providers = new ArrayList<>(providers);
}
public UserAuthResult authenticate(String id, String password) {
_MatrixID mxid = MatrixID.asAcceptable(id);
for (AuthenticatorProvider provider : providers) {
if (!provider.isEnabled()) {
continue;
}
log.info("Attempting auth with " + provider.getClass().getSimpleName());
BackendAuthResult result = provider.authenticate(mxid, password);
if (result.isSuccess()) {
String mxId;
if (UserIdType.Localpart.is(result.getId().getType())) {
mxId = MatrixID.from(result.getId().getValue(), mxCfg.getDomain()).acceptable().getId();
} else if (UserIdType.MatrixID.is(result.getId().getType())) {
mxId = MatrixID.asAcceptable(result.getId().getValue()).getId();
} else {
log.warn("Unsupported User ID type {} for backend {}", result.getId().getType(), provider.getClass().getSimpleName());
continue;
}
UserAuthResult authResult = new UserAuthResult().success(result.getProfile().getDisplayName());
for (_ThreePid pid : result.getProfile().getThreePids()) {
authResult.withThreePid(pid.getMedium(), pid.getAddress());
}
log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName());
for (ThreePid pid : authResult.getThreePids()) {
log.info("Processing {} for {}", pid, id);
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, mxId));
}
invMgr.lookupMappingsForInvites();
return authResult;
}
}
return new UserAuthResult().failure();
}
}

View File

@@ -0,0 +1,86 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.auth;
import io.kamax.matrix.ThreePid;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class UserAuthResult {
private boolean success;
private String displayName;
private String photo;
private Set<ThreePid> threePids = new HashSet<>();
public UserAuthResult failure() {
success = false;
displayName = null;
photo = null;
threePids.clear();
return this;
}
public UserAuthResult success(String displayName) {
setSuccess(true);
setDisplayName(displayName);
return this;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getPhoto() {
return photo;
}
public void setPhoto(String photo) {
this.photo = photo;
}
public UserAuthResult withThreePid(String medium, String address) {
threePids.add(new ThreePid(medium, address));
return this;
}
public Set<ThreePid> getThreePids() {
return Collections.unmodifiableSet(threePids);
}
}

View File

@@ -18,11 +18,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.exception
package io.kamax.mxisd.auth.provider;
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus
import io.kamax.matrix._MatrixID;
public interface AuthenticatorProvider {
boolean isEnabled();
BackendAuthResult authenticate(_MatrixID mxid, String password);
@ResponseStatus(value = HttpStatus.NOT_IMPLEMENTED)
class NotImplementedException extends RuntimeException {
}

View File

@@ -0,0 +1,96 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.auth.provider;
import io.kamax.matrix.ThreePid;
import io.kamax.mxisd.UserID;
import io.kamax.mxisd.UserIdType;
import java.util.HashSet;
import java.util.Set;
public class BackendAuthResult {
public static class BackendAuthProfile {
private String displayName;
private Set<ThreePid> threePids = new HashSet<>();
public String getDisplayName() {
return displayName;
}
public Set<ThreePid> getThreePids() {
return threePids;
}
}
public static BackendAuthResult failure() {
BackendAuthResult r = new BackendAuthResult();
r.success = false;
return r;
}
public BackendAuthResult fail() {
success = false;
return this;
}
public static BackendAuthResult success(String id, UserIdType type, String displayName) {
return success(id, type.getId(), displayName);
}
public static BackendAuthResult success(String id, String type, String displayName) {
BackendAuthResult r = new BackendAuthResult();
r.succeed(id, type, displayName);
return r;
}
public BackendAuthResult succeed(String id, String type, String displayName) {
this.success = true;
this.id = new UserID(type, id);
this.profile.displayName = displayName;
return this;
}
private Boolean success;
private UserID id;
private BackendAuthProfile profile = new BackendAuthProfile();
public Boolean isSuccess() {
return success;
}
public UserID getId() {
return id;
}
public BackendAuthProfile getProfile() {
return profile;
}
public BackendAuthResult withThreePid(ThreePid threePid) {
this.profile.threePids.add(threePid);
return this;
}
}

View File

@@ -0,0 +1,163 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.firebase;
import com.google.firebase.auth.UserInfo;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import io.kamax.matrix.ThreePid;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class GoogleFirebaseAuthenticator extends GoogleFirebaseBackend implements AuthenticatorProvider {
private Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class);
private PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
public GoogleFirebaseAuthenticator(boolean isEnabled, String credsPath, String db) {
super(isEnabled, "AuthenticationProvider", credsPath, db);
}
private void waitOnLatch(BackendAuthResult result, CountDownLatch l, String purpose) {
try {
l.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.warn("Interrupted while waiting for " + purpose);
result.fail();
}
}
private void toEmail(BackendAuthResult result, String email) {
if (StringUtils.isBlank(email)) {
return;
}
result.withThreePid(new ThreePid(ThreePidMedium.Email.getId(), email));
}
private void toMsisdn(BackendAuthResult result, String phoneNumber) {
if (StringUtils.isBlank(phoneNumber)) {
return;
}
try {
String number = phoneUtil.format(
phoneUtil.parse(
phoneNumber,
null // No default region
),
PhoneNumberUtil.PhoneNumberFormat.E164
).substring(1); // We want without the leading +
result.withThreePid(new ThreePid(ThreePidMedium.PhoneNumber.getId(), number));
} catch (NumberParseException e) {
log.warn("Invalid phone number: {}", phoneNumber);
}
}
private void waitOnLatch(CountDownLatch l) {
try {
l.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.warn("Interrupted while waiting for Firebase auth check");
}
}
@Override
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
if (!isEnabled()) {
throw new IllegalStateException();
}
log.info("Trying to authenticate {}", mxid);
final BackendAuthResult result = BackendAuthResult.failure();
String localpart = mxid.getLocalPart();
CountDownLatch l = new CountDownLatch(1);
getFirebase().verifyIdToken(password).addOnSuccessListener(token -> {
try {
if (!StringUtils.equals(localpart, token.getUid())) {
log.info("Failure to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", mxid, localpart, token.getUid());
result.fail();
return;
}
result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), token.getName());
log.info("{} was successfully authenticated", mxid);
log.info("Fetching profile for {}", mxid);
CountDownLatch userRecordLatch = new CountDownLatch(1);
getFirebase().getUser(token.getUid()).addOnSuccessListener(user -> {
try {
toEmail(result, user.getEmail());
toMsisdn(result, user.getPhoneNumber());
for (UserInfo info : user.getProviderData()) {
toEmail(result, info.getEmail());
toMsisdn(result, info.getPhoneNumber());
}
log.info("Got {} 3PIDs in profile", result.getProfile().getThreePids().size());
} finally {
userRecordLatch.countDown();
}
}).addOnFailureListener(e -> {
try {
log.warn("Unable to fetch Firebase user profile for {}", mxid);
result.fail();
} finally {
userRecordLatch.countDown();
}
});
waitOnLatch(result, userRecordLatch, "Firebase user profile");
} finally {
l.countDown();
}
}).addOnFailureListener(e -> {
try {
if (e instanceof IllegalArgumentException) {
log.info("Failure to authenticate {}: invalid firebase token", mxid);
} else {
log.info("Failure to authenticate {}: {}", mxid, e.getMessage(), e);
log.info("Exception", e);
}
result.fail();
} finally {
l.countDown();
}
});
waitOnLatch(result, l, "Firebase auth check");
return result;
}
}

View File

@@ -0,0 +1,88 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.firebase;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseCredential;
import com.google.firebase.auth.FirebaseCredentials;
import com.google.firebase.database.FirebaseDatabase;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.IOException;
public class GoogleFirebaseBackend {
private Logger log = LoggerFactory.getLogger(GoogleFirebaseBackend.class);
private boolean isEnabled;
private FirebaseAuth fbAuth;
protected FirebaseDatabase fbDb;
GoogleFirebaseBackend(boolean isEnabled, String name, String credsPath, String db) {
this.isEnabled = isEnabled;
if (!isEnabled) {
return;
}
try {
FirebaseApp fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), name);
fbAuth = FirebaseAuth.getInstance(fbApp);
FirebaseDatabase.getInstance(fbApp);
log.info("Google Firebase Authentication is ready");
} catch (IOException e) {
throw new RuntimeException("Error when initializing Firebase", e);
}
}
private FirebaseCredential getCreds(String credsPath) throws IOException {
if (StringUtils.isNotBlank(credsPath)) {
return FirebaseCredentials.fromCertificate(new FileInputStream(credsPath));
} else {
return FirebaseCredentials.applicationDefault();
}
}
private FirebaseOptions getOpts(String credsPath, String db) throws IOException {
if (StringUtils.isBlank(db)) {
throw new IllegalArgumentException("Firebase database is not configured");
}
return new FirebaseOptions.Builder()
.setCredential(getCreds(credsPath))
.setDatabaseUrl(db)
.build();
}
FirebaseAuth getFirebase() {
return fbAuth;
}
public boolean isEnabled() {
return isEnabled;
}
}

View File

@@ -0,0 +1,132 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.firebase;
import com.google.firebase.auth.UserRecord;
import com.google.firebase.tasks.OnFailureListener;
import com.google.firebase.tasks.OnSuccessListener;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class GoogleFirebaseProvider extends GoogleFirebaseBackend implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(GoogleFirebaseProvider.class);
private String domain;
public GoogleFirebaseProvider(boolean isEnabled, String credsPath, String db, String domain) {
super(isEnabled, "ThreePidProvider", credsPath, db);
this.domain = domain;
}
private String getMxid(UserRecord record) {
return new MatrixID(record.getUid(), domain).getId();
}
@Override
public boolean isLocal() {
return true;
}
@Override
public int getPriority() {
return 25;
}
private void waitOnLatch(CountDownLatch l) {
try {
l.await(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.warn("Interrupted while waiting for Firebase auth check");
}
}
private Optional<UserRecord> findInternal(String medium, String address) {
final UserRecord[] r = new UserRecord[1];
CountDownLatch l = new CountDownLatch(1);
OnSuccessListener<UserRecord> success = result -> {
log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid());
r[0] = result;
l.countDown();
};
OnFailureListener failure = e -> {
log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage());
r[0] = null;
l.countDown();
};
if (ThreePidMedium.Email.is(medium)) {
log.info("Performing E-mail 3PID lookup for {}", address);
getFirebase().getUserByEmail(address)
.addOnSuccessListener(success)
.addOnFailureListener(failure);
waitOnLatch(l);
} else if (ThreePidMedium.PhoneNumber.is(medium)) {
log.info("Performing msisdn 3PID lookup for {}", address);
getFirebase().getUserByPhoneNumber(address)
.addOnSuccessListener(success)
.addOnFailureListener(failure);
waitOnLatch(l);
} else {
log.info("{} is not a supported 3PID medium", medium);
r[0] = null;
}
return Optional.ofNullable(r[0]);
}
@Override
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
Optional<UserRecord> urOpt = findInternal(request.getType(), request.getThreePid());
return urOpt.map(userRecord -> new SingleLookupReply(request, getMxid(userRecord)));
}
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
List<ThreePidMapping> results = new ArrayList<>();
mappings.parallelStream().forEach(o -> {
Optional<UserRecord> urOpt = findInternal(o.getMedium(), o.getValue());
if (urOpt.isPresent()) {
ThreePidMapping result = new ThreePidMapping();
result.setMedium(o.getMedium());
result.setValue(o.getValue());
result.setMxid(getMxid(urOpt.get()));
results.add(result);
}
});
return results;
}
}

View File

@@ -0,0 +1,134 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sàrl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.google;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import io.kamax.matrix._MatrixID;
import io.kamax.matrix.ThreePid;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.GoogleConfig;
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@Component
public class GoogleProviderBackend implements AuthenticatorProvider {
private final Logger log = LoggerFactory.getLogger(GoogleProviderBackend.class);
private final GoogleConfig cfg;
private final LookupStrategy lookup;
private GoogleIdTokenVerifier verifier;
public Optional<GoogleIdToken> extractToken(String data) throws GeneralSecurityException, IOException {
return Optional.ofNullable(verifier.verify(data));
}
public List<ThreePid> extractThreepids(GoogleIdToken token) {
List<ThreePid> tpids = new ArrayList<>();
tpids.add(new ThreePid("io.kamax.google.id", token.getPayload().getSubject()));
if (token.getPayload().getEmailVerified()) {
tpids.add(new ThreePid("email", token.getPayload().getEmail()));
}
return tpids;
}
@Autowired
public GoogleProviderBackend(GoogleConfig cfg, LookupStrategy lookup) {
this.cfg = cfg;
this.lookup = lookup;
if (isEnabled()) {
try {
HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory)
.setAudience(Collections.singletonList(cfg.getClient().getId()))
.build();
} catch (IOException | GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
}
@Override
public boolean isEnabled() {
return cfg.isEnabled();
}
@Override
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
BackendAuthResult result = new BackendAuthResult();
try {
return extractToken(password).map(idToken -> {
GoogleIdToken.Payload payload = idToken.getPayload();
if (!payload.getEmailVerified()) { // We only want users who validated their email
return BackendAuthResult.failure();
}
// Get user identifier
String userId = payload.getSubject();
// We validate that the user who authenticated has his Google account associated already
return lookup.find("io.kamax.google.id", userId, false).map(r -> {
if (!r.getMxid().equals(mxid)) {
return result.fail();
}
// Get profile information from payload
extractThreepids(idToken).forEach(result::withThreePid);
String name = (String) payload.get("name");
payload.getUnknownKeys().keySet().forEach(key -> {
log.info("Unknown key in Google profile: {} -> ", key, payload.get(key));
});
return result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), name);
}).orElse(BackendAuthResult.failure());
}).orElse(BackendAuthResult.failure());
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
} catch (IOException e) {
log.error("Unable to authenticate via Google due to network error", e);
return result.fail();
}
}
}

View File

@@ -0,0 +1,170 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.ldap;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import io.kamax.matrix.ThreePid;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.LdapConfig;
import io.kamax.mxisd.util.GsonUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@Component
public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvider {
private Logger log = LoggerFactory.getLogger(LdapAuthProvider.class);
private PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
@Autowired
public LdapAuthProvider(LdapConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
@Override
public boolean isEnabled() {
return getCfg().isEnabled();
}
private Optional<String> getMsisdn(String phoneNumber) {
try { // FIXME export into dedicated ThreePid class within SDK (copy from Firebase Auth)
return Optional.of(phoneUtil.format(
phoneUtil.parse(
phoneNumber,
null // No default region
),
PhoneNumberUtil.PhoneNumberFormat.E164
).substring(1)); // We want without the leading +
} catch (NumberParseException e) {
log.warn("Invalid phone number: {}", phoneNumber);
return Optional.empty();
}
}
@Override
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
log.info("Performing auth for {}", mxid);
try (LdapConnection conn = getConn()) {
bind(conn);
String uidType = getAt().getUid().getType();
String userFilterValue = StringUtils.equals(LdapBackend.UID, uidType) ? mxid.getLocalPart() : mxid.getId();
if (StringUtils.isBlank(userFilterValue)) {
log.warn("Username is empty, failing auth");
return BackendAuthResult.failure();
}
String userFilter = "(" + getUidAtt() + "=" + userFilterValue + ")";
userFilter = buildWithFilter(userFilter, getCfg().getAuth().getFilter());
Set<String> attributes = new HashSet<>();
attributes.add(getUidAtt());
attributes.add(getAt().getName());
getAt().getThreepid().forEach((k, v) -> attributes.addAll(v));
String[] attArray = new String[attributes.size()];
attributes.toArray(attArray);
log.debug("Base DN: {}", getBaseDn());
log.debug("Query: {}", userFilter);
log.debug("Attributes: {}", GsonUtil.build().toJson(attArray));
try (EntryCursor cursor = conn.search(getBaseDn(), userFilter, SearchScope.SUBTREE, attArray)) {
while (cursor.next()) {
Entry entry = cursor.get();
String dn = entry.getDn().getName();
log.info("Checking possible match, DN: {}", dn);
if (!getAttribute(entry, getUidAtt()).isPresent()) {
continue;
}
log.info("Attempting authentication on LDAP for {}", dn);
try {
conn.bind(entry.getDn(), password);
} catch (LdapException e) {
log.info("Unable to bind using {} because {}", entry.getDn().getName(), e.getMessage());
return BackendAuthResult.failure();
}
Attribute nameAttribute = entry.get(getAt().getName());
String name = nameAttribute != null ? nameAttribute.get().toString() : null;
log.info("Authentication successful for {}", entry.getDn().getName());
log.info("DN {} is a valid match", dn);
// TODO should we canonicalize the MXID?
BackendAuthResult result = BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, name);
log.info("Processing 3PIDs for profile");
getAt().getThreepid().forEach((k, v) -> {
log.info("Processing 3PID type {}", k);
v.forEach(attId -> {
List<String> values = getAttributes(entry, attId);
log.info("\tAttribute {} has {} value(s)", attId, values.size());
getAttributes(entry, attId).forEach(tpidValue -> {
if (ThreePidMedium.PhoneNumber.is(k)) {
tpidValue = getMsisdn(tpidValue).orElse(tpidValue);
}
result.withThreePid(new ThreePid(k, tpidValue));
});
});
});
log.info("Found {} 3PIDs", result.getProfile().getThreePids().size());
return result;
}
} catch (CursorLdapReferralException e) {
log.warn("Entity for {} is only available via referral, skipping", mxid);
}
log.info("No match were found for {}", mxid);
return BackendAuthResult.failure();
} catch (LdapException | IOException | CursorException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,160 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.ldap;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.LdapConfig;
import org.apache.commons.lang.StringUtils;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.AttributeUtils;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public abstract class LdapBackend {
public static final String UID = "uid";
public static final String MATRIX_ID = "mxid";
private Logger log = LoggerFactory.getLogger(LdapBackend.class);
private LdapConfig cfg;
private MatrixConfig mxCfg;
public LdapBackend(LdapConfig cfg, MatrixConfig mxCfg) {
this.cfg = cfg;
this.mxCfg = mxCfg;
}
protected LdapConfig getCfg() {
return cfg;
}
protected String getBaseDn() {
return cfg.getConnection().getBaseDn();
}
protected LdapConfig.Attribute getAt() {
return cfg.getAttribute();
}
protected String getUidAtt() {
return getAt().getUid().getValue();
}
protected synchronized LdapConnection getConn() throws LdapException {
return new LdapNetworkConnection(cfg.getConnection().getHost(), cfg.getConnection().getPort(), cfg.getConnection().isTls());
}
protected void bind(LdapConnection conn) throws LdapException {
if (StringUtils.isBlank(cfg.getConnection().getBindDn()) && StringUtils.isBlank(cfg.getConnection().getBindPassword())) {
conn.anonymousBind();
} else {
conn.bind(cfg.getConnection().getBindDn(), cfg.getConnection().getBindPassword());
}
}
protected String buildWithFilter(String base, String filter) {
if (StringUtils.isBlank(filter)) {
return base;
} else {
return "(&" + filter + base + ")";
}
}
public static String buildOrQuery(String value, List<String> attributes) {
if (attributes.size() < 1) {
throw new IllegalArgumentException();
}
StringBuilder builder = new StringBuilder();
builder.append("(|");
attributes.forEach(s -> {
builder.append("(");
builder.append(s).append("=").append(value).append(")");
});
builder.append(")");
return builder.toString();
}
public static String buildOrQuery(String value, String... attributes) {
return buildOrQuery(value, Arrays.asList(attributes));
}
public String buildOrQueryWithFilter(String filter, String value, String... attributes) {
return buildWithFilter(buildOrQuery(value, attributes), filter);
}
public String buildMatrixIdFromUid(String uid) {
String uidType = getCfg().getAttribute().getUid().getType();
if (StringUtils.equals(UID, uidType)) {
return "@" + uid + ":" + mxCfg.getDomain();
} else if (StringUtils.equals(MATRIX_ID, uidType)) {
return uid;
} else {
throw new IllegalArgumentException("Bind type " + uidType + " is not supported");
}
}
public Optional<String> getAttribute(Entry entry, String attName) {
Attribute attribute = entry.get(attName);
if (attribute == null) {
return Optional.empty();
}
String value = attribute.get().toString();
if (StringUtils.isBlank(value)) {
log.info("DN {}: empty attribute {}, skipping", attName);
return Optional.empty();
}
return Optional.of(value);
}
public List<String> getAttributes(Entry entry, String attName) {
List<String> values = new ArrayList<>();
javax.naming.directory.Attribute att = AttributeUtils.toAttributes(entry).get(attName);
if (att == null) {
return values;
}
try {
NamingEnumeration<?> list = att.getAll();
while (list.hasMore()) {
values.add(list.next().toString());
}
} catch (NamingException e) {
log.warn("Error while processing LDAP attribute {}, result could be incomplete!", attName, e);
}
return values;
}
}

View File

@@ -0,0 +1,123 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.ldap;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.LdapConfig;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.directory.IDirectoryProvider;
import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.util.GsonUtil;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Component
public class LdapDirectoryProvider extends LdapBackend implements IDirectoryProvider {
private Logger log = LoggerFactory.getLogger(LdapDirectoryProvider.class);
@Autowired
public LdapDirectoryProvider(LdapConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
@Override
public boolean isEnabled() {
return getCfg().isEnabled();
}
protected UserDirectorySearchResult search(String query, List<String> attributes) {
UserDirectorySearchResult result = new UserDirectorySearchResult();
result.setLimited(false);
try (LdapConnection conn = getConn()) {
bind(conn);
LdapConfig.Attribute atCfg = getCfg().getAttribute();
attributes = new ArrayList<>(attributes);
attributes.add(getUidAtt());
String[] attArray = new String[attributes.size()];
attributes.toArray(attArray);
String searchQuery = buildOrQueryWithFilter(getCfg().getDirectory().getFilter(), "*" + query + "*", attArray);
log.debug("Base DN: {}", getBaseDn());
log.debug("Query: {}", searchQuery);
log.debug("Attributes: {}", GsonUtil.build().toJson(attArray));
try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, attArray)) {
while (cursor.next()) {
Entry entry = cursor.get();
log.info("Found possible match, DN: {}", entry.getDn().getName());
getAttribute(entry, getUidAtt()).ifPresent(uid -> {
log.info("DN {} is a valid match", entry.getDn().getName());
try {
UserDirectorySearchResult.Result entryResult = new UserDirectorySearchResult.Result();
entryResult.setUserId(buildMatrixIdFromUid(uid));
getAttribute(entry, atCfg.getName()).ifPresent(entryResult::setDisplayName);
result.addResult(entryResult);
} catch (IllegalArgumentException e) {
log.warn("Bind was found but type {} is not supported", atCfg.getUid().getType());
}
});
}
}
} catch (CursorLdapReferralException e) {
log.warn("An entry is only available via referral, skipping");
} catch (IOException | LdapException | CursorException e) {
throw new InternalServerError(e);
}
return result;
}
@Override
public UserDirectorySearchResult searchByDisplayName(String query) {
log.info("Performing LDAP directory search on display name using '{}'", query);
List<String> attributes = new ArrayList<>();
attributes.add(getAt().getName());
attributes.addAll(getCfg().getDirectory().getAttribute().getOther());
return search(query, attributes);
}
@Override
public UserDirectorySearchResult searchBy3pid(String query) {
log.info("Performing LDAP directory search on 3PIDs using '{}'", query);
List<String> attributes = new ArrayList<>();
attributes.add(getAt().getName());
getCfg().getAttribute().getThreepid().forEach((k, v) -> attributes.addAll(v));
return search(query, attributes);
}
}

View File

@@ -0,0 +1,145 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.ldap;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.LdapConfig;
import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import io.kamax.mxisd.util.GsonUtil;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Component
public class LdapThreePidProvider extends LdapBackend implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(LdapThreePidProvider.class);
public LdapThreePidProvider(LdapConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
@Override
public boolean isEnabled() {
return getCfg().isEnabled();
}
@Override
public boolean isLocal() {
return true;
}
@Override
public int getPriority() {
return 20;
}
private Optional<String> lookup(LdapConnection conn, String medium, String value) {
Optional<String> tPidQueryOpt = getCfg().getIdentity().getQuery(medium);
if (!tPidQueryOpt.isPresent()) {
log.warn("{} is not a configured 3PID type for LDAP lookup", medium);
return Optional.empty();
}
// we merge 3PID specific query with global/specific filter, if one exists.
String tPidQuery = tPidQueryOpt.get().replaceAll(getCfg().getIdentity().getToken(), value);
String searchQuery = buildWithFilter(tPidQuery, getCfg().getIdentity().getFilter());
log.debug("Base DN: {}", getBaseDn());
log.debug("Query: {}", searchQuery);
log.debug("Attributes: {}", GsonUtil.build().toJson(getUidAtt()));
try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, getUidAtt())) {
while (cursor.next()) {
Entry entry = cursor.get();
log.info("Found possible match, DN: {}", entry.getDn().getName());
Optional<String> data = getAttribute(entry, getUidAtt());
if (!data.isPresent()) {
continue;
}
log.info("DN {} is a valid match", entry.getDn().getName());
return Optional.of(buildMatrixIdFromUid(data.get()));
}
} catch (CursorLdapReferralException e) {
log.warn("3PID {} is only available via referral, skipping", value);
} catch (IOException | LdapException | CursorException e) {
throw new InternalServerError(e);
}
return Optional.empty();
}
@Override
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
log.info("Performing LDAP lookup {} of type {}", request.getThreePid(), request.getType());
try (LdapConnection conn = getConn()) {
bind(conn);
return lookup(conn, request.getType(), request.getThreePid()).map(id -> new SingleLookupReply(request, id));
} catch (LdapException | IOException e) {
throw new InternalServerError(e);
}
}
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
log.info("Looking up {} mappings", mappings.size());
List<ThreePidMapping> mappingsFound = new ArrayList<>();
try (LdapConnection conn = getConn()) {
bind(conn);
for (ThreePidMapping mapping : mappings) {
try {
lookup(conn, mapping.getMedium(), mapping.getValue()).ifPresent(id -> {
mapping.setMxid(id);
mappingsFound.add(mapping);
});
} catch (IllegalArgumentException e) {
log.warn("{} is not a supported 3PID type for LDAP lookup", mapping.getMedium());
}
}
} catch (LdapException | IOException e) {
throw new InternalServerError(e);
}
return mappingsFound;
}
}

View File

@@ -0,0 +1,41 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sàrl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.ldap.netiq;
import io.kamax.mxisd.backend.ldap.LdapAuthProvider;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.netiq.NetIqLdapConfig;
import org.springframework.stereotype.Component;
@Component
public class NetIqLdapAuthProvider extends LdapAuthProvider {
public NetIqLdapAuthProvider(NetIqLdapConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
// FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted
@Override
public String buildMatrixIdFromUid(String uid) {
return super.buildMatrixIdFromUid(uid).toLowerCase();
}
}

View File

@@ -0,0 +1,41 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sàrl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.ldap.netiq;
import io.kamax.mxisd.backend.ldap.LdapDirectoryProvider;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.netiq.NetIqLdapConfig;
import org.springframework.stereotype.Component;
@Component
public class NetIqLdapDirectoryProvider extends LdapDirectoryProvider {
public NetIqLdapDirectoryProvider(NetIqLdapConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
// FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted
@Override
public String buildMatrixIdFromUid(String uid) {
return super.buildMatrixIdFromUid(uid).toLowerCase();
}
}

View File

@@ -0,0 +1,41 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sàrl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.ldap.netiq;
import io.kamax.mxisd.backend.ldap.LdapThreePidProvider;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.netiq.NetIqLdapConfig;
import org.springframework.stereotype.Component;
@Component
public class NetIqLdapThreePidProvider extends LdapThreePidProvider {
public NetIqLdapThreePidProvider(NetIqLdapConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
// FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted
@Override
public String buildMatrixIdFromUid(String uid) {
return super.buildMatrixIdFromUid(uid).toLowerCase();
}
}

View File

@@ -0,0 +1,173 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Maxime Dor
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.memory;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix.ThreePid;
import io.kamax.matrix._MatrixID;
import io.kamax.matrix._ThreePid;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.memory.MemoryIdentityConfig;
import io.kamax.mxisd.config.memory.MemoryStoreConfig;
import io.kamax.mxisd.config.memory.MemoryThreePid;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.directory.IDirectoryProvider;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import io.kamax.mxisd.profile.ProfileProvider;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
@Component
public class MemoryIdentityStore implements AuthenticatorProvider, IDirectoryProvider, IThreePidProvider, ProfileProvider {
private final Logger logger = LoggerFactory.getLogger(MemoryIdentityStore.class);
private final MatrixConfig mxCfg;
private final MemoryStoreConfig cfg;
@Autowired
public MemoryIdentityStore(MatrixConfig mxCfg, MemoryStoreConfig cfg) {
this.mxCfg = mxCfg;
this.cfg = cfg;
}
public Optional<MemoryIdentityConfig> findByUsername(String username) {
return cfg.getIdentities().stream()
.filter(id -> StringUtils.equals(id.getUsername(), username))
.findFirst();
}
@Override
public boolean isEnabled() {
return cfg.isEnabled();
}
private UserDirectorySearchResult search(
Predicate<MemoryIdentityConfig> predicate,
Function<MemoryIdentityConfig, UserDirectorySearchResult.Result> mapper
) {
UserDirectorySearchResult search = new UserDirectorySearchResult();
cfg.getIdentities().stream().filter(predicate).map(mapper).forEach(search::addResult);
return search;
}
@Override
public UserDirectorySearchResult searchByDisplayName(String query) {
return search(
entry -> StringUtils.containsIgnoreCase(entry.getUsername(), query),
entry -> {
UserDirectorySearchResult.Result result = new UserDirectorySearchResult.Result();
result.setUserId(MatrixID.from(entry.getUsername(), mxCfg.getDomain()).acceptable().getId());
result.setDisplayName(entry.getUsername());
return result;
}
);
}
@Override
public UserDirectorySearchResult searchBy3pid(String query) {
return search(
entry -> entry.getThreepids().stream()
.anyMatch(tpid -> StringUtils.containsIgnoreCase(tpid.getAddress(), query)),
entry -> {
UserDirectorySearchResult.Result result = new UserDirectorySearchResult.Result();
result.setUserId(MatrixID.from(entry.getUsername(), mxCfg.getDomain()).acceptable().getId());
result.setDisplayName(entry.getUsername());
return result;
}
);
}
@Override
public List<_ThreePid> getThreepids(_MatrixID mxid) {
List<_ThreePid> l = new ArrayList<>();
findByUsername(mxid.getLocalPart()).ifPresent(c -> l.addAll(c.getThreepids()));
return l;
}
@Override
public List<String> getRoles(_MatrixID mxid) {
List<String> l = new ArrayList<>();
findByUsername(mxid.getLocalPart()).ifPresent(c -> l.addAll(c.getRoles()));
return l;
}
@Override
public boolean isLocal() {
return true;
}
@Override
public int getPriority() {
return Integer.MAX_VALUE;
}
@Override
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
logger.info("Performing lookup {} of type {}", request.getThreePid(), request.getType());
ThreePid req = new ThreePid(request.getType(), request.getThreePid());
for (MemoryIdentityConfig id : cfg.getIdentities()) {
for (MemoryThreePid threepid : id.getThreepids()) {
if (req.equals(new ThreePid(threepid.getMedium(), threepid.getAddress()))) {
return Optional.of(new SingleLookupReply(request, new MatrixID(id.getUsername(), mxCfg.getDomain())));
}
}
}
return Optional.empty();
}
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
return Collections.emptyList();
}
@Override
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
return findByUsername(mxid.getLocalPart()).map(id -> {
if (!StringUtils.equals(id.getUsername(), mxid.getLocalPart())) {
return BackendAuthResult.failure();
} else {
BackendAuthResult result = new BackendAuthResult();
id.getThreepids().forEach(tpid -> result.withThreePid(new ThreePid(tpid.getMedium(), tpid.getAddress())));
result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), "");
return result;
}
}).orElseGet(BackendAuthResult::failure);
}
}

View File

@@ -0,0 +1,34 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.rest;
import java.util.ArrayList;
import java.util.List;
public class LookupBulkResponseJson {
private List<LookupSingleResponseJson> lookup = new ArrayList<>();
public List<LookupSingleResponseJson> getLookup() {
return lookup;
}
}

View File

@@ -0,0 +1,40 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.rest;
public class LookupSingleRequestJson {
private String medium;
private String address;
public LookupSingleRequestJson(String medium, String address) {
this.medium = medium;
this.address = address;
}
public String getMedium() {
return medium;
}
public String getAddress() {
return address;
}
}

View File

@@ -18,27 +18,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.lookup
package io.kamax.mxisd.backend.rest;
class SingleLookupRequest extends ALookupRequest {
import io.kamax.mxisd.UserID;
private String type
private String threePid
public class LookupSingleResponseJson {
String getType() {
return type
private String medium;
private String address;
private UserID id;
public String getMedium() {
return medium;
}
void setType(String type) {
this.type = type
public String getAddress() {
return address;
}
String getThreePid() {
return threePid
}
void setThreePid(String threePid) {
this.threePid = threePid
public UserID getId() {
return id;
}
}

View File

@@ -0,0 +1,69 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.rest;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.rest.RestBackendConfig;
import io.kamax.mxisd.util.RestClientUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class RestAuthProvider extends RestProvider implements AuthenticatorProvider {
@Autowired
public RestAuthProvider(RestBackendConfig cfg) {
super(cfg);
}
@Override
public boolean isEnabled() {
return cfg.isEnabled();
}
@Override
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
RestAuthRequestJson auth = new RestAuthRequestJson();
auth.setMxid(mxid.getId());
auth.setLocalpart(mxid.getLocalPart());
auth.setDomain(mxid.getDomain());
auth.setPassword(password);
HttpUriRequest req = RestClientUtils.post(cfg.getEndpoints().getAuth(), gson, "auth", auth);
try (CloseableHttpResponse res = client.execute(req)) {
int status = res.getStatusLine().getStatusCode();
if (status < 200 || status >= 300) {
return BackendAuthResult.failure();
}
return parser.parse(res, "auth", BackendAuthResult.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.rest;
public class RestAuthRequestJson {
private String mxid;
private String localpart;
private String domain;
private String password;
public String getMxid() {
return mxid;
}
public void setMxid(String mxid) {
this.mxid = mxid;
}
public String getLocalpart() {
return localpart;
}
public void setLocalpart(String localpart) {
this.localpart = localpart;
}
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@@ -0,0 +1,84 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.rest;
import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.rest.RestBackendConfig;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.directory.IDirectoryProvider;
import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.util.RestClientUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Component
public class RestDirectoryProvider extends RestProvider implements IDirectoryProvider {
private MatrixConfig mxCfg;
public RestDirectoryProvider(RestBackendConfig cfg, MatrixConfig mxCfg) {
super(cfg);
this.mxCfg = mxCfg;
}
@Override
public boolean isEnabled() {
return cfg.isEnabled() && StringUtils.isNotBlank(cfg.getEndpoints().getDirectory());
}
private UserDirectorySearchResult search(String by, String query) {
UserDirectorySearchRequest request = new UserDirectorySearchRequest(query);
request.setBy(by);
try (CloseableHttpResponse httpResponse = client.execute(RestClientUtils.post(cfg.getEndpoints().getDirectory(), request))) {
int status = httpResponse.getStatusLine().getStatusCode();
if (status < 200 || status >= 300) {
throw new InternalServerError("REST backend: Error: " + IOUtils.toString(httpResponse.getEntity().getContent(), StandardCharsets.UTF_8));
}
UserDirectorySearchResult response = parser.parse(httpResponse, UserDirectorySearchResult.class);
for (UserDirectorySearchResult.Result result : response.getResults()) {
result.setUserId(new MatrixID(result.getUserId(), mxCfg.getDomain()).getId());
}
return response;
} catch (IOException e) {
throw new InternalServerError("REST backend: I/O error: " + e.getMessage());
}
}
@Override
public UserDirectorySearchResult searchByDisplayName(String query) {
return search("name", query);
}
@Override
public UserDirectorySearchResult searchBy3pid(String query) {
return search("threepid", query);
}
}

View File

@@ -0,0 +1,46 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.rest;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.kamax.mxisd.config.rest.RestBackendConfig;
import io.kamax.mxisd.util.GsonParser;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
public class RestProvider {
protected RestBackendConfig cfg;
protected Gson gson;
protected GsonParser parser;
protected CloseableHttpClient client;
public RestProvider(RestBackendConfig cfg) {
this.cfg = cfg;
client = HttpClients.createDefault();
gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
parser = new GsonParser(gson);
}
}

View File

@@ -0,0 +1,131 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.rest;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.UserID;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.rest.RestBackendConfig;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import io.kamax.mxisd.util.RestClientUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Component
public class RestThreePidProvider extends RestProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(RestThreePidProvider.class);
private MatrixConfig mxCfg; // FIXME should be done in the lookup manager
@Autowired
public RestThreePidProvider(RestBackendConfig cfg, MatrixConfig mxCfg) {
super(cfg);
this.mxCfg = mxCfg;
}
// TODO refactor in lookup manager with above FIXME
private _MatrixID getMxId(UserID id) {
if (UserIdType.Localpart.is(id.getType())) {
return new MatrixID(id.getValue(), mxCfg.getDomain());
} else {
return new MatrixID(id.getValue());
}
}
@Override
public boolean isEnabled() {
return cfg.isEnabled();
}
@Override
public boolean isLocal() {
return true;
}
@Override
public int getPriority() {
return 20;
}
// TODO refactor common code
@Override
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
String endpoint = cfg.getEndpoints().getIdentity().getSingle();
HttpUriRequest req = RestClientUtils.post(endpoint, gson, "lookup",
new LookupSingleRequestJson(request.getType(), request.getThreePid()));
try (CloseableHttpResponse res = client.execute(req)) {
int status = res.getStatusLine().getStatusCode();
if (status < 200 || status >= 300) {
log.warn("REST endpoint {} answered with status {}, no binding found", endpoint, status);
return Optional.empty();
}
Optional<LookupSingleResponseJson> responseOpt = parser.parseOptional(res, "lookup", LookupSingleResponseJson.class);
return responseOpt.map(lookupSingleResponseJson -> new SingleLookupReply(request, getMxId(lookupSingleResponseJson.getId())));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// TODO refactor common code
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
List<LookupSingleRequestJson> ioListRequest = mappings.stream()
.map(mapping -> new LookupSingleRequestJson(mapping.getMedium(), mapping.getValue()))
.collect(Collectors.toList());
HttpUriRequest req = RestClientUtils.post(
cfg.getEndpoints().getIdentity().getBulk(), gson, "lookup", ioListRequest);
try (CloseableHttpResponse res = client.execute(req)) {
mappings = new ArrayList<>();
int status = res.getStatusLine().getStatusCode();
if (status < 200 || status >= 300) {
return mappings;
}
LookupBulkResponseJson listIo = parser.parse(res, LookupBulkResponseJson.class);
return listIo.getLookup().stream()
.map(io -> new ThreePidMapping(io.getMedium(), io.getAddress(), getMxId(io.getId()).getId()))
.collect(Collectors.toList());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.sql;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
import io.kamax.mxisd.invitation.InvitationManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class GenericSqlAuthProvider implements AuthenticatorProvider {
private Logger log = LoggerFactory.getLogger(GenericSqlAuthProvider.class);
@Autowired
private ServerConfig srvCfg;
@Autowired
private GenericSqlProviderConfig cfg;
@Autowired
private InvitationManager invMgr;
@Override
public boolean isEnabled() {
return cfg.isEnabled();
}
@Override
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
log.info("Performing dummy authentication try to force invite mapping refresh");
invMgr.lookupMappingsForInvites();
return BackendAuthResult.failure();
}
}

View File

@@ -0,0 +1,114 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.sql;
import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
import io.kamax.mxisd.config.sql.SqlConfig;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.directory.IDirectoryProvider;
import io.kamax.mxisd.exception.InternalServerError;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
import static io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult.Result;
public abstract class GenericSqlDirectoryProvider implements IDirectoryProvider {
private Logger log = LoggerFactory.getLogger(GenericSqlDirectoryProvider.class);
protected SqlConfig cfg;
private MatrixConfig mxCfg;
private SqlConnectionPool pool;
public GenericSqlDirectoryProvider(SqlConfig cfg, MatrixConfig mxCfg) {
this.cfg = cfg;
this.pool = new SqlConnectionPool(cfg);
this.mxCfg = mxCfg;
}
@Override
public boolean isEnabled() {
return cfg.isEnabled();
}
protected void setParameters(PreparedStatement stmt, String searchTerm) throws SQLException {
for (int i = 1; i <= stmt.getParameterMetaData().getParameterCount(); i++) {
stmt.setString(i, searchTerm);
}
}
protected Optional<Result> processRow(ResultSet rSet) throws SQLException {
Result item = new Result();
item.setUserId(rSet.getString(1));
item.setDisplayName(rSet.getString(2));
return Optional.of(item);
}
public UserDirectorySearchResult search(String searchTerm, GenericSqlProviderConfig.Query query) {
try (Connection conn = pool.get()) {
log.info("Will execute query: {}", query.getValue());
try (PreparedStatement stmt = conn.prepareStatement(query.getValue())) {
setParameters(stmt, searchTerm);
try (ResultSet rSet = stmt.executeQuery()) {
UserDirectorySearchResult result = new UserDirectorySearchResult();
result.setLimited(false);
while (rSet.next()) {
processRow(rSet).ifPresent(e -> {
if (StringUtils.equalsIgnoreCase("localpart", query.getType())) {
e.setUserId(new MatrixID(e.getUserId(), mxCfg.getDomain()).getId());
}
result.addResult(e);
});
}
return result;
}
}
} catch (SQLException e) {
throw new InternalServerError(e);
}
}
@Override
public UserDirectorySearchResult searchByDisplayName(String searchTerm) {
log.info("Searching users by display name using '{}'", searchTerm);
return search(searchTerm, cfg.getDirectory().getQuery().getName());
}
@Override
public UserDirectorySearchResult searchBy3pid(String searchTerm) {
log.info("Searching users by 3PID using '{}'", searchTerm);
return search(searchTerm, cfg.getDirectory().getQuery().getThreepid());
}
}

View File

@@ -0,0 +1,36 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.sql;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class GenericSqlThreePidProvider extends SqlThreePidProvider {
@Autowired
public GenericSqlThreePidProvider(GenericSqlProviderConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
}

View File

@@ -0,0 +1,45 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.sql;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import io.kamax.mxisd.config.sql.SqlConfig;
import java.sql.Connection;
import java.sql.SQLException;
public class SqlConnectionPool {
private ComboPooledDataSource ds;
public SqlConnectionPool(SqlConfig cfg) {
ds = new ComboPooledDataSource();
ds.setJdbcUrl("jdbc:" + cfg.getType() + ":" + cfg.getConnection());
ds.setMinPoolSize(1);
ds.setMaxPoolSize(10);
ds.setAcquireIncrement(2);
}
public Connection get() throws SQLException {
return ds.getConnection();
}
}

View File

@@ -0,0 +1,148 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.sql;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix.ThreePid;
import io.kamax.matrix._MatrixID;
import io.kamax.matrix._ThreePid;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.SqlConfig;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import io.kamax.mxisd.profile.ProfileProvider;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
public abstract class SqlThreePidProvider implements IThreePidProvider, ProfileProvider {
private Logger log = LoggerFactory.getLogger(SqlThreePidProvider.class);
private SqlConfig cfg;
private MatrixConfig mxCfg;
private SqlConnectionPool pool;
public SqlThreePidProvider(SqlConfig cfg, MatrixConfig mxCfg) {
this.cfg = cfg;
this.pool = new SqlConnectionPool(cfg);
this.mxCfg = mxCfg;
}
protected Connection getConnection() throws SQLException {
return pool.get();
}
@Override
public boolean isEnabled() {
return cfg.isEnabled();
}
@Override
public boolean isLocal() {
return true;
}
@Override
public int getPriority() {
return 20;
}
@Override
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
log.info("SQL lookup");
String stmtSql = StringUtils.defaultIfBlank(cfg.getIdentity().getMedium().get(request.getType()), cfg.getIdentity().getQuery());
log.info("SQL query: {}", stmtSql);
try (Connection conn = pool.get()) {
try (PreparedStatement stmt = conn.prepareStatement(stmtSql)) {
stmt.setString(1, request.getType().toLowerCase());
stmt.setString(2, request.getThreePid().toLowerCase());
try (ResultSet rSet = stmt.executeQuery()) {
while (rSet.next()) {
String uid = rSet.getString("uid");
log.info("Found match: {}", uid);
if (StringUtils.equals("uid", cfg.getIdentity().getType())) {
log.info("Resolving as localpart");
return Optional.of(new SingleLookupReply(request, new MatrixID(uid, mxCfg.getDomain())));
}
if (StringUtils.equals("mxid", cfg.getIdentity().getType())) {
log.info("Resolving as MXID");
return Optional.of(new SingleLookupReply(request, new MatrixID(uid)));
}
log.info("Identity type is unknown, skipping");
}
log.info("No match found in SQL");
return Optional.empty();
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
return new ArrayList<>();
}
@Override
public List<_ThreePid> getThreepids(_MatrixID mxid) {
List<_ThreePid> threepids = new ArrayList<>();
String stmtSql = cfg.getProfile().getThreepid().getQuery();
try (Connection conn = getConnection()) {
PreparedStatement stmt = conn.prepareStatement(stmtSql);
stmt.setString(1, mxid.getId());
ResultSet rSet = stmt.executeQuery();
while (rSet.next()) {
String medium = rSet.getString("medium");
String address = rSet.getString("address");
threepids.add(new ThreePid(medium, address));
}
return threepids;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> getRoles(_MatrixID mxid) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,69 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.sql;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
import io.kamax.mxisd.exception.ConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@Component
public class SynapseSqlDirectoryProvider extends GenericSqlDirectoryProvider {
@Autowired
public SynapseSqlDirectoryProvider(SynapseSqlProviderConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
if (StringUtils.equals("sqlite", cfg.getType())) {
String userId = "'@' || p.user_id || ':" + mxCfg.getDomain() + "'";
GenericSqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
queries.getName().setValue(
"select " + userId + ", displayname from profiles p where displayname like ?");
queries.getThreepid().setValue(
"select t.user_id, p.displayname " +
"from user_threepids t JOIN profiles p on t.user_id = " + userId + " " +
"where t.address like ?");
} else if (StringUtils.equals("postgresql", cfg.getType())) {
String userId = "concat('@',p.user_id,':" + mxCfg.getDomain() + "')";
GenericSqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
queries.getName().setValue(
"select " + userId + ", displayname from profiles p where displayname ilike ?");
queries.getThreepid().setValue(
"select t.user_id, p.displayname " +
"from user_threepids t JOIN profiles p on t.user_id = " + userId + " " +
"where t.address ilike ?");
} else {
throw new ConfigurationException("Invalid SQL type");
}
}
@Override
protected void setParameters(PreparedStatement stmt, String searchTerm) throws SQLException {
stmt.setString(1, "%" + searchTerm + "%");
}
}

View File

@@ -0,0 +1,69 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.sql;
import io.kamax.matrix.ThreePid;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
import io.kamax.mxisd.profile.ProfileWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.time.Instant;
@Component
public class SynapseSqlThreePidProvider extends SqlThreePidProvider implements ProfileWriter {
private final Logger log = LoggerFactory.getLogger(SynapseSqlThreePidProvider.class);
@Autowired
public SynapseSqlThreePidProvider(SynapseSqlProviderConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
@Override
public boolean addThreepid(_MatrixID mxid, ThreePid tpid) {
try (Connection conn = getConnection()) {
PreparedStatement stmt = conn.prepareStatement("INSERT INTO user_threepids (user_id, medium, address, validated_at, added_at) values (?,?,?,?,?)");
stmt.setString(1, mxid.getId());
stmt.setString(2, tpid.getMedium());
stmt.setString(3, tpid.getAddress());
stmt.setLong(4, Instant.now().toEpochMilli());
stmt.setLong(5, Instant.now().toEpochMilli());
int rows = stmt.executeUpdate();
if (rows != 1) {
log.error("Unable to update 3PID info. Modified row(s): {}", rows);
}
return true;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sàrl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.wordpress;
public class WordpressAuthData {
public String token;
private String userEmail;
private String userNicename;
private String userDisplayName;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getUserEmail() {
return userEmail;
}
public void setUserEmail(String userEmail) {
this.userEmail = userEmail;
}
public String getUserNicename() {
return userNicename;
}
public void setUserNicename(String userNicename) {
this.userNicename = userNicename;
}
public String getUserDisplayName() {
return userDisplayName;
}
public void setUserDisplayName(String userDisplayName) {
this.userDisplayName = userDisplayName;
}
}

View File

@@ -0,0 +1,68 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sàrl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.wordpress;
import io.kamax.matrix.ThreePid;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class WordpressAuthProvider implements AuthenticatorProvider {
private final Logger log = LoggerFactory.getLogger(WordpressAuthProvider.class);
private WordpressRestBackend wordpress;
@Autowired
public WordpressAuthProvider(WordpressRestBackend wordpress) {
this.wordpress = wordpress;
}
@Override
public boolean isEnabled() {
return wordpress.isEnabled();
}
@Override
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
try {
WordpressAuthData data = wordpress.authenticate(mxid.getLocalPart(), password);
BackendAuthResult result = new BackendAuthResult();
if (StringUtils.isNotBlank(data.getUserEmail())) {
result.withThreePid(new ThreePid("email", data.getUserEmail()));
}
result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), data.getUserDisplayName());
return result;
} catch (IllegalArgumentException e) {
log.error("Authentication failed for {}: {}", mxid.getId(), e.getMessage());
return BackendAuthResult.failure();
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sàrl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.wordpress;
import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.wordpress.WordpressConfig;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.directory.IDirectoryProvider;
import io.kamax.mxisd.exception.InternalServerError;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
@Component
public class WordpressDirectoryProvider implements IDirectoryProvider {
private final Logger log = LoggerFactory.getLogger(WordpressDirectoryProvider.class);
private WordpressConfig cfg;
private WordressSqlBackend wordpress;
private MatrixConfig mxCfg;
@Autowired
public WordpressDirectoryProvider(WordpressConfig cfg, WordressSqlBackend wordpress, MatrixConfig mxCfg) {
this.cfg = cfg;
this.wordpress = wordpress;
this.mxCfg = mxCfg;
}
@Override
public boolean isEnabled() {
return wordpress.isEnabled();
}
protected void setParameters(PreparedStatement stmt, String searchTerm) throws SQLException {
for (int i = 1; i <= stmt.getParameterMetaData().getParameterCount(); i++) {
stmt.setString(i, "%" + searchTerm + "%");
}
}
protected Optional<UserDirectorySearchResult.Result> processRow(ResultSet rSet) throws SQLException {
UserDirectorySearchResult.Result item = new UserDirectorySearchResult.Result();
item.setUserId(rSet.getString(1));
item.setDisplayName(rSet.getString(2));
return Optional.of(item);
}
public UserDirectorySearchResult search(String searchTerm, String query) {
try (Connection conn = wordpress.getConnection()) {
log.info("Will execute query: {}", query);
try (PreparedStatement stmt = conn.prepareStatement(query)) {
setParameters(stmt, searchTerm);
try (ResultSet rSet = stmt.executeQuery()) {
UserDirectorySearchResult result = new UserDirectorySearchResult();
result.setLimited(false);
while (rSet.next()) {
processRow(rSet).ifPresent(e -> {
try {
e.setUserId(MatrixID.from(e.getUserId(), mxCfg.getDomain()).valid().getId());
result.addResult(e);
} catch (IllegalArgumentException ex) {
log.warn("Ignoring result {} - Invalid characters for a Matrix ID", e.getUserId());
}
});
}
return result;
}
}
} catch (SQLException e) {
e.printStackTrace();
throw new InternalServerError(e);
}
}
@Override
public UserDirectorySearchResult searchByDisplayName(String searchTerm) {
log.info("Searching users by display name using '{}'", searchTerm);
return search(searchTerm, cfg.getSql().getQuery().getDirectory().get("name"));
}
@Override
public UserDirectorySearchResult searchBy3pid(String searchTerm) {
log.info("Searching users by 3PID using '{}'", searchTerm);
return search(searchTerm, cfg.getSql().getQuery().getDirectory().get("threepid"));
}
}

View File

@@ -0,0 +1,143 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sàrl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.wordpress;
import com.google.gson.JsonObject;
import io.kamax.matrix.json.GsonUtil;
import io.kamax.matrix.json.InvalidJsonException;
import io.kamax.mxisd.config.wordpress.WordpressConfig;
import io.kamax.mxisd.util.RestClientUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class WordpressRestBackend {
private final Logger log = LoggerFactory.getLogger(WordpressRestBackend.class);
private final String jsonPath = "/wp-json";
private final String jwtPath = "/jwt-auth/v1";
private WordpressConfig cfg;
private CloseableHttpClient client;
private String jsonEndpoint;
private String jwtEndpoint;
private String token;
@Autowired
public WordpressRestBackend(WordpressConfig cfg, CloseableHttpClient client) {
this.cfg = cfg;
this.client = client;
if (!cfg.isEnabled()) {
return;
}
jsonEndpoint = cfg.getRest().getBase() + jsonPath;
jwtEndpoint = jsonEndpoint + jwtPath;
validateConfig();
}
private void validateConfig() {
log.info("Validating JWT auth endpoint");
try (CloseableHttpResponse res = client.execute(new HttpGet(jwtEndpoint))) {
int status = res.getStatusLine().getStatusCode();
if (status != 200) {
log.warn("JWT auth endpoint check failed: Got status code {}", status);
return;
}
String data = EntityUtils.toString(res.getEntity());
if (StringUtils.isBlank(data)) {
log.warn("JWT auth endpoint check failed: Got no/empty body data");
}
JsonObject body = GsonUtil.parseObj(data);
if (!body.has("namespace")) {
log.warn("JWT auth endpoint check failed: invalid namespace");
}
log.info("JWT auth endpoint check succeeded");
} catch (InvalidJsonException e) {
log.warn("JWT auth endpoint check failed: Invalid JSON response: {}", e.getMessage());
} catch (IOException e) {
log.warn("JWT auth endpoint check failed: Could not read API endpoint: {}", e.getMessage());
}
}
public boolean isEnabled() {
return cfg.isEnabled();
}
protected WordpressAuthData authenticate(String username, String password) {
JsonObject body = new JsonObject();
body.addProperty("username", username);
body.addProperty("password", password);
HttpPost req = RestClientUtils.post(jwtEndpoint + "/token", body);
try (CloseableHttpResponse res = client.execute(req)) {
int status = res.getStatusLine().getStatusCode();
String bodyRes = EntityUtils.toString(res.getEntity());
if (status != 200) {
throw new IllegalArgumentException(bodyRes);
}
return GsonUtil.get().fromJson(bodyRes, WordpressAuthData.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected void authenticate() {
WordpressAuthData data = authenticate(
cfg.getRest().getCredential().getUsername(),
cfg.getRest().getCredential().getPassword());
log.info("Internal authentication: success, logged in as " + data.getUserNicename());
token = data.getToken();
}
protected CloseableHttpResponse runRequest(HttpRequestBase request) throws IOException {
request.setHeader("Authorization", "Bearer " + token);
return client.execute(request);
}
public CloseableHttpResponse withAuthentication(HttpRequestBase request) throws IOException {
CloseableHttpResponse response = runRequest(request);
if (response.getStatusLine().getStatusCode() == 403) { //FIXME we should check the JWT expiration time
authenticate();
response = runRequest(request);
}
return response;
}
}

View File

@@ -0,0 +1,118 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sàrl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.wordpress;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix.ThreePid;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.wordpress.WordpressConfig;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@Component
public class WordpressThreePidProvider implements IThreePidProvider {
private final Logger log = LoggerFactory.getLogger(WordpressThreePidProvider.class);
private MatrixConfig mxCfg;
private WordpressConfig cfg;
private WordressSqlBackend wordpress;
@Autowired
public WordpressThreePidProvider(MatrixConfig mxCfg, WordpressConfig cfg, WordressSqlBackend wordpress) {
this.mxCfg = mxCfg;
this.cfg = cfg;
this.wordpress = wordpress;
}
@Override
public boolean isEnabled() {
return wordpress.isEnabled();
}
@Override
public boolean isLocal() {
return true;
}
@Override
public int getPriority() {
return 15;
}
protected Optional<_MatrixID> find(ThreePid tpid) {
String query = cfg.getSql().getQuery().getThreepid().get(tpid.getMedium());
if (Objects.isNull(query)) {
return Optional.empty();
}
try (Connection conn = wordpress.getConnection()) {
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setString(1, tpid.getAddress());
try (ResultSet rSet = stmt.executeQuery()) {
while (rSet.next()) {
String uid = rSet.getString("uid");
log.info("Found match: {}", uid);
try {
return Optional.of(MatrixID.from(uid, mxCfg.getDomain()).valid());
} catch (IllegalArgumentException ex) {
log.warn("Ignoring match {} - Invalid characters for a Matrix ID", uid);
}
}
log.info("No valid match found in Wordpress");
return Optional.empty();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
return find(new ThreePid(request.getType(), request.getThreePid())).map(mxid -> new SingleLookupReply(request, mxid));
}
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
for (ThreePidMapping tpidMap : mappings) {
find(new ThreePid(tpidMap.getMedium(), tpidMap.getValue())).ifPresent(mxid -> tpidMap.setMxid(mxid.getId()));
}
return mappings;
}
}

View File

@@ -0,0 +1,61 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sàrl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.wordpress;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import io.kamax.mxisd.config.wordpress.WordpressConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.SQLException;
@Component
public class WordressSqlBackend {
private Logger log = LoggerFactory.getLogger(WordressSqlBackend.class);
private WordpressConfig cfg;
private ComboPooledDataSource ds;
@Autowired
public WordressSqlBackend(WordpressConfig cfg) {
this.cfg = cfg;
ds = new ComboPooledDataSource();
ds.setJdbcUrl("jdbc:" + cfg.getSql().getType() + ":" + cfg.getSql().getConnection());
ds.setMinPoolSize(1);
ds.setMaxPoolSize(10);
ds.setAcquireIncrement(2);
}
public boolean isEnabled() {
return cfg.isEnabled();
}
public Connection getConnection() throws SQLException {
return ds.getConnection();
}
}

View File

@@ -0,0 +1,59 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties("directory")
public class DirectoryConfig {
private final transient Logger log = LoggerFactory.getLogger(DnsOverwriteConfig.class);
public static class Exclude {
private boolean homeserver;
public boolean getHomeserver() {
return homeserver;
}
public Exclude setHomeserver(boolean homeserver) {
this.homeserver = homeserver;
return this;
}
}
private Exclude exclude = new Exclude();
public Exclude getExclude() {
return exclude;
}
public void setExclude(Exclude exclude) {
this.exclude = exclude;
}
}

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