Compare commits

...

50 Commits

Author SHA1 Message Date
Maxime Dor
851e0c9d94 Properly build remote 3PID token request 2017-10-05 16:04:29 +02:00
Maxime Dor
ac1cbc4265 Fix redirect with thymleaf 2017-10-05 16:03:59 +02:00
Maxime Dor
62711ee12e Improve README 2017-10-02 20:07:22 +02:00
Maxime Dor
3954be2f08 Fix recursive logic 2017-10-02 18:03:57 +02:00
Maxime Dor
640512eb27 Line wrap 2017-10-02 16:14:26 +02:00
Maxime Dor
40705b5d47 Improved documentation 2017-10-02 16:10:22 +02:00
Maxime Dor
642d560ba9 Fix spelling 2017-10-02 03:46:30 +02:00
Maxime Dor
b6e86f5b2e Proper order of sections 2017-10-02 03:44:32 +02:00
Maxime Dor
4a99ec5531 Lots of new awesome documentation 2017-10-02 03:42:23 +02:00
Max Dor
9079bb25cc Merge pull request #34 from kamax-io/directory-integration
Directory integration
2017-10-01 22:04:58 +02:00
Maxime Dor
88e86cd0d5 Improve Directory documentation 2017-10-01 21:09:18 +02:00
Maxime Dor
8662b3f39f Stable implementation of Directory integration
- Documentation
- Allow to specific other attributes in LDAP to include in the search
2017-10-01 19:36:11 +02:00
Maxime Dor
d0aac5ac52 User Directory support in REST Backend 2017-10-01 18:13:01 +02:00
Maxime Dor
c702a34aab Fix regression due to bad replace 2017-10-01 16:10:05 +02:00
Maxime Dor
786e4a8f91 Prepare REST backend for directory flow 2017-10-01 02:21:48 +02:00
Maxime Dor
8d0b0edad2 Clarify some items thanks to users feedback 2017-10-01 00:06:03 +02:00
Maxime Dor
88a37c52c0 Skeleton for User directory setup instructions 2017-09-30 00:56:16 +02:00
Maxime Dor
52e4a65c3c Fix query generation 2017-09-30 00:27:36 +02:00
Maxime Dor
69ecef0155 Refactored directory package to include API version 2017-09-29 22:13:51 +02:00
Maxime Dor
f7984bd36e LDAP Directory search support 2017-09-29 20:54:08 +02:00
Maxime Dor
f735b3b730 Merge branch 'master' into directory-integration 2017-09-29 05:43:52 +02:00
Maxime Dor
b6008a41f2 Be consistent with DNS overwrite (always a URL) 2017-09-29 05:38:58 +02:00
Maxime Dor
ed2d13decf Don't mix up configs 2017-09-29 05:34:21 +02:00
Maxime Dor
4f3ecc19f3 Directory integration prototype using Google Firebase auth + Synapse SQL 2017-09-29 02:52:05 +02:00
Maxime Dor
c816217b22 Send new invite notification to same user if rooms are different 2017-09-28 02:45:01 +02:00
Maxime Dor
182f3c4bc3 Skeleton for HS User directory integration 2017-09-27 04:37:45 +02:00
Maxime Dor
2e7b5d2a87 Refactor packages (cosmetic) 2017-09-27 03:59:45 +02:00
Max Dor
09208d55d7 Merge pull request #33 from kamax-io/email-connector-sendgrid
Email connector sendgrid
2017-09-27 03:33:13 +02:00
Maxime Dor
05c76a657e Fix extra placeholders in smtp sender 2017-09-27 03:30:53 +02:00
Maxime Dor
f3bbc7c7c6 Add support for SendGrid as Email notification handler 2017-09-27 01:55:37 +02:00
Maxime Dor
61addd297a Use the correct formatting for MSISDN 2017-09-26 04:26:39 +02:00
Maxime Dor
1de0951733 Support 3PID listing during auth with Google Firebase 2017-09-26 03:11:15 +02:00
Maxime Dor
d348ebd813 Improved README to point to dedicated documents 2017-09-25 18:25:58 +02:00
Maxime Dor
0499c10a2c Better sample config, better README 2017-09-25 18:20:18 +02:00
Maxime Dor
13e248c71e Do not enforce Twilio config by default 2017-09-25 18:04:21 +02:00
Maxime Dor
d221b2c5de Fix groovy rollback issue 2017-09-25 17:56:06 +02:00
Maxime Dor
3a1900cbb2 Add link to 3PID session documentation 2017-09-25 17:15:37 +02:00
Max Dor
9f1867a030 Merge pull request #32 from kamax-io/phone_numbers-validation
Phone numbers validation
2017-09-25 17:12:59 +02:00
Maxime Dor
a061241291 Use relative links 2017-09-25 17:11:58 +02:00
Maxime Dor
fefa81e935 Add link to phone numbers in TOC 2017-09-25 17:06:39 +02:00
Maxime Dor
1e77bf43c6 Add documentation to validate phone numbers 2017-09-25 17:03:50 +02:00
Maxime Dor
c73bbf675e First prototype to validate phone numbers 2017-09-25 05:53:07 +02:00
Maxime Dor
6c2e65ace5 Code formatting, cosmetic 2017-09-25 02:35:16 +02:00
Maxime Dor
33263d3cff Bye bye Groovy, you won't be missed :( 2017-09-25 02:31:31 +02:00
Maxime Dor
af19fed6e7 More links to specific config docs 2017-09-25 00:15:30 +02:00
Maxime Dor
246dc4f8d1 Add web views link 2017-09-25 00:10:25 +02:00
Maxime Dor
31efa3e33f More 3PID sessions configuration documentation 2017-09-25 00:08:58 +02:00
Maxime Dor
bee2a5129b Fix inconsistencies into DNS library 2017-09-24 21:29:14 +02:00
Maxime Dor
f1e78af80b Fix empty JSON object on empty lookup results 2017-09-24 21:12:49 +02:00
Max Dor
e0022e549e Merge pull request #31 from kamax-io/binding-validation
3PID sessions support (email only)
2017-09-24 05:26:37 +02:00
211 changed files with 6775 additions and 3164 deletions

395
README.md
View File

