Compare commits

...

75 Commits

Author SHA1 Message Date
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
104 changed files with 6304 additions and 503 deletions

5
.gitignore vendored
View File

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

137
README.md
View File

@@ -1,23 +1,34 @@
mxisd mxisd - Federated Matrix Identity Server Daemon
----- -----
![Travis-CI build status](https://travis-ci.org/kamax-io/mxisd.svg?branch=master) ![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) | [Lookup process](#lookup-process) | [Packages](#packages) |
[From source](#from-source) | [Configuration](#configuration) | [Network Discovery](#network-discovery) |
[Integration](#integration) | [Support](#support)
# Overview # Overview
mxisd is an implementation of the [Matrix Identity Service specification](https://matrix.org/docs/spec/identity_service/unstable.html) mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures.
which provides an alternative to the vector.im and matrix.org Identity servers.
mxisd is federated and uses a cascading lookup model which performs lookup from a more authoritative to a less authoritative source, usually doing: mxisd uses a cascading lookup model which performs lookup from a more authoritative to a less authoritative source, usually doing:
- Local identity stores: LDAP, etc. - Local identity stores: LDAP, etc.
- Federated identity stores: another Identity Server in charge of a specific domain, if applicable - 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 - Configured identity stores: another Identity Server specifically configured, if part of some sort of group trust
- Root identity store: vector.im/matrix.org central Identity Server - Root identity store: vector.im/matrix.org central Identity Servers
mxisd only aims to support workflow that does NOT break federation or basic lookup process of the Matrix ecosystem. mxisd provides an alternative to [sydent](https://github.com/matrix-org/sydent), while still connecting to the vector.im and matrix.org Identity servers,
by implementing the [Matrix Identity Service specification](https://matrix.org/docs/spec/identity_service/unstable.html).
**NOTE:** mxisd is **NOT** an authenticator module for homeservers. Identity servers are opaque public directories for those who publish their data on it. mxisd only aims to support workflows that do NOT break federation or basic lookup processes of the Matrix ecosystem.
For more details, please read the [Identity Service specification](https://matrix.org/docs/spec/identity_service/unstable.html).
# Features
- Single lookup of 3PID (E-mail, phone number, etc.) by the Matrix Client or Homeserver.
- Bulk lookups when trying to find possible matches within contacts in Android and iOS clients.
- Bind of 3PID by a Matrix user within a Matrix client.
- Support of invitation to rooms by e-mail with e-mail notification to invitee.
- Authentication support in [synapse](https://github.com/matrix-org/synapse) via the [REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth).
In the pipe:
- Support to proxy 3PID bindings in user profile to the central Matrix.org servers
# Lookup Process # Lookup Process
Default Lookup strategy will use a priority order and a configurable recursive/local type of request. Default Lookup strategy will use a priority order and a configurable recursive/local type of request.
@@ -39,21 +50,16 @@ Given the phone number `+123456789`, the following lookup logic will be performe
See [releases]((https://github.com/kamax-io/mxisd/releases)) for native installers of supported systems. 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. 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 ## Debian
### Download ### Download
See the [releases section](https://github.com/kamax-io/mxisd/releases). See the [releases section](https://github.com/kamax-io/mxisd/releases).
### Configure and run
After installation:
1. Copy the sample config file `/etc/mxisd/mxisd-sample.yaml` to `/etc/mxisd/mxisd.yaml`
2. [Configure](#configuration)
3. Start the service: `sudo systemctl start mxisd`
### From source ### From source
Requirements: Requirements:
- fakeroot - fakeroot
@@ -63,9 +69,24 @@ Run:
``` ```
./gradlew buildDeb ./gradlew buildDeb
``` ```
You will find the debian package in `build/dist` You will find the debian package in `build/dist`
## Docker
```
docker pull kamax/mxisd
```
For more info, see [the public repository](https://hub.docker.com/r/kamax/mxisd/)
### From source
[Build mxisd](#build) then build the docker image:
```
./gradlew dockerBuild
```
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 kamax/mxisd:latest-dev
```
# From Source # From Source
## Requirements ## Requirements
- JDK 1.8 - JDK 1.8
@@ -76,15 +97,9 @@ git clone https://github.com/kamax-io/mxisd.git
cd mxisd cd mxisd
./gradlew build ./gradlew build
``` ```
then see the [Configuration](#configuration) section.
## Configure ## Test build
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: Start the server in foreground to validate the build:
``` ```
java -jar build/libs/mxisd.jar java -jar build/libs/mxisd.jar
@@ -92,10 +107,18 @@ java -jar build/libs/mxisd.jar
Ensure the signing key is available: Ensure the signing key is available:
``` ```
curl http://localhost:8090/_matrix/identity/api/v1/pubkey/ed25519:0 $ curl http://localhost:8090/_matrix/identity/api/v1/pubkey/ed25519:0
{"public_key":"..."}
``` ```
Validate your LDAP config and binding info (replace the e-mail): Test basic recursive lookup (requires Internet connection with access to TCP 443):
```
$ curl 'http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=mxisd-lookup-test@kamax.io'
{"address":"mxisd-lookup-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
``` ```
curl "http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=john.doe@example.org" curl "http://localhost:8090/_matrix/identity/api/v1/lookup?medium=email&address=john.doe@example.org"
``` ```
@@ -132,36 +155,62 @@ chmod a+x /opt/mxisd/mxisd.jar
ln -s /opt/mxisd/mxisd.jar /usr/bin/mxisd ln -s /opt/mxisd/mxisd.jar /usr/bin/mxisd
``` ```
2. Copy the config file created earlier `./application.yaml` to `/etc/mxisd/mxisd.yaml` 2. Copy the config file created earlier `./application.example.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 3. [Configure](#configuration)
4. Copy `<repo root>/src/systemd/mxisd.service` to `/etc/systemd/system/` and edit if needed 4. Copy `<repo root>/src/systemd/mxisd.service` to `/etc/systemd/system/` and edit if needed
5. Manage service for auto-startup 5. Enable service for auto-startup
``` ```
# Enable service
systemctl enable mxisd systemctl enable mxisd
```
# Start service 6. Start mxisd
```
systemctl start mxisd systemctl start mxisd
``` ```
# Configuration
After following the specific instructions to create a config file from the sample:
1. Set the `matrix.domain` value to the domain value used in your Home Server configuration
2. Set an absolute location for the signing keys using `key.path`
3. Set a location for the default SQLite persistence using `storage.provider.sqlite.database`
4. Configure the E-mail invite sender with items starting in `invite.sender.email`
In case your IS public domain does not match your Matrix domain, see `server.name` and `server.publicUrl`
config items.
If you want to use the LDAP backend:
1. Enable it with `ldap.enabled`
2. Configure connection options using items starting in `ldap.connection`
3. You may want to valid default values for `ldap.attribute` items
# Network Discovery # Network Discovery
To allow other federated Identity Server to reach yours, configure the following DNS SRV entry (adapt to your values): To allow other federated Identity Server to reach yours, the same algorithm used for Homeservers takes place:
1. Check for the appropriate DNS SRV record
2. If not found, use the base domain
If your Identity Server public hostname does not match your Matrix domain, configure the following DNS SRV entry
and replace `matrix.example.com` by your Identity server public hostname - **Make sure to end with a final dot!**
``` ```
_matrix-identity._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com. _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. 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. 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. See the [integration section](#integration) for more details.
# Integration # Integration
- [HTTPS and Reverse proxy](https://github.com/kamax-io/mxisd/wiki/HTTPS) - [HTTPS and Reverse proxy](https://github.com/kamax-io/mxisd/wiki/HTTPS)
- [synapse](https://github.com/kamax-io/mxisd/wiki/Homeserver-Integration) - [synapse](https://github.com/kamax-io/mxisd/wiki/Homeserver-Integration) as Identity server
- [synapse with REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth/blob/master/README.md)
as authentication module
# Support # Support
## Community ## 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) If you need help, want to report a bug or just say hi, you can reach us on Matrix at
For more high-level discussion about the Identity Server architecture/API, go to [#matrix-identity:matrix.org](https://matrix.to/#/#matrix-identity:matrix.org) [#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io) or [directly peek anonymously](https://view.matrix.org/room/!NPRUEisLjcaMtHIzDr: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)
## Professional ## Professional
If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open source technologies/products, If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open source technologies/products,

View File

@@ -1,148 +1,414 @@
server: # Sample configuration file explaining all possible options, their default value and if they are required or not.
#
# Any optional configuration item will be prefixed by # (comment character) with the configuration item following
# directly without any whitespace character.
# Default values for optional configuration item will also follow such item.
#
# Any mandatory configuration item will not be prefixed by # and will also contain a value as example that must be
# changed. It is advised to re-create a clean config file with only the required configuration item.
# Indicate on which port the Identity Server will listen. #######################
# # Matrix config items #
# This is be default an unencrypted port. #######################
# HTTPS can be configured using Tomcat configuration properties. # Matrix domain, same as the domain configure in your Homeserver configuration.
port: 8090 #
# This is used to build the various identifiers for identity, auth and directory.
# Realm under which this Identity Server is authoritative, required. matrix.domain: ''
#
# This is used to avoid unnecessary connections and endless recursive lookup.
# e.g. domain name in e-mails.
name: 'example.org'
key: #######################
# Server config items #
#######################
# Indicate on which port the Identity Server will listen.
#
# This is be default an unencrypted port.
# HTTPS can be configured using Tomcat configuration properties.
#
#server.port: 8090
# Absolute path for the Identity Server signing key, required.
# During testing, /var/tmp/mxisd.key is a possible value # Public hostname of this identity server.
# #
# For production, use a stable location like: # This would be typically be the same as your Matrix domain.
# - /var/opt/mxisd/sign.key # In case it is not, set this value.
# - /var/local/mxisd/sign.key #
# - /var/lib/mxisd/sign.key # This value is used in various signatures within the Matrix protocol and should be a reachable hostname.
path: '%SIGNING_KEYS_PATH%' # You can validate by ensuring you see a JSON answer when calling (replace the domain):
# https://example.org/_matrix/identity/status
#
#server.name: 'example.org'
# Public URL to reach this identity server
#
# This is used with 3PID invites in room and other Homeserver key verification workflow.
# If left unconfigured, it will be generated from the server name.
#
# You should typically set this value if you want to change the public port under which
# this Identity server is reachable.
#
# %SERVER_NAME% placeholder is available to avoid configuration duplication.
# e.g. 'https://%SERVER_NAME%:8443'
#
#server.publicUrl: 'https://example.org'
# This element contains all the configuration item for lookup strategies #############################
lookup: # Signing keys config items #
#############################
# Configuration items for recursion-type of lookup # Absolute path for the Identity Server signing key.
# # During testing, /var/tmp/mxisd.key is a possible value
# Lookup access are divided into two types: #
# - Local # For production, use a stable location like:
# - Remote # - /var/opt/mxisd/sign.key
# # - /var/local/mxisd/sign.key
# This is similar to DNS lookup and recursion and is therefore prone to the same vulnerabilities. # - /var/lib/mxisd/sign.key
# By default, only non-public hosts are allowed to perform recursive lookup. key.path: '/path/to/sign.key'
#
# 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: ''
ldap: #################################
enabled: false # Recurisve lookup config items #
tls: false #################################
host: 'localhost' # Configuration items for recursion-type of lookup
port: 389 #
bindDn: 'CN=Matrix Identity Server,CN=Users,DC=example,DC=org' # Lookup access are divided into two types:
bindPassword: 'password' # - Local
baseDn: 'CN=Users,DC=example,DC=org' # - 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.
# How should we resolve the Matrix ID in case of a match using the attribute. # Enable recursive lookup globally
# #
# The following type are supported: #lookup.recursive.enabled: true
# - 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. # Whitelist of CIDR that will trigger a recursive lookup.
mappings: # The default list includes all private IPv4 address and the IPv6 loopback.
email: "(|(mailPrimaryAddress=%3pid)(mail=%3pid)(otherMailbox=%3pid))" #
#lookup.recursive.allowedCidr:
# - '127.0.0.0/8'
# - '10.0.0.0/8'
# - '172.16.0.0/12'
# - '192.168.0.0/16'
# - '::1/128'
# Phone numbers query.
# # In case no binding is found, query an application server which implements the single lookup end-point
# Phone numbers use the MSISDN format: https://en.wikipedia.org/wiki/MSISDN # to return bridge virtual user that would allow the user to be contacted directly by the said bridge.
# 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. # If a binding is returned, the application server is not expected to sign the message as it is not meant to be
msisdn: "(|(telephoneNumber=+%3pid)(mobile=+%3pid)(homePhone=+%3pid)(otherTelephone=+%3pid)(otherMobile=+%3pid)(otherHomePhone=+%3pid))" # 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!
# Enable unknown 3PID bridging globally
#
#lookup.recursive.bridge.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.
#
#lookup.recursive.bridge.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.
#
#lookup.recursive.bridge.server: ''
# Configure each 3PID type with a specific application server. Remove config item or leave empty/blank to disable.
#
#lookup.recursive.bridge.mappings.email: 'http://localhost:8091'
#lookup.recursive.bridge.mappings.msisdn: ''
forward: #####################
# LDAP config items #
#####################
# Global enable/disable switch
#
#ldap.enabled: false
# List of forwarders to use to try to match a 3PID.
# #### Connection related config items
# Each server will be tried in the given order, going to the next if no binding was found or an error occurred. # If the connection should be secure
# These are the current root Identity Servers of the Matrix network. #
servers: #ldap.connection.tls: false
- "https://matrix.org"
- "https://vector.im"
# Host to connect to
#
#ldap.connection.host: 'localhost'
# Port to connect to
#
#ldap.connection.port: 389
# Bind DN for the connection.
#
# If Bind DN and password are empty, anonymous authentication is performed
#
#ldap.connection.bindDn: 'CN=Matrix Identity Server,CN=Users,DC=example,DC=org'
# Bind password for the connection.
#
#ldap.connection.bindPassword: 'password'
# Base DN used in all queries
#
#ldap.connection.baseDn: 'CN=Users,DC=example,DC=org'
#### How to map Matrix attributes with LDAP attributes when performing lookup/auth
#
# 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'
#
#ldap.attribute.uid.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.
#
#ldap.attribute.uid.value: 'userPrincipalName'
# The display name of the user
#
#ldap.attribute.name: 'displayName'
#### Configuration section relating the authentication of users performed via LDAP.
#
# This can be done using the REST Auth module for synapse and pointing it to the identity server.
# See https://github.com/kamax-io/matrix-synapse-rest-auth
#
# During authentication, What to filter potential users by, typically by using a dedicated group.
# If this value is not set, login check will be performed for all entities within the LDAP
#
# Example: (memberOf=CN=Matrix Users,CN=Users,DC=example,DC=org)
#
#ldap.auth.filter: ''
#### Configuration section relating to identity lookups
#
# E-mail query
#
#ldap.identity.medium.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.
#
#ldap.identity.medium.msisdn: "(|(telephoneNumber=+%3pid)(mobile=+%3pid)(homePhone=+%3pid)(otherTelephone=+%3pid)(otherMobile=+%3pid)(otherHomePhone=+%3pid))"
############################
# SQL Provider config item #
############################
#
# Example configuration to integrate with synapse SQLite DB (default configuration)
#
#sql.enabled: true
#sql.type: 'sqlite'
#sql.connection: '/var/lib/matrix-synapse/homeserver.db'
#
# Example configuration to integrate with synapse PostgreSQL DB
#sql.enabled: true
#sql.type: 'postgresql'
#sql.connection: '//dnsOrIpToServer/dbName?user=synapseDbUser&password=synapseDbPassword'
#
# Configuration for an arbitrary server with arbitrary driver
#
# sql.identity.type possible values:
# - uid Returned value is the localpart of the Matrix ID
# - mxid Full Matrix ID, including domain
#
# sql.identity.query MUST contain a column with label 'uid'
#
# If you would like to overwrite the global lookup query for specific medium type,
# add a config item (see below for example) in the following format
# sql.identity.medium.theMediumIdYouWant: 'the query'
#sql.enabled: true
#sql.type: 'jdbcDriverName'
#sql.connection: '//dnsOrIpToServer/dbName?user=synapseDbUser&password=synapseDbPassword'
#sql.identity.type: 'mxid'
#sql.identity.query: 'SELECT raw AS uid FROM table WHERE medium = ? AND address = ?'
#sql.identity.medium.email: 'SELECT raw AS uid FROM emailTable WHERE address = ?'
#######################################
# Lookup queries forward config items #
#######################################
# 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.
#
#forward.servers:
# - "https://matrix.org"
# - "https://vector.im"
#############################
# 3PID invites config items #
#############################
#
#### E-mail invite sender
#
# SMTP host
invite.sender.email.host: "smtp.example.org"
# SMTP port
invite.sender.email.port: 587
# TLS mode for the connection.
#
# Possible values:
# 0 Disable TLS entirely
# 1 Enable TLS if supported by server
# 2 Force TLS and fail if not available
#
#invite.sender.email.tls: 1
# Login for SMTP
invite.sender.email.login: "matrix-identity@example.org"
# Password for the account
invite.sender.email.password: "ThePassword"
# The e-mail to send as. If empty, will be the same as login
invite.sender.email.email: "matrix-identity@example.org"
# The display name used in the e-mail
#
#invite.sender.email.name: "mxisd Identity Server"
# The E-mail template to use, using built-in template by default
#
# The template is expected to be a full e-mail body, including client headers, using MIME and UTF-8 encoding.
# The following headers will be set by mxisd directly and should not be present in the template:
# - From
# - To
# - Date
# - Message-Id
# - X-Mailer
#
# The following placeholders are available:
# - %DOMAIN% Domain name as per server.name config item
# - %DOMAIN_PRETTY% Word capitalize version of the domain. e.g. example.org -> Example.org
# - %FROM_EMAIL% Value of this section's email config item
# - %FROM_NAME% Value of this section's name config item
# - %SENDER_ID% Matrix ID of the invitation sender
# - %SENDER_NAME% Display name of the invitation sender, empty if not available
# - %SENDER_NAME_OR_ID% Value of %SENDER_NAME% or, if empty, value of %SENDER_ID%
# - %INVITE_MEDIUM% Medium of the invite (e.g. email, msisdn)
# - %INVITE_ADDRESS% Address used to invite
# - %ROOM_ID% ID of the room where the invitation took place
# - %ROOM_NAME% Name of the room, empty if not available
# - %ROOM_NAME_OR_ID% Value of %ROOM_NAME% or, if empty, value of %ROOM_ID%
#
#invite.sender.email.template: "/absolute/path/to/file"
############################
# Persistence config items #
############################
# Configure the storage backend, usually a DB
# Possible built-in values:
# sqlite SQLite backend, default
#
#storage.backend: 'sqlite'
#### Generic SQLite provider config
#
# Path to the SQLite DB file, required if SQLite backend is chosen
#
# Examples:
# - /var/opt/mxisd/mxisd.db
# - /var/local/mxisd/mxisd.db
# - /var/lib/mxisd/mxisd.db
#
storage.provider.sqlite.database: '/path/to/mxisd.db'
######################
# DNS-related config #
######################
# The domain to overwrite
#
#dns.overwrite.homeserver.name: 'example.org'
# - 'env' from environment variable specified by value
# - any other value will use the value as-is as host
#
#dns.overwrite.homeserver.type: 'raw'
# The value to use, depending on the type.
# Protocol will always be HTTPS
#
#dns.overwrite.homeserver.value: 'localhost:8448'

View File

@@ -40,6 +40,9 @@ def debBuildConfPath = "${debBuildBasePath}${debConfPath}"
def debBuildDataPath = "${debBuildBasePath}${debDataPath}" def debBuildDataPath = "${debBuildBasePath}${debDataPath}"
def debBuildSystemdPath = "${debBuildBasePath}${debSystemdPath}" def debBuildSystemdPath = "${debBuildBasePath}${debSystemdPath}"
def dockerImageName = "kamax/mxisd"
def dockerImageTag = "${dockerImageName}:${gitVersion()}"
String gitVersion() { String gitVersion() {
def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?") def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?")
ByteArrayOutputStream out = new ByteArrayOutputStream() ByteArrayOutputStream out = new ByteArrayOutputStream()
@@ -62,6 +65,7 @@ buildscript {
} }
repositories { repositories {
maven { url "https://kamax.io/maven/releases/" }
mavenCentral() mavenCentral()
} }
@@ -75,11 +79,14 @@ dependencies {
// Spring Boot - standalone app // 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.3.RELEASE'
// Matrix Java SDK
compile 'io.kamax:matrix-java-sdk:0.0.2'
// ed25519 handling // ed25519 handling
compile 'net.i2p.crypto:eddsa:0.1.0' compile 'net.i2p.crypto:eddsa:0.1.0'
// LDAP connector // 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 // DNS lookups
compile 'dnsjava:dnsjava:2.1.8' compile 'dnsjava:dnsjava:2.1.8'
@@ -93,7 +100,24 @@ dependencies {
// Phone numbers validation // Phone numbers validation
compile 'com.googlecode.libphonenumber:libphonenumber:8.7.1' 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 Firebase Authentication backend
compile 'com.google.firebase:firebase-admin:5.3.0'
// ORMLite
compile 'com.j256.ormlite:ormlite-jdbc:5.0'
// SQLite
compile 'org.xerial:sqlite-jdbc:3.20.0'
// PostgreSQL
compile 'org.postgresql:postgresql:42.1.4'
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
testCompile 'com.github.tomakehurst:wiremock:2.8.0'
} }
springBoot { springBoot {
@@ -104,6 +128,17 @@ springBoot {
] ]
} }
processResources {
doLast {
copy {
from('build/resources/main/application.yaml') {
rename 'application.yaml', 'mxisd.yaml'
}
into 'build/resources/main'
}
}
}
task buildDeb(dependsOn: build) { task buildDeb(dependsOn: build) {
doLast { doLast {
def v = gitVersion() def v = gitVersion()
@@ -133,10 +168,16 @@ task buildDeb(dependsOn: build) {
into debBuildConfPath into debBuildConfPath
} }
ant.replace( ant.replaceregexp(
file: "${debBuildConfPath}/${debConfFileName}", file: "${debBuildConfPath}/${debConfFileName}",
token: '%SIGNING_KEYS_PATH%', match: "key.path:(.*)",
value: "${debDataPath}/signing.key" 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 { copy {
@@ -182,3 +223,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"
}
}
}

169
docs/backends/rest.md Normal file
View File

@@ -0,0 +1,169 @@
# REST backend
The REST backend allows you to query arbitrary REST JSON endpoints as backends for the following flows:
- Identity lookup
- Authentication
## Configuration
| Key | Default | Description |
---------------------------------|---------------------------------------|------------------------------------------------------|
| rest.enabled | false | Globally enable/disable the REST backend |
| rest.host | *empty* | Default base URL to use for the different endpoints. |
| rest.endpoints.auth | /_mxisd/identity/api/v1/auth | Endpoint to validate credentials |
| rest.endpoints.identity.single | /_mxisd/identity/api/v1/lookup/single | Endpoint to lookup a single 3PID |
| rest.endpoints.identity.bulk | /_mxisd/identity/api/v1/lookup/bulk | Endpoint to lookup 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 only mandatory if at least one endpoint is not a full URL.
## Endpoints
### Authenticate
Configured with `rest.endpoints.auth`
HTTP method: `POST`
Encoding: JSON UTF-8
#### Request Body
```
{
"auth": {
"mxid": "@john.doe:example.org",
"localpart": "john.doe",
"domain": "example.org",
"password": "passwordOfTheUser"
}
}
```
#### Response Body
If the authentication fails:
```
{
"auth": {
"success": false
}
}
```
If the authentication succeed:
- `auth.id` supported values: `localpart`, `mxid`
- `auth.profile` and any sub-member are all optional
```
{
"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"
}
]
}
}
}
```
### Lookup
#### Single
Configured with `rest.endpoints.identity.single`
HTTP method: `POST`
Encoding: JSON UTF-8
#### Request Body
```
{
"lookup": {
"medium": "email",
"address": "john.doe@example.org"
}
}
```
#### Response Body
If a match was found:
- `lookup.id.type` supported values: `localpart`, `mxid`
```
{
"lookup": {
"medium": "email",
"address": "john.doe@example.org",
"id": {
"type": "mxid",
"value": "@john:example.org"
}
}
}
```
If no match was found:
```
{}
```
#### Bulk
Configured with `rest.endpoints.identity.bulk`
HTTP method: `POST`
Encoding: JSON UTF-8
#### Request Body
```
{
"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`
```
{
"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:
```
{
"lookup": []
}
```

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

@@ -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;
// FIXME this should be in matrix-java-sdk
public class ThreePid {
private String medium;
private String address;
public ThreePid(String medium, String address) {
this.medium = medium;
this.address = address;
}
public String getMedium() {
return medium;
}
public String getAddress() {
return address;
}
@Override
public String toString() {
return getMedium() + ":" + getAddress();
}
}

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,93 @@
/*
* 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._MatrixID;
import io.kamax.mxisd.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);
@Autowired
private List<AuthenticatorProvider> providers = new ArrayList<>();
@Autowired
private MatrixConfig mxCfg;
@Autowired
private InvitationManager invMgr;
public UserAuthResult authenticate(String id, String password) {
_MatrixID mxid = new MatrixID(id);
for (AuthenticatorProvider provider : providers) {
if (!provider.isEnabled()) {
continue;
}
BackendAuthResult result = provider.authenticate(mxid, password);
if (result.isSuccess()) {
String mxId;
if (UserIdType.Localpart.is(result.getId().getType())) {
mxId = new MatrixID(result.getId().getValue(), mxCfg.getDomain()).getId();
} else if (UserIdType.MatrixID.is(result.getId().getType())) {
mxId = new MatrixID(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(mxId, 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, authResult.getMxid()));
}
invMgr.lookupMappingsForInvites();
return authResult;
}
}
return new UserAuthResult().failure();
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.ThreePidMedium;
import io.kamax.mxisd.ThreePid;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class UserAuthResult {
private boolean success;
private String mxid;
private String displayName;
private List<ThreePid> threePids = new ArrayList<>();
public UserAuthResult failure() {
success = false;
mxid = null;
displayName = null;
return this;
}
public UserAuthResult success(String mxid, String displayName) {
setSuccess(true);
setMxid(mxid);
setDisplayName(displayName);
return this;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMxid() {
return mxid;
}
public void setMxid(String mxid) {
this.mxid = mxid;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public UserAuthResult withThreePid(ThreePidMedium medium, String address) {
return withThreePid(medium.getId(), address);
}
public UserAuthResult withThreePid(String medium, String address) {
threePids.add(new ThreePid(medium, address));
return this;
}
public List<ThreePid> getThreePids() {
return Collections.unmodifiableList(threePids);
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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._MatrixID;
public interface AuthenticatorProvider {
boolean isEnabled();
BackendAuthResult authenticate(_MatrixID mxid, String password);
}

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.auth.provider;
import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.UserID;
import io.kamax.mxisd.UserIdType;
import java.util.ArrayList;
import java.util.List;
public class BackendAuthResult {
public static class BackendAuthProfile {
private String displayName;
private List<ThreePid> threePids = new ArrayList<>();
public String getDisplayName() {
return displayName;
}
public List<ThreePid> getThreePids() {
return threePids;
}
}
public static BackendAuthResult failure() {
BackendAuthResult r = new BackendAuthResult();
r.success = false;
return r;
}
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.success = true;
r.id = new UserID(type, id);
r.profile = new BackendAuthProfile();
r.profile.displayName = displayName;
return r;
}
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,193 @@
/*
* 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.*
import com.google.firebase.internal.NonNull
import com.google.firebase.tasks.OnFailureListener
import com.google.firebase.tasks.OnSuccessListener
import io.kamax.matrix.ThreePidMedium
import io.kamax.matrix._MatrixID
import io.kamax.mxisd.ThreePid
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
import java.util.regex.Pattern
public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
private Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class);
private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)"); // FIXME use matrix-java-sdk
private boolean isEnabled;
private String domain;
private FirebaseApp fbApp;
private FirebaseAuth fbAuth;
private void waitOnLatch(BackendAuthResult result, CountDownLatch l, long timeout, TimeUnit unit, String purpose) {
try {
l.await(timeout, unit);
} catch (InterruptedException e) {
log.warn("Interrupted while waiting for " + purpose);
result.failure();
}
}
public GoogleFirebaseAuthenticator(boolean isEnabled) {
this.isEnabled = isEnabled;
}
public GoogleFirebaseAuthenticator(String credsPath, String db, String domain) {
this(true);
this.domain = domain;
try {
fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "AuthenticationProvider");
fbAuth = FirebaseAuth.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();
}
@Override
public boolean isEnabled() {
return isEnabled;
}
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);
BackendAuthResult result = BackendAuthResult.failure();
String localpart = m.group(1);
CountDownLatch l = new CountDownLatch(1);
fbAuth.verifyIdToken(password).addOnSuccessListener(new OnSuccessListener<FirebaseToken>() {
@Override
void onSuccess(FirebaseToken token) {
try {
if (!StringUtils.equals(localpart, token.getUid())) {
log.info("Failture to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", id, localpart, token.getUid());
result = BackendAuthResult.failure();
return;
}
result = BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, token.getName());
log.info("{} was successfully authenticated", mxid);
log.info("Fetching profile for {}", mxid);
CountDownLatch userRecordLatch = new CountDownLatch(1);
fbAuth.getUser(token.getUid()).addOnSuccessListener(new OnSuccessListener<UserRecord>() {
@Override
void onSuccess(UserRecord user) {
try {
if (StringUtils.isNotBlank(user.getEmail())) {
result.withThreePid(new ThreePid(ThreePidMedium.Email.getId(), user.getEmail()));
}
if (StringUtils.isNotBlank(user.getPhoneNumber())) {
result.withThreePid(new ThreePid(ThreePidMedium.PhoneNumber.getId(), user.getPhoneNumber()));
}
} finally {
userRecordLatch.countDown();
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
void onFailure(@NonNull Exception e) {
try {
log.warn("Unable to fetch Firebase user profile for {}", mxid);
result = BackendAuthResult.failure();
} finally {
userRecordLatch.countDown();
}
}
});
waitOnLatch(result, userRecordLatch, 30, TimeUnit.SECONDS, "Firebase user profile");
} finally {
l.countDown()
}
}
}).addOnFailureListener(new OnFailureListener() {
@Override
void onFailure(@NonNull Exception e) {
try {
if (e instanceof IllegalArgumentException) {
log.info("Failure to authenticate {}: invalid firebase token", mxid);
} else {
log.info("Failure to authenticate {}: {}", id, e.getMessage(), e);
log.info("Exception", e);
}
result = BackendAuthResult.failure();
} finally {
l.countDown()
}
}
});
waitOnLatch(result, l, 30, TimeUnit.SECONDS, "Firebase auth check");
return result;
}
}

View File

@@ -0,0 +1,191 @@
/*
* 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.auth.UserRecord
import com.google.firebase.internal.NonNull
import com.google.firebase.tasks.OnFailureListener
import com.google.firebase.tasks.OnSuccessListener
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.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
import java.util.regex.Pattern
public class GoogleFirebaseProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(GoogleFirebaseProvider.class);
private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)");
private boolean isEnabled;
private String domain;
private FirebaseApp fbApp;
private FirebaseAuth fbAuth;
public GoogleFirebaseProvider(boolean isEnabled) {
this.isEnabled = isEnabled;
}
public GoogleFirebaseProvider(String credsPath, String db, String domain) {
this(true);
this.domain = domain;
try {
fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "ThreePidProvider");
fbAuth = FirebaseAuth.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();
}
private String getMxid(UserRecord record) {
return "@${record.getUid()}:${domain}";
}
@Override
public boolean isEnabled() {
return isEnabled;
}
@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) {
UserRecord r;
CountDownLatch l = new CountDownLatch(1);
OnSuccessListener<UserRecord> success = new OnSuccessListener<UserRecord>() {
@Override
void onSuccess(UserRecord result) {
log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid())
r = result;
l.countDown()
}
};
OnFailureListener failure = new OnFailureListener() {
@Override
void onFailure(@NonNull Exception e) {
log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage())
r = null;
l.countDown()
}
};
if (ThreePidMedium.Email.is(medium)) {
log.info("Performing E-mail 3PID lookup for {}", address)
fbAuth.getUserByEmail(address)
.addOnSuccessListener(success)
.addOnFailureListener(failure);
waitOnLatch(l);
} else if (ThreePidMedium.PhoneNumber.is(medium)) {
log.info("Performing msisdn 3PID lookup for {}", address)
fbAuth.getUserByPhoneNumber(address)
.addOnSuccessListener(success)
.addOnFailureListener(failure);
waitOnLatch(l);
} else {
log.info("{} is not a supported 3PID medium", medium);
r = null;
}
return Optional.ofNullable(r);
}
@Override
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
Optional<UserRecord> urOpt = findInternal(request.getType(), request.getThreePid())
if (urOpt.isPresent()) {
return Optional.of(new SingleLookupReply(request, getMxid(urOpt.get())));
}
return Optional.empty();
}
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
List<ThreePidMapping> results = new ArrayList<>();
mappings.parallelStream().forEach(new Consumer<ThreePidMapping>() {
@Override
void accept(ThreePidMapping 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,125 @@
/*
* 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.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.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.stereotype.Component;
import java.io.IOException;
@Component
public class LdapAuthProvider extends LdapGenericBackend implements AuthenticatorProvider {
private Logger log = LoggerFactory.getLogger(LdapAuthProvider.class);
private String getUidAttribute() {
return getCfg().getAttribute().getUid().getValue();
}
@Override
public boolean isEnabled() {
return getCfg().isEnabled();
}
@Override
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
log.info("Performing auth for {}", mxid);
LdapConnection conn = getConn();
try {
bind(conn);
String uidType = getCfg().getAttribute().getUid().getType();
String userFilterValue = StringUtils.equals(LdapThreePidProvider.UID, uidType) ? mxid.getLocalPart() : mxid.getId();
String userFilter = "(" + getCfg().getAttribute().getUid().getValue() + "=" + userFilterValue + ")";
if (!StringUtils.isBlank(getCfg().getAuth().getFilter())) {
userFilter = "(&" + getCfg().getAuth().getFilter() + userFilter + ")";
}
EntryCursor cursor = conn.search(getCfg().getConn().getBaseDn(), userFilter, SearchScope.SUBTREE, getUidAttribute(), getCfg().getAttribute().getName());
try {
while (cursor.next()) {
Entry entry = cursor.get();
String dn = entry.getDn().getName();
log.info("Checking possible match, DN: {}", dn);
Attribute attribute = entry.get(getUidAttribute());
if (attribute == null) {
log.info("DN {}: no attribute {}, skpping", dn, getUidAttribute());
continue;
}
String data = attribute.get().toString();
if (data.length() < 1) {
log.info("DN {}: empty attribute {}, skipping", getUidAttribute());
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(getCfg().getAttribute().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?
return BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, name);
}
} catch (CursorLdapReferralException e) {
log.warn("Entity for {} is only available via referral, skipping", mxid);
} finally {
cursor.close();
}
log.info("No match were found for {}", mxid);
return BackendAuthResult.failure();
} catch (LdapException | IOException | CursorException e) {
throw new RuntimeException(e);
} finally {
try {
conn.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.ldap.LdapConfig;
import org.apache.commons.lang.StringUtils;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class LdapGenericBackend {
private Logger log = LoggerFactory.getLogger(LdapGenericBackend.class);
@Autowired
private LdapConfig ldapCfg;
protected LdapConnection getConn() {
return new LdapNetworkConnection(ldapCfg.getConn().getHost(), ldapCfg.getConn().getPort(), ldapCfg.getConn().isTls());
}
protected void bind(LdapConnection conn) throws LdapException {
if (StringUtils.isBlank(ldapCfg.getConn().getBindDn()) && StringUtils.isBlank(ldapCfg.getConn().getBindPassword())) {
conn.anonymousBind();
} else {
conn.bind(ldapCfg.getConn().getBindDn(), ldapCfg.getConn().getBindPassword());
}
}
protected LdapConfig getCfg() {
return ldapCfg;
}
}

View File

@@ -18,12 +18,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.lookup.provider package io.kamax.mxisd.backend.ldap
import io.kamax.mxisd.config.LdapConfig import io.kamax.mxisd.config.MatrixConfig
import io.kamax.mxisd.config.ServerConfig import io.kamax.mxisd.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.provider.IThreePidProvider
import org.apache.commons.lang.StringUtils import org.apache.commons.lang.StringUtils
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException 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.cursor.EntryCursor
@@ -31,29 +32,29 @@ 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.entry.Entry
import org.apache.directory.api.ldap.model.message.SearchScope import org.apache.directory.api.ldap.model.message.SearchScope
import org.apache.directory.ldap.client.api.LdapConnection import org.apache.directory.ldap.client.api.LdapConnection
import org.apache.directory.ldap.client.api.LdapNetworkConnection
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
class LdapProvider implements IThreePidProvider { class LdapThreePidProvider extends LdapGenericBackend implements IThreePidProvider {
public static final String UID = "uid" public static final String UID = "uid"
public static final String MATRIX_ID = "mxid" public static final String MATRIX_ID = "mxid"
private Logger log = LoggerFactory.getLogger(LdapProvider.class) private Logger log = LoggerFactory.getLogger(LdapThreePidProvider.class)
@Autowired @Autowired
private ServerConfig srvCfg private MatrixConfig mxCfg
@Autowired
private LdapConfig ldapCfg
@Override @Override
boolean isEnabled() { boolean isEnabled() {
return ldapCfg.getEnabled() return getCfg().isEnabled()
}
private String getUidAttribute() {
return getCfg().getAttribute().getUid().getValue();
} }
@Override @Override
@@ -67,39 +68,42 @@ class LdapProvider implements IThreePidProvider {
} }
Optional<String> lookup(LdapConnection conn, String medium, String value) { Optional<String> lookup(LdapConnection conn, String medium, String value) {
Optional<String> queryOpt = ldapCfg.getMapping(medium) String uidAttribute = getUidAttribute()
Optional<String> queryOpt = getCfg().getIdentity().getQuery(medium)
if (!queryOpt.isPresent()) { if (!queryOpt.isPresent()) {
log.warn("{} is not a configured 3PID type for LDAP lookup", medium) log.warn("{} is not a configured 3PID type for LDAP lookup", medium)
return Optional.empty() return Optional.empty()
} }
String searchQuery = queryOpt.get().replaceAll("%3pid", value) String searchQuery = queryOpt.get().replaceAll("%3pid", value)
EntryCursor cursor = conn.search(ldapCfg.getBaseDn(), searchQuery, SearchScope.SUBTREE, ldapCfg.getAttribute()) EntryCursor cursor = conn.search(getCfg().getConn().getBaseDn(), searchQuery, SearchScope.SUBTREE, uidAttribute)
try { try {
while (cursor.next()) { while (cursor.next()) {
Entry entry = cursor.get() Entry entry = cursor.get()
log.info("Found possible match, DN: {}", entry.getDn().getName()) log.info("Found possible match, DN: {}", entry.getDn().getName())
Attribute attribute = entry.get(ldapCfg.getAttribute()) Attribute attribute = entry.get(uidAttribute)
if (attribute == null) { if (attribute == null) {
log.info("DN {}: no attribute {}, skpping", entry.getDn(), ldapCfg.getAttribute()) log.info("DN {}: no attribute {}, skpping", entry.getDn(), getCfg().getAttribute())
continue continue
} }
String data = attribute.get().toString() String data = attribute.get().toString()
if (data.length() < 1) { if (data.length() < 1) {
log.info("DN {}: empty attribute {}, skipping", ldapCfg.getAttribute()) log.info("DN {}: empty attribute {}, skipping", getCfg().getAttribute())
continue continue
} }
StringBuilder matrixId = new StringBuilder() StringBuilder matrixId = new StringBuilder()
// TODO Should we turn this block into a map of functions? // TODO Should we turn this block into a map of functions?
if (StringUtils.equals(UID, ldapCfg.getType())) { String uidType = getCfg().getAttribute().getUid().getType()
matrixId.append("@").append(data).append(":").append(srvCfg.getName()) if (StringUtils.equals(UID, uidType)) {
} else if (StringUtils.equals(MATRIX_ID, ldapCfg.getType())) { matrixId.append("@").append(data).append(":").append(mxCfg.getDomain())
} else if (StringUtils.equals(MATRIX_ID, uidType)) {
matrixId.append(data) matrixId.append(data)
} else { } else {
log.warn("Bind was found but type {} is not supported", ldapCfg.getType()) log.warn("Bind was found but type {} is not supported", uidType)
continue continue
} }
@@ -116,23 +120,16 @@ class LdapProvider implements IThreePidProvider {
} }
@Override @Override
Optional<?> find(SingleLookupRequest request) { Optional<SingleLookupReply> find(SingleLookupRequest request) {
log.info("Performing LDAP lookup ${request.getThreePid()} of type ${request.getType()}") log.info("Performing LDAP lookup ${request.getThreePid()} of type ${request.getType()}")
LdapConnection conn = new LdapNetworkConnection(ldapCfg.getHost(), ldapCfg.getPort(), ldapCfg.getTls()) LdapConnection conn = getConn()
try { try {
conn.bind(ldapCfg.getBindDn(), ldapCfg.getBindPassword()) bind(conn)
Optional<String> mxid = lookup(conn, request.getType(), request.getThreePid()) Optional<String> mxid = lookup(conn, request.getType(), request.getThreePid())
if (mxid.isPresent()) { if (mxid.isPresent()) {
return Optional.of([ return Optional.of(new SingleLookupReply(request, mxid.get()));
address : request.getThreePid(),
medium : request.getType(),
mxid : mxid.get(),
not_before: 0,
not_after : 9223372036854775807,
ts : 0
])
} }
} finally { } finally {
conn.close() conn.close()
@@ -147,9 +144,9 @@ class LdapProvider implements IThreePidProvider {
log.info("Looking up {} mappings", mappings.size()) log.info("Looking up {} mappings", mappings.size())
List<ThreePidMapping> mappingsFound = new ArrayList<>() List<ThreePidMapping> mappingsFound = new ArrayList<>()
LdapConnection conn = new LdapNetworkConnection(ldapCfg.getHost(), ldapCfg.getPort()) LdapConnection conn = getConn()
try { try {
conn.bind(ldapCfg.getBindDn(), ldapCfg.getBindPassword()) bind(conn)
for (ThreePidMapping mapping : mappings) { for (ThreePidMapping mapping : mappings) {
try { try {

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

@@ -0,0 +1,43 @@
/*
* 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.mxisd.UserID;
public class LookupSingleResponseJson {
private String medium;
private String address;
private UserID id;
public String getMedium() {
return medium;
}
public String getAddress() {
return address;
}
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,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.SqlProviderConfig;
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 SqlAuthProvider implements AuthenticatorProvider {
private Logger log = LoggerFactory.getLogger(SqlAuthProvider.class);
@Autowired
private ServerConfig srvCfg;
@Autowired
private SqlProviderConfig 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,108 @@
/*
* 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.SqlProviderConfig;
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.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.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Component
public class SqlThreePidProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(SqlThreePidProvider.class);
@Autowired
private MatrixConfig mxCfg;
@Autowired
private SqlProviderConfig cfg;
@Override
public boolean isEnabled() {
return cfg.isEnabled();
}
@Override
public boolean isLocal() {
return true;
}
@Override
public int getPriority() {
return 20;
}
private Connection getConn() throws SQLException {
return DriverManager.getConnection("jdbc:" + cfg.getType() + ":" + cfg.getConnection());
}
@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 (PreparedStatement stmt = getConn().prepareStatement(stmtSql)) {
stmt.setString(1, request.getType().toLowerCase());
stmt.setString(2, request.getThreePid().toLowerCase());
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<>();
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.Optional;
@Configuration
@ConfigurationProperties("dns.overwrite")
public class DnsOverwrite {
private Logger log = LoggerFactory.getLogger(DnsOverwrite.class);
@Autowired
private ServerConfig srvCfg;
@Autowired
private DnsOverwriteEntry homeserver;
public Optional<DnsOverwriteEntry> findHost(String lookup) {
if (homeserver != null && StringUtils.equalsIgnoreCase(lookup, homeserver.getName())) {
return Optional.of(homeserver);
}
return Optional.empty();
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties("dns.overwrite.homeserver")
public class DnsOverwriteEntry {
private String name;
private String type;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getTarget() {
if (StringUtils.equals("env", getType())) {
return System.getenv(getValue());
} else {
return getValue();
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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 io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.backend.firebase.GoogleFirebaseAuthenticator;
import io.kamax.mxisd.backend.firebase.GoogleFirebaseProvider;
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.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@ConfigurationProperties("firebase")
public class FirebaseConfig {
private Logger log = LoggerFactory.getLogger(FirebaseConfig.class);
@Autowired
private MatrixConfig mxCfg;
private boolean enabled;
private String credentials;
private String database;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getCredentials() {
return credentials;
}
public void setCredentials(String credentials) {
this.credentials = credentials;
}
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
@PostConstruct
private void postConstruct() {
log.info("--- Firebase configuration ---");
log.info("Enabled: {}", isEnabled());
if (isEnabled()) {
log.info("Credentials: {}", getCredentials());
log.info("Database: {}", getDatabase());
}
}
@Bean
public AuthenticatorProvider getAuthProvider() {
if (!enabled) {
return new GoogleFirebaseAuthenticator(false);
} else {
return new GoogleFirebaseAuthenticator(credentials, database, mxCfg.getDomain());
}
}
@Bean
public IThreePidProvider getLookupProvider() {
if (!enabled) {
return new GoogleFirebaseProvider(false);
} else {
return new GoogleFirebaseProvider(credentials, database, mxCfg.getDomain());
}
}
}

View File

@@ -27,7 +27,7 @@ import org.springframework.context.annotation.Configuration
@ConfigurationProperties(prefix = "forward") @ConfigurationProperties(prefix = "forward")
class ForwardConfig { class ForwardConfig {
private List<String> servers private List<String> servers = new ArrayList<>()
List<String> getServers() { List<String> getServers() {
return servers return servers

View File

@@ -20,6 +20,7 @@
package io.kamax.mxisd.config package io.kamax.mxisd.config
import io.kamax.mxisd.exception.ConfigurationException
import org.apache.commons.lang.StringUtils import org.apache.commons.lang.StringUtils
import org.springframework.beans.factory.InitializingBean import org.springframework.beans.factory.InitializingBean
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
@@ -42,7 +43,7 @@ class KeyConfig implements InitializingBean {
@Override @Override
void afterPropertiesSet() throws Exception { void afterPropertiesSet() throws Exception {
if (StringUtils.isBlank(getPath())) { if (StringUtils.isBlank(getPath())) {
throw new RuntimeException("Key path must be configured!") throw new ConfigurationException("key.path")
} }
} }

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

@@ -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 io.kamax.mxisd.exception.ConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@ConfigurationProperties("matrix")
public class MatrixConfig {
private Logger log = LoggerFactory.getLogger(MatrixConfig.class);
private String domain;
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
@PostConstruct
public void build() {
log.info("--- Matrix config ---");
if (StringUtils.isBlank(domain)) {
throw new ConfigurationException("matrix.domain");
}
log.info("Domain: {}", getDomain());
}
}

View File

@@ -1,3 +1,23 @@
/*
* 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 package io.kamax.mxisd.config
import org.slf4j.Logger import org.slf4j.Logger
@@ -15,7 +35,7 @@ class RecursiveLookupBridgeConfig implements InitializingBean {
private boolean enabled private boolean enabled
private boolean recursiveOnly private boolean recursiveOnly
private String server private String server
private Map<String, String> mappings private Map<String, String> mappings = new HashMap<>()
boolean getEnabled() { boolean getEnabled() {
return enabled return enabled
@@ -51,10 +71,13 @@ class RecursiveLookupBridgeConfig implements InitializingBean {
@Override @Override
void afterPropertiesSet() throws Exception { void afterPropertiesSet() throws Exception {
log.info("--- Bridge integration lookups config ---")
log.info("Enabled: {}", getEnabled()) log.info("Enabled: {}", getEnabled())
log.info("Recursive only: {}", getRecursiveOnly()) if (getEnabled()) {
log.info("Server: {}", getServer()) log.info("Recursive only: {}", getRecursiveOnly())
log.info("Mappings: {}", mappings.size()) log.info("Fallback Server: {}", getServer())
log.info("Mappings: {}", mappings.size())
}
} }
} }

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.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties("storage.provider.sqlite")
public class SQLiteStorageConfig {
private String database;
public String getDatabase() {
return database;
}
public void setDatabase(String database) {
this.database = database;
}
}

View File

@@ -21,7 +21,10 @@
package io.kamax.mxisd.config package io.kamax.mxisd.config
import org.apache.commons.lang.StringUtils import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.InitializingBean import org.springframework.beans.factory.InitializingBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
@@ -29,7 +32,14 @@ import org.springframework.context.annotation.Configuration
@ConfigurationProperties(prefix = "server") @ConfigurationProperties(prefix = "server")
class ServerConfig implements InitializingBean { class ServerConfig implements InitializingBean {
private Logger log = LoggerFactory.getLogger(ServerConfig.class);
@Autowired
private MatrixConfig mxCfg;
private String name private String name
private int port
private String publicUrl
String getName() { String getName() {
return name return name
@@ -39,11 +49,47 @@ class ServerConfig implements InitializingBean {
this.name = name this.name = name
} }
int getPort() {
return port
}
void setPort(int port) {
this.port = port
}
String getPublicUrl() {
return publicUrl
}
void setPublicUrl(String publicUrl) {
this.publicUrl = publicUrl
}
@Override @Override
void afterPropertiesSet() throws Exception { void afterPropertiesSet() throws Exception {
log.info("--- Server config ---")
if (StringUtils.isBlank(getName())) { if (StringUtils.isBlank(getName())) {
throw new RuntimeException("Server name must be configured. Use the same realm as your Homeserver") setName(mxCfg.getDomain());
log.debug("server.name is empty, using matrix.domain");
} }
if (StringUtils.isBlank(getPublicUrl())) {
setPublicUrl("https://${getName()}");
log.debug("Public URL is empty, generating from name");
} else {
setPublicUrl(StringUtils.replace(getPublicUrl(), "%SERVER_NAME%", getName()));
}
try {
new URL(getPublicUrl())
} catch (MalformedURLException e) {
log.warn("Public URL is not valid: {}", StringUtils.defaultIfBlank(e.getMessage(), "<no reason provided>"))
}
log.info("Name: {}", getName())
log.info("Port: {}", getPort())
log.info("Public URL: {}", getPublicUrl())
} }
} }

View File

@@ -0,0 +1,51 @@
/*
* 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 io.kamax.mxisd.exception.ConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@ConfigurationProperties("storage")
public class StorageConfig {
private String backend;
public String getBackend() {
return backend;
}
public void setBackend(String backend) {
this.backend = backend;
}
@PostConstruct
private void postConstruct() {
if (StringUtils.isBlank(getBackend())) {
throw new ConfigurationException("storage.backend");
}
}
}

View File

@@ -0,0 +1,135 @@
/*
* 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.invite.sender;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.io.File;
@Configuration
@ConfigurationProperties(prefix = "invite.sender.email")
public class EmailSenderConfig {
private Logger log = LoggerFactory.getLogger(EmailSenderConfig.class);
private String host;
private int port;
private int tls;
private String login;
private String password;
private String email;
private String name;
private String template;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getTls() {
return tls;
}
public void setTls(int tls) {
this.tls = tls;
}
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTemplate() {
return template;
}
public void setTemplate(String template) {
this.template = template;
}
@PostConstruct
private void postConstruct() {
log.info("--- E-mail Invite Sender config ---");
log.info("Host: {}", getHost());
log.info("Port: {}", getPort());
log.info("TLS Mode: {}", getTls());
log.info("Login: {}", getLogin());
log.info("Has password: {}", StringUtils.isBlank(getPassword()));
log.info("E-mail: {}", getEmail());
if (!StringUtils.startsWith(getTemplate(), "classpath:")) {
if (StringUtils.isBlank(getTemplate())) {
log.warn("invite.sender.template is empty! Will not send invites");
} else {
File cp = new File(getTemplate()).getAbsoluteFile();
log.info("Template: {}", cp.getAbsolutePath());
if (!cp.exists() || !cp.isFile() || !cp.canRead()) {
log.warn(getTemplate() + " does not exist, is not a file or cannot be read");
}
}
} else {
log.info("Template: Built-in");
}
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.ldap;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "ldap.attribute")
public class LdapAttributeConfig {
private LdapAttributeUidConfig uid;
private String name;
public LdapAttributeUidConfig getUid() {
return uid;
}
public void setUid(LdapAttributeUidConfig uid) {
this.uid = uid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.ldap;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "ldap.attribute.uid")
public class LdapAttributeUidConfig {
private String type;
private String value;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

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.config.ldap;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "ldap.auth")
public class LdapAuthConfig {
private String filter;
public String getFilter() {
return filter;
}
public void setFilter(String filter) {
this.filter = filter;
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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.ldap
import groovy.json.JsonOutput
import io.kamax.mxisd.backend.ldap.LdapThreePidProvider
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
import javax.annotation.PostConstruct
@Configuration
@ConfigurationProperties(prefix = "ldap")
class LdapConfig {
private Logger log = LoggerFactory.getLogger(LdapConfig.class)
private boolean enabled
@Autowired
private LdapConnectionConfig conn
private LdapAttributeConfig attribute
private LdapAuthConfig auth
private LdapIdentityConfig identity
boolean isEnabled() {
return enabled
}
void setEnabled(boolean enabled) {
this.enabled = enabled
}
LdapConnectionConfig getConn() {
return conn
}
void setConn(LdapConnectionConfig conn) {
this.conn = conn
}
LdapAttributeConfig getAttribute() {
return attribute
}
void setAttribute(LdapAttributeConfig attribute) {
this.attribute = attribute
}
LdapAuthConfig getAuth() {
return auth
}
void setAuth(LdapAuthConfig auth) {
this.auth = auth
}
LdapIdentityConfig getIdentity() {
return identity
}
void setIdentity(LdapIdentityConfig identity) {
this.identity = identity
}
@PostConstruct
void afterPropertiesSet() {
log.info("--- LDAP Config ---")
log.info("Enabled: {}", isEnabled())
if (!isEnabled()) {
return
}
if (StringUtils.isBlank(conn.getHost())) {
throw new IllegalStateException("LDAP Host must be configured!")
}
if (1 > conn.getPort() || 65535 < conn.getPort()) {
throw new IllegalStateException("LDAP port is not valid")
}
if (StringUtils.isBlank(attribute.getUid().getType())) {
throw new IllegalStateException("Attribute UID Type cannot be empty")
}
if (StringUtils.isBlank(attribute.getUid().getValue())) {
throw new IllegalStateException("Attribute UID value cannot be empty")
}
String uidType = attribute.getUid().getType();
if (!StringUtils.equals(LdapThreePidProvider.UID, uidType) && !StringUtils.equals(LdapThreePidProvider.MATRIX_ID, uidType)) {
throw new IllegalArgumentException("Unsupported LDAP UID type: " + uidType)
}
log.info("Host: {}", conn.getHost())
log.info("Port: {}", conn.getPort())
log.info("Bind DN: {}", conn.getBindDn())
log.info("Base DN: {}", conn.getBaseDn())
log.info("Attribute: {}", JsonOutput.toJson(attribute))
log.info("Auth: {}", JsonOutput.toJson(auth))
log.info("Identity: {}", JsonOutput.toJson(identity))
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.ldap;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "ldap.connection")
public class LdapConnectionConfig {
private boolean tls;
private String host;
private int port;
private String bindDn;
private String bindPassword;
private String baseDn;
public boolean isTls() {
return tls;
}
public void setTls(boolean tls) {
this.tls = tls;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getBindDn() {
return bindDn;
}
public void setBindDn(String bindDn) {
this.bindDn = bindDn;
}
public String getBindPassword() {
return bindPassword;
}
public void setBindPassword(String bindPassword) {
this.bindPassword = bindPassword;
}
public String getBaseDn() {
return baseDn;
}
public void setBaseDn(String baseDn) {
this.baseDn = baseDn;
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.ldap;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@Configuration
@ConfigurationProperties(prefix = "ldap.identity")
public class LdapIdentityConfig {
private Map<String, String> medium = new HashMap<>();
public Map<String, String> getMedium() {
return medium;
}
public Optional<String> getQuery(String key) {
return Optional.ofNullable(medium.get(key));
}
public void setMedium(Map<String, String> medium) {
this.medium = medium;
}
}

View File

@@ -0,0 +1,149 @@
/*
* 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.rest;
import io.kamax.mxisd.exception.ConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.net.MalformedURLException;
import java.net.URL;
@Configuration
@ConfigurationProperties("rest")
public class RestBackendConfig {
public static class IdentityEndpoints {
private String single;
private String bulk;
public String getSingle() {
return single;
}
public void setSingle(String single) {
this.single = single;
}
public String getBulk() {
return bulk;
}
public void setBulk(String bulk) {
this.bulk = bulk;
}
}
public static class Endpoints {
private IdentityEndpoints identity = new IdentityEndpoints();
private String auth;
public IdentityEndpoints getIdentity() {
return identity;
}
public void setIdentity(IdentityEndpoints identity) {
this.identity = identity;
}
public String getAuth() {
return auth;
}
public void setAuth(String auth) {
this.auth = auth;
}
}
private Logger log = LoggerFactory.getLogger(RestBackendConfig.class);
private boolean enabled;
private String host;
private Endpoints endpoints = new Endpoints();
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Endpoints getEndpoints() {
return endpoints;
}
public void setEndpoints(Endpoints endpoints) {
this.endpoints = endpoints;
}
private String buildEndpointUrl(String endpoint) {
if (StringUtils.startsWith(endpoint, "/")) {
if (StringUtils.isBlank(getHost())) {
throw new ConfigurationException("rest.host");
}
try {
new URL(getHost());
} catch (MalformedURLException e) {
throw new ConfigurationException("rest.host", e.getMessage());
}
return getHost() + endpoint;
} else {
return endpoint;
}
}
@PostConstruct
public void build() {
log.info("--- REST backend config ---");
log.info("Enabled: {}", isEnabled());
if (isEnabled()) {
endpoints.setAuth(buildEndpointUrl(endpoints.getAuth()));
endpoints.identity.setSingle(buildEndpointUrl(endpoints.identity.getSingle()));
endpoints.identity.setBulk(buildEndpointUrl(endpoints.identity.getBulk()));
log.info("Host: {}", getHost());
log.info("Auth endpoint: {}", endpoints.getAuth());
log.info("Identity Single endpoint: {}", endpoints.identity.getSingle());
log.info("Identity Bulk endpoint: {}", endpoints.identity.getBulk());
}
}
}

View File

@@ -0,0 +1,21 @@
package io.kamax.mxisd.config.sql;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
// Unused
@Configuration
@ConfigurationProperties("sql.auth")
public class SqlProviderAuthConfig {
private boolean enabled;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}

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.config.sql;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@ConfigurationProperties("sql")
public class SqlProviderConfig {
private Logger log = LoggerFactory.getLogger(SqlProviderConfig.class);
private boolean enabled;
private String type;
private String connection;
private SqlProviderAuthConfig auth;
private SqlProviderIdentityConfig identity;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getConnection() {
return connection;
}
public void setConnection(String connection) {
this.connection = connection;
}
public SqlProviderAuthConfig getAuth() {
return auth;
}
public void setAuth(SqlProviderAuthConfig auth) {
this.auth = auth;
}
public SqlProviderIdentityConfig getIdentity() {
return identity;
}
public void setIdentity(SqlProviderIdentityConfig identity) {
this.identity = identity;
}
@PostConstruct
private void postConstruct() {
log.info("--- SQL Provider config ---");
log.info("Enabled: {}", isEnabled());
if (isEnabled()) {
log.info("Type: {}", getType());
log.info("Connection: {}", getConnection());
log.info("Auth enabled: {}", getAuth().isEnabled());
log.info("Identy type: {}", getIdentity().getType());
log.info("Identity medium queries: {}", new Gson().toJson(getIdentity().getMedium()));
}
}
}

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.config.sql;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@ConfigurationProperties("sql.identity")
public class SqlProviderIdentityConfig {
private String type;
private String query;
private Map<String, String> medium = new HashMap<>();
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public Map<String, String> getMedium() {
return medium;
}
public void setMedium(Map<String, String> medium) {
this.medium = medium;
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.kamax.mxisd.auth.AuthManager;
import io.kamax.mxisd.auth.UserAuthResult;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@RestController
@CrossOrigin
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class AuthController {
private Logger log = LoggerFactory.getLogger(AuthController.class);
private Gson gson = new Gson();
@Autowired
private AuthManager mgr;
@RequestMapping(value = "/_matrix-internal/identity/v1/check_credentials", method = RequestMethod.POST)
public String checkCredentials(HttpServletRequest req) {
try {
JsonElement el = new JsonParser().parse(IOUtils.toString(req.getInputStream(), StandardCharsets.UTF_8));
if (!el.isJsonObject() || !el.getAsJsonObject().has("user")) {
throw new IllegalArgumentException("Missing user key");
}
JsonObject authData = el.getAsJsonObject().get("user").getAsJsonObject();
if (!authData.has("id") || !authData.has("password")) {
throw new IllegalArgumentException("Missing id or password keys");
}
String id = authData.get("id").getAsString();
log.info("Requested to check credentials for {}", id);
String password = authData.get("password").getAsString();
UserAuthResult result = mgr.authenticate(id, password);
JsonObject authObj = new JsonObject();
authObj.addProperty("success", result.isSuccess());
if (result.isSuccess()) {
authObj.addProperty("mxid", result.getMxid());
authObj.addProperty("display_name", result.getDisplayName());
}
JsonObject obj = new JsonObject();
obj.add("authentication", authObj);
return gson.toJson(obj);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* 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.exception.BadRequestException;
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
@ControllerAdvice
@ResponseBody
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class DefaultExceptionHandler {
private Logger log = LoggerFactory.getLogger(DefaultExceptionHandler.class);
private static Gson gson = new Gson();
static String handle(String erroCode, String error) {
JsonObject obj = new JsonObject();
obj.addProperty("errcode", erroCode);
obj.addProperty("error", error);
return gson.toJson(obj);
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException.class)
public String handle(MissingServletRequestParameterException e) {
return handle("M_INVALID_BODY", e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MappingAlreadyExistsException.class)
public String handle(MappingAlreadyExistsException e) {
return handle("M_ALREADY_EXISTS", e.getMessage());
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BadRequestException.class)
public String handle(BadRequestException e) {
return handle("M_BAD_REQUEST", e.getMessage());
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(RuntimeException.class)
public String handle(HttpServletRequest req, RuntimeException e) {
log.error("Unknown error when handling {}", req.getRequestURL(), e);
return handle("M_UNKNOWN", StringUtils.defaultIfBlank(e.getMessage(), "An uknown error occured. Contact the server administrator if this persists."));
}
}

View File

@@ -0,0 +1,27 @@
/*
* 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;
public class IdentityAPIv1 {
public static final String BASE = "/_matrix/identity/api/v1";
}

View File

@@ -20,10 +20,22 @@
package io.kamax.mxisd.controller.v1 package io.kamax.mxisd.controller.v1
import io.kamax.mxisd.exception.NotImplementedException import com.google.gson.Gson
import io.kamax.matrix.MatrixID
import io.kamax.mxisd.config.ServerConfig
import io.kamax.mxisd.controller.v1.io.ThreePidInviteReplyIO
import io.kamax.mxisd.invitation.IThreePidInvite
import io.kamax.mxisd.invitation.IThreePidInviteReply
import io.kamax.mxisd.invitation.InvitationManager
import io.kamax.mxisd.invitation.ThreePidInvite
import io.kamax.mxisd.key.KeyManager
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
@@ -31,15 +43,38 @@ import javax.servlet.http.HttpServletRequest
import static org.springframework.web.bind.annotation.RequestMethod.POST import static org.springframework.web.bind.annotation.RequestMethod.POST
@RestController @RestController
@CrossOrigin
@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
class InvitationController { class InvitationController {
private Logger log = LoggerFactory.getLogger(InvitationController.class) private Logger log = LoggerFactory.getLogger(InvitationController.class)
@RequestMapping(value = "/_matrix/identity/api/v1/store-invite", method = POST) @Autowired
String store(HttpServletRequest request) { private InvitationManager mgr
log.error("{} was requested but not implemented", request.getRequestURL())
throw new NotImplementedException() @Autowired
private KeyManager keyMgr
@Autowired
private ServerConfig srvCfg
private Gson gson = new Gson()
@RequestMapping(value = "/store-invite", method = POST)
String store(
HttpServletRequest request,
@RequestParam String sender,
@RequestParam String medium,
@RequestParam String address,
@RequestParam("room_id") String roomId) {
Map<String, String> parameters = new HashMap<>()
for (String key : request.getParameterMap().keySet()) {
parameters.put(key, request.getParameter(key));
}
IThreePidInvite invite = new ThreePidInvite(new MatrixID(sender), medium, address, roomId, parameters)
IThreePidInviteReply reply = mgr.storeInvite(invite)
return gson.toJson(new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), srvCfg.getPublicUrl()))
} }
} }

View File

@@ -20,22 +20,25 @@
package io.kamax.mxisd.controller.v1 package io.kamax.mxisd.controller.v1
import com.google.gson.Gson
import groovy.json.JsonOutput import groovy.json.JsonOutput
import io.kamax.mxisd.controller.v1.io.KeyValidityJson
import io.kamax.mxisd.exception.BadRequestException import io.kamax.mxisd.exception.BadRequestException
import io.kamax.mxisd.exception.NotImplementedException
import io.kamax.mxisd.key.KeyManager import io.kamax.mxisd.key.KeyManager
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.PathVariable import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.*
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
import static org.springframework.web.bind.annotation.RequestMethod.GET import static org.springframework.web.bind.annotation.RequestMethod.GET
@RestController @RestController
@CrossOrigin
@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
class KeyController { class KeyController {
private Logger log = LoggerFactory.getLogger(KeyController.class) private Logger log = LoggerFactory.getLogger(KeyController.class)
@@ -43,29 +46,36 @@ class KeyController {
@Autowired @Autowired
private KeyManager keyMgr private KeyManager keyMgr
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/{keyType}:{keyId}", method = GET) private Gson gson = new Gson();
private String validKey = gson.toJson(new KeyValidityJson(true));
private String invalidKey = gson.toJson(new KeyValidityJson(false));
@RequestMapping(value = "/pubkey/{keyType}:{keyId}", method = GET)
String getKey(@PathVariable String keyType, @PathVariable int keyId) { String getKey(@PathVariable String keyType, @PathVariable int keyId) {
if (!"ed25519".contentEquals(keyType)) { if (!"ed25519".contentEquals(keyType)) {
throw new BadRequestException("Invalid algorithm: " + keyType) throw new BadRequestException("Invalid algorithm: " + keyType)
} }
log.info("Key {}:{} was requested", keyType, keyId)
return JsonOutput.toJson([ return JsonOutput.toJson([
public_key: keyMgr.getPublicKeyBase64(keyId) public_key: keyMgr.getPublicKeyBase64(keyId)
]) ])
} }
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/ephemeral/isvalid", method = GET) @RequestMapping(value = "/pubkey/ephemeral/isvalid", method = GET)
String checkEphemeralKeyValidity(HttpServletRequest request) { String checkEphemeralKeyValidity(HttpServletRequest request) {
log.error("{} was requested but not implemented", request.getRequestURL()) log.warn("Ephemeral key was request but no ephemeral key are generated, replying not valid")
throw new NotImplementedException() return invalidKey
} }
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/isvalid", method = GET) @RequestMapping(value = "/pubkey/isvalid", method = GET)
String checkKeyValidity(HttpServletRequest request) { String checkKeyValidity(HttpServletRequest request, @RequestParam("public_key") String pubKey) {
log.error("{} was requested but not implemented", request.getRequestURL()) log.info("Validating public key {}", pubKey)
throw new NotImplementedException() // TODO do in manager
boolean valid = StringUtils.equals(pubKey, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()))
return valid ? validKey : invalidKey
} }
} }

View File

@@ -20,17 +20,20 @@
package io.kamax.mxisd.controller.v1 package io.kamax.mxisd.controller.v1
import com.google.gson.Gson
import com.google.gson.JsonObject
import groovy.json.JsonOutput import groovy.json.JsonOutput
import groovy.json.JsonSlurper import groovy.json.JsonSlurper
import io.kamax.mxisd.lookup.BulkLookupRequest import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson
import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.*
import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.strategy.LookupStrategy import io.kamax.mxisd.lookup.strategy.LookupStrategy
import io.kamax.mxisd.signature.SignatureManager import io.kamax.mxisd.signature.SignatureManager
import org.apache.commons.lang.StringUtils import org.apache.commons.lang.StringUtils
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@@ -41,10 +44,13 @@ import static org.springframework.web.bind.annotation.RequestMethod.GET
import static org.springframework.web.bind.annotation.RequestMethod.POST import static org.springframework.web.bind.annotation.RequestMethod.POST
@RestController @RestController
@CrossOrigin
@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
class MappingController { class MappingController {
private Logger log = LoggerFactory.getLogger(MappingController.class) private Logger log = LoggerFactory.getLogger(MappingController.class)
private JsonSlurper json = new JsonSlurper() private JsonSlurper json = new JsonSlurper()
private Gson gson = new Gson()
@Autowired @Autowired
private LookupStrategy strategy private LookupStrategy strategy
@@ -52,38 +58,50 @@ class MappingController {
@Autowired @Autowired
private SignatureManager signMgr private SignatureManager signMgr
@RequestMapping(value = "/_matrix/identity/api/v1/lookup", method = GET) private void setRequesterInfo(ALookupRequest lookupReq, HttpServletRequest req) {
String lookup(HttpServletRequest request, @RequestParam String medium, @RequestParam String address) { lookupReq.setRequester(req.getRemoteAddr())
String remote = StringUtils.defaultIfBlank(request.getHeader("X-FORWARDED-FOR"), request.getRemoteAddr()) String xff = req.getHeader("X-FORWARDED-FOR")
log.info("Got request from {}", remote) lookupReq.setRecursive(StringUtils.isNotBlank(xff))
if (lookupReq.isRecursive()) {
lookupReq.setRecurseHosts(Arrays.asList(xff.split(",")))
}
lookupReq.setUserAgent(req.getHeader("USER-AGENT"))
}
@RequestMapping(value = "/lookup", method = GET)
String lookup(HttpServletRequest request, @RequestParam String medium, @RequestParam String address) {
SingleLookupRequest lookupRequest = new SingleLookupRequest() SingleLookupRequest lookupRequest = new SingleLookupRequest()
lookupRequest.setRequester(remote) setRequesterInfo(lookupRequest, request)
lookupRequest.setType(medium) lookupRequest.setType(medium)
lookupRequest.setThreePid(address) lookupRequest.setThreePid(address)
Optional<?> lookupOpt = strategy.find(lookupRequest) log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive())
Optional<SingleLookupReply> lookupOpt = strategy.find(lookupRequest)
if (!lookupOpt.isPresent()) { if (!lookupOpt.isPresent()) {
log.info("No mapping was found, return empty JSON object") log.info("No mapping was found, return empty JSON object")
return JsonOutput.toJson([]) return JsonOutput.toJson([])
} }
def lookup = lookupOpt.get() SingleLookupReply lookup = lookupOpt.get()
if (lookup['signatures'] == null) { if (lookup.isSigned()) {
log.info("lookup is not signed yet, we sign it") log.info("Lookup is already signed, sending as-is")
lookup['signatures'] = signMgr.signMessage(JsonOutput.toJson(lookup)) return lookup.getBody();
} } else {
log.info("Lookup is not signed, signing")
JsonObject obj = new Gson().toJsonTree(new SingeLookupReplyJson(lookup)).getAsJsonObject()
obj.add("signatures", signMgr.signMessageGson(gson.toJson(obj)))
return JsonOutput.toJson(lookup) return gson.toJson(obj)
}
} }
@RequestMapping(value = "/_matrix/identity/api/v1/bulk_lookup", method = POST) @RequestMapping(value = "/bulk_lookup", method = POST)
String bulkLookup(HttpServletRequest request) { 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() BulkLookupRequest lookupRequest = new BulkLookupRequest()
lookupRequest.setRequester(remote) setRequesterInfo(lookupRequest, request)
log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive())
ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(request.getInputStream().getText()) ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(request.getInputStream().getText())
List<ThreePidMapping> mappings = new ArrayList<>() List<ThreePidMapping> mappings = new ArrayList<>()

View File

@@ -25,7 +25,8 @@ import com.google.gson.JsonObject
import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson
import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson
import io.kamax.mxisd.exception.BadRequestException import io.kamax.mxisd.exception.BadRequestException
import io.kamax.mxisd.lookup.ThreePid import io.kamax.mxisd.invitation.InvitationManager
import io.kamax.mxisd.lookup.ThreePidValidation
import io.kamax.mxisd.mapping.MappingManager import io.kamax.mxisd.mapping.MappingManager
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.apache.commons.lang.StringUtils import org.apache.commons.lang.StringUtils
@@ -33,21 +34,24 @@ import org.apache.http.HttpStatus
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.PathVariable import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.*
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpServletRequest import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse import javax.servlet.http.HttpServletResponse
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@RestController @RestController
@CrossOrigin
@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
class SessionController { class SessionController {
@Autowired @Autowired
private MappingManager mgr private MappingManager mgr
@Autowired
private InvitationManager invMgr;
private Gson gson = new Gson() private Gson gson = new Gson()
private Logger log = LoggerFactory.getLogger(SessionController.class) private Logger log = LoggerFactory.getLogger(SessionController.class)
@@ -56,7 +60,7 @@ class SessionController {
gson.fromJson(new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8), obj) gson.fromJson(new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8), obj)
} }
@RequestMapping(value = "/_matrix/identity/api/v1/validate/{medium}/requestToken") @RequestMapping(value = "/validate/{medium}/requestToken")
String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) { String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) {
log.info("Requested: {}", request.getRequestURL(), request.getQueryString()) log.info("Requested: {}", request.getRequestURL(), request.getQueryString())
@@ -77,7 +81,7 @@ class SessionController {
return gson.toJson(obj) return gson.toJson(obj)
} }
@RequestMapping(value = "/_matrix/identity/api/v1/validate/{medium}/submitToken") @RequestMapping(value = "/validate/{medium}/submitToken")
String validate(HttpServletRequest request, String validate(HttpServletRequest request,
@RequestParam String sid, @RequestParam String sid,
@RequestParam("client_secret") String secret, @RequestParam String token) { @RequestParam("client_secret") String secret, @RequestParam String token) {
@@ -88,15 +92,15 @@ class SessionController {
return "{}" return "{}"
} }
@RequestMapping(value = "/_matrix/identity/api/v1/3pid/getValidated3pid") @RequestMapping(value = "/3pid/getValidated3pid")
String check(HttpServletRequest request, HttpServletResponse response, String check(HttpServletRequest request, HttpServletResponse response,
@RequestParam String sid, @RequestParam("client_secret") String secret) { @RequestParam String sid, @RequestParam("client_secret") String secret) {
log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString()) log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString())
Optional<ThreePid> result = mgr.getValidated(sid, secret) Optional<ThreePidValidation> result = mgr.getValidated(sid, secret)
if (result.isPresent()) { if (result.isPresent()) {
log.info("requested session was validated") log.info("requested session was validated")
ThreePid pid = result.get() ThreePidValidation pid = result.get()
JsonObject obj = new JsonObject() JsonObject obj = new JsonObject()
obj.addProperty("medium", pid.getMedium()) obj.addProperty("medium", pid.getMedium())
@@ -115,7 +119,7 @@ class SessionController {
} }
} }
@RequestMapping(value = "/_matrix/identity/api/v1/3pid/bind") @RequestMapping(value = "/3pid/bind")
String bind(HttpServletRequest request, HttpServletResponse response, String bind(HttpServletRequest request, HttpServletResponse response,
@RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String mxid) { @RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String mxid) {
String data = IOUtils.toString(request.getReader()) String data = IOUtils.toString(request.getReader())
@@ -131,6 +135,10 @@ class SessionController {
obj.addProperty("error", e.getMessage()) obj.addProperty("error", e.getMessage())
response.setStatus(HttpStatus.SC_BAD_REQUEST) response.setStatus(HttpStatus.SC_BAD_REQUEST)
return gson.toJson(obj) return gson.toJson(obj)
} finally {
// If a user registers, there is no standard login event. Instead, this is the only way to trigger
// resolution at an appropriate time. Meh at synapse/Riot!
invMgr.lookupMappingsForInvites()
} }
} }

View File

@@ -0,0 +1,49 @@
/*
* 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 org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@CrossOrigin
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class StatusController {
private Gson gson = new Gson();
@RequestMapping(value = "/_matrix/identity/status")
public String getStatus() {
// TODO link to backend
JsonObject status = new JsonObject();
status.addProperty("health", "OK");
JsonObject obj = new JsonObject();
obj.add("status", status);
return gson.toJson(obj);
}
}

View File

@@ -1,3 +1,23 @@
/*
* 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.io; package io.kamax.mxisd.controller.v1.io;
import io.kamax.mxisd.mapping.MappingSession; import io.kamax.mxisd.mapping.MappingSession;

View File

@@ -0,0 +1,35 @@
/*
* 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.io;
public class KeyValidityJson {
private boolean valid;
public KeyValidityJson(boolean isValid) {
this.valid = isValid;
}
public boolean isValid() {
return valid;
}
}

View File

@@ -1,3 +1,23 @@
/*
* 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.io; package io.kamax.mxisd.controller.v1.io;
public class SessionEmailTokenRequestJson extends GenericTokenRequestJson { public class SessionEmailTokenRequestJson extends GenericTokenRequestJson {

View File

@@ -1,3 +1,23 @@
/*
* 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.io; package io.kamax.mxisd.controller.v1.io;
import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.NumberParseException;

View File

@@ -0,0 +1,75 @@
/*
* 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.io;
import io.kamax.mxisd.lookup.SingleLookupReply;
import java.util.HashMap;
import java.util.Map;
public class SingeLookupReplyJson {
private String address;
private String medium;
private String mxid;
private long not_after;
private long not_before;
private long ts;
private Map<String, Map<String, String>> signatures = new HashMap<>();
public SingeLookupReplyJson(SingleLookupReply reply) {
this.address = reply.getRequest().getThreePid();
this.medium = reply.getRequest().getType();
this.mxid = reply.getMxid().getId();
this.not_after = reply.getNotAfter().toEpochMilli();
this.not_before = reply.getNotBefore().toEpochMilli();
this.ts = reply.getTimestamp().toEpochMilli();
}
public String getAddress() {
return address;
}
public String getMedium() {
return medium;
}
public String getMxid() {
return mxid;
}
public long getNot_after() {
return not_after;
}
public long getNot_before() {
return not_before;
}
public long getTs() {
return ts;
}
public boolean isSigned() {
return signatures != null && !signatures.isEmpty();
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.io;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import java.util.Collections;
import java.util.List;
public class ThreePidInviteReplyIO {
private String token;
private List<Key> public_keys;
private String display_name;
public ThreePidInviteReplyIO(IThreePidInviteReply reply, String pubKey, String publicUrl) {
this.token = reply.getToken();
this.public_keys = Collections.singletonList(new Key(pubKey, publicUrl));
this.display_name = reply.getDisplayName();
}
public String getToken() {
return token;
}
public List<Key> getPublic_keys() {
return public_keys;
}
public String getDisplay_name() {
return display_name;
}
public class Key {
private String key_validity_url;
private String public_key;
public Key(String key, String publicUrl) {
this.key_validity_url = publicUrl + "/_matrix/identity/api/v1/pubkey/isvalid";
this.public_key = key;
}
public String getKey_validity_url() {
return key_validity_url;
}
public String getPublic_key() {
return public_key;
}
}
}

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.exception;
import java.util.Optional;
public class ConfigurationException extends RuntimeException {
private String key;
private String detailedMsg;
public ConfigurationException(String key) {
super("Invalid or empty value for configuration key " + key);
}
public ConfigurationException(Throwable t) {
super(t.getMessage(), t);
}
public ConfigurationException(String key, String detailedMsg) {
this(key);
this.detailedMsg = detailedMsg;
}
public Optional<String> getDetailedMessage() {
return Optional.ofNullable(detailedMsg);
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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.exception;
public class InvalidResponseJsonException extends RuntimeException {
public InvalidResponseJsonException(String s) {
super(s);
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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.exception;
public class JsonMemberNotFoundException extends RuntimeException {
public JsonMemberNotFoundException(String s) {
super(s);
}
}

View File

@@ -0,0 +1,29 @@
/*
* 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.exception;
public class MappingAlreadyExistsException extends RuntimeException {
public MappingAlreadyExistsException() {
super("A mapping already exists for this 3PID");
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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.invitation;
import io.kamax.matrix._MatrixID;
import java.util.Map;
public interface IThreePidInvite {
_MatrixID getSender();
String getMedium();
String getAddress();
String getRoomId();
Map<String, String> getProperties();
}

View File

@@ -0,0 +1,33 @@
/*
* 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.invitation;
public interface IThreePidInviteReply {
String getId();
IThreePidInvite getInvite();
String getToken();
String getDisplayName();
}

View File

@@ -0,0 +1,339 @@
/*
* 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.invitation;
import com.google.gson.Gson;
import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.config.DnsOverwrite;
import io.kamax.mxisd.config.DnsOverwriteEntry;
import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
import io.kamax.mxisd.invitation.sender.IInviteSender;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
import io.kamax.mxisd.signature.SignatureManager;
import io.kamax.mxisd.storage.IStorage;
import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.xbill.DNS.*;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
@Component
public class InvitationManager {
private Logger log = LoggerFactory.getLogger(InvitationManager.class);
private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>();
@Autowired
private IStorage storage;
@Autowired
private LookupStrategy lookupMgr;
@Autowired
private SignatureManager signMgr;
@Autowired
private DnsOverwrite dns;
private Map<String, IInviteSender> senders;
private CloseableHttpClient client;
private Gson gson;
private Timer refreshTimer;
private String getId(IThreePidInvite invite) {
return invite.getSender().getDomain().toLowerCase() + invite.getMedium().toLowerCase() + invite.getAddress().toLowerCase();
}
@PostConstruct
private void postConstruct() {
gson = new Gson();
log.info("Loading saved invites");
Collection<ThreePidInviteIO> ioList = storage.getInvites();
ioList.forEach(io -> {
log.info("Processing invite {}", gson.toJson(io));
ThreePidInvite invite = new ThreePidInvite(
new MatrixID(io.getSender()),
io.getMedium(),
io.getAddress(),
io.getRoomId(),
io.getProperties()
);
ThreePidInviteReply reply = new ThreePidInviteReply(getId(invite), invite, io.getToken(), "");
invitations.put(reply.getId(), reply);
});
// FIXME export such madness into matrix-java-sdk with a nice wrapper to talk to a homeserver
try {
SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build();
HostnameVerifier hostnameVerifier = new NoopHostnameVerifier();
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
client = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build();
} catch (Exception e) {
// FIXME do better...
throw new RuntimeException(e);
}
log.info("Setting up invitation mapping refresh timer");
refreshTimer = new Timer();
refreshTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
try {
lookupMappingsForInvites();
} catch (Throwable t) {
log.error("Error when running background mapping refresh", t);
}
}
}, 5000L, TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES)); // FIXME make configurable
}
@PreDestroy
private void preDestroy() {
ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.MINUTES);
}
private String getIdForLog(IThreePidInviteReply reply) {
return reply.getInvite().getSender().getId() + ":" + reply.getInvite().getRoomId() + ":" + reply.getInvite().getMedium() + ":" + reply.getInvite().getAddress();
}
String getSrvRecordName(String domain) {
return "_matrix._tcp." + domain;
}
// TODO use caching mechanism
// TODO export in matrix-java-sdk
String findHomeserverForDomain(String domain) {
Optional<DnsOverwriteEntry> entryOpt = dns.findHost(domain);
if (entryOpt.isPresent()) {
DnsOverwriteEntry entry = entryOpt.get();
log.info("Found DNS overwrite for {} to {}", entry.getName(), entry.getTarget());
return "https://" + entry.getTarget();
}
log.debug("Performing SRV lookup for {}", domain);
String lookupDns = getSrvRecordName(domain);
log.info("Lookup name: {}", lookupDns);
try {
List<SRVRecord> srvRecords = new ArrayList<>();
Record[] rawRecords = new Lookup(lookupDns, Type.SRV).run();
if (rawRecords != null && rawRecords.length > 0) {
for (Record record : rawRecords) {
if (Type.SRV == record.getType()) {
srvRecords.add((SRVRecord) record);
} else {
log.info("Got non-SRV record: {}", record.toString());
}
}
srvRecords.sort(Comparator.comparingInt(SRVRecord::getPriority));
for (SRVRecord record : srvRecords) {
log.info("Found SRV record: {}", record.toString());
return "https://" + record.getTarget().toString(true) + ":" + record.getPort();
}
} else {
log.info("No SRV record for {}", lookupDns);
}
} catch (TextParseException e) {
log.warn("Unable to perform DNS SRV query for {}: {}", lookupDns, e.getMessage());
}
log.info("Performing basic lookup using domain name {}", domain);
return "https://" + domain + ":8448";
}
@Autowired
public InvitationManager(List<IInviteSender> senderList) {
senders = new HashMap<>();
senderList.forEach(sender -> senders.put(sender.getMedium(), sender));
}
public synchronized IThreePidInviteReply storeInvite(IThreePidInvite invitation) { // TODO better sync
IInviteSender sender = senders.get(invitation.getMedium());
if (sender == null) {
throw new BadRequestException("Medium type " + invitation.getMedium() + " is not supported");
}
String invId = getId(invitation);
log.info("Handling invite for {}:{} from {} in room {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender(), invitation.getRoomId());
if (invitations.containsKey(invId)) { // FIXME we need to lookup using the HS domain too!!
log.info("Invite is already pending for {}:{}, returning data", invitation.getMedium(), invitation.getAddress());
return invitations.get(invId);
}
Optional<?> result = lookupMgr.find(invitation.getMedium(), invitation.getAddress(), true);
if (result.isPresent()) {
log.info("Mapping for {}:{} already exists, refusing to store invite", invitation.getMedium(), invitation.getAddress());
throw new MappingAlreadyExistsException();
}
String token = RandomStringUtils.randomAlphanumeric(64);
String displayName = invitation.getAddress().substring(0, 3) + "...";
IThreePidInviteReply reply = new ThreePidInviteReply(invId, invitation, token, displayName);
log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress());
sender.send(reply);
log.info("Storing invite under ID {}", invId);
storage.insertInvite(reply);
invitations.put(invId, reply);
log.info("A new invite has been created for {}:{} on HS {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender().getDomain());
return reply;
}
public void lookupMappingsForInvites() {
if (!invitations.isEmpty()) {
log.info("Checking for existing mapping for pending invites");
for (IThreePidInviteReply reply : invitations.values()) {
log.info("Processing invite {}", getIdForLog(reply));
ForkJoinPool.commonPool().submit(new MappingChecker(reply));
}
}
}
public void publishMappingIfInvited(ThreePidMapping threePid) {
log.info("Looking up possible pending invites for {}:{}", threePid.getMedium(), threePid.getValue());
for (IThreePidInviteReply reply : invitations.values()) {
if (StringUtils.equalsIgnoreCase(reply.getInvite().getMedium(), threePid.getMedium()) && StringUtils.equalsIgnoreCase(reply.getInvite().getAddress(), threePid.getValue())) {
log.info("{}:{} has an invite pending on HS {}, publishing mapping", threePid.getMedium(), threePid.getValue(), reply.getInvite().getSender().getDomain());
publishMapping(reply, threePid.getMxid());
}
}
}
private void publishMapping(IThreePidInviteReply reply, String mxid) {
String medium = reply.getInvite().getMedium();
String address = reply.getInvite().getAddress();
String domain = reply.getInvite().getSender().getDomain();
log.info("Discovering HS for domain {}", domain);
String hsUrlOpt = findHomeserverForDomain(domain);
// TODO this is needed as this will block if called during authentication cycle due to synapse implementation
new Thread(() -> { // FIXME need to make this retry-able and within a general background working pool
HttpPost req = new HttpPost(hsUrlOpt + "/_matrix/federation/v1/3pid/onbind");
// Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr
JSONObject obj = new JSONObject(); // TODO use Gson instead
obj.put("mxid", mxid);
obj.put("token", reply.getToken());
obj.put("signatures", signMgr.signMessageJson(obj.toString()));
JSONObject objUp = new JSONObject();
objUp.put("mxid", mxid);
objUp.put("medium", medium);
objUp.put("address", address);
objUp.put("sender", reply.getInvite().getSender().getId());
objUp.put("room_id", reply.getInvite().getRoomId());
objUp.put("signed", obj);
JSONObject content = new JSONObject(); // TODO use Gson instead
JSONArray invites = new JSONArray();
invites.put(objUp);
content.put("invites", invites);
content.put("medium", medium);
content.put("address", address);
content.put("mxid", mxid);
content.put("signatures", signMgr.signMessageJson(content.toString()));
StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8);
entity.setContentType("application/json");
req.setEntity(entity);
try {
log.info("Posting onBind event to {}", req.getURI());
CloseableHttpResponse response = client.execute(req);
int statusCode = response.getStatusLine().getStatusCode();
log.info("Answer code: {}", statusCode);
if (statusCode >= 300) {
log.warn("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
} else {
invitations.remove(getId(reply.getInvite()));
storage.deleteInvite(reply.getId());
log.info("Removed invite from internal store");
}
response.close();
} catch (IOException e) {
log.warn("Unable to tell HS {} about invite being mapped", domain, e);
}
}).start();
}
private class MappingChecker implements Runnable {
private IThreePidInviteReply reply;
public MappingChecker(IThreePidInviteReply reply) {
this.reply = reply;
}
@Override
public void run() {
try {
log.info("Searching for mapping created since invite {} was created", getIdForLog(reply));
Optional<SingleLookupReply> result = lookupMgr.find(reply.getInvite().getMedium(), reply.getInvite().getAddress(), true);
if (result.isPresent()) {
SingleLookupReply lookup = result.get();
log.info("Found mapping for pending invite {}", getIdForLog(reply));
publishMapping(reply, lookup.getMxid().getId());
} else {
log.info("No mapping for pending invite {}", getIdForLog(reply));
}
} catch (Throwable t) {
log.error("Unable to process invite", t);
}
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.invitation;
import io.kamax.matrix._MatrixID;
import java.util.HashMap;
import java.util.Map;
public class ThreePidInvite implements IThreePidInvite {
private _MatrixID sender;
private String medium;
private String address;
private String roomId;
private Map<String, String> properties;
public ThreePidInvite(_MatrixID sender, String medium, String address, String roomId) {
this.sender = sender;
this.medium = medium;
this.address = address;
this.roomId = roomId;
this.properties = new HashMap<>();
}
public ThreePidInvite(_MatrixID sender, String medium, String address, String roomId, Map<String, String> properties) {
this(sender, medium, address, roomId);
this.properties = properties;
}
@Override
public _MatrixID getSender() {
return sender;
}
@Override
public String getMedium() {
return medium;
}
@Override
public String getAddress() {
return address;
}
@Override
public String getRoomId() {
return roomId;
}
@Override
public Map<String, String> getProperties() {
return properties;
}
}

View File

@@ -0,0 +1,57 @@
/*
* 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.invitation;
public class ThreePidInviteReply implements IThreePidInviteReply {
private String id;
private IThreePidInvite invite;
private String token;
private String displayName;
public ThreePidInviteReply(String id, IThreePidInvite invite, String token, String displayName) {
this.id = id;
this.invite = invite;
this.token = token;
this.displayName = displayName;
}
@Override
public String getId() {
return id;
}
@Override
public IThreePidInvite getInvite() {
return invite;
}
@Override
public String getToken() {
return token;
}
@Override
public String getDisplayName() {
return displayName;
}
}

View File

@@ -0,0 +1,138 @@
/*
* 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.invitation.sender;
import com.sun.mail.smtp.SMTPTransport;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.invite.sender.EmailSenderConfig;
import io.kamax.mxisd.exception.ConfigurationException;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
public class EmailInviteSender implements IInviteSender {
private Logger log = LoggerFactory.getLogger(EmailInviteSender.class);
@Autowired
private EmailSenderConfig cfg;
@Autowired
private MatrixConfig mxCfg;
@Autowired
private ApplicationContext app;
private Session session;
private InternetAddress sender;
@PostConstruct
private void postConstruct() {
try {
session = Session.getInstance(System.getProperties());
sender = new InternetAddress(cfg.getEmail(), cfg.getName());
} catch (UnsupportedEncodingException e) {
// What are we supposed to do with this?!
throw new ConfigurationException(e);
}
}
@Override
public String getMedium() {
return ThreePidMedium.Email.getId();
}
@Override
public void send(IThreePidInviteReply invite) {
if (!ThreePidMedium.Email.is(invite.getInvite().getMedium())) {
throw new IllegalArgumentException(invite.getInvite().getMedium() + " is not a supported 3PID type");
}
try {
String domainPretty = WordUtils.capitalizeFully(mxCfg.getDomain());
String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", "");
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
String templateBody = IOUtils.toString(
StringUtils.startsWith(cfg.getTemplate(), "classpath:") ?
app.getResource(cfg.getTemplate()).getInputStream() : new FileInputStream(cfg.getTemplate()),
StandardCharsets.UTF_8);
templateBody = templateBody.replace("%DOMAIN%", mxCfg.getDomain());
templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty);
templateBody = templateBody.replace("%FROM_EMAIL%", cfg.getEmail());
templateBody = templateBody.replace("%FROM_NAME%", cfg.getName());
templateBody = templateBody.replace("%SENDER_ID%", invite.getInvite().getSender().getId());
templateBody = templateBody.replace("%SENDER_NAME%", senderName);
templateBody = templateBody.replace("%SENDER_NAME_OR_ID%", senderNameOrId);
templateBody = templateBody.replace("%INVITE_MEDIUM%", invite.getInvite().getMedium());
templateBody = templateBody.replace("%INVITE_ADDRESS%", invite.getInvite().getAddress());
templateBody = templateBody.replace("%ROOM_ID%", invite.getInvite().getRoomId());
templateBody = templateBody.replace("%ROOM_NAME%", roomName);
templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(templateBody, StandardCharsets.UTF_8));
msg.setHeader("X-Mailer", "mxisd"); // TODO set version
msg.setSentDate(new Date());
msg.setFrom(sender);
msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress());
msg.saveChanges();
log.info("Sending invite to {} via SMTP using {}:{}", invite.getInvite().getAddress(), cfg.getHost(), cfg.getPort());
SMTPTransport transport = (SMTPTransport) session.getTransport("smtp");
transport.setStartTLS(cfg.getTls() > 0);
transport.setRequireStartTLS(cfg.getTls() > 1);
log.info("Connecting to {}:{}", cfg.getHost(), cfg.getPort());
transport.connect(cfg.getHost(), cfg.getPort(), cfg.getLogin(), cfg.getPassword());
try {
transport.sendMessage(msg, InternetAddress.parse(invite.getInvite().getAddress()));
log.info("Invite to {} was sent", invite.getInvite().getAddress());
} finally {
transport.close();
}
} catch (IOException | MessagingException e) {
throw new RuntimeException("Unable to send e-mail invite to " + invite.getInvite().getAddress(), e);
}
}
}

View File

@@ -0,0 +1,31 @@
/*
* 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.invitation.sender;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
public interface IInviteSender {
String getMedium();
void send(IThreePidInviteReply invite);
}

View File

@@ -20,9 +20,23 @@
package io.kamax.mxisd.lookup; package io.kamax.mxisd.lookup;
import java.util.List;
public abstract class ALookupRequest { public abstract class ALookupRequest {
private String id;
private String requester; private String requester;
private String userAgent;
private boolean isRecursive;
private List<String> recurseHosts;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getRequester() { public String getRequester() {
return requester; return requester;
@@ -32,4 +46,28 @@ public abstract class ALookupRequest {
this.requester = requester; this.requester = requester;
} }
public String getUserAgent() {
return userAgent;
}
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}
public boolean isRecursive() {
return isRecursive;
}
public void setRecursive(boolean recursive) {
isRecursive = recursive;
}
public List<String> getRecurseHosts() {
return recurseHosts;
}
public void setRecurseHosts(List<String> recurseHosts) {
this.recurseHosts = recurseHosts;
}
} }

View File

@@ -0,0 +1,116 @@
/*
* 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;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson;
import java.time.Instant;
public class SingleLookupReply {
private static Gson gson = new Gson();
private boolean isRecursive;
private boolean isSigned;
private String body;
private SingleLookupRequest request;
private _MatrixID mxid;
private Instant notBefore;
private Instant notAfter;
private Instant timestamp;
public static SingleLookupReply fromRecursive(SingleLookupRequest request, String body) {
SingleLookupReply reply = new SingleLookupReply();
reply.isRecursive = true;
reply.request = request;
reply.body = body;
try {
SingeLookupReplyJson json = gson.fromJson(body, SingeLookupReplyJson.class);
reply.mxid = new MatrixID(json.getMxid());
reply.notAfter = Instant.ofEpochMilli(json.getNot_after());
reply.notBefore = Instant.ofEpochMilli(json.getNot_before());
reply.timestamp = Instant.ofEpochMilli(json.getTs());
reply.isSigned = json.isSigned();
} catch (JsonSyntaxException e) {
// stub - we only want to try, nothing more
}
return reply;
}
private SingleLookupReply() {
// stub
}
public SingleLookupReply(SingleLookupRequest request, String mxid) {
this(request, new MatrixID(mxid));
}
public SingleLookupReply(SingleLookupRequest request, _MatrixID mxid) {
this(request, mxid, Instant.now(), Instant.ofEpochMilli(0), Instant.ofEpochMilli(253402300799000L));
}
public SingleLookupReply(SingleLookupRequest request, _MatrixID mxid, Instant timestamp, Instant notBefore, Instant notAfter) {
this.request = request;
this.mxid = mxid;
this.timestamp = timestamp;
this.notBefore = notBefore;
this.notAfter = notAfter;
}
public boolean isRecursive() {
return isRecursive;
}
public boolean isSigned() {
return isSigned;
}
public String getBody() {
return body;
}
public SingleLookupRequest getRequest() {
return request;
}
public _MatrixID getMxid() {
return mxid;
}
public Instant getNotBefore() {
return notBefore;
}
public Instant getNotAfter() {
return notAfter;
}
public Instant getTimestamp() {
return timestamp;
}
}

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

@@ -21,6 +21,7 @@
package io.kamax.mxisd.lookup; package io.kamax.mxisd.lookup;
import groovy.json.JsonOutput; import groovy.json.JsonOutput;
import io.kamax.mxisd.ThreePid;
public class ThreePidMapping { public class ThreePidMapping {
@@ -28,6 +29,20 @@ public class ThreePidMapping {
private String value; private String value;
private String mxid; private String mxid;
public ThreePidMapping() {
// stub
}
public ThreePidMapping(ThreePid threePid, String mxid) {
this(threePid.getMedium(), threePid.getAddress(), mxid);
}
public ThreePidMapping(String medium, String value, String mxid) {
setMedium(medium);
setValue(value);
setMxid(mxid);
}
public String getMedium() { public String getMedium() {
return medium; return medium;
} }

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.lookup;
import io.kamax.mxisd.ThreePid;
import java.time.Instant;
public class ThreePidValidation extends ThreePid {
private Instant validation;
public ThreePidValidation(String medium, String address, Instant validation) {
super(medium, address);
this.validation = validation;
}
public Instant getValidation() {
return validation;
}
}

View File

@@ -20,12 +20,13 @@
package io.kamax.mxisd.lookup.fetcher package io.kamax.mxisd.lookup.fetcher
import io.kamax.mxisd.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.ThreePidMapping
interface IBridgeFetcher { interface IBridgeFetcher {
Optional<?> find(SingleLookupRequest request) Optional<SingleLookupReply> find(SingleLookupRequest request)
List<ThreePidMapping> populate(List<ThreePidMapping> mappings) List<ThreePidMapping> populate(List<ThreePidMapping> mappings)

View File

@@ -20,13 +20,15 @@
package io.kamax.mxisd.lookup.fetcher package io.kamax.mxisd.lookup.fetcher
import io.kamax.mxisd.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.ThreePidMapping
interface IRemoteIdentityServerFetcher { interface IRemoteIdentityServerFetcher {
boolean isUsable(String remote) boolean isUsable(String remote)
Optional<?> find(String remote, String type, String threePid) Optional<SingleLookupReply> find(String remote, SingleLookupRequest request)
List<ThreePidMapping> find(String remote, List<ThreePidMapping> mappings) List<ThreePidMapping> find(String remote, List<ThreePidMapping> mappings)

View File

@@ -21,6 +21,7 @@
package io.kamax.mxisd.lookup.provider; package io.kamax.mxisd.lookup.provider;
import io.kamax.mxisd.config.RecursiveLookupBridgeConfig; import io.kamax.mxisd.config.RecursiveLookupBridgeConfig;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher; import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher;
@@ -46,16 +47,16 @@ public class BridgeFetcher implements IBridgeFetcher {
private RemoteIdentityServerFetcher fetcher; private RemoteIdentityServerFetcher fetcher;
@Override @Override
public Optional<?> find(SingleLookupRequest request) { public Optional<SingleLookupReply> find(SingleLookupRequest request) {
Optional<String> mediumUrl = Optional.ofNullable(cfg.getMappings().get(request.getType())); Optional<String> mediumUrl = Optional.ofNullable(cfg.getMappings().get(request.getType()));
if (mediumUrl.isPresent() && !StringUtils.isBlank(mediumUrl.get())) { if (mediumUrl.isPresent() && !StringUtils.isBlank(mediumUrl.get())) {
log.info("Using specific medium bridge lookup URL {}", mediumUrl.get()); log.info("Using specific medium bridge lookup URL {}", mediumUrl.get());
return fetcher.find(mediumUrl.get(), request.getType(), request.getThreePid()); return fetcher.find(mediumUrl.get(), request);
} else if (!StringUtils.isBlank(cfg.getServer())) { } else if (!StringUtils.isBlank(cfg.getServer())) {
log.info("Using generic bridge lookup URL {}", cfg.getServer()); log.info("Using generic bridge lookup URL {}", cfg.getServer());
return fetcher.find(cfg.getServer(), request.getType(), request.getThreePid()); return fetcher.find(cfg.getServer(), request);
} else { } else {
log.info("No bridge lookup URL found/configured, skipping"); log.info("No bridge lookup URL found/configured, skipping");

View File

@@ -20,7 +20,8 @@
package io.kamax.mxisd.lookup.provider package io.kamax.mxisd.lookup.provider
import io.kamax.mxisd.config.ServerConfig import io.kamax.mxisd.config.MatrixConfig
import io.kamax.mxisd.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher
@@ -43,7 +44,7 @@ class DnsLookupProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(DnsLookupProvider.class) private Logger log = LoggerFactory.getLogger(DnsLookupProvider.class)
@Autowired @Autowired
private ServerConfig srvCfg private MatrixConfig mxCfg
@Autowired @Autowired
private IRemoteIdentityServerFetcher fetcher private IRemoteIdentityServerFetcher fetcher
@@ -78,7 +79,7 @@ class DnsLookupProvider implements IThreePidProvider {
// TODO use caching mechanism // TODO use caching mechanism
Optional<String> findIdentityServerForDomain(String domain) { Optional<String> findIdentityServerForDomain(String domain) {
if (StringUtils.equals(srvCfg.getName(), domain)) { if (StringUtils.equals(mxCfg.getDomain(), domain)) {
log.info("We are authoritative for {}, no remote lookup", domain) log.info("We are authoritative for {}, no remote lookup", domain)
return Optional.empty() return Optional.empty()
} }
@@ -124,7 +125,7 @@ class DnsLookupProvider implements IThreePidProvider {
} }
@Override @Override
Optional<?> find(SingleLookupRequest request) { Optional<SingleLookupReply> find(SingleLookupRequest request) {
if (!StringUtils.equals("email", request.getType())) { // TODO use enum if (!StringUtils.equals("email", request.getType())) { // TODO use enum
log.info("Skipping unsupported type {} for {}", request.getType(), request.getThreePid()) log.info("Skipping unsupported type {} for {}", request.getType(), request.getThreePid())
return Optional.empty() return Optional.empty()
@@ -137,7 +138,7 @@ class DnsLookupProvider implements IThreePidProvider {
Optional<String> baseUrl = findIdentityServerForDomain(domain) Optional<String> baseUrl = findIdentityServerForDomain(domain)
if (baseUrl.isPresent()) { if (baseUrl.isPresent()) {
return fetcher.find(baseUrl.get(), request.getType().toString(), request.getThreePid()) return fetcher.find(baseUrl.get(), request)
} }
return Optional.empty() return Optional.empty()

View File

@@ -21,6 +21,7 @@
package io.kamax.mxisd.lookup.provider package io.kamax.mxisd.lookup.provider
import io.kamax.mxisd.config.ForwardConfig import io.kamax.mxisd.config.ForwardConfig
import io.kamax.mxisd.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher
@@ -56,9 +57,9 @@ class ForwarderProvider implements IThreePidProvider {
} }
@Override @Override
Optional<?> find(SingleLookupRequest request) { Optional<SingleLookupReply> find(SingleLookupRequest request) {
for (String root : cfg.getServers()) { for (String root : cfg.getServers()) {
Optional<?> answer = fetcher.find(root, request.getType(), request.getThreePid()) Optional<SingleLookupReply> answer = fetcher.find(root, request)
if (answer.isPresent()) { if (answer.isPresent()) {
return answer return answer
} }

View File

@@ -20,6 +20,7 @@
package io.kamax.mxisd.lookup.provider package io.kamax.mxisd.lookup.provider
import io.kamax.mxisd.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.ThreePidMapping
@@ -34,7 +35,7 @@ interface IThreePidProvider {
*/ */
int getPriority() // Should not be here but let's KISS for now int getPriority() // Should not be here but let's KISS for now
Optional<?> find(SingleLookupRequest request) Optional<SingleLookupReply> find(SingleLookupRequest request)
List<ThreePidMapping> populate(List<ThreePidMapping> mappings) List<ThreePidMapping> populate(List<ThreePidMapping> mappings)

View File

@@ -24,6 +24,8 @@ import groovy.json.JsonException
import groovy.json.JsonOutput import groovy.json.JsonOutput
import groovy.json.JsonSlurper import groovy.json.JsonSlurper
import io.kamax.mxisd.controller.v1.ClientBulkLookupRequest import io.kamax.mxisd.controller.v1.ClientBulkLookupRequest
import io.kamax.mxisd.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher
import org.apache.http.HttpEntity import org.apache.http.HttpEntity
@@ -77,24 +79,26 @@ public class RemoteIdentityServerFetcher implements IRemoteIdentityServerFetcher
} }
@Override @Override
Optional<?> find(String remote, String type, String threePid) { Optional<SingleLookupReply> find(String remote, SingleLookupRequest request) {
log.info("Looking up {} 3PID {} using {}", type, threePid, remote) log.info("Looking up {} 3PID {} using {}", request.getType(), request.getThreePid(), remote)
HttpURLConnection rootSrvConn = (HttpURLConnection) new URL( HttpURLConnection rootSrvConn = (HttpURLConnection) new URL(
"${remote}/_matrix/identity/api/v1/lookup?medium=${type}&address=${threePid}" "${remote}/_matrix/identity/api/v1/lookup?medium=${request.getType()}&address=${request.getThreePid()}"
).openConnection() ).openConnection()
try { try {
def output = json.parseText(rootSrvConn.getInputStream().getText()) String outputRaw = rootSrvConn.getInputStream().getText()
def output = json.parseText(outputRaw)
if (output['address']) { if (output['address']) {
log.info("Found 3PID mapping: {}", output) log.info("Found 3PID mapping: {}", output)
return Optional.of(output)
return Optional.of(SingleLookupReply.fromRecursive(request, outputRaw))
} }
log.info("Empty 3PID mapping from {}", remote) log.info("Empty 3PID mapping from {}", remote)
return Optional.empty() return Optional.empty()
} catch (IOException e) { } catch (IOException e) {
log.warn("Error looking up 3PID mapping {}: {}", threePid, e.getMessage()) log.warn("Error looking up 3PID mapping {}: {}", request.getThreePid(), e.getMessage())
return Optional.empty() return Optional.empty()
} catch (JsonException e) { } catch (JsonException e) {
log.warn("Invalid JSON answer from {}", remote) log.warn("Invalid JSON answer from {}", remote)

View File

@@ -21,6 +21,7 @@
package io.kamax.mxisd.lookup.strategy package io.kamax.mxisd.lookup.strategy
import io.kamax.mxisd.lookup.BulkLookupRequest import io.kamax.mxisd.lookup.BulkLookupRequest
import io.kamax.mxisd.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.provider.IThreePidProvider import io.kamax.mxisd.lookup.provider.IThreePidProvider
@@ -29,7 +30,11 @@ interface LookupStrategy {
List<IThreePidProvider> getLocalProviders() List<IThreePidProvider> getLocalProviders()
Optional<?> find(SingleLookupRequest request) Optional<SingleLookupReply> find(String medium, String address, boolean recursive)
Optional<SingleLookupReply> find(SingleLookupRequest request)
Optional<SingleLookupReply> findRecursive(SingleLookupRequest request)
List<ThreePidMapping> find(BulkLookupRequest requests) List<ThreePidMapping> find(BulkLookupRequest requests)

View File

@@ -22,10 +22,7 @@ package io.kamax.mxisd.lookup.strategy
import edazdarevic.commons.net.CIDRUtils import edazdarevic.commons.net.CIDRUtils
import io.kamax.mxisd.config.RecursiveLookupConfig import io.kamax.mxisd.config.RecursiveLookupConfig
import io.kamax.mxisd.lookup.ALookupRequest import io.kamax.mxisd.lookup.*
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.fetcher.IBridgeFetcher
import io.kamax.mxisd.lookup.provider.IThreePidProvider import io.kamax.mxisd.lookup.provider.IThreePidProvider
import org.slf4j.Logger import org.slf4j.Logger
@@ -93,13 +90,17 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea
} }
List<IThreePidProvider> listUsableProviders(ALookupRequest request) { List<IThreePidProvider> listUsableProviders(ALookupRequest request) {
return listUsableProviders(request, false);
}
List<IThreePidProvider> listUsableProviders(ALookupRequest request, boolean forceRecursive) {
List<IThreePidProvider> usableProviders = new ArrayList<>() List<IThreePidProvider> usableProviders = new ArrayList<>()
boolean canRecurse = isAllowedForRecursive(request.getRequester()) boolean canRecurse = forceRecursive || isAllowedForRecursive(request.getRequester())
log.info("Host {} allowed for recursion: {}", request.getRequester(), canRecurse) log.info("Host {} allowed for recursion: {}", request.getRequester(), canRecurse)
for (IThreePidProvider provider : providers) { for (IThreePidProvider provider : providers) {
if (provider.isEnabled() && (provider.isLocal() || canRecurse)) { if (provider.isEnabled() && (provider.isLocal() || canRecurse || forceRecursive)) {
usableProviders.add(provider) usableProviders.add(provider)
} }
} }
@@ -118,15 +119,27 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea
} }
@Override @Override
Optional<?> find(SingleLookupRequest request) { Optional<SingleLookupReply> find(String medium, String address, boolean recursive) {
for (IThreePidProvider provider : listUsableProviders(request)) { SingleLookupRequest req = new SingleLookupRequest();
Optional<?> lookupDataOpt = provider.find(request) req.setType(medium)
req.setThreePid(address)
req.setRequester("Internal")
return find(req, recursive)
}
Optional<SingleLookupReply> find(SingleLookupRequest request, boolean forceRecursive) {
for (IThreePidProvider provider : listUsableProviders(request, forceRecursive)) {
Optional<SingleLookupReply> lookupDataOpt = provider.find(request)
if (lookupDataOpt.isPresent()) { if (lookupDataOpt.isPresent()) {
return lookupDataOpt return lookupDataOpt
} }
} }
if (recursiveCfg.getBridge().getEnabled() && (!recursiveCfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))) { if (
recursiveCfg.getBridge() != null &&
recursiveCfg.getBridge().getEnabled() &&
(!recursiveCfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))
) {
log.info("Using bridge failover for lookup") log.info("Using bridge failover for lookup")
return bridge.find(request) return bridge.find(request)
} }
@@ -134,6 +147,16 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea
return Optional.empty() return Optional.empty()
} }
@Override
Optional<SingleLookupReply> find(SingleLookupRequest request) {
return find(request, false)
}
@Override
Optional<SingleLookupReply> findRecursive(SingleLookupRequest request) {
return find(request, true)
}
@Override @Override
List<ThreePidMapping> find(BulkLookupRequest request) { List<ThreePidMapping> find(BulkLookupRequest request) {
List<ThreePidMapping> mapToDo = new ArrayList<>(request.getMappings()) List<ThreePidMapping> mapToDo = new ArrayList<>(request.getMappings())

View File

@@ -1,7 +1,27 @@
/*
* 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.mapping; package io.kamax.mxisd.mapping;
import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.lookup.ThreePid; import io.kamax.mxisd.lookup.ThreePidValidation;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -16,7 +36,6 @@ public class MappingManager {
private Logger log = LoggerFactory.getLogger(MappingManager.class); private Logger log = LoggerFactory.getLogger(MappingManager.class);
private Map<String, Session> threePidLookups = new WeakHashMap<>();
private Map<String, Session> sessions = new HashMap<>(); private Map<String, Session> sessions = new HashMap<>();
private Timer cleaner; private Timer cleaner;
@@ -31,7 +50,6 @@ public class MappingManager {
log.info("Session {} is obsolete, removing", s.sid); log.info("Session {} is obsolete, removing", s.sid);
sessions.remove(s.sid); sessions.remove(s.sid);
threePidLookups.remove(s.hash);
} }
} }
} }
@@ -45,16 +63,9 @@ public class MappingManager {
} while (sessions.containsKey(sid)); } while (sessions.containsKey(sid));
String threePidHash = data.getMedium() + data.getValue(); String threePidHash = data.getMedium() + data.getValue();
Session session = threePidLookups.get(threePidHash); // TODO think how to handle different requests for the same e-mail
if (session != null) { Session session = new Session(sid, threePidHash, data);
sid = session.sid; sessions.put(sid, session);
} 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); log.info("Created new session {} to validate {} {}", sid, session.medium, session.address);
return sid; return sid;
@@ -72,10 +83,10 @@ public class MappingManager {
s.validationTimestamp = Instant.now(); s.validationTimestamp = Instant.now();
} }
public Optional<ThreePid> getValidated(String sid, String secret) { public Optional<ThreePidValidation> getValidated(String sid, String secret) {
Session s = sessions.get(sid); Session s = sessions.get(sid);
if (s != null && StringUtils.equals(s.secret, secret)) { if (s != null && StringUtils.equals(s.secret, secret)) {
return Optional.of(new ThreePid(s.medium, s.address, s.validationTimestamp)); return Optional.of(new ThreePidValidation(s.medium, s.address, s.validationTimestamp));
} }
return Optional.empty(); return Optional.empty();

View File

@@ -1,3 +1,23 @@
/*
* 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.mapping; package io.kamax.mxisd.mapping;
public interface MappingSession { public interface MappingSession {

View File

@@ -20,9 +20,11 @@
package io.kamax.mxisd.signature package io.kamax.mxisd.signature
import com.google.gson.JsonObject
import io.kamax.mxisd.config.ServerConfig import io.kamax.mxisd.config.ServerConfig
import io.kamax.mxisd.key.KeyManager import io.kamax.mxisd.key.KeyManager
import net.i2p.crypto.eddsa.EdDSAEngine import net.i2p.crypto.eddsa.EdDSAEngine
import org.json.JSONObject
import org.springframework.beans.factory.InitializingBean import org.springframework.beans.factory.InitializingBean
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@@ -40,14 +42,31 @@ class SignatureManager implements InitializingBean {
private EdDSAEngine signEngine private EdDSAEngine signEngine
Map<?, ?> signMessage(String message) { private String sign(String message) {
byte[] signRaw = signEngine.signOneShot(message.getBytes()) byte[] signRaw = signEngine.signOneShot(message.getBytes())
String sign = Base64.getEncoder().encodeToString(signRaw) return Base64.getEncoder().encodeToString(signRaw)
return [ }
"${srvCfg.getName()}": [
"ed25519:${keyMgr.getCurrentIndex()}": sign JSONObject signMessageJson(String message) {
] String sign = sign(message)
]
JSONObject keySignature = new JSONObject()
keySignature.put("ed25519:${keyMgr.getCurrentIndex()}", sign)
JSONObject signature = new JSONObject()
signature.put("${srvCfg.getName()}", keySignature)
return signature
}
JsonObject signMessageGson(String message) {
String sign = sign(message)
JsonObject keySignature = new JsonObject()
keySignature.addProperty("ed25519:${keyMgr.getCurrentIndex()}", sign)
JsonObject signature = new JsonObject()
signature.add("${srvCfg.getName()}", keySignature);
return signature
} }
@Override @Override

View File

@@ -0,0 +1,38 @@
/*
* 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.spring;
import io.kamax.mxisd.exception.ConfigurationException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
public class ConfigurationFailureAnalyzer extends AbstractFailureAnalyzer<ConfigurationException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure, ConfigurationException cause) {
String message = cause.getMessage();
if (cause.getDetailedMessage().isPresent()) {
message += " - " + cause.getDetailedMessage().get();
}
return new FailureAnalysis(message, "Double check the key value", cause);
}
}

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.storage;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO;
import java.util.Collection;
public interface IStorage {
Collection<ThreePidInviteIO> getInvites();
void insertInvite(IThreePidInviteReply data);
void deleteInvite(String id);
}

View File

@@ -0,0 +1,93 @@
/*
* 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.storage.ormlite;
import com.j256.ormlite.dao.CloseableWrappedIterable;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.jdbc.JdbcConnectionSource;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.storage.IStorage;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class OrmLiteSqliteStorage implements IStorage {
private Dao<ThreePidInviteIO, String> invDao;
OrmLiteSqliteStorage(String path) {
try {
File parent = new File(path).getParentFile();
if (!parent.mkdirs() && !parent.isDirectory()) {
throw new RuntimeException("Unable to create DB parent directory: " + parent);
}
ConnectionSource connPool = new JdbcConnectionSource("jdbc:sqlite:" + path);
invDao = DaoManager.createDao(connPool, ThreePidInviteIO.class);
TableUtils.createTableIfNotExists(connPool, ThreePidInviteIO.class);
} catch (SQLException e) {
throw new RuntimeException(e); // FIXME do better
}
}
@Override
public Collection<ThreePidInviteIO> getInvites() {
try (CloseableWrappedIterable<ThreePidInviteIO> t = invDao.getWrappedIterable()) {
List<ThreePidInviteIO> ioList = new ArrayList<>();
t.forEach(ioList::add);
return ioList;
} catch (IOException e) {
throw new RuntimeException(e); // FIXME do better
}
}
@Override
public void insertInvite(IThreePidInviteReply data) {
try {
int updated = invDao.create(new ThreePidInviteIO(data));
if (updated != 1) {
throw new RuntimeException("Unexpected row count after DB action: " + updated);
}
} catch (SQLException e) {
throw new RuntimeException(e); // FIXME do better
}
}
@Override
public void deleteInvite(String id) {
try {
int updated = invDao.deleteById(id);
if (updated != 1) {
throw new RuntimeException("Unexpected row count after DB action: " + updated);
}
} catch (SQLException e) {
throw new RuntimeException(e); // FIXME do better
}
}
}

View File

@@ -0,0 +1,76 @@
/*
* 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.storage.ormlite;
import io.kamax.mxisd.config.SQLiteStorageConfig;
import io.kamax.mxisd.config.StorageConfig;
import io.kamax.mxisd.exception.ConfigurationException;
import io.kamax.mxisd.storage.IStorage;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class OrmLiteSqliteStorageBeanFactory implements FactoryBean<IStorage> {
@Autowired
private StorageConfig storagecfg;
@Autowired
private SQLiteStorageConfig cfg;
private OrmLiteSqliteStorage storage;
@PostConstruct
private void postConstruct() {
if (StringUtils.equals("sqlite", storagecfg.getBackend())) {
if (StringUtils.isBlank(cfg.getDatabase())) {
throw new ConfigurationException("storage.provider.sqlite.database");
}
storage = new OrmLiteSqliteStorage(cfg.getDatabase());
}
}
@Override
public IStorage getObject() throws Exception {
if (storage == null) {
throw new FactoryBeanNotInitializedException();
}
return storage;
}
@Override
public Class<?> getObjectType() {
return OrmLiteSqliteStorage.class;
}
@Override
public boolean isSingleton() {
return true;
}
}

View File

@@ -0,0 +1,106 @@
/*
* 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.storage.ormlite;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import org.apache.commons.lang.StringUtils;
import java.util.HashMap;
import java.util.Map;
@DatabaseTable(tableName = "invite_3pid")
public class ThreePidInviteIO {
private static Gson gson = new Gson();
@DatabaseField(id = true)
private String id;
@DatabaseField(canBeNull = false)
private String token;
@DatabaseField(canBeNull = false)
private String sender;
@DatabaseField(canBeNull = false)
private String medium;
@DatabaseField(canBeNull = false)
private String address;
@DatabaseField(canBeNull = false)
private String roomId;
@DatabaseField
private String properties;
public ThreePidInviteIO() {
// needed for ORMlite
}
public ThreePidInviteIO(IThreePidInviteReply data) {
this.id = data.getId();
this.token = data.getToken();
this.sender = data.getInvite().getSender().getId();
this.medium = data.getInvite().getMedium();
this.address = data.getInvite().getAddress();
this.roomId = data.getInvite().getRoomId();
this.properties = gson.toJson(data.getInvite().getProperties());
}
public String getId() {
return id;
}
public String getToken() {
return token;
}
public String getSender() {
return sender;
}
public String getMedium() {
return medium;
}
public String getAddress() {
return address;
}
public String getRoomId() {
return roomId;
}
public Map<String, String> getProperties() {
if (StringUtils.isBlank(properties)) {
return new HashMap<>();
}
return gson.fromJson(properties, new TypeToken<Map<String, String>>() {
}.getType());
}
}

View File

@@ -0,0 +1,91 @@
/*
* 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.util;
import com.google.gson.*;
import io.kamax.mxisd.exception.InvalidResponseJsonException;
import io.kamax.mxisd.exception.JsonMemberNotFoundException;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
public class GsonParser {
private JsonParser parser = new JsonParser();
private Gson gson;
public GsonParser() {
this(new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create());
}
public GsonParser(Gson gson) {
this.gson = gson;
}
public JsonObject parse(InputStream stream) throws IOException {
JsonElement el = parser.parse(IOUtils.toString(stream, StandardCharsets.UTF_8));
if (!el.isJsonObject()) {
throw new InvalidResponseJsonException("Response body is not a JSON object");
}
return el.getAsJsonObject();
}
public <T> T parse(HttpResponse res, Class<T> type) throws IOException {
return gson.fromJson(parse(res.getEntity().getContent()), type);
}
public JsonObject parse(InputStream stream, String property) throws IOException {
JsonObject obj = parse(stream);
if (!obj.has(property)) {
throw new JsonMemberNotFoundException("Member " + property + " does not exist");
}
JsonElement el = obj.get(property);
if (!el.isJsonObject()) {
throw new InvalidResponseJsonException("Member " + property + " is not a JSON object");
}
return el.getAsJsonObject();
}
public <T> T parse(InputStream stream, String memberName, Class<T> type) throws IOException {
JsonObject obj = parse(stream, memberName);
return gson.fromJson(obj, type);
}
public <T> T parse(HttpResponse res, String memberName, Class<T> type) throws IOException {
return parse(res.getEntity().getContent(), memberName, type);
}
public <T> Optional<T> parseOptional(HttpResponse res, String memberName, Class<T> type) throws IOException {
try {
return Optional.of(parse(res.getEntity().getContent(), memberName, type));
} catch (JsonMemberNotFoundException e) {
return Optional.empty();
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* 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.util;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
public class JsonUtils {
public static JsonObject getObj(Gson gson, String property, Object value) {
JsonObject obj = new JsonObject();
obj.add(property, gson.toJsonTree(value));
return obj;
}
public static String getObjAsString(Gson gson, String property, Object value) {
return gson.toJson(getObj(gson, property, value));
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.util;
import com.google.gson.Gson;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import java.nio.charset.StandardCharsets;
public class RestClientUtils {
public static HttpPost post(String url, String body) {
StringEntity entity = new StringEntity(body, StandardCharsets.UTF_8);
entity.setContentType(ContentType.APPLICATION_JSON.toString());
HttpPost req = new HttpPost(url);
req.setEntity(entity);
return req;
}
public static HttpPost post(String url, Gson gson, String member, Object o) {
return post(url, JsonUtils.getObjAsString(gson, member, o));
}
public static HttpPost post(String url, Gson gson, Object o) {
return post(url, gson.toJson(o));
}
}

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