@@ -2,217 +2,166 @@ mxisd - Federated Matrix Identity Server Daemon
-----
![Travis-CI build status](https://travis-ci.org/kamax-io/mxisd.svg?branch=master)
[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)
- [Features](#features)
- [Why use mxisd](#why-use-mxisd)
- [Quick start](#quick-start)
- [Support](#support)
- [Contribute](#contribute)
- [FAQ](#faq)
- [Contact](#contact)
# Overview
mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures.
mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures with enhanced features.
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.
- Federated identity stores: another Identity Server in charge of a specific domain, if applicable
- 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 Servers
It is specifically designed to connect to an Identity store (AD/Samba/LDAP, SQL Database, Web services/application, ...)
and ease the integration of the Matrix ecosystem with an existing infrastructure, or to build a new one using lasting
tools.
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).
The core principle of mxisd is to map between Matrix IDs and 3PIDs (Thrid-party Identifiers) for the Homeserver and its
users. 3PIDs can be anything that identify a user, like:
- Full name
- Email address
- Phone number
- Employee number
- Skype/Live ID
- Twitter handle
- Facebook ID
- ...
mxisd only aims to support workflows that do NOT break federation or basic lookup processes of the Matrix ecosystem.
mxisd is an enhanced Identity service, which implements the [Matrix Identity service API](https://matrix.org/docs/spec/identity_service/unstable.html)
but also several other features that greatly enhance user experience within Matrix.
mxisd is the one stop shop for anything regarding Authentication, Directory and Identity management in Matrix built as a
single coherent product.
# 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).
As a [regular Matrix Identity service](docs/features/identity.md):
- Search for people by 3PID using its own Identity stores (LDAP, SQL, etc.)
- Invite people to rooms by 3PID using its own Identity stores, with [notifications](docs/README.md)
to the invitee (Email, SMS, etc.)
- Allow users to add 3PIDs to their settings/profile
In the pipe:
- Support to proxy 3PID bindings in user profile to the central Matrix.org servers
As an enhanced Identity service:
- Use a recursive lookup mechanism when searching and inviting people by 3PID, allowing to fetch data from:
- Own Identity store
- Federated Identity servers, if applicable to the 3PID
- Arbitrary Identity servers
- Central Matrix Identity servers
- [Extensive control of where 3PIDs are transmited](docs/sessions/3pid.md), so they are not leaked publicly by users
- [Authentication support](docs/features/authentication.md) for [synapse](https://github.com/matrix-org/synapse) via the
[REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth)
- [Directory integration](docs/features/directory-users.md) which allows you to search for users within your
organisation, even without prior Matrix contact
- [Auto-fill of user profile](docs/features/authentication.md) (Display name, 3PIDs) via the
[REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth)
# Lookup Process
Default Lookup strategy will use a priority order and a configurable recursive/local type of request.
# Why use mxisd
- Use your existing Identity store, do not duplicate information
- Auto-fill user profiles with relevant information
- As an organisation, stay in control of 3PIDs so they are not published to the central Matrix.org servers where they
currently **cannot be removed**
- Users can directly find each other using whatever attribute is relevant within your Identity store
- Federate your Identity lookups so you can discover others and/or others can discover you, all with extensive ACLs
## E-mail
Given the 3PID `john.doe@example.org`, the following will be performed until a mapping is found:
- LDAP: lookup the Matrix ID (partial or complete) from a configurable attribute using a dedicated query.
- DNS: lookup another Identity Server using the domain part of an e-mail and:
- Look for a SRV record under `_matrix-identity._tcp.example.org`
- Lookup using the base domain name `example.org`
- Forwarder: Proxy the request to other configurable identity servers.
# Quick Start
1. [Preparation](#preparation)
2. [Install](#install)
3. [Configure](#configure)
4. [Integrate](#integrate)
5. [Validate](#validate)
## Phone number
Given the phone number `+123456789`, the following lookup logic will be performed:
- LDAP: lookup the Matrix ID (partial or complete) from a configurable attribute using a dedicated query.
- Forwarder: Proxy the request to other configurable identity servers.
Following these quick start instructions, you will have a basic setup that can perform recursive/federated lookups and
talk to the central Matrix.org Identity service.
This will be a good ground work for further integration with your existing Identity stores.
# Packages
See [releases]((https://github.com/kamax-io/mxisd/releases)) for native installers of supported systems.
If none is available, please use other packages or build from source.
## Preparation
You will need:
- Homeserver
- Reverse proxy with regular TLS/SSL certificate (Let's encrypt) for your mxisd domain
## Debian
### Download
See the [releases section](https://github.com/kamax-io/mxisd/releases).
As synapse requires an HTTPS connection when talking to an Identity service, a reverse proxy is required as mxisd does
not support HTTPS listener at this time.
### 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`
For maximum integration, it is best to have your Homeserver and mxisd reachable via the same hostname.
You can also use a dedicated domain for mxisd, but will not have access to some features.
### From source
Requirements:
- fakeroot
- dpkg-deb
Be aware of a [NAT/Reverse proxy gotcha](https://github.com/kamax-io/mxisd/wiki/Gotchas#nating) if you use the same
hostname.
Run:
```
./gradlew buildDeb
```
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
## Requirements
- JDK 1.8
## Build
```
git clone https://github.com/kamax-io/mxisd.git
cd mxisd
./gradlew build
```
then see the [Configuration](#configuration) section.
## Test build
Start the server in foreground to validate the build:
```
java -jar build/libs/mxisd.jar
```
Ensure the signing key is available:
```
$ curl http://localhost:8090/_matrix/identity/api/v1/pubkey/ed25519:0
{"public_key":"..."}
```
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"
```
If you plan on testing the integration with a homeserver, you will need to run an HTTPS reverse proxy in front of it
as the reference Home Server implementation [synapse](https://github.com/matrix-org/synapse) requires a HTTPS connection
to an ID server.
See the [Integration section](https://github.com/kamax-io/mxisd#integration) for more details.
The following Quick Start guide assumes you will host the Homeserver and mxisd under the same hostname.
If you would like a high-level view of the infrastructure and how each feature is integrated, see the
[dedicated document](docs/architecture.md)
## Install
After [building](#build) the software, run all the following commands as `root` or using `sudo`
1. Prepare files and directories:
Install via:
- [Debian package](docs/install/debian.md)
- [Docker image](docs/install/docker.md)
- [Sources](docs/build.md)
See the [Latest release](https://github.com/kamax-io/mxisd/releases/latest) for links to each.
## Configure
Create/edit a minimal configuration (see installer doc for the location):
```
# Create a dedicated user
useradd -r mxisd
matrix.domain: 'MyMatrixDomain.org'
key.path: '/path/to/signing.key.file'
storage.provider.sqlite.database: '/path/to/mxisd.db'
```
- `matrix.domain` should be set to your Homeserver domain
- `key.path` will store the signing keys, which must be kept safe!
- `storage.provider.sqlite.database` is the location of the SQLite Database file which will hold state (invites, etc.)
# Create bin directory
mkdir /opt/mxisd
If your HS/mxisd hostname is not the same as your Matrix domain, configure `server.name`.
Complete configuration guide is available [here](docs/configure.md).
# Create config directory and set ownership
mkdir /etc/mxisd
chown mxisd /etc/mxisd
# Create data directory and set ownership
mkdir /var/opt/mxisd
chown mxisd /var/opt/mxisd
# Copy <repo root>/build/libs/mxisd.jar to bin directory
cp ./build/libs/mxisd.jar /opt/mxisd/
chown mxisd /opt/mxisd/mxisd.jar
chmod a+x /opt/mxisd/mxisd.jar
# Create symlink for easy exec
ln -s /opt/mxisd/mxisd.jar /usr/bin/mxisd
## Integrate
For an overview of a typical mxisd infrastructure, see the [dedicated document](docs/architecture.md)
### Reverse proxy
#### Apache2
In the VirtualHost handling the domain with SSL, add the following line and replace `0.0.0.0` by the right address/host.
**This line MUST be present before the one for the homeserver!**
```
ProxyPass /_matrix/identity/ http://0.0.0.0:8090/_matrix/identity/
```
2. Copy the config file created earlier `./application.example.yaml` to `/etc/mxisd/mxisd.yaml`
3. [Configure](#configuration)
4. Copy `<repo root>/src/systemd/mxisd.service` to `/etc/systemd/system/` and edit if needed
5. Enable service for auto-startup
Typical VirtualHost configuration would be:
```
systemctl enable mxisd
```
6. Start mxisd
```
systemctl start mxisd
<VirtualHost *:443>
ServerName example.org
...
ProxyPreserveHost on
ProxyPass /_matrix/identity/ http://10.1.2.3:8090/_matrix/identity/
ProxyPass /_matrix/ http://10.1.2.3:8008/_matrix/
</VirtualHost>
```
# 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. 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.
## Backends
### LDAP (AD, Samba, LDAP)
If you want to use LDAP backend as an Identity store:
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
### SQL (SQLite, PostgreSQL)
If you want to connect to use a synapse DB (SQLite or PostgreSQL) as Identity store, follow the example config for `sql` config items.
### REST (Webapps/websites integration)
If you want to use the REST backend as an Identity store:
1. Enable it with `rest.enabled`
2. Configure options starting with `rest` and see the dedicated documentation in `docs/backends/rest.md`
# Network Discovery
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!**
### Synapse
Add your mxisd domain into the `homeserver.yaml` at `trusted_third_party_id_servers` and restart synapse.
In a typical configuration, you would end up with something similair to:
```
_matrix-identity._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com.
trusted_third_party_id_servers:
- matrix.org
- vector.im
- example.org
```
This would only apply for 3PID that are DNS-based, like e-mails. For anything else, like phone numbers, no federation
is currently possible.
It is recommended to remove `matrix.org` and `vector.im` so only your own Identity server is allowed by synapse.
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.
### Federation and network discovery
See the [dedicated document](docs/features/federation.md).
# Integration
- [HTTPS and Reverse proxy](https://github.com/kamax-io/mxisd/wiki/HTTPS)
- [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
## Validate
Log in using your Matrix client and set `https://example.org` as your Identity server URL, replacing `example.org` by
the relevant hostname which you configured in your reverse proxy.
Invite `mxisd-lookup-test@kamax.io` to a room, which should be turned into a Matrix invite to `@mxisd-lookup-test:kamax.io`.
**NOTE:** you might not see a Matrix suggestion for the e-mail address, which is normal. Still proceed with the invite.
If it worked, it means you are up and running and can enjoy mxisd in its basic mode! Congratulations!
If it did not work, [get in touch](#support) and we'll do our best to get you started.
You can now integrate mxisd further with your infrastructure using the various [features](docs/README.md) guides.
# Support
## Community
@@ -224,3 +173,87 @@ For more high-level discussion about the Identity Server architecture/API, go to
## Professional
If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open source technologies/products,
please visit [our website](https://www.kamax.io/) to get in touch with us and get a quote.
We offer affordable monthly/yearly support plans for mxisd, synapse or your full Matrix infrastructure.
# Contribute
First and foremost, the best way to contribute is to use mxisd and tell us about it!
We would love to hear about your experience and get your feedback on how to make it an awesome product.
You can contribute as a community member by:
- Opening issues for any weird behaviour or bug. mxisd should feel natural, let us know if it does not!
- Helping us improve the documentation: tell us what is good or not good (in an issue or in Matrix), or make a PR with
changes you feel improve the doc.
- Contribute code directly: we love contributors! All your contributions will be licensed under AGPLv3.
- Donate! any donation is welcome, regardless how small or big. This will directly be used for the fixed costs and
developer time.
You can contribute as an organisation/corporation by:
- Get a [support contract](#support-professional). This is the best way you can help us as it ensures mxisd is
maintained regularly and you get direct access to the support team.
- Sponsoring new features or bug fixes. [Get in touch](#contact) so we can discuss it further.
# FAQ
### Do I need to use mxisd if I run a Homeserver?
No, but it is recommended, even if you don't use any backends or integration.
mxisd in its default configuration will use federation and involve the central Matrix.org Identity servers when
performing queries, giving you access to at least the same information as if you were not running it.
It will also give your users a choice to make their 3PIDs available publicly, ensuring they are made aware of the
privacy consequences, which is not the case with the central Matrix.org servers.
So mxisd is like your gatekeeper and guardian angel. It does not change what you already know, just adds some nice
simple features on top of it.
### I already use the synapse LDAP3 auth provider, why should I care about mxisd?
The [synapse LDAP3 auth provider](https://github.com/matrix-org/matrix-synapse-ldap3) only handles on specific flow:
validate credentials at login.
It does not:
- Auto-provision user profiles
- Integrate with Identity management
- Integrate with Directory searches
- Protect you against the username case sensitivites issues in synapse
mxisd is a replacement and enhancement of it, and also offers coherent results in all areas, which LDAP3 auth provider
does not.
### I saw that sydent is the official Identity server implemenation of the Matrix team, I should use that!
You can, but [sydent](https://github.com/matrix-org/sydent):
- [should not be used and/or self-hosted](https://github.com/matrix-org/sydent/issues/22)
- is not meant to be linked to a specific Homeserver / domain
- cannot handle federation or proxy lookups, effectively isolating your users from the rest of the network
- forces you to duplicate all your identity data, so people can be found by 3PIDs
- forces users to enter all their emails and phone numbers manually in their profile
So really, you should go with mxisd.
### I'm not sure I understand what an "Identity server" is supposed to be or do
The current Identity service API is more a placeholder, as the Matrix devs did not have time so far to really work on
what they want to do with that part of the ecosystem. Therefore, "Identity" is a misleading word currently.
Given the scope of the current Identity Service API, it would be best called "Invitation service".
Because the current scope is so limited and no integration is done with the Homeserver, there was a big lack of features
for groups/corporations/organisation. This is where mxisd comes in.
mxisd implements the Identity Service API and also a set of features which are expected by regular users, truly living
up to its "Identity server" name.
### So mxisd is just a big hack! I don't want to use non-official features!
mxisd primary concern is to always be compatible with the Matrix ecosystem and the Identity service API.
Whenever the API will be updated and/or enhanced, mxisd will follow, remaining 100% compatible with the ecosystem.
We also directly talk with the Matrix developers to ensure all features we implement have their approval, and that we
are in line with their vision of Identity management within the Matrix ecosystem.
Therefore, using mxisd is a safe choice. It will be like using the central Matrix.org Identity servers, yet not closing
the door to very nice enhancements and integrations.
### Should I use mxisd if I don't host my own Homeserver?
No
# Contact
Get in touch via:
- Matrix at [#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io)
- Email, see our website: [Kamax.io](https://www.kamax.io)

View File

@@ -1,11 +1,6 @@
# Sample configuration file explaining all possible options, their default value and if they are required or not.
# Sample configuration file explaining the minimum required keys to be set to run mxisd
#
# 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.
# For a complete list of options, see https://github.com/kamax-io/mxisd
#######################
# Matrix config items #
@@ -16,48 +11,9 @@
matrix.domain: ''
#######################
# 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
# Public hostname of this identity server.
#
# This would be typically be the same as your Matrix domain.
# In case it is not, set this value.
#
# This value is used in various signatures within the Matrix protocol and should be a reachable hostname.
# 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'
#############################
# Signing keys config items #
#############################
################
# Signing keys #
################
# Absolute path for the Identity Server signing key.
# During testing, /var/tmp/mxisd.key is a possible value
#
@@ -65,309 +21,7 @@ matrix.domain: ''
# - /var/opt/mxisd/sign.key
# - /var/local/mxisd/sign.key
# - /var/lib/mxisd/sign.key
key.path: '/path/to/sign.key'
#################################
# Recurisve lookup config items #
#################################
# Configuration items for recursion-type of lookup
#
# Lookup access are divided into two types:
# - Local
# - Remote
#
# This is similar to DNS lookup and recursion and is therefore prone to the same vulnerabilities.
# By default, only non-public hosts are allowed to perform recursive lookup.
#
# This will also prevent very basic endless loops where host A ask host B, which in turn is configured to ask host A,
# which would then ask host B again, etc.
# Enable recursive lookup globally
#
#lookup.recursive.enabled: true
# Whitelist of CIDR that will trigger a recursive lookup.
# The default list includes all private IPv4 address and the IPv6 loopback.
#
#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'
# 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!
# 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: ''
#####################
# LDAP config items #
#####################
# Global enable/disable switch
#
#ldap.enabled: false
#### Connection related config items
# If the connection should be secure
#
#ldap.connection.tls: false
# 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"
key.path: ''
############################
@@ -380,10 +34,7 @@ invite.sender.email.email: "matrix-identity@example.org"
#
#storage.backend: 'sqlite'
#### Generic SQLite provider config
#
# Path to the SQLite DB file, required if SQLite backend is chosen
# Path to the SQLite DB file
#
# Examples:
# - /var/opt/mxisd/mxisd.db
@@ -393,22 +44,53 @@ invite.sender.email.email: "matrix-identity@example.org"
storage.provider.sqlite.database: '/path/to/mxisd.db'
################
# LDAP Backend #
################
# If you would like to integrate with your AD/Samba/LDAP server,
# see https://github.com/kamax-io/mxisd/blob/master/docs/backends/ldap.md
######################
# DNS-related config #
######################
# The domain to overwrite
###############
# SQL Backend #
###############
# If you would like to integrate with a MySQL/MariaDB/PostgreQL/SQLite DB,
# see https://github.com/kamax-io/mxisd/blob/master/docs/backends/sql.md
################
# REST Backend #
################
# If you would like to integrate with an existing web service/webapp,
# see https://github.com/kamax-io/mxisd/blob/master/docs/backends/rest.md
#################################################
# Notifications for invites/addition to profile #
#################################################
# If you would like to change the content,
# see https://github.com/kamax-io/mxisd/blob/master/docs/threepids/notifications/template-generator.md
#
#dns.overwrite.homeserver.name: 'example.org'
# - 'env' from environment variable specified by value
# - any other value will use the value as-is as host
#### E-mail invite sender
#
#dns.overwrite.homeserver.type: 'raw'
# SMTP host
threepid.medium.email.connectors.smtp.host: "smtp.example.org"
# SMTP port
threepid.medium.email.connectors.smtp.port: 587
# The value to use, depending on the type.
# Protocol will always be HTTPS
# TLS mode for the connection.
#
#dns.overwrite.homeserver.value: 'localhost:8448'
# Possible values:
# 0 Disable TLS entirely
# 1 Enable TLS if supported by server (default)
# 2 Force TLS and fail if not available
#
#threepid.medium.email.connectors.smtp.tls: 1
# Login for SMTP
threepid.medium.email.connectors.smtp.login: "matrix-identity@example.org"
# Password for the account
threepid.medium.email.connectors.smtp.password: "ThePassword"
# The e-mail to send as. If empty, will be the same as login
threepid.medium.email.identity.from: "matrix-identity@example.org"

View File

@@ -1,5 +1,3 @@
import java.util.regex.Pattern
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
@@ -20,7 +18,9 @@ import java.util.regex.Pattern
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
apply plugin: 'groovy'
import java.util.regex.Pattern
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
def confFileName = "application.example.yaml"
@@ -47,7 +47,7 @@ String gitVersion() {
def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?")
ByteArrayOutputStream out = new ByteArrayOutputStream()
exec {
commandLine = [ 'git', 'describe', '--always', '--dirty' ]
commandLine = ['git', 'describe', '--always', '--dirty']
standardOutput = out
}
def v = out.toString().replace(System.lineSeparator(), '')
@@ -70,9 +70,6 @@ repositories {
}
dependencies {
// We are a groovy project
compile 'org.codehaus.groovy:groovy-all:2.4.7'
// Easy file management
compile 'commons-io:commons-io:2.5'
@@ -113,12 +110,21 @@ dependencies {
// ORMLite
compile 'com.j256.ormlite:ormlite-jdbc:5.0'
// Connection Pool
compile 'com.mchange:c3p0:0.9.5.2'
// SQLite
compile 'org.xerial:sqlite-jdbc:3.20.0'
// PostgreSQL
compile 'org.postgresql:postgresql:42.1.4'
// Twilio SDK for SMS
compile 'com.twilio.sdk:twilio:7.14.5'
// SendGrid SDK to send emails from GCE
compile 'com.sendgrid:sendgrid-java:2.2.2'
testCompile 'junit:junit:4.12'
testCompile 'com.github.tomakehurst:wiremock:2.8.0'
}

25
docs/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Table of Contents
- Installation
- [Debian package](install/debian.md)
- [Docker](install/docker.md)
- [Build from source](build.md)
- [Architecture overview](architecture.md)
- [Configuration](configure.md)
- Features
- [Matrix Identity Service](features/identity.md)
- [Homeserver Authentication](features/authentication.md)
- [Directory seach](features/directory-users.md)
- [Identity server Federation](features/federation.md)
- [Bridge integration](features/bridge-integration.md)
- Backends
- [LDAP](backends/ldap.md)
- [SQL](backends/sql.md)
- [REST](backends/rest.md)
- [Google Firebase](backends/firebase.md)
- Notifications
- Handlers
- [Basic](threepids/notifications/basic-handler.md)
- [SendGrid](threepids/notifications/sendgrid-handler.md)
- [Sessions](sessions/3pid.md)
- [Views](sessions/3pid-views.md)

43
docs/architecture.md Normal file
View File

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

View File

@@ -0,0 +1,9 @@
# Google Firebase
## Configuration
To be completed. For now, see default structure and values:
```
firebase:
enabled: false
credentials: '/path/to/firebase/credentials.json'
database: 'https://my-project.firebaseio.com/'
```

86
docs/backends/ldap.md Normal file
View File

@@ -0,0 +1,86 @@
# AD/Samba/LDAP backend
## Configuration
### Structure and default values
```
ldap:
enabled: false
filter: ''
connection:
host: ''
tls: false
port: 389
bindDn: ''
bindPassword: ''
baseDn: ''
attribute:
uid:
type: 'uid'
value: 'userPrincipalName'
name: 'displayName'
threepid:
email:
- 'mailPrimaryAddress'
- 'mail'
- 'otherMailbox'
msisdn:
- 'telephoneNumber'
- 'mobile'
- 'homePhone'
- 'otherTelephone'
- 'otherMobile'
- 'otherHomePhone'
auth:
filter: ''
directory:
attribute:
other: []
filter: ''
identity:
filter: ''
medium:
email: ''
msisdn: ''
```
### General
| Item | Description |
|-----------|-------------------------------------------------------------------------------------------|
| `enabled` | Globaly enable/disable the LDAP backend |
| `filter` | Global filter to apply on all LDAP queries. Can be overwritten in each applicable section |
### Connection
| Item | Description |
|----------------|------------------------------------------------------|
| `host` | Host to connect to |
| `port` | Port to use |
| `tls` | boolean to use TLS or not (STARTLS is not supported) |
| `bindDn` | Bind DN for authentication |
| `bindPassword` | Bind password |
| `baseDn` | Base DN for queries |
### Attributes
| Item | Description |
|-------------|------------------------------------------------------------------------------------------------------------------------|
| `uid.type` | Indicate how to process the User ID (UID) attribute: |
| | - `uid` will consider the value as the [Localpart](https://matrix.org/docs/spec/intro.html#user-identifiers) |
| | - `mxid` will consider the value as a complete [Matrix ID](https://matrix.org/docs/spec/intro.html#user-identifiers) |
| `uid.value` | Attribute name refering to the User ID. This is typically `userPrincipalName` on AD/Samba setups and `uid` in LDAP |
| `name` | Attribute name that contains the [Display Name](https://matrix.org/docs/spec/intro.html#profiles) of the user |
| `threepid` | Namespace where each key is a 3PID type and contains a list of attributes |
### Authentication
| Item | Description |
|----------|--------------------------------------------------------------------------------------------------|
| `filter` | Specific user filter applied during authentication. Global filter is used if empty/blank/not set |
### Directory
| Item | Description |
|-------------------|---------------------------------------------------------------------|
| `attribute.other` | Additional attributes to be used when performing directory searches |
| `filter` | Specific user filter applied during directory search. |
| | Global filter is used if empty/blank/not set |
### Identity
| Item | Description |
|----------|---------------------------------------------------------------------------------------------------|
| `filter` | Specific user filter applied during identity search. Global filter is used if empty/blank/not set |
| `medium` | Namespace to overwrite generated queries from the list of attributes for each 3PID medium |

View File

@@ -6,33 +6,33 @@ The REST backend allows you to query identity data in existing webapps, like:
- self-hosted clouds (Nextcloud, ownCloud, ...)
It supports the following mxisd flows:
- Identity lookup
- Authentication
- [Authentication](#authentication)
- [Directory](#directory)
- [Identity](#identity)
To integrate this backend with your webapp, you will need to implement three specific REST endpoints detailed below.
## 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 query a single 3PID |
| rest.endpoints.identity.bulk | /_mxisd/identity/api/v1/lookup/bulk | Endpoint to query a list of 3PID |
| rest.endpoints.auth | /_mxisd/backend/api/v1/auth/login | Validate credentials and get user profile |
| rest.endpoints.directory | /_mxisd/backend/api/v1/directory/user/search | Search for users by arbitrary input |
| rest.endpoints.identity.single | /_mxisd/backend/api/v1/identity/single | Endpoint to query a single 3PID |
| rest.endpoints.identity.bulk | /_mxisd/backend/api/v1/identity/bulk | Endpoint to query a list of 3PID |
Endpoint values can handle two formats:
- URL Path starting with `/` that gets happened to the `rest.host`
- Full URL, if you want each endpoint to go to a specific server/protocol/port
`rest.host` is only mandatory if at least one endpoint is not a full URL.
`rest.host` is mandatory if at least one endpoint is not a full URL.
## Endpoints
### Authenticate
Configured with `rest.endpoints.auth`
### Authentication
HTTP method: `POST`
Encoding: JSON UTF-8
Content-type: JSON UTF-8
#### Request Body
```
@@ -84,12 +84,51 @@ If the authentication succeed:
}
```
### Lookup
#### Single
Configured with `rest.endpoints.identity.single`
### Directory
HTTP method: `POST`
Encoding: JSON UTF-8
Content-type: JSON UTF-8
#### Request Body
```
{
"by": "<search type>",
"search_term": "doe"
}
```
`by` can be:
- `name`
- `threepid`
#### Response Body:
If users found:
```
{
"limited": false,
"results": [
{
"avatar_url": "http://domain.tld/path/to/avatar.png",
"display_name": "John Doe",
"user_id": "UserIdLocalpart"
},
{
...
}
]
}
```
If no user found:
```
{
"limited": false,
"results": []
}
```
### Identity
#### Single 3PID lookup
HTTP method: `POST`
Content-type: JSON UTF-8
#### Request Body
```
@@ -122,11 +161,9 @@ If no match was found:
{}
```
#### Bulk
Configured with `rest.endpoints.identity.bulk`
#### Bulk 3PID lookup
HTTP method: `POST`
Encoding: JSON UTF-8
Content-type: JSON UTF-8
#### Request Body
```

23
docs/backends/sql.md Normal file
View File

@@ -0,0 +1,23 @@
# SQL Backend
## Configuration
To be completed. For now, see default structure and values:
```
sql:
enabled: false
type: 'sqlite' or 'postgresql'
connection: ''
auth:
enabled: false
directory:
enabled: false
query:
name:
type: 'localpart'
value: 'SELECT 1'
threepid:
type: 'localpart'
value: 'SELECT 1'
identity:
type: 'localpart'
query: 'SELECT user_id AS uid FROM user_threepids WHERE medium = ? AND address = ?'
```

105
docs/build.md Normal file
View File

@@ -0,0 +1,105 @@
# From source
- [Binaries](#binaries)
- [Debian package](#debian-package)
- [Docker image](#docker-image)
## Binaries
### Requirements
- JDK 1.8
### Build
```
git clone https://github.com/kamax-io/mxisd.git
cd mxisd
./gradlew build
```
Create a new configuration file by coping `application.example.yaml` to `application.yaml` and edit to your needs.
For advanced configuration, see the [Configure section](configure.md).
Start the server in foreground to validate the build and configuration:
```
java -jar build/libs/mxisd.jar
```
Ensure the signing key is available:
```
$ curl 'http://localhost:8090/_matrix/identity/api/v1/pubkey/ed25519:0'
{"public_key":"..."}
```
Test basic recursive lookup (requires Internet connection with access to TCP 443):
```
$ 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'
```
If you plan on testing the integration with a homeserver, you will need to run an HTTPS reverse proxy in front of it
as the reference Home Server implementation [synapse](https://github.com/matrix-org/synapse) requires a HTTPS connection
to an ID server.
See the [Integration section](#integration) for more details.
### Install
1. Prepare files and directories:
```
# Create a dedicated user
useradd -r mxisd
# Create bin directory
mkdir /opt/mxisd
# Create config directory and set ownership
mkdir /etc/opt/mxisd
chown mxisd /etc/opt/mxisd
# Create data directory and set ownership
mkdir /var/opt/mxisd
chown mxisd /var/opt/mxisd
# Copy <repo root>/build/libs/mxisd.jar to bin directory
cp ./build/libs/mxisd.jar /opt/mxisd/
chown mxisd /opt/mxisd/mxisd.jar
chmod a+x /opt/mxisd/mxisd.jar
# Create symlink for easy exec
ln -s /opt/mxisd/mxisd.jar /usr/bin/mxisd
```
2. Copy the sample config file `./application.example.yaml` to `/etc/opt/mxisd/mxisd.yaml`, edit to your needs
4. Copy `<repo root>/src/systemd/mxisd.service` to `/etc/systemd/system/` and edit if needed
5. Enable service for auto-startup
```
systemctl enable mxisd
```
6. Start mxisd
```
systemctl start mxisd
```
## Debian package
### Requirements
- fakeroot
- dpkg-deb
### Build
[Build mxisd](#build) then:
```
./gradlew buildDeb
```
You will find the debian package in `build/dist`
## Docker image
[Build mxisd](#build) then:
```
./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
```

183
docs/configure.md Normal file
View File

@@ -0,0 +1,183 @@
# Configuration
- [Syntax](#syntax)
- [Variables](#variables)
- [Categories](#categories)
## Syntax
The configuration file is YAML based, allowing two types of syntax.
Properties-like:
```
my.config.item: 'value'
```
Object-like:
```
my:
config:
item: 'value'
```
These can also be combined within the same file.
Both syntax will be used interchangeably in these documents.
Default values for each possible option are documented [here](../src/main/resources/application.yaml)
## Variables
It is possible to copy the value of a configuration item into another using the syntax `${config.key.item}`.
Example that will copy the value of `matrix.domain` into `server.name`:
```
matrix:
domain: 'example.org'
server:
name: '${matrix.domain}'
```
**WARNING:** mxisd might overwrite/adapt some values during launch. Those changes will not be reflected into copied keys.
## Categories
For each category below, the base configuration path will be given, which needs to be appened to every configuration
item described.
Example: if the base path was `basePath` and the following table was given:
| Name | Purpose |
|------|---------|
| item1 | To give an example |
| item2 | To give another example |
The following configurations could be used, all being equivalent:
```
basePath.item1: 'myValue'
basePath.item2: 'myOtherValue'
```
```
basePath:
item1: 'myValue'
item2: 'myOtherValue'
```
```
basePath.item1: 'myValue'
basePath:
item2: 'myOtherValue'
```
---
In case a relative base path is given, it is appended to the one above.
Example: With base path `basePath`, the relative base `relativeBasePath` and the following table:
| Name | Purpose |
|------|---------|
| item1 | To give an example |
| item2 | To give another example |
The following configurations could be used, all being equivalent:
```
basePath.relativeBasePath.item1: 'myValue'
basePath.relativeBasePath.item2: 'myOtherValue'
```
```
basePath:
relativeBasePath:
item1: 'myValue'
item2: 'myOtherValue'
```
```
basePath.relativeBasePath.item1: 'myValue'
basePath:
relativeBasePath:
item2: 'myOtherValue'
```
### Matrix
Base path: `matrix`
| Name | Purpose |
|------|---------|
| `domain` | Matrix domain name, same as the Homeserver, used to build appropriate Matrix IDs |
---
Relative base path: `identity`
| Name | Purpose |
|------|---------|
| `servers` | Namespace to create arbitrary list of Identity servers, usable in other parts of the configuration |
Example:
```
matrix.identity.servers:
root:
- 'https://matrix.org'
```
Create a list under the label `root` containing a single Identity server, `https://matrix.org`
### Server
| Name | Purpose |
|------|---------|
| `name` | Public hostname of mxisd, if different from the Matrix domain |
| `port` | HTTP port to listen on (unencrypted) |
| `publicUrl` | Defaults to `https://${server.name}` |
### Storage
Base path: `storage`
| Name | Purpose |
|------|---------|
| `backend` | Specify which SQL backend to use. only `sqlite` is currently supported. |
---
Relative base path: `provider.sqlite`
| Name | Purpose |
|------|---------|
| `database` | Absolute location of the SQLite database |
### Backends
- [LDAP](backends/ldap.md)
- [SQL](backends/sql.md)
- [REST](backends/rest.md)
- [Google Firebase](backends/firebase.md)
### Lookups
work in progress, should not be configured.
### Sessions
See the [dedicated document](sessions/3pid.md)
### Notifications
Base path: `notification`
| Name | Purpose |
|------|---------|
| handler | Namespace to specify the handler to use for each 3PID |
| handlers | Namespace used by individual handlers for their own configuration |
Example:
```
notification:
handler:
email: 'sendgrid'
msisdn: 'raw'
handlers:
raw:
...
sendgrid:
...
```
- Emails notifications would use the `sendgrid` handler, which define its own configuration user `handlers.sendgrid`
- Phone notification would use the `raw` handler, basic default built-in handler of mxisd
#### Handlers
Relative base path: `handlers`
Built-in:
- [Basic](threepids/notifications/basic-handler.md)
- [SendGrid](threepids/notifications/sendgrid-handler.md)
### Views
See the [dedicated document](sessions/3pid-views.md)
### DNS Overwite
Specific to other features.

View File

@@ -0,0 +1,28 @@
# Authentication
Performed via [synapse with REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth/blob/master/README.md)
Point the `endpoint` to mxisd internal IP on port 8090
## Overview
```
Backends
Client +------+
| +-------------------------+ +--> | LDAP |
| +---------------+ /_matrix/identity | mxisd | | +------+
+-> | Reverse proxy | >------------------+ | | |
+--|------------+ | | | | +--------+
| +-----> Check wiht backends >------+--> | SQL DB |
Login request | | | | +--------+
| | | | | |
| +--------------------------+ | +-----|-------------------+ +--> Others
+-> | Homeserver | | |
| | | |
| - Validate credentials >----+ |
| Using REST auth module | |
| | |
| - Auto-provision <-------------------<+
| user profiles | If valid credentials and supported by backend
+--------------------------+
```
## Profile auto-fill
To be documented

View File

@@ -0,0 +1 @@
To be documented

View File

@@ -0,0 +1,167 @@
# User Directory
- [Description](#description)
- [Overview](#overview)
- [Requirements](#requirements)
- [Configuration](#configuration)
- [Reverse Proxy](#reverse-proxy)
- [DNS Overwrite](#dns-overwrite)
- [Backends](#backends)
- [LDAP](#ldap)
- [SQL](#sql)
- [REST](#rest)
## Description
This feature allows you to search for existing and/or potential users that are already present in your Identity backend
or that already share a room with you on the Homeserver.
Without any integration, synapse:
- Only search within the users **already** known to you
- Only search on the Display Name and the Matrix ID
With mxisd integration, you can:
- Search on Matrix ID, Display name and 3PIDs (Email, phone numbers) of any users already in your configured backend
- Search for users which you are not in contact with yet. Super useful for corporations who want to give Matrix access
internally, so users can just find themselves **prior** to having any common room(s)
- Use any attribute of your backend to extend the search!
- Include your homeserver search results to those found by mxisd (default behaviour, no configuration required)
By integrating mxisd, you get the default behaviour with all the extras, ensuring your users will always find each other.
## Overview
This is performed by intercepting the Homeserver endpoint `/_matrix/client/r0/user_directory/search` like so:
```
+----------------------------------------------+
Client --> | Reverse proxy Step 2
| Step 1 +-------------------------+
| /_matrix/client/r0/user_directory/search ----------> | | Search in +---------+
| /\ | mxisd - Identity server | -----------> | Backend |
| /_matrix/* \----------------------------- | | all users +---------+
| | Step 4: Send back merged results +-------------------------+
+ | |
| Step 3
| |
| +------------+ Search in known users
\--> | Homeserver | <----------------------------------------/
+------------+ /_matrix/client/r0/user_directory/search
```
Steps:
1. The intercepted request is directly sent to mxisd instead of the Homeserver.
2. Enabled backends are queried for any match on the search value sent by the client.
3. The Homeserver, from which the request was intercepted, is queried using the same request as the client.
Its address is resolved using the DNS Overwrite feature to reach its internal address on a non-encrypted port.
4. Results from backends and the Homeserver are merged together and sent back to the client, believing it was the HS
which directly answered the request.
## Requirements
- Reverse proxy setup, which you should already have in place if you use mxisd
- Compatible backends:
- LDAP
- SQL
- REST
## Configuration
### Reverse Proxy
Apache2 configuration to put under the relevant virtual domain:
```
ProxyPreserveHost on
ProxyPass /_matrix/identity/ http://mxisdInternalIpAddress:8090/_matrix/identity/
ProxyPass /_matrix/client/r0/user_directory/ http://mxisdInternalIpAddress:8090/_matrix/client/r0/user_directory/
ProxyPass /_matrix/ http://HomeserverInternalIpAddress:8008/_matrix/
```
`ProxyPreserveHost` or equivalent must be enabled to detect to which Homeserver mxisd should talk to when building
results.
### DNS Overwrite
Just like you need to configure a reverse proxy to send client requests to mxisd, you also need to configure mxisd with
the internal IP of the Homeserver so it can talk to it directly to integrate its directory search.
To do so, use the following configuration:
```
dns.overwrite.homeserver.client:
- name: 'example.org'
value: 'http://localhost:8008'
```
`name` must be the hostname of the URL that clients use when connecting to the Homeserver.
In case the hostname is the same as your Matrix domain, you can use `${matrix.domain}` to auto-populate the value using
the `matrix.domain` configuration option and avoid duplicating it.
`value` is the base intenral URL of the Homeserver, without any `/_matrix/..` or trailing `/`.
### Backends
#### LDAP
To ensure Directory feature works, here's how the LDAP configuration should look like:
```
ldap:
enabled: false
filter: '(memberOf=CN=Matrix Users,OU=Groups,DC=example,DC=org)'
connection:
host: 'ldapIpOrDomain'
bindDn: 'CN=Matrix Identity Server,OU=Accounts,DC=example,DC=org'
bindPassword: 'mxisd'
baseDn: 'OU=Accounts,DC=example,DC=org'
attribute:
uid:
type: 'uid'
value: 'userPrincipalName'
name: 'displayName'
threepid:
email:
- 'mailPrimaryAddress'
- 'mail'
- 'otherMailbox'
msisdn:
- 'telephoneNumber'
- 'mobile'
- 'homePhone'
- 'otherTelephone'
- 'otherMobile'
- 'otherHomePhone'
directory:
attribute:
other:
- 'employeeNumber'
- 'someOtherAttribute'
```
Only include the `attribute` sub-sections if you would like to set another value. Else, it is best not to include them
to inherit the default values.
If you would like to include an attribute which is not a display name or a 3PID, you can use the
`directory.attribute.other` to list any extra attributes you want included in searches. If you do not want to include
any extra attribute, that configuration section can be skipped.
#### SQL
If you plan to integrate directory search directly with synapse, use the `synapseSql` provider, based on the following
config:
```
synapseSql:
enabled: true
type: <database ID>
connection: '<connection info>'
```
`type` and `connection`, including any other configuration item, follow the same values as the regular [SQL backend](../backends/sql.md).
---
For the regular SQL backend, the following configuration items are available:
```
sql:
directory:
enabled: true
query:
name:
type: 'localpart'
value: 'SELECT idColumn, displayNameColumn FROM table WHERE displayNameColumn LIKE ?'
threepid:
type: 'localpart'
value: 'SELECT idColumn, displayNameColumn FROM table WHERE threepidColumn LIKE ?'
```
For each query, `type` can be used to tell mxisd how to process the ID column:
- `localpart` will append the `matrix.domain` to it
- `mxid` will use the ID as-is. If it is not a valid Matrix ID, the search will fail.
`value` is the SQL query and must return two columns:
- The first being the User ID
- The second being its display name
#### REST
See the [dedicated document](../backends/rest.md)

View File

@@ -0,0 +1,33 @@
# Identity service Federation
## Overview
```
+-------------------+ +-------------> +----------+
| mxisd | | | Backends |
| | | +------> +----------+
| | | |
| Invites / Lookups | | |
Federated | +--------+ | | | +-------------------+
Identity ---->| Remote |>-----------+ +------> | Remote Federated |
Server | +--------+ | | | mxisd servers |
| | | +-------------------+
| +--------+ | |
Homeserver --->| Local |>------------------+
and clients | +--------+ | | +--------------------------+
+-------------------+ +------> | Central Identity service |
| Matrix.org / Vector.im |
+--------------------------+
```
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
## Configuration
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.
```
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 which is what you get in a regular setup with a reverse proxy from 443 to TCP 8090 of mxisd.

View File

@@ -0,0 +1,3 @@
To be documented.
Implementation of the [Matrix Identity service API](https://matrix.org/docs/spec/identity_service/unstable.html)

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

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

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

@@ -0,0 +1,14 @@
# Docker
## Fetch
Pull the latest stable image:
```
docker pull kamax/mxisd
```
## Run
Run it (adapt volume paths to your host):
```
docker run --rm -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t kamax/mxisd
```
For more info, including the list of possible tags, see [the public repository](https://hub.docker.com/r/kamax/mxisd/)

View File

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

View File

@@ -6,8 +6,10 @@
- [Session scope](#session-scope)
- [Notifications](#notifications)
- [Email](#email)
- [Phone numbers](#msisdn-phone-numbers)
- [Usage](#usage)
- [Configuration](#configuration)
- [Web views](#web-views)
- [Scenarios](#scenarios)
- [Default](#default)
- [Local sessions only](#local-sessions-only)
@@ -15,7 +17,7 @@
- [Sessions disabled](#sessions-disabled)
## Overview
When adding an email, a phone number or any other kind of 3PID (Third-Party Identifier),
When adding an email, a phone number or any other kind of 3PID (Third-Party Identifier) in a Matrix client,
the identity server is called to validate the 3PID.
Once this 3PID is validated, the Homeserver will publish the user Matrix ID on the Identity Server and
@@ -97,11 +99,17 @@ Built-in generators and connectors for supported 3PID types:
### Email
Generators:
- Template
- [Template](../threepids/notifications/template-generator.md)
Connectors:
- SMTP
- [SMTP](../threepids/medium/email/smtp-connector.md)
#### MSISDN (Phone numbers)
Generators:
- [Template](../threepids/notifications/template-generator.md)
Connectors:
- [Twilio](../threepids/medium/msisdn/twilio-connector.md) with SMS
## Usage
### Configuration
@@ -114,29 +122,22 @@ Please refer to the full example config file to see which keys are mandatory and
matrix:
identity:
servers:
root: # Not to be included in config! Already present in default config!
- 'https://matrix.org'
configExample: # Not to be included in config! Already present in default config!
- 'https://example.org'
threepid:
medium:
email:
connector: 'smtp'
generator: 'template'
connector: 'example1' # Not to be included in config! Already present in default config!
generator: 'example2' # Not to be included in config! Already present in default config!
connectors:
smtp:
host: ''
port: 587
tls: 1
login: ''
password: ''
example1:
generators:
template: # Not to be included in config! Already present in default config!
invite: 'classpath:email/invite-template.eml'
session:
validation:
local: 'classpath:email/validate-local-template.eml'
remote: 'classpath:email/validate-remote-template.eml'
example1:
key: "value"
example2:
key: "value"
session:
policy:
@@ -157,7 +158,7 @@ session:
```
`matrix.identity.servers` is the namespace to configure arbitrary list of Identity servers with a label as parent key.
In the above example, the list with label `configExample` contains a single server entry pointing to `https://matrix.org`.
In the above example, the list with label `configExample` contains a single server entry pointing to `https://example.org`.
**NOTE:** The server list is set to `root` by default and should typically NOT be included in your config.
@@ -181,14 +182,8 @@ ID for each generator.
- `connector` is given the ID of the connector to be used at runtime.
- `generator` is given the ID of the generator to be used at runtime.
In the above example, emails notifications are generated by the `template` module and sent with the `smtp` module.
mxisd comes with the following IDs built-in:
**Connectors**
- `smtp` for a basic SMTP connector, attempting STARTLS by default.
**Generators**
- `template`, loading content from template files, using built-in mxisd templates by default.
In the above example, emails notifications are generated by the `example2` module and sent with the `example1` module.
By default, `template` is used as generator and `smtp` as connector.
---
@@ -207,6 +202,14 @@ Each scope is divided into three parts:
If both `toLocal` and `toRemote` are enabled, the user will be offered to initiate a remote session once their 3PID
locally validated.
### Web views
Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submited.
If the session or token is invalid, an error page is displayed.
Workflow pages are also available for the remote 3PID session process.
See [the dedicated document](3pid-views.md)
on how to configure/customize/brand those pages to your liking.
### Scenarios
It is important to keep in mind that mxisd does not create bindings, irrelevant if a user added a 3PID to their profile.
Instead, when queried for bindings, mxisd will query Identity backends which are responsible to store this kind of information.

View File

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

View File

@@ -0,0 +1,15 @@
# SMS notifications - Twilio connector
Connector ID: `twilio`
Example configuration:
```
threepid:
medium:
msisdn:
connectors:
twilio:
accountSid: 'myAccountSid'
authToken: 'myAuthToken'
number: '+123456789'
```

View File

@@ -0,0 +1,66 @@
# Basic Notification handler
Basic notification handler which uses two components:
- Content generator, to produce the notifications
- Connectors to send the notification content
This handler can be used with the 3PID types:
- `email`
- `msisdn` (Phone numbers)
## Generators
- [Template](template-generator.md)
## Connectors
- Email
- [SMTP](../medium/email/smtp-connector.md)
- SMS
- [Twilio](../medium/msisdn/twilio-connector.md)
## Configuration
Enabled by default or with:
```
notification:
handler:
email: 'raw'
```
**WARNING:** Will be consolidated soon, prone to breaking changes.
Structure and default values:
```
threepid:
medium:
email:
identity:
from: ''
name: ''
connector: 'smtp'
generator: 'template'
connectors:
smtp:
host: ''
port: 587
tls: 1
login: ''
password: ''
generators:
template:
invite: 'classpath:threepids/email/invite-template.eml'
session:
validation:
local: 'classpath:threepids/email/validate-local-template.eml'
remote: 'classpath:threepids/email/validate-remote-template.eml'
msisdn:
connector: 'twilio'
generator: 'template'
connectors:
twilio:
accountSid: ''
authToken: ''
number: ''
generators:
template:
invite: 'classpath:threepids/sms/invite-template.txt'
session:
validation:
local: 'classpath:threepids/sms/validate-local-template.txt'
remote: 'classpath:threepids/sms/validate-remote-template.txt'
```

View File

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

View File

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

View File

@@ -1,193 +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.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

@@ -1,191 +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.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

@@ -1,57 +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.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

@@ -1,169 +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.backend.ldap
import io.kamax.mxisd.config.MatrixConfig
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.apache.directory.api.ldap.model.cursor.CursorLdapReferralException
import org.apache.directory.api.ldap.model.cursor.EntryCursor
import org.apache.directory.api.ldap.model.entry.Attribute
import org.apache.directory.api.ldap.model.entry.Entry
import org.apache.directory.api.ldap.model.message.SearchScope
import org.apache.directory.ldap.client.api.LdapConnection
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
@Component
class LdapThreePidProvider extends LdapGenericBackend implements IThreePidProvider {
public static final String UID = "uid"
public static final String MATRIX_ID = "mxid"
private Logger log = LoggerFactory.getLogger(LdapThreePidProvider.class)
@Autowired
private MatrixConfig mxCfg
@Override
boolean isEnabled() {
return getCfg().isEnabled()
}
private String getUidAttribute() {
return getCfg().getAttribute().getUid().getValue();
}
@Override
boolean isLocal() {
return true
}
@Override
int getPriority() {
return 20
}
Optional<String> lookup(LdapConnection conn, String medium, String value) {
String uidAttribute = getUidAttribute()
Optional<String> queryOpt = getCfg().getIdentity().getQuery(medium)
if (!queryOpt.isPresent()) {
log.warn("{} is not a configured 3PID type for LDAP lookup", medium)
return Optional.empty()
}
String searchQuery = queryOpt.get().replaceAll("%3pid", value)
EntryCursor cursor = conn.search(getCfg().getConn().getBaseDn(), searchQuery, SearchScope.SUBTREE, uidAttribute)
try {
while (cursor.next()) {
Entry entry = cursor.get()
log.info("Found possible match, DN: {}", entry.getDn().getName())
Attribute attribute = entry.get(uidAttribute)
if (attribute == null) {
log.info("DN {}: no attribute {}, skpping", entry.getDn(), getCfg().getAttribute())
continue
}
String data = attribute.get().toString()
if (data.length() < 1) {
log.info("DN {}: empty attribute {}, skipping", getCfg().getAttribute())
continue
}
StringBuilder matrixId = new StringBuilder()
// TODO Should we turn this block into a map of functions?
String uidType = getCfg().getAttribute().getUid().getType()
if (StringUtils.equals(UID, uidType)) {
matrixId.append("@").append(data).append(":").append(mxCfg.getDomain())
} else if (StringUtils.equals(MATRIX_ID, uidType)) {
matrixId.append(data)
} else {
log.warn("Bind was found but type {} is not supported", uidType)
continue
}
log.info("DN {} is a valid match", entry.getDn().getName())
return Optional.of(matrixId.toString())
}
} catch (CursorLdapReferralException e) {
log.warn("3PID {} is only available via referral, skipping", value)
} finally {
cursor.close()
}
return Optional.empty()
}
@Override
Optional<SingleLookupReply> find(SingleLookupRequest request) {
log.info("Performing LDAP lookup ${request.getThreePid()} of type ${request.getType()}")
LdapConnection conn = getConn()
try {
bind(conn)
Optional<String> mxid = lookup(conn, request.getType(), request.getThreePid())
if (mxid.isPresent()) {
return Optional.of(new SingleLookupReply(request, mxid.get()));
}
} finally {
conn.close()
}
log.info("No match found")
return Optional.empty()
}
@Override
List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
log.info("Looking up {} mappings", mappings.size())
List<ThreePidMapping> mappingsFound = new ArrayList<>()
LdapConnection conn = getConn()
try {
bind(conn)
for (ThreePidMapping mapping : mappings) {
try {
Optional<String> mxid = lookup(conn, mapping.getMedium(), mapping.getValue())
if (mxid.isPresent()) {
mapping.setMxid(mxid.get())
mappingsFound.add(mapping)
}
} catch (IllegalArgumentException e) {
log.warn("{} is not a supported 3PID type for LDAP lookup", mapping.getMedium())
}
}
} finally {
conn.close()
}
return mappingsFound
}
}

View File

@@ -1,83 +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.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.InitializingBean
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
@Configuration
@ConfigurationProperties(prefix = "lookup.recursive.bridge")
class RecursiveLookupBridgeConfig implements InitializingBean {
private Logger log = LoggerFactory.getLogger(RecursiveLookupBridgeConfig.class)
private boolean enabled
private boolean recursiveOnly
private String server
private Map<String, String> mappings = new HashMap<>()
boolean getEnabled() {
return enabled
}
void setEnabled(boolean enabled) {
this.enabled = enabled
}
boolean getRecursiveOnly() {
return recursiveOnly
}
void setRecursiveOnly(boolean recursiveOnly) {
this.recursiveOnly = recursiveOnly
}
String getServer() {
return server
}
void setServer(String server) {
this.server = server
}
Map<String, String> getMappings() {
return mappings
}
void setMappings(Map<String, String> mappings) {
this.mappings = mappings
}
@Override
void afterPropertiesSet() throws Exception {
log.info("--- Bridge integration lookups config ---")
log.info("Enabled: {}", getEnabled())
if (getEnabled()) {
log.info("Recursive only: {}", getRecursiveOnly())
log.info("Fallback Server: {}", getServer())
log.info("Mappings: {}", mappings.size())
}
}
}

View File

@@ -1,129 +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.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

@@ -1,21 +0,0 @@
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

@@ -1,96 +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.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

@@ -1,121 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.controller.v1
import com.google.gson.Gson
import com.google.gson.JsonObject
import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson
import io.kamax.mxisd.lookup.*
import io.kamax.mxisd.lookup.strategy.LookupStrategy
import io.kamax.mxisd.signature.SignatureManager
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.CrossOrigin
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import javax.servlet.http.HttpServletRequest
import static org.springframework.web.bind.annotation.RequestMethod.GET
import static org.springframework.web.bind.annotation.RequestMethod.POST
@RestController
@CrossOrigin
@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
class MappingController {
private Logger log = LoggerFactory.getLogger(MappingController.class)
private JsonSlurper json = new JsonSlurper()
private Gson gson = new Gson()
@Autowired
private LookupStrategy strategy
@Autowired
private SignatureManager signMgr
private void setRequesterInfo(ALookupRequest lookupReq, HttpServletRequest req) {
lookupReq.setRequester(req.getRemoteAddr())
String xff = req.getHeader("X-FORWARDED-FOR")
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()
setRequesterInfo(lookupRequest, request)
lookupRequest.setType(medium)
lookupRequest.setThreePid(address)
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()) {
log.info("No mapping was found, return empty JSON object")
return JsonOutput.toJson([])
}
SingleLookupReply lookup = lookupOpt.get()
if (lookup.isSigned()) {
log.info("Lookup is already signed, sending as-is")
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 gson.toJson(obj)
}
}
@RequestMapping(value = "/bulk_lookup", method = POST)
String bulkLookup(HttpServletRequest request) {
BulkLookupRequest lookupRequest = new BulkLookupRequest()
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())
List<ThreePidMapping> mappings = new ArrayList<>()
for (List<String> mappingRaw : input.getThreepids()) {
ThreePidMapping mapping = new ThreePidMapping()
mapping.setMedium(mappingRaw.get(0))
mapping.setValue(mappingRaw.get(1))
mappings.add(mapping)
}
lookupRequest.setMappings(mappings)
ClientBulkLookupAnswer answer = new ClientBulkLookupAnswer()
answer.addAll(strategy.find(lookupRequest))
return JsonOutput.toJson(answer)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,154 +0,0 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.threepid.notification.email;
import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
import io.kamax.mxisd.config.threepid.medium.EmailTemplateConfig;
import io.kamax.mxisd.controller.v1.IdentityAPIv1;
import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession;
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 java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@Component
public class EmailNotificationGenerator implements IEmailNotificationGenerator {
private Logger log = LoggerFactory.getLogger(EmailNotificationGenerator.class);
private EmailConfig cfg;
private EmailTemplateConfig templateCfg;
private MatrixConfig mxCfg;
private ServerConfig srvCfg;
@Autowired
private ApplicationContext app;
@Autowired
public EmailNotificationGenerator(EmailTemplateConfig templateCfg, EmailConfig cfg, MatrixConfig mxCfg, ServerConfig srvCfg) {
this.cfg = cfg;
this.templateCfg = templateCfg;
this.mxCfg = mxCfg;
this.srvCfg = srvCfg;
}
@Override
public String getId() {
return "template";
}
private String getTemplateContent(String location) {
try {
InputStream is = StringUtils.startsWith(location, "classpath:") ?
app.getResource(location).getInputStream() : new FileInputStream(location);
return IOUtils.toString(is, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new InternalServerError("Unable to read template content at " + location + ": " + e.getMessage());
}
}
private String populateCommon(String content, ThreePid recipient) {
String domainPretty = WordUtils.capitalizeFully(mxCfg.getDomain());
content = content.replace("%DOMAIN%", mxCfg.getDomain());
content = content.replace("%DOMAIN_PRETTY%", domainPretty);
content = content.replace("%FROM_EMAIL%", cfg.getIdentity().getFrom());
content = content.replace("%FROM_NAME%", cfg.getIdentity().getName());
content = content.replace("%RECIPIENT_MEDIUM%", recipient.getMedium());
content = content.replace("%RECIPIENT_ADDRESS%", recipient.getAddress());
return content;
}
private String getTemplateAndPopulate(String location, ThreePid recipient) {
return populateCommon(getTemplateContent(location), recipient);
}
@Override
public String getForInvite(IThreePidInviteReply invite) {
ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress());
String templateBody = getTemplateAndPopulate(templateCfg.getInvite(), tpid);
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());
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%", tpid.getMedium());
templateBody = templateBody.replace("%INVITE_ADDRESS%", tpid.getAddress());
templateBody = templateBody.replace("%ROOM_ID%", invite.getInvite().getRoomId());
templateBody = templateBody.replace("%ROOM_NAME%", roomName);
templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
return templateBody;
}
@Override
public String getForValidation(IThreePidSession session) {
log.info("Generating notification content for 3PID Session validation");
String templateBody = getTemplateAndPopulate(templateCfg.getSession().getValidation().getLocal(), session.getThreePid());
// FIXME should have a global link builder, most likely in the SDK?
String validationLink = srvCfg.getPublicUrl() + IdentityAPIv1.BASE +
"/validate/" + session.getThreePid().getMedium() +
"/submitToken?sid=" + session.getId() + "&client_secret=" + session.getSecret() +
"&token=" + session.getToken();
templateBody = templateBody.replace("%VALIDATION_LINK%", validationLink);
templateBody = templateBody.replace("%VALIDATION_TOKEN%", session.getToken());
return templateBody;
}
@Override
public String getForRemoteValidation(IThreePidSession session) {
log.info("Generating notification content for remote-only 3PID session");
String templateBody = getTemplateAndPopulate(templateCfg.getSession().getValidation().getRemote(), session.getThreePid());
// FIXME should have a global link builder, most likely in the SDK?
String validationLink = srvCfg.getPublicUrl() + IdentityAPIv1.BASE +
"/validate/" + session.getThreePid().getMedium() +
"/submitToken?sid=" + session.getId() + "&client_secret=" + session.getSecret() +
"&token=" + session.getToken();
templateBody = templateBody.replace("%VALIDATION_LINK%", validationLink);
templateBody = templateBody.replace("%VALIDATION_TOKEN%", session.getToken());
return templateBody;
}
}

View File

@@ -1,87 +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.threepid.notification.email;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
import io.kamax.mxisd.exception.ConfigurationException;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.notification.INotificationHandler;
import io.kamax.mxisd.threepid.connector.email.IEmailConnector;
import io.kamax.mxisd.threepid.session.IThreePidSession;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class EmailNotificationHandler implements INotificationHandler {
private EmailConfig cfg;
private IEmailNotificationGenerator generator;
private IEmailConnector connector;
@Autowired
public EmailNotificationHandler(EmailConfig cfg, List<IEmailNotificationGenerator> generators, List<IEmailConnector> connectors) {
this.cfg = cfg;
generator = generators.stream()
.filter(o -> StringUtils.equals(cfg.getGenerator(), o.getId()))
.findFirst()
.orElseThrow(() -> new ConfigurationException("Email notification generator [" + cfg.getGenerator() + "] could not be found"));
connector = connectors.stream()
.filter(o -> StringUtils.equals(cfg.getConnector(), o.getId()))
.findFirst()
.orElseThrow(() -> new ConfigurationException("Email sender connector [" + cfg.getConnector() + "] could not be found"));
}
@Override
public String getMedium() {
return ThreePidMedium.Email.getId();
}
private void send(String recipient, String content) {
connector.send(
cfg.getIdentity().getFrom(),
cfg.getIdentity().getName(),
recipient,
content
);
}
@Override
public void sendForInvite(IThreePidInviteReply invite) {
send(invite.getInvite().getAddress(), generator.getForInvite(invite));
}
@Override
public void sendForValidation(IThreePidSession session) {
send(session.getThreePid().getAddress(), generator.getForValidation(session));
}
@Override
public void sendForRemoteValidation(IThreePidSession session) {
send(session.getThreePid().getAddress(), generator.getForRemoteValidation(session));
}
}

View File

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

View File

@@ -48,4 +48,22 @@ public class ThreePid {
return getMedium() + ":" + getAddress();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ThreePid threePid = (ThreePid) o;
if (!medium.equals(threePid.medium)) return false;
return address.equals(threePid.address);
}
@Override
public int hashCode() {
int result = medium.hashCode();
result = 31 * result + address.hashCode();
return result;
}
}

View File

@@ -71,14 +71,14 @@ public class AuthManager {
continue;
}
UserAuthResult authResult = new UserAuthResult().success(mxId, result.getProfile().getDisplayName());
UserAuthResult authResult = new UserAuthResult().success(result.getProfile().getDisplayName());
for (ThreePid pid : result.getProfile().getThreePids()) {
authResult.withThreePid(pid.getMedium(), pid.getAddress());
}
log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName());
for (ThreePid pid : authResult.getThreePids()) {
log.info("Processing {} for {}", pid, id);
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, authResult.getMxid()));
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, mxId));
}
invMgr.lookupMappingsForInvites();

View File

@@ -20,31 +20,30 @@
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;
import java.util.HashSet;
import java.util.Set;
public class UserAuthResult {
private boolean success;
private String mxid;
private String displayName;
private List<ThreePid> threePids = new ArrayList<>();
private String photo;
private Set<ThreePid> threePids = new HashSet<>();
public UserAuthResult failure() {
success = false;
mxid = null;
displayName = null;
photo = null;
threePids.clear();
return this;
}
public UserAuthResult success(String mxid, String displayName) {
public UserAuthResult success(String displayName) {
setSuccess(true);
setMxid(mxid);
setDisplayName(displayName);
return this;
@@ -58,14 +57,6 @@ public class UserAuthResult {
this.success = success;
}
public String getMxid() {
return mxid;
}
public void setMxid(String mxid) {
this.mxid = mxid;
}
public String getDisplayName() {
return displayName;
}
@@ -74,8 +65,12 @@ public class UserAuthResult {
this.displayName = displayName;
}
public UserAuthResult withThreePid(ThreePidMedium medium, String address) {
return withThreePid(medium.getId(), address);
public String getPhoto() {
return photo;
}
public void setPhoto(String photo) {
this.photo = photo;
}
public UserAuthResult withThreePid(String medium, String address) {
@@ -84,8 +79,8 @@ public class UserAuthResult {
return this;
}
public List<ThreePid> getThreePids() {
return Collections.unmodifiableList(threePids);
public Set<ThreePid> getThreePids() {
return Collections.unmodifiableSet(threePids);
}
}

View File

@@ -24,21 +24,21 @@ import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.UserID;
import io.kamax.mxisd.UserIdType;
import java.util.ArrayList;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
public class BackendAuthResult {
public static class BackendAuthProfile {
private String displayName;
private List<ThreePid> threePids = new ArrayList<>();
private Set<ThreePid> threePids = new HashSet<>();
public String getDisplayName() {
return displayName;
}
public List<ThreePid> getThreePids() {
public Set<ThreePid> getThreePids() {
return threePids;
}
}
@@ -49,20 +49,27 @@ public class BackendAuthResult {
return r;
}
public void fail() {
success = false;
}
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;
r.succeed(id, type, displayName);
return r;
}
public void succeed(String id, String type, String displayName) {
this.success = true;
this.id = new UserID(type, id);
this.profile = new BackendAuthProfile();
this.profile.displayName = displayName;
}
private Boolean success;
private UserID id;
private BackendAuthProfile profile = new BackendAuthProfile();

View File

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

View File

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

View File

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

View File

@@ -24,6 +24,8 @@ import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.LdapConfig;
import org.apache.commons.lang.StringUtils;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
@@ -35,6 +37,7 @@ import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
@@ -44,8 +47,9 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
private Logger log = LoggerFactory.getLogger(LdapAuthProvider.class);
private String getUidAttribute() {
return getCfg().getAttribute().getUid().getValue();
@Autowired
public LdapAuthProvider(LdapConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
@Override
@@ -57,37 +61,26 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
log.info("Performing auth for {}", mxid);
LdapConnection conn = getConn();
try {
try (LdapConnection conn = getConn()) {
bind(conn);
String uidType = getCfg().getAttribute().getUid().getType();
String userFilterValue = StringUtils.equals(LdapThreePidProvider.UID, uidType) ? mxid.getLocalPart() : mxid.getId();
String uidType = getAt().getUid().getType();
String userFilterValue = StringUtils.equals(LdapGenericBackend.UID, uidType) ? mxid.getLocalPart() : mxid.getId();
if (StringUtils.isBlank(userFilterValue)) {
log.warn("Username is empty, failing auth");
return BackendAuthResult.failure();
}
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 {
String userFilter = "(" + getUidAtt() + "=" + userFilterValue + ")";
userFilter = buildWithFilter(userFilter, getCfg().getAuth().getFilter());
try (EntryCursor cursor = conn.search(getBaseDn(), userFilter, SearchScope.SUBTREE, getUidAtt(), getAt().getName())) {
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());
if (!getAttribute(entry, getUidAtt()).isPresent()) {
continue;
}
@@ -99,7 +92,7 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
return BackendAuthResult.failure();
}
Attribute nameAttribute = entry.get(getCfg().getAttribute().getName());
Attribute nameAttribute = entry.get(getAt().getName());
String name = nameAttribute != null ? nameAttribute.get().toString() : null;
log.info("Authentication successful for {}", entry.getDn().getName());
@@ -110,20 +103,12 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
}
} 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,120 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.ldap;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.LdapAttributeConfig;
import io.kamax.mxisd.config.ldap.LdapConfig;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.directory.IDirectoryProvider;
import io.kamax.mxisd.exception.InternalServerError;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Component
public class LdapDirectoryProvider extends LdapGenericBackend implements IDirectoryProvider {
private Logger log = LoggerFactory.getLogger(LdapDirectoryProvider.class);
@Autowired
public LdapDirectoryProvider(LdapConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
@Override
public boolean isEnabled() {
return getCfg().isEnabled();
}
protected UserDirectorySearchResult search(String query, List<String> attributes) {
UserDirectorySearchResult result = new UserDirectorySearchResult();
result.setLimited(false);
try (LdapConnection conn = getConn()) {
bind(conn);
LdapAttributeConfig atCfg = getCfg().getAttribute();
attributes = new ArrayList<>(attributes);
attributes.add(getUidAtt());
String[] attArray = new String[attributes.size()];
attributes.toArray(attArray);
String searchQuery = buildOrQueryWithFilter(getCfg().getDirectory().getFilter(), "*" + query + "*", attArray);
log.debug("Query: {}", searchQuery);
try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, attArray)) {
while (cursor.next()) {
Entry entry = cursor.get();
log.info("Found possible match, DN: {}", entry.getDn().getName());
getAttribute(entry, getUidAtt()).ifPresent(uid -> {
log.info("DN {} is a valid match", entry.getDn().getName());
try {
UserDirectorySearchResult.Result entryResult = new UserDirectorySearchResult.Result();
entryResult.setUserId(buildMatrixIdFromUid(uid));
getAttribute(entry, atCfg.getName()).ifPresent(entryResult::setDisplayName);
result.addResult(entryResult);
} catch (IllegalArgumentException e) {
log.warn("Bind was found but type {} is not supported", atCfg.getUid().getType());
}
});
}
}
} catch (CursorLdapReferralException e) {
log.warn("An entry is only available via referral, skipping");
} catch (IOException | LdapException | CursorException e) {
throw new InternalServerError(e);
}
return result;
}
@Override
public UserDirectorySearchResult searchByDisplayName(String query) {
log.info("Performing LDAP directory search on display name using '{}'", query);
List<String> attributes = new ArrayList<>();
attributes.add(getAt().getName());
attributes.addAll(getCfg().getDirectory().getAttribute().getOther());
return search(query, attributes);
}
@Override
public UserDirectorySearchResult searchBy3pid(String query) {
log.info("Performing LDAP directory search on 3PIDs using '{}'", query);
List<String> attributes = new ArrayList<>();
attributes.add(getAt().getName());
getCfg().getAttribute().getThreepid().forEach((k, v) -> attributes.addAll(v));
return search(query, attributes);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,7 +33,10 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -43,11 +46,17 @@ public class SqlThreePidProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(SqlThreePidProvider.class);
@Autowired
private SqlProviderConfig cfg;
private MatrixConfig mxCfg;
private SqlConnectionPool pool;
@Autowired
private SqlProviderConfig cfg;
public SqlThreePidProvider(SqlProviderConfig cfg, MatrixConfig mxCfg) {
this.cfg = cfg;
this.pool = new SqlConnectionPool(cfg);
this.mxCfg = mxCfg;
}
@Override
public boolean isEnabled() {
@@ -64,20 +73,17 @@ public class SqlThreePidProvider implements IThreePidProvider {
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)) {
try (Connection conn = pool.get()) {
try (PreparedStatement stmt = conn.prepareStatement(stmtSql)) {
stmt.setString(1, request.getType().toLowerCase());
stmt.setString(2, request.getThreePid().toLowerCase());
ResultSet rSet = stmt.executeQuery();
try (ResultSet rSet = stmt.executeQuery()) {
while (rSet.next()) {
String uid = rSet.getString("uid");
log.info("Found match: {}", uid);
@@ -95,6 +101,8 @@ public class SqlThreePidProvider implements IThreePidProvider {
log.info("No match found in SQL");
return Optional.empty();
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
}

View File

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

View File

@@ -0,0 +1,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.config;
import com.google.gson.Gson;
import io.kamax.mxisd.util.GsonUtil;
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.util.ArrayList;
import java.util.List;
@Configuration
@ConfigurationProperties("dns.overwrite")
public class DnsOverwriteConfig {
private Logger log = LoggerFactory.getLogger(DnsOverwriteConfig.class);
public static class Entry {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
public static class Type {
List<Entry> client = new ArrayList<>();
List<Entry> federation = new ArrayList<>();
public List<Entry> getClient() {
return client;
}
public void setClient(List<Entry> client) {
this.client = client;
}
public List<Entry> getFederation() {
return federation;
}
public void setFederation(List<Entry> federation) {
this.federation = federation;
}
}
private Type homeserver = new Type();
public Type getHomeserver() {
return homeserver;
}
public void setHomeserver(Type homeserver) {
this.homeserver = homeserver;
}
@PostConstruct
public void build() {
Gson gson = GsonUtil.build();
log.info("--- DNS Overwrite config ---");
log.info("Homeserver:");
log.info("\tClient: {}", gson.toJson(getHomeserver().getClient()));
log.info("\tFederation: {}", gson.toJson(getHomeserver().getFederation()));
}
}

View File

@@ -71,7 +71,7 @@ public class FirebaseConfig {
}
@PostConstruct
private void postConstruct() {
public void build() {
log.info("--- Firebase configuration ---");
log.info("Enabled: {}", isEnabled());
if (isEnabled()) {
@@ -82,20 +82,12 @@ public class FirebaseConfig {
@Bean
public AuthenticatorProvider getAuthProvider() {
if (!enabled) {
return new GoogleFirebaseAuthenticator(false);
} else {
return new GoogleFirebaseAuthenticator(credentials, database, mxCfg.getDomain());
}
return new GoogleFirebaseAuthenticator(enabled, credentials, database);
}
@Bean
public IThreePidProvider getLookupProvider() {
if (!enabled) {
return new GoogleFirebaseProvider(false);
} else {
return new GoogleFirebaseProvider(credentials, database, mxCfg.getDomain());
}
return new GoogleFirebaseProvider(enabled, credentials, database, mxCfg.getDomain());
}
}

View File

@@ -18,23 +18,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config
package io.kamax.mxisd.config;
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
@ConfigurationProperties(prefix = "forward")
class ForwardConfig {
public class ForwardConfig {
private List<String> servers = new ArrayList<>()
private List<String> servers = new ArrayList<>();
List<String> getServers() {
return servers
public List<String> getServers() {
return servers;
}
void setServers(List<String> servers) {
this.servers = servers
public void setServers(List<String> servers) {
this.servers = servers;
}
}

View File

@@ -18,32 +18,33 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config
package io.kamax.mxisd.config;
import io.kamax.mxisd.exception.ConfigurationException
import org.apache.commons.lang.StringUtils
import org.springframework.beans.factory.InitializingBean
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
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(prefix = "key")
class KeyConfig implements InitializingBean {
public class KeyConfig {
private String path
private String path;
void setPath(String path) {
this.path = path
public void setPath(String path) {
this.path = path;
}
String getPath() {
return path
public String getPath() {
return path;
}
@Override
void afterPropertiesSet() throws Exception {
@PostConstruct
public void build() {
if (StringUtils.isBlank(getPath())) {
throw new ConfigurationException("key.path")
throw new ConfigurationException("key.path");
}
}

View File

@@ -0,0 +1,86 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config;
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.util.HashMap;
import java.util.Map;
@Configuration
@ConfigurationProperties(prefix = "lookup.recursive.bridge")
public class RecursiveLookupBridgeConfig {
private Logger log = LoggerFactory.getLogger(RecursiveLookupBridgeConfig.class);
private boolean enabled;
private boolean recursiveOnly;
private String server;
private Map<String, String> mappings = new HashMap<>();
public boolean getEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean getRecursiveOnly() {
return recursiveOnly;
}
public void setRecursiveOnly(boolean recursiveOnly) {
this.recursiveOnly = recursiveOnly;
}
public String getServer() {
return server;
}
public void setServer(String server) {
this.server = server;
}
public Map<String, String> getMappings() {
return mappings;
}
public void setMappings(Map<String, String> mappings) {
this.mappings = mappings;
}
@PostConstruct
public void build() {
log.info("--- Bridge integration lookups config ---");
log.info("Enabled: {}", getEnabled());
if (getEnabled()) {
log.info("Recursive only: {}", getRecursiveOnly());
log.info("Fallback Server: {}", getServer());
log.info("Mappings: {}", mappings.size());
}
}
}

View File

@@ -20,48 +20,41 @@
package io.kamax.mxisd.config;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
@ConfigurationProperties("dns.overwrite.homeserver")
public class DnsOverwriteEntry {
@ConfigurationProperties(prefix = "lookup.recursive")
public class RecursiveLookupConfig {
private String name;
private String type;
private String value;
private boolean enabled;
private List<String> allowedCidr;
private RecursiveLookupBridgeConfig bridge;
public String getName() {
return name;
public boolean isEnabled() {
return enabled;
}
public void setName(String name) {
this.name = name;
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getType() {
return type;
public List<String> getAllowedCidr() {
return allowedCidr;
}
public void setType(String type) {
this.type = type;
public void setAllowedCidr(List<String> allowedCidr) {
this.allowedCidr = allowedCidr;
}
public String getValue() {
return value;
public RecursiveLookupBridgeConfig getBridge() {
return bridge;
}
public void setValue(String value) {
this.value = value;
}
public String getTarget() {
if (StringUtils.equals("env", getType())) {
return System.getenv(getValue());
} else {
return getValue();
}
public void setBridge(RecursiveLookupBridgeConfig bridge) {
this.bridge = bridge;
}
}

View File

@@ -18,56 +18,59 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config
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.beans.factory.annotation.Autowired
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
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;
import java.net.MalformedURLException;
import java.net.URL;
@Configuration
@ConfigurationProperties(prefix = "server")
class ServerConfig implements InitializingBean {
public class ServerConfig {
private Logger log = LoggerFactory.getLogger(ServerConfig.class);
@Autowired
private MatrixConfig mxCfg;
private String name
private int port
private String publicUrl
private String name;
private int port;
private String publicUrl;
String getName() {
return name
public String getName() {
return name;
}
void setName(String name) {
this.name = name
public void setName(String name) {
this.name = name;
}
int getPort() {
return port
public int getPort() {
return port;
}
void setPort(int port) {
this.port = port
public void setPort(int port) {
this.port = port;
}
String getPublicUrl() {
return publicUrl
public String getPublicUrl() {
return publicUrl;
}
void setPublicUrl(String publicUrl) {
this.publicUrl = publicUrl
public void setPublicUrl(String publicUrl) {
this.publicUrl = publicUrl;
}
@Override
void afterPropertiesSet() throws Exception {
log.info("--- Server config ---")
@PostConstruct
public void build() {
log.info("--- Server config ---");
if (StringUtils.isBlank(getName())) {
setName(mxCfg.getDomain());
@@ -75,21 +78,21 @@ class ServerConfig implements InitializingBean {
}
if (StringUtils.isBlank(getPublicUrl())) {
setPublicUrl("https://${getName()}");
setPublicUrl("https://" + getName());
log.debug("Public URL is empty, generating from name");
} else {
setPublicUrl(StringUtils.replace(getPublicUrl(), "%SERVER_NAME%", getName()));
}
try {
new URL(getPublicUrl())
new URL(getPublicUrl());
} catch (MalformedURLException e) {
log.warn("Public URL is not valid: {}", StringUtils.defaultIfBlank(e.getMessage(), "<no reason provided>"))
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())
log.info("Name: {}", getName());
log.info("Port: {}", getPort());
log.info("Public URL: {}", getPublicUrl());
}
}

View File

@@ -23,12 +23,17 @@ 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.List;
import java.util.Map;
@Configuration
@ConfigurationProperties(prefix = "ldap.attribute")
public class LdapAttributeConfig {
private LdapAttributeUidConfig uid;
private String name;
private Map<String, List<String>> threepid = new HashMap<>();
public LdapAttributeUidConfig getUid() {
return uid;
@@ -46,4 +51,12 @@ public class LdapAttributeConfig {
this.name = name;
}
public Map<String, List<String>> getThreepid() {
return threepid;
}
public void setThreepid(Map<String, List<String>> threepid) {
this.threepid = threepid;
}
}

View File

@@ -0,0 +1,209 @@
/*
* 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 com.google.gson.Gson;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.backend.ldap.LdapGenericBackend;
import io.kamax.mxisd.exception.ConfigurationException;
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;
import java.util.ArrayList;
import java.util.List;
@Configuration
@ConfigurationProperties(prefix = "ldap")
public class LdapConfig {
private Logger log = LoggerFactory.getLogger(LdapConfig.class);
private static Gson gson = new Gson();
private boolean enabled;
private String filter;
public static class Directory {
public static class Attribute {
private List<String> other = new ArrayList<>();
public List<String> getOther() {
return other;
}
public void setOther(List<String> other) {
this.other = other;
}
}
private Attribute attribute = new Attribute();
private String filter;
public Attribute getAttribute() {
return attribute;
}
public void setAttribute(Attribute attribute) {
this.attribute = attribute;
}
public String getFilter() {
return filter;
}
public void setFilter(String filter) {
this.filter = filter;
}
}
@Autowired
private LdapConnectionConfig conn;
private LdapAttributeConfig attribute;
private LdapAuthConfig auth;
private Directory directory;
private LdapIdentityConfig identity;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getFilter() {
return filter;
}
public void setFilter(String filter) {
this.filter = filter;
}
public LdapConnectionConfig getConn() {
return conn;
}
public void setConn(LdapConnectionConfig conn) {
this.conn = conn;
}
public LdapAttributeConfig getAttribute() {
return attribute;
}
public void setAttribute(LdapAttributeConfig attribute) {
this.attribute = attribute;
}
public LdapAuthConfig getAuth() {
return auth;
}
public void setAuth(LdapAuthConfig auth) {
this.auth = auth;
}
public Directory getDirectory() {
return directory;
}
public void setDirectory(Directory directory) {
this.directory = directory;
}
public LdapIdentityConfig getIdentity() {
return identity;
}
public void setIdentity(LdapIdentityConfig identity) {
this.identity = identity;
}
@PostConstruct
public void build() {
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 (conn.getPort() < 1 || conn.getPort() > 65535) {
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(LdapGenericBackend.UID, uidType) && !StringUtils.equals(LdapGenericBackend.MATRIX_ID, uidType)) {
throw new IllegalArgumentException("Unsupported LDAP UID type: " + uidType);
}
if (StringUtils.isBlank(identity.getToken())) {
throw new ConfigurationException("ldap.identity.token");
}
// Build queries
attribute.getThreepid().forEach((k, v) -> {
if (StringUtils.isBlank(identity.getMedium().get(k))) {
if (ThreePidMedium.PhoneNumber.is(k)) {
identity.getMedium().put(k, LdapGenericBackend.buildOrQuery("+" + getIdentity().getToken(), v));
} else {
identity.getMedium().put(k, LdapGenericBackend.buildOrQuery(getIdentity().getToken(), v));
}
}
});
getAuth().setFilter(StringUtils.defaultIfBlank(getAuth().getFilter(), getFilter()));
getDirectory().setFilter(StringUtils.defaultIfBlank(getDirectory().getFilter(), getFilter()));
getIdentity().setFilter(StringUtils.defaultIfBlank(getIdentity().getFilter(), getFilter()));
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: {}", gson.toJson(attribute));
log.info("Auth: {}", gson.toJson(auth));
log.info("Directory: {}", gson.toJson(directory));
log.info("Identity: {}", gson.toJson(identity));
}
}

View File

@@ -31,8 +31,26 @@ import java.util.Optional;
@ConfigurationProperties(prefix = "ldap.identity")
public class LdapIdentityConfig {
private String filter;
private String token;
private Map<String, String> medium = new HashMap<>();
public String getFilter() {
return filter;
}
public void setFilter(String filter) {
this.filter = filter;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Map<String, String> getMedium() {
return medium;
}

View File

@@ -60,16 +60,9 @@ public class RestBackendConfig {
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;
}
private String directory;
private IdentityEndpoints identity = new IdentityEndpoints();
public String getAuth() {
return auth;
@@ -79,6 +72,22 @@ public class RestBackendConfig {
this.auth = auth;
}
public String getDirectory() {
return directory;
}
public void setDirectory(String directory) {
this.directory = directory;
}
public IdentityEndpoints getIdentity() {
return identity;
}
public void setIdentity(IdentityEndpoints identity) {
this.identity = identity;
}
}
private Logger log = LoggerFactory.getLogger(RestBackendConfig.class);
@@ -136,11 +145,13 @@ public class RestBackendConfig {
if (isEnabled()) {
endpoints.setAuth(buildEndpointUrl(endpoints.getAuth()));
endpoints.setDirectory(buildEndpointUrl(endpoints.getDirectory()));
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("Directory endpoint: {}", endpoints.getDirectory());
log.info("Identity Single endpoint: {}", endpoints.identity.getSingle());
log.info("Identity Bulk endpoint: {}", endpoints.identity.getBulk());
}

View File

@@ -0,0 +1,221 @@
package io.kamax.mxisd.config.sql;
import io.kamax.mxisd.util.GsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public abstract class SqlConfig {
private Logger log = LoggerFactory.getLogger(SqlConfig.class);
public static class Query {
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;
}
}
public static class Type {
private SqlProviderConfig.Query name = new SqlProviderConfig.Query();
private SqlProviderConfig.Query threepid = new SqlProviderConfig.Query();
public SqlProviderConfig.Query getName() {
return name;
}
public void setName(SqlProviderConfig.Query name) {
this.name = name;
}
public SqlProviderConfig.Query getThreepid() {
return threepid;
}
public void setThreepid(SqlProviderConfig.Query threepid) {
this.threepid = threepid;
}
}
public static class Auth {
private Boolean enabled;
public Boolean isEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}
public static class Directory {
private Boolean enabled;
private SqlProviderConfig.Type query = new SqlProviderConfig.Type();
public Boolean isEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public SqlProviderConfig.Type getQuery() {
return query;
}
public void setQuery(SqlProviderConfig.Type query) {
this.query = query;
}
}
public static class Identity {
private Boolean enabled;
private String type;
private String query;
private Map<String, String> medium = new HashMap<>();
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 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;
}
}
private boolean enabled;
private String type;
private String connection;
private SqlProviderConfig.Auth auth = new SqlProviderConfig.Auth();
private SqlProviderConfig.Directory directory = new SqlProviderConfig.Directory();
private SqlProviderConfig.Identity identity = new SqlProviderConfig.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 SqlProviderConfig.Auth getAuth() {
return auth;
}
public void setAuth(SqlProviderConfig.Auth auth) {
this.auth = auth;
}
public SqlProviderConfig.Directory getDirectory() {
return directory;
}
public void setDirectory(SqlProviderConfig.Directory directory) {
this.directory = directory;
}
public SqlProviderConfig.Identity getIdentity() {
return identity;
}
public void setIdentity(SqlProviderConfig.Identity identity) {
this.identity = identity;
}
protected abstract String getProviderName();
public void build() {
log.info("--- " + getProviderName() + " Provider config ---");
if (getAuth().isEnabled() == null) {
getAuth().setEnabled(isEnabled());
}
if (getDirectory().isEnabled() == null) {
getDirectory().setEnabled(isEnabled());
}
if (getIdentity().isEnabled() == null) {
getIdentity().setEnabled(isEnabled());
}
log.info("Enabled: {}", isEnabled());
if (isEnabled()) {
log.info("Type: {}", getType());
log.info("Connection: {}", getConnection());
log.info("Auth enabled: {}", getAuth().isEnabled());
log.info("Directory queries: {}", GsonUtil.build().toJson(getDirectory().getQuery()));
log.info("Identity type: {}", getIdentity().getType());
log.info("Identity medium queries: {}", GsonUtil.build().toJson(getIdentity().getMedium()));
}
}
}

View File

@@ -22,40 +22,23 @@ package io.kamax.mxisd.config.sql;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
@Configuration
@ConfigurationProperties("sql.identity")
public class SqlProviderIdentityConfig {
@ConfigurationProperties("sql")
@Primary
public class SqlProviderConfig extends SqlConfig {
private String type;
private String query;
private Map<String, String> medium = new HashMap<>();
public String getType() {
return type;
@Override
protected String getProviderName() {
return "Generic SQL";
}
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;
@PostConstruct
public void build() {
super.build();
}
}

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.config.sql.synapse;
import io.kamax.mxisd.config.sql.SqlConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@ConfigurationProperties("synapseSql")
public class SynapseSqlProviderConfig extends SqlConfig {
@Override
protected String getProviderName() {
return "Synapse SQL";
}
@PostConstruct
public void build() {
super.build();
}
}

View File

@@ -0,0 +1,202 @@
/*
* 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.threepid.connector;
import io.kamax.mxisd.util.GsonUtil;
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("notification.handlers.sendgrid")
public class EmailSendGridConfig {
public static class EmailTemplate {
public static class EmailBody {
private String text;
private String html;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getHtml() {
return html;
}
public void setHtml(String html) {
this.html = html;
}
}
private String subject;
private EmailBody body = new EmailBody();
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public EmailBody getBody() {
return body;
}
public void setBody(EmailBody body) {
this.body = body;
}
}
public static class Api {
private String key;
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
}
public static class Identity {
private String from;
private String name;
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static class Templates {
public static class TemplateSession {
private EmailTemplate local = new EmailTemplate();
private EmailTemplate remote = new EmailTemplate();
public EmailTemplate getLocal() {
return local;
}
public void setLocal(EmailTemplate local) {
this.local = local;
}
public EmailTemplate getRemote() {
return remote;
}
public void setRemote(EmailTemplate remote) {
this.remote = remote;
}
}
private EmailTemplate invite = new EmailTemplate();
private TemplateSession session = new TemplateSession();
public EmailTemplate getInvite() {
return invite;
}
public void setInvite(EmailTemplate invite) {
this.invite = invite;
}
public TemplateSession getSession() {
return session;
}
public void setSession(TemplateSession session) {
this.session = session;
}
}
private Logger log = LoggerFactory.getLogger(EmailSendGridConfig.class);
private Api api = new Api();
private Identity identity = new Identity();
private Templates templates = new Templates();
public Api getApi() {
return api;
}
public void setApi(Api api) {
this.api = api;
}
public Identity getIdentity() {
return identity;
}
public void setIdentity(Identity identity) {
this.identity = identity;
}
public Templates getTemplates() {
return templates;
}
public void setTemplates(Templates templates) {
this.templates = templates;
}
@PostConstruct
public void build() {
log.info("--- Email SendGrid connector config ---");
log.info("API key configured?: {}", StringUtils.isNotBlank(api.getKey()));
log.info("Identity: {}", GsonUtil.build().toJson(identity));
log.info("Templates: {}", GsonUtil.build().toJson(templates));
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.threepid.connector;
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(prefix = PhoneTwilioConfig.NAMESPACE)
public class PhoneTwilioConfig {
static final String NAMESPACE = "threepid.medium.msisdn.connectors.twilio";
private Logger log = LoggerFactory.getLogger(PhoneTwilioConfig.class);
private String accountSid;
private String authToken;
private String number;
public String getAccountSid() {
return accountSid;
}
public void setAccountSid(String accountSid) {
this.accountSid = accountSid;
}
public String getAuthToken() {
return authToken;
}
public void setAuthToken(String authToken) {
this.authToken = authToken;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@PostConstruct
public void build() {
log.info("--- Phone SMS Twilio connector config ---");
log.info("Account SID: {}", getAccountSid());
log.info("Sender number: {}", getNumber());
}
}

View File

@@ -36,6 +36,8 @@ import javax.annotation.PostConstruct;
@ConfigurationProperties("threepid.medium.email")
public class EmailConfig {
private Logger log = LoggerFactory.getLogger(EmailConfig.class);
public static class Identity {
private String from;
private String name;
@@ -61,8 +63,6 @@ public class EmailConfig {
private String generator;
private String connector;
private Logger log = LoggerFactory.getLogger(EmailConfig.class);
private MatrixConfig mxCfg;
private Identity identity = new Identity();

View File

@@ -18,35 +18,28 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config;
package io.kamax.mxisd.config.threepid.medium;
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;
import javax.annotation.PostConstruct;
@Configuration
@ConfigurationProperties("dns.overwrite")
public class DnsOverwrite {
@ConfigurationProperties("threepid.medium.email.generators.template")
public class EmailTemplateConfig extends GenericTemplateConfig {
private Logger log = LoggerFactory.getLogger(DnsOverwrite.class);
private static Logger log = LoggerFactory.getLogger(EmailTemplateConfig.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();
@PostConstruct
public void build() {
log.info("--- E-mail Generator templates config ---");
log.info("Invite: {}", getName(getInvite()));
log.info("Session validation:");
log.info("\tLocal: {}", getName(getSession().getValidation().getLocal()));
log.info("\tRemote: {}", getName(getSession().getValidation().getRemote()));
}
}

View File

@@ -21,21 +21,12 @@
package io.kamax.mxisd.config.threepid.medium;
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;
public class GenericTemplateConfig {
@Configuration
@ConfigurationProperties("threepid.medium.email.generators.template")
public class EmailTemplateConfig {
private static Logger log = LoggerFactory.getLogger(EmailTemplateConfig.class);
private static final String classpathPrefix = "classpath:";
private static String getName(String path) {
protected static String getName(String path) {
if (StringUtils.startsWith(path, classpathPrefix)) {
return "Built-in (" + path.substring(classpathPrefix.length()) + ")";
}
@@ -95,13 +86,4 @@ public class EmailTemplateConfig {
return session;
}
@PostConstruct
public void build() {
log.info("--- E-mail Generator templates config ---");
log.info("Invite: {}", getName(getInvite()));
log.info("Session validation:");
log.info("\tLocal: {}", getName(getSession().getValidation().getLocal()));
log.info("\tRemote: {}", getName(getSession().getValidation().getRemote()));
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.threepid.medium;
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("threepid.medium.msisdn")
public class PhoneConfig {
private Logger log = LoggerFactory.getLogger(PhoneConfig.class);
private String generator;
private String connector;
public String getGenerator() {
return generator;
}
public void setGenerator(String generator) {
this.generator = generator;
}
public String getConnector() {
return connector;
}
public void setConnector(String connector) {
this.connector = connector;
}
@PostConstruct
public void build() {
log.info("--- Phone config ---");
if (StringUtils.isBlank(getGenerator())) {
throw new ConfigurationException("generator");
}
if (StringUtils.isBlank(getConnector())) {
throw new ConfigurationException("connector");
}
log.info("Generator: {}", getGenerator());
log.info("Connector: {}", getConnector());
}
}

View File

@@ -0,0 +1,45 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config.threepid.medium;
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("threepid.medium.msisdn.generators.template")
public class PhoneSmsTemplateConfig extends GenericTemplateConfig {
private static Logger log = LoggerFactory.getLogger(EmailTemplateConfig.class);
@PostConstruct
public void build() {
log.info("--- SMS Generator templates config ---");
log.info("Invite: {}", getName(getInvite()));
log.info("Session validation:");
log.info("\tLocal: {}", getName(getSession().getValidation().getLocal()));
log.info("\tRemote: {}", getName(getSession().getValidation().getRemote()));
}
}

View File

@@ -18,41 +18,40 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config
package io.kamax.mxisd.config.threepid.notification;
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
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.util.HashMap;
import java.util.Map;
@Configuration
@ConfigurationProperties(prefix = "lookup.recursive")
class RecursiveLookupConfig {
@ConfigurationProperties("notification")
public class NotificationConfig {
private boolean enabled
private List<String> allowedCidr
private RecursiveLookupBridgeConfig bridge
private Logger log = LoggerFactory.getLogger(NotificationConfig.class);
boolean isEnabled() {
return enabled
private Map<String, String> handler = new HashMap<>();
public Map<String, String> getHandler() {
return handler;
}
void setEnabled(boolean enabled) {
this.enabled = enabled
public void setHandler(Map<String, String> handler) {
this.handler = handler;
}
List<String> getAllowedCidr() {
return allowedCidr
}
void setAllowedCidr(List<String> allowedCidr) {
this.allowedCidr = allowedCidr
}
RecursiveLookupBridgeConfig getBridge() {
return bridge
}
void setBridge(RecursiveLookupBridgeConfig bridge) {
this.bridge = bridge
@PostConstruct
public void build() {
log.info("--- Notification config ---");
log.info("Handlers:");
handler.forEach((k, v) -> {
log.info("\t{}: {}", k, v);
});
}
}

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