Compare commits
108 Commits
v0.3.0-bet
...
v0.8.3
Author | SHA1 | Date | |
---|---|---|---|
|
1e413af019 | ||
|
a0f8af820e | ||
|
5ef145212a | ||
|
91ccb75fa1 | ||
|
ac6f549618 | ||
|
7f9c7aa76d | ||
|
02688942fd | ||
|
48668bcd92 | ||
|
a9627121fa | ||
|
3fc86465f8 | ||
|
d93b546e3c | ||
|
ea15f24d41 | ||
|
290a32d640 | ||
|
10f9126cb6 | ||
|
c3385b38dc | ||
|
61fec4aec7 | ||
|
1db76139a9 | ||
|
a27858082c | ||
|
ea08a80504 | ||
|
cb3130d365 | ||
|
7189a4b100 | ||
|
f71cdbf83e | ||
|
665a284f4b | ||
|
5e142eb41d | ||
|
9fede41904 | ||
|
5871bb6609 | ||
|
5dbaca643a | ||
|
bf9576f9c3 | ||
|
773f38d349 | ||
|
6a5a4b3c1c | ||
|
7fff2448a1 | ||
|
6571ff76b1 | ||
|
16690a0329 | ||
|
6ac593f0fa | ||
|
1581ab9e07 | ||
|
a1adca72e8 | ||
|
e2b3920840 | ||
|
aaa742f6d2 | ||
|
959feb686c | ||
|
d9c5c5056a | ||
|
83fafdcfeb | ||
|
e916ecd08b | ||
|
1461d8ef6c | ||
|
19c1214e4a | ||
|
b976f69c39 | ||
|
3675da4a0f | ||
|
077955d538 | ||
|
9af0cd3615 | ||
|
2bf68538c3 | ||
|
6c02e478d9 | ||
|
284da779f9 | ||
|
af161296b3 | ||
|
6317acd7fc | ||
|
30260af1f2 | ||
|
3b697e86ac | ||
|
b4f0645257 | ||
|
0e48edf86e | ||
|
7e92bfa474 | ||
|
851e0c9d94 | ||
|
ac1cbc4265 | ||
|
62711ee12e | ||
|
3954be2f08 | ||
|
640512eb27 | ||
|
40705b5d47 | ||
|
642d560ba9 | ||
|
b6e86f5b2e | ||
|
4a99ec5531 | ||
|
9079bb25cc | ||
|
88e86cd0d5 | ||
|
8662b3f39f | ||
|
d0aac5ac52 | ||
|
c702a34aab | ||
|
786e4a8f91 | ||
|
8d0b0edad2 | ||
|
88a37c52c0 | ||
|
52e4a65c3c | ||
|
69ecef0155 | ||
|
f7984bd36e | ||
|
f735b3b730 | ||
|
b6008a41f2 | ||
|
ed2d13decf | ||
|
4f3ecc19f3 | ||
|
c816217b22 | ||
|
182f3c4bc3 | ||
|
2e7b5d2a87 | ||
|
09208d55d7 | ||
|
05c76a657e | ||
|
f3bbc7c7c6 | ||
|
61addd297a | ||
|
1de0951733 | ||
|
d348ebd813 | ||
|
0499c10a2c | ||
|
13e248c71e | ||
|
d221b2c5de | ||
|
3a1900cbb2 | ||
|
9f1867a030 | ||
|
a061241291 | ||
|
fefa81e935 | ||
|
1e77bf43c6 | ||
|
c73bbf675e | ||
|
6c2e65ace5 | ||
|
33263d3cff | ||
|
af19fed6e7 | ||
|
246dc4f8d1 | ||
|
31efa3e33f | ||
|
bee2a5129b | ||
|
f1e78af80b | ||
|
e0022e549e |
12
.travis.yml
12
.travis.yml
@@ -1,4 +1,8 @@
|
|||||||
language: groovy
|
language: java
|
||||||
|
before_cache:
|
||||||
jdk:
|
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||||
- oraclejdk8
|
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.gradle/caches/
|
||||||
|
- $HOME/.gradle/wrapper/
|
||||||
|
14
Dockerfile
14
Dockerfile
@@ -1,11 +1,17 @@
|
|||||||
FROM openjdk:8-jre-alpine
|
FROM openjdk:8-jre-alpine
|
||||||
|
|
||||||
|
RUN apk update && apk add bash && rm -rf /var/lib/apk/* /var/cache/apk/*
|
||||||
|
|
||||||
VOLUME /etc/mxisd
|
VOLUME /etc/mxisd
|
||||||
VOLUME /var/mxisd
|
VOLUME /var/mxisd
|
||||||
EXPOSE 8090
|
EXPOSE 8090
|
||||||
|
|
||||||
ADD build/libs/mxisd.jar /mxisd.jar
|
|
||||||
ADD src/docker/start.sh /start.sh
|
|
||||||
|
|
||||||
ENV JAVA_OPTS=""
|
ENV JAVA_OPTS=""
|
||||||
CMD [ "/start.sh" ]
|
ENV CONF_FILE_PATH="/etc/mxisd/mxisd.yaml"
|
||||||
|
ENV SIGN_KEY_PATH="/var/mxisd/sign.key"
|
||||||
|
ENV SQLITE_DATABASE_PATH="/var/mxisd/mxisd.db"
|
||||||
|
|
||||||
|
CMD [ "/start.sh" ]
|
||||||
|
|
||||||
|
ADD src/docker/start.sh /start.sh
|
||||||
|
ADD build/libs/mxisd.jar /mxisd.jar
|
||||||
|
290
README.md
290
README.md
@@ -2,225 +2,107 @@ mxisd - Federated Matrix Identity Server Daemon
|
|||||||
-----
|
-----
|
||||||

|

|
||||||
|
|
||||||
[Overview](#overview) | [Features](#features) | [Lookup process](#lookup-process) | [Packages](#packages) |
|
- [Overview](#overview)
|
||||||
[From source](#from-source) | [Configuration](#configuration) | [Network Discovery](#network-discovery) |
|
- [Features](#features)
|
||||||
[Integration](#integration) | [Support](#support)
|
- [Why use mxisd](#why-use-mxisd)
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
- [Support](#support)
|
||||||
|
- [Contribute](#contribute)
|
||||||
|
- [FAQ](#faq)
|
||||||
|
- [Contact](#contact)
|
||||||
|
|
||||||
# Overview
|
# 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](#features).
|
||||||
|
|
||||||
|
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 uses a cascading lookup model which performs lookup from a more authoritative to a less authoritative source, usually doing:
|
The core principle of mxisd is to map between Matrix IDs and 3PIDs (Third-party Identifiers) for the Homeserver and its
|
||||||
- Local identity stores: LDAP, etc.
|
users. 3PIDs can be anything that identify a user, like:
|
||||||
- Federated identity stores: another Identity Server in charge of a specific domain, if applicable
|
- Full name
|
||||||
- Configured identity stores: another Identity Server specifically configured, if part of some sort of group trust
|
- Email address
|
||||||
- Root identity store: vector.im/matrix.org central Identity Servers
|
- Phone number
|
||||||
|
- Employee number
|
||||||
|
- Skype/Live ID
|
||||||
|
- Twitter handle
|
||||||
|
- Facebook ID
|
||||||
|
- ...
|
||||||
|
|
||||||
mxisd provides an alternative to [sydent](https://github.com/matrix-org/sydent), while still connecting to the vector.im and matrix.org Identity servers,
|
mxisd is an enhanced Identity service, which implements the
|
||||||
by implementing the [Matrix Identity Service specification](https://matrix.org/docs/spec/identity_service/unstable.html).
|
[Matrix Identity service API](https://matrix.org/docs/spec/identity_service/unstable.html) but also several
|
||||||
|
[other features](#features) that greatly enhance user experience within Matrix.
|
||||||
|
|
||||||
mxisd only aims to support workflows that do NOT break federation or basic lookup processes of the Matrix ecosystem.
|
mxisd is the one stop shop for anything regarding Authentication, Directory and Identity management in Matrix built as a
|
||||||
|
single coherent product.
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
- Single lookup of 3PID (E-mail, phone number, etc.) by the Matrix Client or Homeserver.
|
As a [regular Matrix Identity service](docs/features/identity.md):
|
||||||
- Bulk lookups when trying to find possible matches within contacts in Android and iOS clients.
|
- Search for people by 3PID using its own Identity stores (LDAP, SQL, etc.)
|
||||||
- Bind of 3PID by a Matrix user within a Matrix client.
|
- Invite people to rooms by 3PID using its own Identity stores, with [notifications](docs/README.md)
|
||||||
- Support of invitation to rooms by e-mail with e-mail notification to invitee.
|
to the invitee (Email, SMS, etc.)
|
||||||
- Authentication support in [synapse](https://github.com/matrix-org/synapse) via the [REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth).
|
- Allow users to add 3PIDs to their settings/profile
|
||||||
|
|
||||||
In the pipe:
|
As an enhanced Identity service:
|
||||||
- Support to proxy 3PID bindings in user profile to the central Matrix.org servers
|
- 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
|
# Why use mxisd
|
||||||
Default Lookup strategy will use a priority order and a configurable recursive/local type of request.
|
- 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
|
# Getting started
|
||||||
Given the 3PID `john.doe@example.org`, the following will be performed until a mapping is found:
|
See the [dedicated document](docs/getting-started.md)
|
||||||
- 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.
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
## Debian
|
|
||||||
### Download
|
|
||||||
See the [releases section](https://github.com/kamax-io/mxisd/releases).
|
|
||||||
|
|
||||||
### Configure and run
|
|
||||||
After installation:
|
|
||||||
1. Copy the sample config file `/etc/mxisd/mxisd-sample.yaml` to `/etc/mxisd/mxisd.yaml`
|
|
||||||
2. [Configure](#configuration)
|
|
||||||
3. Start the service: `sudo systemctl start mxisd`
|
|
||||||
|
|
||||||
### From source
|
|
||||||
Requirements:
|
|
||||||
- fakeroot
|
|
||||||
- dpkg-deb
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
After [building](#build) the software, run all the following commands as `root` or using `sudo`
|
|
||||||
1. Prepare files and directories:
|
|
||||||
```
|
|
||||||
# Create a dedicated user
|
|
||||||
useradd -r mxisd
|
|
||||||
|
|
||||||
# Create bin directory
|
|
||||||
mkdir /opt/mxisd
|
|
||||||
|
|
||||||
# Create config directory and set ownership
|
|
||||||
mkdir /etc/mxisd
|
|
||||||
chown mxisd /etc/mxisd
|
|
||||||
|
|
||||||
# Create data directory and set ownership
|
|
||||||
mkdir /var/opt/mxisd
|
|
||||||
chown mxisd /var/opt/mxisd
|
|
||||||
|
|
||||||
# Copy <repo root>/build/libs/mxisd.jar to bin directory
|
|
||||||
cp ./build/libs/mxisd.jar /opt/mxisd/
|
|
||||||
chown mxisd /opt/mxisd/mxisd.jar
|
|
||||||
chmod a+x /opt/mxisd/mxisd.jar
|
|
||||||
|
|
||||||
# Create symlink for easy exec
|
|
||||||
ln -s /opt/mxisd/mxisd.jar /usr/bin/mxisd
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Copy the config file created earlier `./application.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
|
|
||||||
```
|
|
||||||
systemctl enable mxisd
|
|
||||||
```
|
|
||||||
6. Start mxisd
|
|
||||||
```
|
|
||||||
systemctl start mxisd
|
|
||||||
```
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
After following the specific instructions to create a config file from the sample:
|
|
||||||
1. Set the `matrix.domain` value to the domain value used in your Home Server configuration
|
|
||||||
2. Set an absolute location for the signing keys using `key.path`
|
|
||||||
3. 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!**
|
|
||||||
```
|
|
||||||
_matrix-identity._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com.
|
|
||||||
```
|
|
||||||
This would only apply for 3PID that are DNS-based, like e-mails. For anything else, like phone numbers, no federation
|
|
||||||
is currently possible.
|
|
||||||
|
|
||||||
The port must be HTTPS capable. Typically, the port `8090` of mxisd should be behind a reverse proxy which does HTTPS.
|
|
||||||
See the [integration section](#integration) for more details.
|
|
||||||
|
|
||||||
# Integration
|
|
||||||
- [HTTPS and Reverse proxy](https://github.com/kamax-io/mxisd/wiki/HTTPS)
|
|
||||||
- [synapse](https://github.com/kamax-io/mxisd/wiki/Homeserver-Integration) as Identity server
|
|
||||||
- [synapse with REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth/blob/master/README.md)
|
|
||||||
as authentication module
|
|
||||||
|
|
||||||
# Support
|
# Support
|
||||||
## Community
|
## Community
|
||||||
If you need help, want to report a bug or just say hi, you can reach us on Matrix at
|
If you need help, want to report a bug or just say hi, you can reach us on Matrix at
|
||||||
[#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io) or [directly peek anonymously](https://view.matrix.org/room/!NPRUEisLjcaMtHIzDr:kamax.io/).
|
[#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io) or
|
||||||
|
[directly peek anonymously](https://view.matrix.org/room/!NPRUEisLjcaMtHIzDr:kamax.io/).
|
||||||
For more high-level discussion about the Identity Server architecture/API, go to
|
For more high-level discussion about the Identity Server architecture/API, go to
|
||||||
[#matrix-identity:matrix.org](https://matrix.to/#/#matrix-identity:matrix.org)
|
[#matrix-identity:matrix.org](https://matrix.to/#/#matrix-identity:matrix.org)
|
||||||
|
|
||||||
## Professional
|
## Professional
|
||||||
If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open source technologies/products,
|
If you would prefer professional support/custom development for mxisd and/or for Matrix in general, including other open
|
||||||
please visit [our website](https://www.kamax.io/) to get in touch with us and get a quote.
|
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!](https://liberapay.com/maximusdor/) Any donation is welcome, regardless how small or big, and will directly
|
||||||
|
be used for the fixed costs and developer time of mxisd.
|
||||||
|
|
||||||
|
You can contribute as an organisation/corporation by:
|
||||||
|
- Get a [support contract](#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
|
||||||
|
See the [dedicated document](docs/faq.md)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
@@ -1,373 +1,30 @@
|
|||||||
# 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
|
# For a complete list of options, see https://github.com/kamax-io/mxisd
|
||||||
# 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.
|
|
||||||
|
|
||||||
#######################
|
#######################
|
||||||
# Matrix config items #
|
# Matrix config items #
|
||||||
#######################
|
#######################
|
||||||
# Matrix domain, same as the domain configure in your Homeserver configuration.
|
# Matrix domain, same as the domain configure in your Homeserver configuration.
|
||||||
|
# (note: in Synapse Homeserver, the Matrix domain may be defined as 'server_name' in configuration file).
|
||||||
#
|
#
|
||||||
# This is used to build the various identifiers for identity, auth and directory.
|
# This is used to build the various identifiers for identity, auth and directory.
|
||||||
matrix.domain: ''
|
matrix.domain: ''
|
||||||
|
|
||||||
|
|
||||||
|
################
|
||||||
#######################
|
# Signing keys #
|
||||||
# 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 #
|
|
||||||
#############################
|
|
||||||
# Absolute path for the Identity Server signing key.
|
# Absolute path for the Identity Server signing key.
|
||||||
# During testing, /var/tmp/mxisd.key is a possible value
|
# During testing, /var/tmp/mxisd.key is a possible value
|
||||||
#
|
#
|
||||||
# For production, use a stable location like:
|
# For production, recommended location shall be one of the following:
|
||||||
# - /var/opt/mxisd/sign.key
|
# - /var/opt/mxisd/sign.key
|
||||||
# - /var/local/mxisd/sign.key
|
# - /var/local/mxisd/sign.key
|
||||||
# - /var/lib/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:
|
# The signing key is auto-generated during execution time if not present.
|
||||||
# - Local
|
key.path: ''
|
||||||
# - 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"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
@@ -380,10 +37,7 @@ invite.sender.email.email: "matrix-identity@example.org"
|
|||||||
#
|
#
|
||||||
#storage.backend: 'sqlite'
|
#storage.backend: 'sqlite'
|
||||||
|
|
||||||
|
# Path to the SQLite DB file
|
||||||
#### Generic SQLite provider config
|
|
||||||
#
|
|
||||||
# Path to the SQLite DB file, required if SQLite backend is chosen
|
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# - /var/opt/mxisd/mxisd.db
|
# - /var/opt/mxisd/mxisd.db
|
||||||
@@ -393,22 +47,53 @@ invite.sender.email.email: "matrix-identity@example.org"
|
|||||||
storage.provider.sqlite.database: '/path/to/mxisd.db'
|
storage.provider.sqlite.database: '/path/to/mxisd.db'
|
||||||
|
|
||||||
|
|
||||||
|
################
|
||||||
|
# LDAP Backend #
|
||||||
|
################
|
||||||
|
# If you would like to integrate with your AD/Samba/LDAP server,
|
||||||
|
# see https://github.com/kamax-io/mxisd/blob/master/docs/backends/ldap.md
|
||||||
|
|
||||||
######################
|
###############
|
||||||
# DNS-related config #
|
# SQL Backend #
|
||||||
######################
|
###############
|
||||||
# The domain to overwrite
|
# 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'
|
#### E-mail invite sender
|
||||||
|
|
||||||
|
|
||||||
# - 'env' from environment variable specified by value
|
|
||||||
# - any other value will use the value as-is as host
|
|
||||||
#
|
#
|
||||||
#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.
|
# TLS mode for the connection.
|
||||||
# Protocol will always be HTTPS
|
|
||||||
#
|
#
|
||||||
#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.
|
||||||
|
threepid.medium.email.identity.from: "matrix-identity@example.org"
|
||||||
|
79
build.gradle
79
build.gradle
@@ -1,5 +1,3 @@
|
|||||||
import java.util.regex.Pattern
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* mxisd - Matrix Identity Server Daemon
|
* mxisd - Matrix Identity Server Daemon
|
||||||
* Copyright (C) 2017 Maxime Dor
|
* 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/>.
|
* 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'
|
apply plugin: 'org.springframework.boot'
|
||||||
|
|
||||||
def confFileName = "application.example.yaml"
|
def confFileName = "application.example.yaml"
|
||||||
@@ -47,7 +47,7 @@ String gitVersion() {
|
|||||||
def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?")
|
def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?")
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
||||||
exec {
|
exec {
|
||||||
commandLine = [ 'git', 'describe', '--always', '--dirty' ]
|
commandLine = ['git', 'describe', '--tags', '--always', '--dirty']
|
||||||
standardOutput = out
|
standardOutput = out
|
||||||
}
|
}
|
||||||
def v = out.toString().replace(System.lineSeparator(), '')
|
def v = out.toString().replace(System.lineSeparator(), '')
|
||||||
@@ -66,24 +66,22 @@ buildscript {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven { url "https://kamax.io/maven/releases/" }
|
maven { url "https://kamax.io/maven/releases/" }
|
||||||
|
maven { url "https://kamax.io/maven/snapshots/" }
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// We are a groovy project
|
|
||||||
compile 'org.codehaus.groovy:groovy-all:2.4.7'
|
|
||||||
|
|
||||||
// Easy file management
|
// Easy file management
|
||||||
compile 'commons-io:commons-io:2.5'
|
compile 'commons-io:commons-io:2.5'
|
||||||
|
|
||||||
// Spring Boot - standalone app
|
// Spring Boot - standalone app
|
||||||
compile 'org.springframework.boot:spring-boot-starter-web:1.5.3.RELEASE'
|
compile 'org.springframework.boot:spring-boot-starter-web:1.5.10.RELEASE'
|
||||||
|
|
||||||
// Thymeleaf for HTML templates
|
// Thymeleaf for HTML templates
|
||||||
compile "org.springframework.boot:spring-boot-starter-thymeleaf:1.5.3.RELEASE"
|
compile "org.springframework.boot:spring-boot-starter-thymeleaf:1.5.10.RELEASE"
|
||||||
|
|
||||||
// Matrix Java SDK
|
// Matrix Java SDK
|
||||||
compile 'io.kamax:matrix-java-sdk:0.0.2'
|
compile 'io.kamax:matrix-java-sdk:0.0.11'
|
||||||
|
|
||||||
// ed25519 handling
|
// ed25519 handling
|
||||||
compile 'net.i2p.crypto:eddsa:0.1.0'
|
compile 'net.i2p.crypto:eddsa:0.1.0'
|
||||||
@@ -97,9 +95,6 @@ dependencies {
|
|||||||
// HTTP connections
|
// HTTP connections
|
||||||
compile 'org.apache.httpcomponents:httpclient:4.5.3'
|
compile 'org.apache.httpcomponents:httpclient:4.5.3'
|
||||||
|
|
||||||
// JSON
|
|
||||||
compile 'com.google.code.gson:gson:2.8.1'
|
|
||||||
|
|
||||||
// Phone numbers validation
|
// Phone numbers validation
|
||||||
compile 'com.googlecode.libphonenumber:libphonenumber:8.7.1'
|
compile 'com.googlecode.libphonenumber:libphonenumber:8.7.1'
|
||||||
|
|
||||||
@@ -113,12 +108,24 @@ dependencies {
|
|||||||
// ORMLite
|
// ORMLite
|
||||||
compile 'com.j256.ormlite:ormlite-jdbc:5.0'
|
compile 'com.j256.ormlite:ormlite-jdbc:5.0'
|
||||||
|
|
||||||
|
// Connection Pool
|
||||||
|
compile 'com.mchange:c3p0:0.9.5.2'
|
||||||
|
|
||||||
// SQLite
|
// SQLite
|
||||||
compile 'org.xerial:sqlite-jdbc:3.20.0'
|
compile 'org.xerial:sqlite-jdbc:3.20.0'
|
||||||
|
|
||||||
// PostgreSQL
|
// PostgreSQL
|
||||||
compile 'org.postgresql:postgresql:42.1.4'
|
compile 'org.postgresql:postgresql:42.1.4'
|
||||||
|
|
||||||
|
// MariaDB/MySQL
|
||||||
|
compile 'org.mariadb.jdbc:mariadb-java-client:2.1.2'
|
||||||
|
|
||||||
|
// Twilio SDK for SMS
|
||||||
|
compile 'com.twilio.sdk:twilio:7.14.5'
|
||||||
|
|
||||||
|
// SendGrid SDK to send emails from GCE
|
||||||
|
compile 'com.sendgrid:sendgrid-java:2.2.2'
|
||||||
|
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
testCompile 'com.github.tomakehurst:wiremock:2.8.0'
|
testCompile 'com.github.tomakehurst:wiremock:2.8.0'
|
||||||
}
|
}
|
||||||
@@ -160,8 +167,8 @@ task buildDeb(dependsOn: build) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ant.chmod(
|
ant.chmod(
|
||||||
file: "${debBuildBinPath}/mxisd.jar",
|
file: "${debBuildBinPath}/mxisd.jar",
|
||||||
perm: 'a+x'
|
perm: 'a+x'
|
||||||
)
|
)
|
||||||
|
|
||||||
copy {
|
copy {
|
||||||
@@ -172,15 +179,15 @@ task buildDeb(dependsOn: build) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ant.replaceregexp(
|
ant.replaceregexp(
|
||||||
file: "${debBuildConfPath}/${debConfFileName}",
|
file: "${debBuildConfPath}/${debConfFileName}",
|
||||||
match: "key.path:(.*)",
|
match: "key.path:(.*)",
|
||||||
replace: "key.path: '${debDataPath}/signing.key'"
|
replace: "key.path: '${debDataPath}/signing.key'"
|
||||||
)
|
)
|
||||||
|
|
||||||
ant.replaceregexp(
|
ant.replaceregexp(
|
||||||
file: "${debBuildConfPath}/${debConfFileName}",
|
file: "${debBuildConfPath}/${debConfFileName}",
|
||||||
match: "storage.provider.sqlite.database:(.*)",
|
match: "storage.provider.sqlite.database:(.*)",
|
||||||
replace: "storage.provider.sqlite.database: '${debDataPath}/mxisd.db'"
|
replace: "storage.provider.sqlite.database: '${debDataPath}/mxisd.db'"
|
||||||
)
|
)
|
||||||
|
|
||||||
copy {
|
copy {
|
||||||
@@ -189,25 +196,25 @@ task buildDeb(dependsOn: build) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ant.replace(
|
ant.replace(
|
||||||
file: "${debBuildDebianPath}/control",
|
file: "${debBuildDebianPath}/control",
|
||||||
token: 'Version: 0',
|
token: 'Version: 0',
|
||||||
value: "Version: ${v}"
|
value: "Version: ${v}"
|
||||||
)
|
)
|
||||||
|
|
||||||
ant.replace(
|
ant.replace(
|
||||||
file: "${debBuildDebianPath}/postinst",
|
file: "${debBuildDebianPath}/postinst",
|
||||||
token: '%DEB_DATA_DIR%',
|
token: '%DEB_DATA_DIR%',
|
||||||
value: debDataPath
|
value: debDataPath
|
||||||
)
|
)
|
||||||
|
|
||||||
ant.chmod(
|
ant.chmod(
|
||||||
file: "${debBuildDebianPath}/postinst",
|
file: "${debBuildDebianPath}/postinst",
|
||||||
perm: 'a+x'
|
perm: 'a+x'
|
||||||
)
|
)
|
||||||
|
|
||||||
ant.chmod(
|
ant.chmod(
|
||||||
file: "${debBuildDebianPath}/prerm",
|
file: "${debBuildDebianPath}/prerm",
|
||||||
perm: 'a+x'
|
perm: 'a+x'
|
||||||
)
|
)
|
||||||
|
|
||||||
copy {
|
copy {
|
||||||
@@ -217,11 +224,11 @@ task buildDeb(dependsOn: build) {
|
|||||||
|
|
||||||
exec {
|
exec {
|
||||||
commandLine(
|
commandLine(
|
||||||
'fakeroot',
|
'fakeroot',
|
||||||
'dpkg-deb',
|
'dpkg-deb',
|
||||||
'-b',
|
'-b',
|
||||||
debBuildBasePath,
|
debBuildBasePath,
|
||||||
"${project.buildDir}/dist"
|
"${project.buildDir}/dist"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
docs/README.md
Normal file
26
docs/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 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)
|
||||||
|
- [Wordpress](backends/wordpress.md)
|
||||||
|
- Notifications
|
||||||
|
- Handlers
|
||||||
|
- [Basic](threepids/notifications/basic-handler.md)
|
||||||
|
- [SendGrid](threepids/notifications/sendgrid-handler.md)
|
||||||
|
- [Sessions](sessions/3pid.md)
|
||||||
|
- [Views](sessions/3pid-views.md)
|
1
docs/_config.yml
Normal file
1
docs/_config.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
theme: jekyll-theme-cayman
|
43
docs/architecture.md
Normal file
43
docs/architecture.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Architecture
|
||||||
|
## Overview
|
||||||
|
### Basic setup without integration or incoming 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).
|
6
docs/backends/README.md
Normal file
6
docs/backends/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Identity Stores (Backends)
|
||||||
|
- [Samba / Active Directory / LDAP](ldap.md)
|
||||||
|
- [SQL Databases](sql.md)
|
||||||
|
- [Website / Web service / Web app](rest.md)
|
||||||
|
- [Google Firebase](firebase.md)
|
||||||
|
- [Wordpress](wordpress.md)
|
19
docs/backends/firebase.md
Normal file
19
docs/backends/firebase.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Google Firebase
|
||||||
|
https://firebase.google.com/
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
This backend requires a suitable Matrix client capable of performing Firebase authentication and passing the following
|
||||||
|
information:
|
||||||
|
- Firebase User ID as Matrix username
|
||||||
|
- Firebase token as Matrix password
|
||||||
|
|
||||||
|
If your client is Riot, you will need a custom version.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
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/'
|
||||||
|
```
|
140
docs/backends/ldap.md
Normal file
140
docs/backends/ldap.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# LDAP (Samba / Active Directory / OpenLDAP)
|
||||||
|
## Getting started
|
||||||
|
To use your LDAP backend, add the bare minimum configuration in mxisd config file:
|
||||||
|
```
|
||||||
|
ldap.enabled: true
|
||||||
|
ldap.connection.host: 'ldapHostnameOrIp'
|
||||||
|
ldap.connection.bindDn: 'CN=My Mxisd User,OU=Users,DC=example,DC=org'
|
||||||
|
ldap.connection.bindPassword: 'TheUserPassword'
|
||||||
|
ldap.connection.baseDn: 'OU=Users,DC=example,DC=org'
|
||||||
|
```
|
||||||
|
These are standard LDAP connection configuration. mxisd will try to connect on port default port 389 without encryption.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If you would like to use a TLS/SSL connection, use the following configuration options (STARTLS not supported):
|
||||||
|
```
|
||||||
|
ldap.connection.tls: true
|
||||||
|
ldap.connection.port: 12345
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
You can also set a default global filter on any LDAP queries:
|
||||||
|
```
|
||||||
|
ldap.filter: '(memberOf=CN=My Matrix Users,OU=Groups,DC=example,DC=org)'
|
||||||
|
```
|
||||||
|
This example would only return users part of the group called `My Matrix Users`.
|
||||||
|
This can be overwritten or append in each specific flow describe below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
LDAP features are based on mapping LDAP attributes to Matrix concepts, like a Matrix ID, its localpart, the user display
|
||||||
|
name, their email(s) and/or phone number(s).
|
||||||
|
|
||||||
|
Default attributes are well suited for Active Directory/Samba. In case you are using a native LDAP backend, you will
|
||||||
|
most certainly configure those mappings.
|
||||||
|
|
||||||
|
The following example would set the `uid` attribute as localpart and the Matrix display name to `cn`
|
||||||
|
```
|
||||||
|
ldap.attribute.uid.type: 'uid'
|
||||||
|
ldap.attribute.uid.value: 'uid'
|
||||||
|
ldap.attribute.name: 'cn'
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also change the attribute lists for 3PID, like email or phone numbers.
|
||||||
|
The following example would overwrite the [default list of attributes](../../src/main/resources/application.yaml#L67)
|
||||||
|
for emails and phone number:
|
||||||
|
```
|
||||||
|
ldap.attribute.threepid.email:
|
||||||
|
- 'mail'
|
||||||
|
- 'otherMailAttribute'
|
||||||
|
|
||||||
|
ldap.attribute.threepid.msisdn:
|
||||||
|
- 'phone'
|
||||||
|
- 'otherPhoneAttribute'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Identity
|
||||||
|
Identity features (related to 3PID invites or searches) are enabled and configured using default values and no specific
|
||||||
|
configuration item is needed to get started.
|
||||||
|
|
||||||
|
If you would like to overwrite some global configuration relative to filter and/or attributes, see the Identity section
|
||||||
|
of the Configuration below.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
No further configuration is needed to enable authentication with LDAP once globally enabled and configured.
|
||||||
|
You have the possiblity to use a different query filter if you wish, see Configuration below.
|
||||||
|
|
||||||
|
Profile auto-fill is enabled by default. It will use the `name` and `threepid` configuration options to get a lit of
|
||||||
|
attributes to be used to build the user profile to pass on to synapse during authentication.
|
||||||
|
|
||||||
|
## Directory
|
||||||
|
No further configuration is needed to enable directory with LDAP once globally enabled and configured.
|
||||||
|
|
||||||
|
If you would like to use extra attributes in search that are not 3PIDs, like nicknames, group names, employee number:
|
||||||
|
```
|
||||||
|
ldap.directory.attribute.other:
|
||||||
|
- 'myNicknameAttribute'
|
||||||
|
- 'memberOf'
|
||||||
|
- 'employeeNumberAttribute'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
Please read the [Configuration](../configure.md) explanatory note if you are not familiar with the terms used below.
|
||||||
|
|
||||||
|
### General
|
||||||
|
Base path: `ldap`
|
||||||
|
|
||||||
|
| 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
|
||||||
|
Base path: `ldap.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
|
||||||
|
Base path: `ldap.attribute`
|
||||||
|
|
||||||
|
| 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
|
||||||
|
Base path: `ldap.auth`
|
||||||
|
|
||||||
|
| Item | Description |
|
||||||
|
|----------|--------------------------------------------------------------------------------------------------|
|
||||||
|
| `filter` | Specific user filter applied during authentication. Global filter is used if empty/blank/not set |
|
||||||
|
|
||||||
|
### Directory
|
||||||
|
Base path: `ldap.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
|
||||||
|
Base path: `ldap.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 |
|
@@ -6,33 +6,33 @@ The REST backend allows you to query identity data in existing webapps, like:
|
|||||||
- self-hosted clouds (Nextcloud, ownCloud, ...)
|
- self-hosted clouds (Nextcloud, ownCloud, ...)
|
||||||
|
|
||||||
It supports the following mxisd flows:
|
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.
|
To integrate this backend with your webapp, you will need to implement three specific REST endpoints detailed below.
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
| Key | Default | Description |
|
| Key | Default | Description |
|
||||||
---------------------------------|---------------------------------------|------------------------------------------------------|
|
---------------------------------|----------------------------------------------|------------------------------------------------------|
|
||||||
| rest.enabled | false | Globally enable/disable the REST backend |
|
| rest.enabled | false | Globally enable/disable the REST backend |
|
||||||
| rest.host | *empty* | Default base URL to use for the different endpoints. |
|
| 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.auth | /_mxisd/backend/api/v1/auth/login | Validate credentials and get user profile |
|
||||||
| rest.endpoints.identity.single | /_mxisd/identity/api/v1/lookup/single | Endpoint to query a single 3PID |
|
| rest.endpoints.directory | /_mxisd/backend/api/v1/directory/user/search | Search for users by arbitrary input |
|
||||||
| rest.endpoints.identity.bulk | /_mxisd/identity/api/v1/lookup/bulk | Endpoint to query a list of 3PID |
|
| 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:
|
Endpoint values can handle two formats:
|
||||||
- URL Path starting with `/` that gets happened to the `rest.host`
|
- 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
|
- 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
|
## Endpoints
|
||||||
### Authenticate
|
### Authentication
|
||||||
Configured with `rest.endpoints.auth`
|
|
||||||
|
|
||||||
HTTP method: `POST`
|
HTTP method: `POST`
|
||||||
Encoding: JSON UTF-8
|
Content-type: JSON UTF-8
|
||||||
|
|
||||||
#### Request Body
|
#### Request Body
|
||||||
```
|
```
|
||||||
@@ -84,12 +84,51 @@ If the authentication succeed:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Lookup
|
### Directory
|
||||||
#### Single
|
HTTP method: `POST`
|
||||||
Configured with `rest.endpoints.identity.single`
|
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`
|
HTTP method: `POST`
|
||||||
Encoding: JSON UTF-8
|
Content-type: JSON UTF-8
|
||||||
|
|
||||||
#### Request Body
|
#### Request Body
|
||||||
```
|
```
|
||||||
@@ -122,11 +161,9 @@ If no match was found:
|
|||||||
{}
|
{}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Bulk
|
#### Bulk 3PID lookup
|
||||||
Configured with `rest.endpoints.identity.bulk`
|
|
||||||
|
|
||||||
HTTP method: `POST`
|
HTTP method: `POST`
|
||||||
Encoding: JSON UTF-8
|
Content-type: JSON UTF-8
|
||||||
|
|
||||||
#### Request Body
|
#### Request Body
|
||||||
```
|
```
|
||||||
@@ -175,4 +212,4 @@ If no match was found:
|
|||||||
{
|
{
|
||||||
"lookup": []
|
"lookup": []
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
23
docs/backends/sql.md
Normal file
23
docs/backends/sql.md
Normal 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 = ?'
|
||||||
|
```
|
55
docs/backends/wordpress.md
Normal file
55
docs/backends/wordpress.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Wordpress
|
||||||
|
This Identity store allows you to use user accounts registered on your Wordpress setup.
|
||||||
|
Two types of connections are required for full support:
|
||||||
|
- [REST API](https://developer.wordpress.org/rest-api/) with JWT authentication
|
||||||
|
- Direct SQL access
|
||||||
|
|
||||||
|
This Identity store supports the following features:
|
||||||
|
- [Authentication](../features/authentication.md)
|
||||||
|
- [Directory](../features/directory-users.md)
|
||||||
|
- [Identity](../features/identity.md)
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
- [Wordpress](https://wordpress.org/download/) >= 4.4
|
||||||
|
- Permalink structure set to `Post Name`
|
||||||
|
- [JWT Auth plugin for REST API](https://wordpress.org/plugins/jwt-authentication-for-wp-rest-api/)
|
||||||
|
- SQL Credentials to the Wordpress Database
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
### Wordpress
|
||||||
|
#### JWT Auth
|
||||||
|
Set a JWT secret into `wp-config.php` like so:
|
||||||
|
```
|
||||||
|
define('JWT_AUTH_SECRET_KEY', 'your-top-secret-key');
|
||||||
|
```
|
||||||
|
`your-top-secret-key` should be set to a randomly generated value which is kept secret.
|
||||||
|
|
||||||
|
#### Rewrite of `index.php`
|
||||||
|
Wordpress is normally configured with rewrite of `index.php` so it does not appear in URLs.
|
||||||
|
If this is not the case for your installation, the mxisd URL will need to be appended with `/index.php`
|
||||||
|
|
||||||
|
### mxisd
|
||||||
|
Enable in the configuration:
|
||||||
|
```
|
||||||
|
wordpress.enabled: true
|
||||||
|
```
|
||||||
|
Configure the URL to your Wordpress installation - see above about added `/index.php`:
|
||||||
|
```
|
||||||
|
wordpress.rest.base: 'http://localhost:8080'
|
||||||
|
```
|
||||||
|
Configure the SQL connection to your Wordpress database:
|
||||||
|
```
|
||||||
|
wordpress.sql.connection: '//127.0.0.1/wordpress?user=root&password=example'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
By default, MySQL database is expected. If you use another database, use:
|
||||||
|
```
|
||||||
|
wordpress.sql.type: 'jdbc-scheme'
|
||||||
|
```
|
||||||
|
With possible values:
|
||||||
|
- `mysql`
|
||||||
|
- `mariadb`
|
||||||
|
- `postgresql`
|
||||||
|
- `sqlite`
|
105
docs/build.md
Normal file
105
docs/build.md
Normal 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
183
docs/configure.md
Normal 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 appended 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.
|
65
docs/faq.md
Normal file
65
docs/faq.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
In its default configuration, mxisd will talk to the central Matrix Identity servers and use other federated public
|
||||||
|
servers when performing queries, giving you access to at least the same information as if you were not running it.
|
||||||
|
|
||||||
|
It will also give your users a choice to make their 3PIDs available publicly, ensuring they are made aware of the
|
||||||
|
privacy consequences, which is not the case with the central Matrix.org servers.
|
||||||
|
|
||||||
|
So mxisd is like your gatekeeper and guardian angel. It does not change what you already know, just adds some nice
|
||||||
|
simple features on top of it.
|
||||||
|
|
||||||
|
### I already use the synapse LDAP3 auth provider, why should I care about mxisd?
|
||||||
|
The [synapse LDAP3 auth provider](https://github.com/matrix-org/matrix-synapse-ldap3) is not longer maintained and
|
||||||
|
only handles on specific flow: validate credentials at login.
|
||||||
|
|
||||||
|
It does not:
|
||||||
|
- Auto-provision user profiles
|
||||||
|
- Integrate with Identity management
|
||||||
|
- Integrate with Directory searches
|
||||||
|
- Protect you against the username case sensitivites issues in synapse
|
||||||
|
|
||||||
|
mxisd is a replacement and enhancement of it, offering coherent results in all areas, which LDAP3 auth provider
|
||||||
|
does not.
|
||||||
|
|
||||||
|
### Sydent is the official Identity server implementation of the Matrix team, why not use that?
|
||||||
|
You can, but [sydent](https://github.com/matrix-org/sydent):
|
||||||
|
- [should not be used and/or self-hosted](https://github.com/matrix-org/sydent/issues/22)
|
||||||
|
- is not meant to be linked to a specific Homeserver / domain
|
||||||
|
- cannot handle federation or proxy lookups, effectively isolating your users from the rest of the network
|
||||||
|
- forces you to duplicate all your identity data, so people can be found by 3PIDs
|
||||||
|
- forces users to enter all their emails and phone numbers manually in their profile
|
||||||
|
|
||||||
|
So really, you should go with mxisd.
|
||||||
|
|
||||||
|
### Will I loose access to the central Matrix.org/Vector.im Identity data if I use mxisd?
|
||||||
|
In its default configuration, mxisd act as a proxy to Matrix.org/Vector.im. You will have access to the same data and
|
||||||
|
behaviour than if you were using them directly. There is no downside in using mxisd with the default configuration.
|
||||||
|
|
||||||
|
mxisd can also be configured not to talk to the central Identity servers if you wish.
|
||||||
|
|
||||||
|
### 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
|
162
docs/features/authentication.md
Normal file
162
docs/features/authentication.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# Authentication
|
||||||
|
|
||||||
|
- [Description](#description)
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Getting started](#getting-started)
|
||||||
|
- [Synapse](#synapse)
|
||||||
|
- [mxisd](#mxisd)
|
||||||
|
- [Validate](#validate)
|
||||||
|
- [Next steps](#next-steps)
|
||||||
|
- [Profile auto-fil](#profile-auto-fill)
|
||||||
|
- [Advanced Authentication](#advanced-authentication)
|
||||||
|
- [Requirements](#requirements)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Reverse Proxy](#reverse-proxy)
|
||||||
|
- [Apache2](#apache2)
|
||||||
|
- [DNS Overwrite](#dns-overwrite)
|
||||||
|
- [Backends](#backends)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
Authentication is an enhanced Identity feature of mxisd to ensure coherent and centralized identity management.
|
||||||
|
|
||||||
|
It allows to use Identity stores configured in mxisd to authenticate users on your Homeserver.
|
||||||
|
|
||||||
|
This feature can also provide the ability to users to login on the Homeserver using their third party identities (3PIDs) provided by an Identity store.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
An overview of the Authentication process is depicted below:
|
||||||
|
|
||||||
|
```
|
||||||
|
Backends
|
||||||
|
Client +------+
|
||||||
|
| +-------------------------+ +--> | LDAP |
|
||||||
|
| +---------------+ /_matrix/identity | mxisd | | +------+
|
||||||
|
+-> | Reverse proxy | >------------------+ | | |
|
||||||
|
+--|------------+ | | | | +--------+
|
||||||
|
| +-----> Check with backends >------+--> | SQL DB |
|
||||||
|
Login request | | | | +--------+
|
||||||
|
| | | | | |
|
||||||
|
| +--------------------------+ | +-----|-------------------+ +--> Others
|
||||||
|
+-> | Homeserver | | |
|
||||||
|
| | | |
|
||||||
|
| - Validate credentials >----+ |
|
||||||
|
| Using REST auth module | |
|
||||||
|
| | |
|
||||||
|
| - Auto-provision <-------------------<+
|
||||||
|
| user profiles | If valid credentials and supported by backend
|
||||||
|
+--------------------------+
|
||||||
|
```
|
||||||
|
Performed on [synapse with REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth/blob/master/README.md)
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
Authentication is possible by linking synapse and mxisd together using the REST auth module
|
||||||
|
(also known as password provider).
|
||||||
|
|
||||||
|
### Synapse
|
||||||
|
- Install the [REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth).
|
||||||
|
- Edit your synapse configuration:
|
||||||
|
- As described by the auth module documentation
|
||||||
|
- Set `endpoint` to `http://mxisdAddress:8090` - Replace `mxisdAddress` by an IP/host name that provides a direct
|
||||||
|
connection to mxisd.
|
||||||
|
This **MUST NOT** be a public address, and SHOULD NOT go through a reverse proxy.
|
||||||
|
- Restart synapse
|
||||||
|
|
||||||
|
### mxisd
|
||||||
|
- Configure and enable at least one [Identity store](../backends/)
|
||||||
|
- Restart mxisd
|
||||||
|
|
||||||
|
### Validate
|
||||||
|
Login on the Homeserver using credentials present in your backend.
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
### Profile auto-fill
|
||||||
|
Auto-filling user profile depends on two conditions:
|
||||||
|
- The REST auth module is configured for it, which is the case by default
|
||||||
|
- Your Identity store is configured to provide profile data. See your Identity store [documentation](../backends/) on
|
||||||
|
how to enable the feature.
|
||||||
|
|
||||||
|
|
||||||
|
## Advanced Authentication
|
||||||
|
The Authentication feature allows users to login to their Homeserver by using their 3PIDs registered in an available Identity store.
|
||||||
|
|
||||||
|
This is performed by intercepting the Homeserver endpoint `/_matrix/client/r0/login` as depicted below:
|
||||||
|
|
||||||
|
```
|
||||||
|
+----------------------------+
|
||||||
|
| Reverse Proxy |
|
||||||
|
| |
|
||||||
|
| | Step 1 +---------------------------+ Step 2
|
||||||
|
| | | |
|
||||||
|
Client+---->| /_matrix/client/r0/login +---------------->| | Look up address +---------+
|
||||||
|
| ^ | | mxisd - Identity server +----------------->| Backend |
|
||||||
|
| | | | | +---------+
|
||||||
|
| /_matrix/* +--+ +---------------------+ |
|
||||||
|
| | | +---------------+-----------+
|
||||||
|
| | | Step 4 |
|
||||||
|
| | | | Step 3
|
||||||
|
+---------------|------------+ |
|
||||||
|
| | /_matrix/client/r0/login
|
||||||
|
| +--------------+ |
|
||||||
|
| | | |
|
||||||
|
+---------------------->| Homeserver |<----+
|
||||||
|
| |
|
||||||
|
+--------------+
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Steps of user authentication using a 3PID:
|
||||||
|
1. The intercepted login request is directly sent to mxisd instead of the Homeserver.
|
||||||
|
2. Enabled backends are queried for a matching user identity in order to modify the request to use the user name.
|
||||||
|
3. The Homeserver, from which the request was intercepted, is queried using the request at previous step. Its address is resolved using the DNS Overwrite feature to reach its internal address on a non-encrypted port.
|
||||||
|
4. The response from the Homeserver is sent back to the client, believing it was the HS which directly answered.
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
- Reverse proxy setup
|
||||||
|
- Homeserver
|
||||||
|
- Compatible Identity backends:
|
||||||
|
- LDAP
|
||||||
|
- REST
|
||||||
|
- Wordpress
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
#### Reverse Proxy
|
||||||
|
|
||||||
|
##### Apache2
|
||||||
|
The specific configuration to put under the relevant `VirtualHost`:
|
||||||
|
```
|
||||||
|
ProxyPass /_matrix/client/r0/login http://localhost:8090/_matrix/client/r0/login
|
||||||
|
```
|
||||||
|
`ProxyPreserveHost` or equivalent must be enabled to detect to which Homeserver mxisd should talk to when building results.
|
||||||
|
|
||||||
|
Your VirtualHost should now look like this:
|
||||||
|
```
|
||||||
|
<VirtualHost *:443>
|
||||||
|
ServerName example.org
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
ProxyPreserveHost on
|
||||||
|
ProxyPass /_matrix/client/r0/login http://localhost:8090/_matrix/client/r0/login
|
||||||
|
ProxyPass /_matrix/identity/ http://localhost:8090/_matrix/identity/
|
||||||
|
ProxyPass /_matrix/ http://localhost:8008/_matrix/
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DNS Overwrite
|
||||||
|
Just like you need to configure a reverse proxy to send client requests to mxisd, you also need to configure mxisd with the internal IP of the Homeserver so it can talk to it directly to integrate its directory search.
|
||||||
|
|
||||||
|
|
||||||
|
To do so, put the following configuration in your `application.yaml`:
|
||||||
|
```
|
||||||
|
dns.overwrite.homeserver.client:
|
||||||
|
- name: 'example.org'
|
||||||
|
value: 'http://localhost:8008'
|
||||||
|
```
|
||||||
|
`name` must be the hostname of the URL that clients use when connecting to the Homeserver.
|
||||||
|
In case the hostname is the same as your Matrix domain, you can use `${matrix.domain}` to auto-populate the `value` using the `matrix.domain` configuration option and avoid duplicating it.
|
||||||
|
|
||||||
|
value is the base internal URL of the Homeserver, without any /_matrix/.. or trailing /.
|
||||||
|
|
||||||
|
#### Backends
|
||||||
|
The Backends should be configured as described in the documentation of the [Directory User](directory-users.md) feature.
|
1
docs/features/bridge-integration.md
Normal file
1
docs/features/bridge-integration.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
To be documented
|
231
docs/features/directory-users.md
Normal file
231
docs/features/directory-users.md
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
# User Directory
|
||||||
|
- [Description](#description)
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Requirements](#requirements)
|
||||||
|
- [Configuration](#configuration)
|
||||||
|
- [Reverse Proxy](#reverse-proxy)
|
||||||
|
- [Apache2](#apache2)
|
||||||
|
- [nginx](#nginx)
|
||||||
|
- [DNS Overwrite](#dns-overwrite)
|
||||||
|
- [Backends](#backends)
|
||||||
|
- [LDAP](#ldap)
|
||||||
|
- [SQL](#sql)
|
||||||
|
- [REST](#rest)
|
||||||
|
- [Next steps](#next-steps)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
This feature allows you to search for existing and/or potential users that are already present in your Identity backend
|
||||||
|
or that already share a room with you on the Homeserver.
|
||||||
|
|
||||||
|
Without any integration, synapse:
|
||||||
|
- Only search within the users **already** known to you
|
||||||
|
- 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
|
||||||
|
The specific configuration to put under the relevant `VirtualHost`:
|
||||||
|
```
|
||||||
|
ProxyPass /_matrix/client/r0/user_directory/ http://0.0.0.0:8090/_matrix/client/r0/user_directory/
|
||||||
|
```
|
||||||
|
`ProxyPreserveHost` or equivalent must be enabled to detect to which Homeserver mxisd should talk to when building
|
||||||
|
results.
|
||||||
|
|
||||||
|
Your `VirtualHost` should now look like this:
|
||||||
|
```
|
||||||
|
<VirtualHost *:443>
|
||||||
|
ServerName example.org
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
ProxyPreserveHost on
|
||||||
|
ProxyPass /_matrix/client/r0/user_directory/ http://localhost:8090/_matrix/client/r0/user_directory/
|
||||||
|
ProxyPass /_matrix/identity/ http://localhost:8090/_matrix/identity/
|
||||||
|
ProxyPass /_matrix/ http://localhost:8008/_matrix/
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### nginx
|
||||||
|
The specific configuration to add under your `server` section is:
|
||||||
|
```
|
||||||
|
location /_matrix/client/r0/user_directory {
|
||||||
|
proxy_pass http://0.0.0.0:8090/_matrix/client/r0/user_directory;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Your `server` section should now look like this:
|
||||||
|
```
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name example.org;
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
location /_matrix/client/r0/user_directory {
|
||||||
|
proxy_pass http://localhost:8090/_matrix/client/r0/user_directory;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /_matrix/identity {
|
||||||
|
proxy_pass http://localhost:8090/_matrix/identity;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /_matrix {
|
||||||
|
proxy_pass http://localhost:8008/_matrix;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DNS Overwrite
|
||||||
|
Just like you need to configure a reverse proxy to send client requests to mxisd, you also need to configure mxisd with
|
||||||
|
the internal IP of the Homeserver so it can talk to it directly to integrate its directory search.
|
||||||
|
|
||||||
|
To do so, use the following configuration:
|
||||||
|
```
|
||||||
|
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)
|
||||||
|
|
||||||
|
#### Wordpress
|
||||||
|
See the [dedicated document](../backends/wordpress.md)
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
### Homeserver results
|
||||||
|
You can configure if the Homeserver should be queried at all when doing a directory search.
|
||||||
|
To disable Homeserver results, set the following in mxisd config file:
|
||||||
|
```
|
||||||
|
directory.exclude.homeserever: true
|
||||||
|
```
|
33
docs/features/federation.md
Normal file
33
docs/features/federation.md
Normal 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.
|
18
docs/features/identity.md
Normal file
18
docs/features/identity.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Matrix Identity Service
|
||||||
|
**WARNING**: This document is incomplete and can be missleading.
|
||||||
|
|
||||||
|
Implementation of the [Unofficial Matrix Identity Service API](https://kamax.io/matrix/api/identity_service/unstable.html).
|
||||||
|
|
||||||
|
## Invitation
|
||||||
|
Resolution can be customized using the following configuration:
|
||||||
|
|
||||||
|
`invite.resolution.recursive`
|
||||||
|
- Default value: `true`
|
||||||
|
- Description: Control if the pending invite resolution should be done recursively or not.
|
||||||
|
**DANGER ZONE:** This setting has the potential to create "an isolated island", which can have unexpected side effects
|
||||||
|
and break invites in rooms. This will most likely not have the effect you think it does. Only change the value if you
|
||||||
|
understand the consequences.
|
||||||
|
|
||||||
|
`invite.resolution.timer`
|
||||||
|
- Default value: `1`
|
||||||
|
- Description: How often, in minutes, mxisd should try to resolve pending invites.
|
12
docs/features/profile.md
Normal file
12
docs/features/profile.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Profile enhancement
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Reverse proxy
|
||||||
|
|
||||||
|
#### Apache
|
||||||
|
```
|
||||||
|
ProxyPassMatch "^/_matrix/client/r0/profile/([^/]+)$" "http://127.0.0.1:8090/_matrix/client/r0/profile/$1"
|
||||||
|
ProxyPassMatch "^/_matrix/client/r0/profile/([^/]+)/(.+)" "http://127.0.0.1:8008/_matrix/client/r0/profile/$1/$2"
|
||||||
|
|
||||||
|
```
|
146
docs/getting-started.md
Normal file
146
docs/getting-started.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# Getting started
|
||||||
|
1. [Preparation](#preparation)
|
||||||
|
2. [Install](#install)
|
||||||
|
3. [Configure](#configure)
|
||||||
|
4. [Integrate](#integrate)
|
||||||
|
5. [Validate](#validate)
|
||||||
|
6. [Next steps](#next-steps)
|
||||||
|
|
||||||
|
Following these quick start instructions, you will have a basic setup that can perform recursive/federated lookups and
|
||||||
|
talk to the central Matrix.org Identity service.
|
||||||
|
This will be a good ground work for further integration with your existing Identity stores.
|
||||||
|
|
||||||
|
## Preparation
|
||||||
|
You will need:
|
||||||
|
- Homeserver
|
||||||
|
- Reverse proxy with regular TLS/SSL certificate (Let's encrypt) for your mxisd domain
|
||||||
|
|
||||||
|
As synapse requires an HTTPS connection when talking to an Identity service, a reverse proxy is required as mxisd does
|
||||||
|
not support HTTPS listener at this time.
|
||||||
|
|
||||||
|
For maximum integration, it is best to have your Homeserver and mxisd reachable via the same hostname.
|
||||||
|
You can also use a dedicated domain for mxisd, but will not have access to some features.
|
||||||
|
|
||||||
|
Be aware of a [NAT/Reverse proxy gotcha](https://github.com/kamax-io/mxisd/wiki/Gotchas#nating) if you use the same
|
||||||
|
hostname.
|
||||||
|
|
||||||
|
The following Quick Start guide assumes you will host the Homeserver and mxisd under the same hostname.
|
||||||
|
If you would like a high-level view of the infrastructure and how each feature is integrated, see the
|
||||||
|
[dedicated document](architecture.md)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
Install via:
|
||||||
|
- [Debian package](install/debian.md)
|
||||||
|
- [Docker image](install/docker.md)
|
||||||
|
- [Sources](build.md)
|
||||||
|
|
||||||
|
See the [Latest release](https://github.com/kamax-io/mxisd/releases/latest) for links to each.
|
||||||
|
|
||||||
|
## Configure
|
||||||
|
**NOTE**: please view the install instruction for your platform, as this step might be optional/handled for you.
|
||||||
|
|
||||||
|
Create/edit a minimal configuration (see installer doc for the location):
|
||||||
|
```
|
||||||
|
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.)
|
||||||
|
|
||||||
|
If your HS/mxisd hostname is not the same as your Matrix domain, configure `server.name`.
|
||||||
|
Complete configuration guide is available [here](configure.md).
|
||||||
|
|
||||||
|
## Integrate
|
||||||
|
For an overview of a typical mxisd infrastructure, see the [dedicated document](architecture.md)
|
||||||
|
### Reverse proxy
|
||||||
|
#### Apache2
|
||||||
|
In the `VirtualHost` section handling the domain with SSL, add the following and replace `0.0.0.0` by the internal
|
||||||
|
hostname/IP pointing to mxisd.
|
||||||
|
**This line MUST be present before the one for the homeserver!**
|
||||||
|
```
|
||||||
|
ProxyPass /_matrix/identity/ http://0.0.0.0:8090/_matrix/identity/
|
||||||
|
```
|
||||||
|
|
||||||
|
Typical configuration would look like:
|
||||||
|
```
|
||||||
|
<VirtualHost *:443>
|
||||||
|
ServerName example.org
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
ProxyPreserveHost on
|
||||||
|
ProxyPass /_matrix/identity/ http://localhost:8090/_matrix/identity/
|
||||||
|
ProxyPass /_matrix/ http://localhost:8008/_matrix/
|
||||||
|
</VirtualHost>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### nginx
|
||||||
|
In the `server` section handling the domain with SSL, add the following and replace `0.0.0.0` with the internal
|
||||||
|
hostname/IP pointing to mxisd.
|
||||||
|
**This line MUST be present before the one for the homeserver!**
|
||||||
|
```
|
||||||
|
location /_matrix/identity {
|
||||||
|
proxy_pass http://0.0.0.0:8090/_matrix/identity;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Typical configuration would look like:
|
||||||
|
```
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name example.org;
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
location /_matrix/identity {
|
||||||
|
proxy_pass http://localhost:8090/_matrix/identity;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /_matrix {
|
||||||
|
proxy_pass http://localhost:8008/_matrix;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Synapse
|
||||||
|
Add your mxisd domain into the `homeserver.yaml` at `trusted_third_party_id_servers` and restart synapse.
|
||||||
|
In a typical configuration, you would end up with something similair to:
|
||||||
|
```
|
||||||
|
trusted_third_party_id_servers:
|
||||||
|
- matrix.org
|
||||||
|
- vector.im
|
||||||
|
- example.org
|
||||||
|
```
|
||||||
|
It is recommended to remove `matrix.org` and `vector.im` so only your own Identity server is authoritative for your HS.
|
||||||
|
|
||||||
|
## Validate
|
||||||
|
Log in using your Matrix client and set `https://example.org` as your Identity server URL, replacing `example.org` by
|
||||||
|
the relevant hostname which you configured in your reverse proxy.
|
||||||
|
Invite `mxisd-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](README.md) guides.
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
Once your mxisd server is up and running, here are the next steps to further enhance and integrate your installation:
|
||||||
|
|
||||||
|
Enable extra features:
|
||||||
|
- [Federation](features/federation.md)
|
||||||
|
- [Authenticate with synapse](features/authentication.md), profile auto-provisioning if you wish
|
||||||
|
- [Directory search](features/directory-users.md)
|
||||||
|
|
||||||
|
Use your Identity stores:
|
||||||
|
- [LDAP / Samba / Active directory](backends/ldap.md)
|
||||||
|
- [SQL Database](backends/sql.md)
|
||||||
|
- [Website / Web service / Web app](backends/rest.md)
|
||||||
|
- [Google Firebase](backends/firebase.md)
|
||||||
|
- [Wordpress](backends/wordpress.md)
|
39
docs/install/debian.md
Normal file
39
docs/install/debian.md
Normal 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
|
||||||
|
```
|
22
docs/install/docker.md
Normal file
22
docs/install/docker.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Docker
|
||||||
|
## Fetch
|
||||||
|
Pull the latest stable image:
|
||||||
|
```
|
||||||
|
docker pull kamax/mxisd
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configure
|
||||||
|
On first run, simply using `MATRIX_DOMAIN` as an environment variable will create a default config for you.
|
||||||
|
You can also provide a configuration file named `mxisd.yaml` in the volume mapped to `/etc/mxisd` before starting your
|
||||||
|
container.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
Use the following command after adapting to your needs:
|
||||||
|
- The `MATRIX_DOMAIN` environment variable to yours
|
||||||
|
- The volumes host paths
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --rm -e MATRIX_DOMAIN=example.org -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t kamax/mxisd
|
||||||
|
```
|
||||||
|
|
||||||
|
For more info, including the list of possible tags, see [the public repository](https://hub.docker.com/r/kamax/mxisd/)
|
82
docs/sessions/3pid-views.md
Normal file
82
docs/sessions/3pid-views.md
Normal 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.
|
@@ -6,8 +6,10 @@
|
|||||||
- [Session scope](#session-scope)
|
- [Session scope](#session-scope)
|
||||||
- [Notifications](#notifications)
|
- [Notifications](#notifications)
|
||||||
- [Email](#email)
|
- [Email](#email)
|
||||||
|
- [Phone numbers](#msisdn-phone-numbers)
|
||||||
- [Usage](#usage)
|
- [Usage](#usage)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
|
- [Web views](#web-views)
|
||||||
- [Scenarios](#scenarios)
|
- [Scenarios](#scenarios)
|
||||||
- [Default](#default)
|
- [Default](#default)
|
||||||
- [Local sessions only](#local-sessions-only)
|
- [Local sessions only](#local-sessions-only)
|
||||||
@@ -15,7 +17,7 @@
|
|||||||
- [Sessions disabled](#sessions-disabled)
|
- [Sessions disabled](#sessions-disabled)
|
||||||
|
|
||||||
## Overview
|
## 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.
|
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
|
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
|
### Email
|
||||||
Generators:
|
Generators:
|
||||||
- Template
|
- [Template](../threepids/notifications/template-generator.md)
|
||||||
|
|
||||||
Connectors:
|
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
|
## Usage
|
||||||
### Configuration
|
### Configuration
|
||||||
@@ -114,29 +122,22 @@ Please refer to the full example config file to see which keys are mandatory and
|
|||||||
matrix:
|
matrix:
|
||||||
identity:
|
identity:
|
||||||
servers:
|
servers:
|
||||||
root: # Not to be included in config! Already present in default config!
|
configExample: # Not to be included in config! Already present in default config!
|
||||||
- 'https://matrix.org'
|
- 'https://example.org'
|
||||||
|
|
||||||
|
|
||||||
threepid:
|
threepid:
|
||||||
medium:
|
medium:
|
||||||
email:
|
email:
|
||||||
connector: 'smtp'
|
connector: 'example1' # Not to be included in config! Already present in default config!
|
||||||
generator: 'template'
|
generator: 'example2' # Not to be included in config! Already present in default config!
|
||||||
connectors:
|
connectors:
|
||||||
smtp:
|
example1:
|
||||||
host: ''
|
|
||||||
port: 587
|
|
||||||
tls: 1
|
|
||||||
login: ''
|
|
||||||
password: ''
|
|
||||||
generators:
|
generators:
|
||||||
template: # Not to be included in config! Already present in default config!
|
example1:
|
||||||
invite: 'classpath:email/invite-template.eml'
|
key: "value"
|
||||||
session:
|
example2:
|
||||||
validation:
|
key: "value"
|
||||||
local: 'classpath:email/validate-local-template.eml'
|
|
||||||
remote: 'classpath:email/validate-remote-template.eml'
|
|
||||||
|
|
||||||
session:
|
session:
|
||||||
policy:
|
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.
|
`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.
|
**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.
|
- `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.
|
- `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.
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -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
|
If both `toLocal` and `toRemote` are enabled, the user will be offered to initiate a remote session once their 3PID
|
||||||
locally validated.
|
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
|
### Scenarios
|
||||||
It is important to keep in mind that mxisd does not create bindings, irrelevant if a user added a 3PID to their profile.
|
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.
|
Instead, when queried for bindings, mxisd will query Identity backends which are responsible to store this kind of information.
|
||||||
@@ -273,14 +276,14 @@ session:
|
|||||||
**IMPORTANT**: When using local-only mode, you will also need to link mxisd to synapse if you want user searches and invites to work.
|
**IMPORTANT**: When using local-only mode, you will also need to link mxisd to synapse if you want user searches and invites to work.
|
||||||
To do so, add/edit the following configuration keys:
|
To do so, add/edit the following configuration keys:
|
||||||
```
|
```
|
||||||
sql:
|
synapseSql:
|
||||||
enabled: true
|
enabled: true
|
||||||
type: 'postgresql'
|
type: 'SET TO PROPER VALUE'
|
||||||
connection: ''
|
connection: 'SET TO PROPER VALUE'
|
||||||
```
|
```
|
||||||
- `sql.enabled` set to `true` to activate the SQL backend.
|
- `synapseSql.enabled` set to `true` to activate the SQL backend.
|
||||||
- `sql.type` can be set to `sqlite` or `postgresql`, depending on your synapse setup.
|
- `synapseSql.type` can be set to `sqlite` or `postgresql`, depending on your synapse setup.
|
||||||
- `sql.connection` use a JDBC format which is appened after the `jdbc:type:` connection URI.
|
- `synapseSql.connection` use a JDBC format which is appened after the `jdbc:type:` connection URI.
|
||||||
Example values for each type:
|
Example values for each type:
|
||||||
- `sqlite`: `/path/to/homeserver.db`
|
- `sqlite`: `/path/to/homeserver.db`
|
||||||
- `postgresql`: `//localhost/database?user=synapse&password=synapse`
|
- `postgresql`: `//localhost/database?user=synapse&password=synapse`
|
||||||
|
19
docs/threepids/medium/email/smtp-connector.md
Normal file
19
docs/threepids/medium/email/smtp-connector.md
Normal 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'
|
||||||
|
```
|
15
docs/threepids/medium/msisdn/twilio-connector.md
Normal file
15
docs/threepids/medium/msisdn/twilio-connector.md
Normal 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'
|
||||||
|
|
||||||
|
```
|
66
docs/threepids/notifications/basic-handler.md
Normal file
66
docs/threepids/notifications/basic-handler.md
Normal 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'
|
||||||
|
```
|
9
docs/threepids/notifications/sendgrid-handler.md
Normal file
9
docs/threepids/notifications/sendgrid-handler.md
Normal 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'
|
||||||
|
```
|
73
docs/threepids/notifications/template-generator.md
Normal file
73
docs/threepids/notifications/template-generator.md
Normal 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. |
|
@@ -1,2 +1,25 @@
|
|||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dspring.config.location=/etc/mxisd/ -Dspring.config.name=mxisd -jar /mxisd.jar
|
if [[ -n "$CONF_FILE_PATH" ]] && [ ! -f "$CONF_FILE_PATH" ]; then
|
||||||
|
echo "Generating config file $CONF_FILE_PATH"
|
||||||
|
touch "CONF_FILE_PATH"
|
||||||
|
|
||||||
|
if [[ -n "$MATRIX_DOMAIN" ]]; then
|
||||||
|
echo "Setting matrix domain to $MATRIX_DOMAIN"
|
||||||
|
echo "matrix.domain: $MATRIX_DOMAIN" >> "$CONF_FILE_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$SIGN_KEY_PATH" ]]; then
|
||||||
|
echo "Setting signing key path to $SIGN_KEY_PATH"
|
||||||
|
echo "key.path: $SIGN_KEY_PATH" >> "$CONF_FILE_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$SQLITE_DATABASE_PATH" ]]; then
|
||||||
|
echo "Setting SQLite DB path to $SQLITE_DATABASE_PATH"
|
||||||
|
echo "storage.provider.sqlite.database: $SQLITE_DATABASE_PATH" >> "$CONF_FILE_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting mxisd..."
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dspring.config.location=/etc/mxisd/ -Dspring.config.name=mxisd -jar /mxisd.jar
|
||||||
|
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,108 +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.sql;
|
|
||||||
|
|
||||||
import io.kamax.matrix.MatrixID;
|
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
|
||||||
import io.kamax.mxisd.config.sql.SqlProviderConfig;
|
|
||||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
|
||||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
|
||||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
|
||||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.sql.*;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class SqlThreePidProvider implements IThreePidProvider {
|
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(SqlThreePidProvider.class);
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MatrixConfig mxCfg;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private SqlProviderConfig cfg;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return cfg.isEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLocal() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getPriority() {
|
|
||||||
return 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Connection getConn() throws SQLException {
|
|
||||||
return DriverManager.getConnection("jdbc:" + cfg.getType() + ":" + cfg.getConnection());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
|
||||||
log.info("SQL lookup");
|
|
||||||
String stmtSql = StringUtils.defaultIfBlank(cfg.getIdentity().getMedium().get(request.getType()), cfg.getIdentity().getQuery());
|
|
||||||
log.info("SQL query: {}", stmtSql);
|
|
||||||
try (PreparedStatement stmt = getConn().prepareStatement(stmtSql)) {
|
|
||||||
stmt.setString(1, request.getType().toLowerCase());
|
|
||||||
stmt.setString(2, request.getThreePid().toLowerCase());
|
|
||||||
|
|
||||||
ResultSet rSet = stmt.executeQuery();
|
|
||||||
while (rSet.next()) {
|
|
||||||
String uid = rSet.getString("uid");
|
|
||||||
log.info("Found match: {}", uid);
|
|
||||||
if (StringUtils.equals("uid", cfg.getIdentity().getType())) {
|
|
||||||
log.info("Resolving as localpart");
|
|
||||||
return Optional.of(new SingleLookupReply(request, new MatrixID(uid, mxCfg.getDomain())));
|
|
||||||
}
|
|
||||||
if (StringUtils.equals("mxid", cfg.getIdentity().getType())) {
|
|
||||||
log.info("Resolving as MXID");
|
|
||||||
return Optional.of(new SingleLookupReply(request, new MatrixID(uid)));
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Identity type is unknown, skipping");
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("No match found in SQL");
|
|
||||||
return Optional.empty();
|
|
||||||
} catch (SQLException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,58 +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.springframework.boot.context.properties.ConfigurationProperties
|
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@ConfigurationProperties(prefix = "lookup.recursive")
|
|
||||||
class RecursiveLookupConfig {
|
|
||||||
|
|
||||||
private boolean enabled
|
|
||||||
private List<String> allowedCidr
|
|
||||||
private RecursiveLookupBridgeConfig bridge
|
|
||||||
|
|
||||||
boolean isEnabled() {
|
|
||||||
return enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
void setEnabled(boolean enabled) {
|
|
||||||
this.enabled = enabled
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> getAllowedCidr() {
|
|
||||||
return allowedCidr
|
|
||||||
}
|
|
||||||
|
|
||||||
void setAllowedCidr(List<String> allowedCidr) {
|
|
||||||
this.allowedCidr = allowedCidr
|
|
||||||
}
|
|
||||||
|
|
||||||
RecursiveLookupBridgeConfig getBridge() {
|
|
||||||
return bridge
|
|
||||||
}
|
|
||||||
|
|
||||||
void setBridge(RecursiveLookupBridgeConfig bridge) {
|
|
||||||
this.bridge = bridge
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,85 +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 org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@ConfigurationProperties(prefix = "ldap.connection")
|
|
||||||
public class LdapConnectionConfig {
|
|
||||||
|
|
||||||
private boolean tls;
|
|
||||||
private String host;
|
|
||||||
private int port;
|
|
||||||
private String bindDn;
|
|
||||||
private String bindPassword;
|
|
||||||
private String baseDn;
|
|
||||||
|
|
||||||
public boolean isTls() {
|
|
||||||
return tls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTls(boolean tls) {
|
|
||||||
this.tls = tls;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHost() {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHost(String host) {
|
|
||||||
this.host = host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPort(int port) {
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBindDn() {
|
|
||||||
return bindDn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBindDn(String bindDn) {
|
|
||||||
this.bindDn = bindDn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBindPassword() {
|
|
||||||
return bindPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBindPassword(String bindPassword) {
|
|
||||||
this.bindPassword = bindPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBaseDn() {
|
|
||||||
return baseDn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBaseDn(String baseDn) {
|
|
||||||
this.baseDn = baseDn;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,89 +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.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import io.kamax.mxisd.auth.AuthManager;
|
|
||||||
import io.kamax.mxisd.auth.UserAuthResult;
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@CrossOrigin
|
|
||||||
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
|
||||||
public class AuthController {
|
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(AuthController.class);
|
|
||||||
|
|
||||||
private Gson gson = new Gson();
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private AuthManager mgr;
|
|
||||||
|
|
||||||
@RequestMapping(value = "/_matrix-internal/identity/v1/check_credentials", method = RequestMethod.POST)
|
|
||||||
public String checkCredentials(HttpServletRequest req) {
|
|
||||||
try {
|
|
||||||
JsonElement el = new JsonParser().parse(IOUtils.toString(req.getInputStream(), StandardCharsets.UTF_8));
|
|
||||||
if (!el.isJsonObject() || !el.getAsJsonObject().has("user")) {
|
|
||||||
throw new IllegalArgumentException("Missing user key");
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject authData = el.getAsJsonObject().get("user").getAsJsonObject();
|
|
||||||
if (!authData.has("id") || !authData.has("password")) {
|
|
||||||
throw new IllegalArgumentException("Missing id or password keys");
|
|
||||||
}
|
|
||||||
|
|
||||||
String id = authData.get("id").getAsString();
|
|
||||||
log.info("Requested to check credentials for {}", id);
|
|
||||||
String password = authData.get("password").getAsString();
|
|
||||||
|
|
||||||
UserAuthResult result = mgr.authenticate(id, password);
|
|
||||||
|
|
||||||
JsonObject authObj = new JsonObject();
|
|
||||||
authObj.addProperty("success", result.isSuccess());
|
|
||||||
if (result.isSuccess()) {
|
|
||||||
authObj.addProperty("mxid", result.getMxid());
|
|
||||||
authObj.addProperty("display_name", result.getDisplayName());
|
|
||||||
}
|
|
||||||
JsonObject obj = new JsonObject();
|
|
||||||
|
|
||||||
obj.add("authentication", authObj);
|
|
||||||
return gson.toJson(obj);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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.controller.v1;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
|
||||||
import io.kamax.mxisd.exception.InternalServerError;
|
|
||||||
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
|
|
||||||
import io.kamax.mxisd.exception.MatrixException;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
@ControllerAdvice
|
|
||||||
@ResponseBody
|
|
||||||
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
|
||||||
public class DefaultExceptionHandler {
|
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(DefaultExceptionHandler.class);
|
|
||||||
|
|
||||||
private static Gson gson = new Gson();
|
|
||||||
|
|
||||||
static String handle(String erroCode, String error) {
|
|
||||||
JsonObject obj = new JsonObject();
|
|
||||||
obj.addProperty("errcode", erroCode);
|
|
||||||
obj.addProperty("error", error);
|
|
||||||
return gson.toJson(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(InternalServerError.class)
|
|
||||||
public String handle(InternalServerError e, HttpServletResponse response) {
|
|
||||||
if (StringUtils.isNotBlank(e.getInternalReason())) {
|
|
||||||
log.error("Reference #{} - {}", e.getReference(), e.getInternalReason());
|
|
||||||
} else {
|
|
||||||
log.error("Reference #{}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return handleGeneric(e, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ExceptionHandler(MatrixException.class)
|
|
||||||
public String handleGeneric(MatrixException e, HttpServletResponse response) {
|
|
||||||
response.setStatus(e.getStatus());
|
|
||||||
return handle(e.getErrorCode(), e.getError());
|
|
||||||
}
|
|
||||||
|
|
||||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|
||||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
|
||||||
public String handle(MissingServletRequestParameterException e) {
|
|
||||||
return handle("M_INVALID_BODY", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|
||||||
@ExceptionHandler(MappingAlreadyExistsException.class)
|
|
||||||
public String handle(MappingAlreadyExistsException e) {
|
|
||||||
return handle("M_ALREADY_EXISTS", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
|
||||||
@ExceptionHandler(BadRequestException.class)
|
|
||||||
public String handle(BadRequestException e) {
|
|
||||||
return handle("M_BAD_REQUEST", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
|
||||||
@ExceptionHandler(RuntimeException.class)
|
|
||||||
public String handle(HttpServletRequest req, RuntimeException e) {
|
|
||||||
log.error("Unknown error when handling {}", req.getRequestURL(), e);
|
|
||||||
return handle(
|
|
||||||
"M_UNKNOWN",
|
|
||||||
StringUtils.defaultIfBlank(
|
|
||||||
e.getMessage(),
|
|
||||||
"An internal server error occured. If this error persists, please contact support with reference #" +
|
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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()))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -18,16 +18,16 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.kamax.mxisd
|
package io.kamax.mxisd;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
class MatrixIdentityServerApplication {
|
public class MatrixIdentityServerApplication {
|
||||||
|
|
||||||
static void main(String[] args) throws Exception {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(MatrixIdentityServerApplication.class, args)
|
SpringApplication.run(MatrixIdentityServerApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -21,8 +21,9 @@
|
|||||||
package io.kamax.mxisd.auth;
|
package io.kamax.mxisd.auth;
|
||||||
|
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.matrix._MatrixID;
|
import io.kamax.matrix._MatrixID;
|
||||||
import io.kamax.mxisd.ThreePid;
|
import io.kamax.matrix._ThreePid;
|
||||||
import io.kamax.mxisd.UserIdType;
|
import io.kamax.mxisd.UserIdType;
|
||||||
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||||
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
||||||
@@ -52,7 +53,7 @@ public class AuthManager {
|
|||||||
private InvitationManager invMgr;
|
private InvitationManager invMgr;
|
||||||
|
|
||||||
public UserAuthResult authenticate(String id, String password) {
|
public UserAuthResult authenticate(String id, String password) {
|
||||||
_MatrixID mxid = new MatrixID(id);
|
_MatrixID mxid = MatrixID.asAcceptable(id);
|
||||||
for (AuthenticatorProvider provider : providers) {
|
for (AuthenticatorProvider provider : providers) {
|
||||||
if (!provider.isEnabled()) {
|
if (!provider.isEnabled()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -63,22 +64,22 @@ public class AuthManager {
|
|||||||
|
|
||||||
String mxId;
|
String mxId;
|
||||||
if (UserIdType.Localpart.is(result.getId().getType())) {
|
if (UserIdType.Localpart.is(result.getId().getType())) {
|
||||||
mxId = new MatrixID(result.getId().getValue(), mxCfg.getDomain()).getId();
|
mxId = MatrixID.from(result.getId().getValue(), mxCfg.getDomain()).acceptable().getId();
|
||||||
} else if (UserIdType.MatrixID.is(result.getId().getType())) {
|
} else if (UserIdType.MatrixID.is(result.getId().getType())) {
|
||||||
mxId = new MatrixID(result.getId().getValue()).getId();
|
mxId = MatrixID.asAcceptable(result.getId().getValue()).getId();
|
||||||
} else {
|
} else {
|
||||||
log.warn("Unsupported User ID type {} for backend {}", result.getId().getType(), provider.getClass().getSimpleName());
|
log.warn("Unsupported User ID type {} for backend {}", result.getId().getType(), provider.getClass().getSimpleName());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
UserAuthResult authResult = new UserAuthResult().success(mxId, result.getProfile().getDisplayName());
|
UserAuthResult authResult = new UserAuthResult().success(result.getProfile().getDisplayName());
|
||||||
for (ThreePid pid : result.getProfile().getThreePids()) {
|
for (_ThreePid pid : result.getProfile().getThreePids()) {
|
||||||
authResult.withThreePid(pid.getMedium(), pid.getAddress());
|
authResult.withThreePid(pid.getMedium(), pid.getAddress());
|
||||||
}
|
}
|
||||||
log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName());
|
log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName());
|
||||||
for (ThreePid pid : authResult.getThreePids()) {
|
for (ThreePid pid : authResult.getThreePids()) {
|
||||||
log.info("Processing {} for {}", pid, id);
|
log.info("Processing {} for {}", pid, id);
|
||||||
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, authResult.getMxid()));
|
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, mxId));
|
||||||
}
|
}
|
||||||
|
|
||||||
invMgr.lookupMappingsForInvites();
|
invMgr.lookupMappingsForInvites();
|
@@ -20,31 +20,30 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.auth;
|
package io.kamax.mxisd.auth;
|
||||||
|
|
||||||
import io.kamax.matrix.ThreePidMedium;
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.mxisd.ThreePid;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class UserAuthResult {
|
public class UserAuthResult {
|
||||||
|
|
||||||
private boolean success;
|
private boolean success;
|
||||||
private String mxid;
|
|
||||||
private String displayName;
|
private String displayName;
|
||||||
private List<ThreePid> threePids = new ArrayList<>();
|
private String photo;
|
||||||
|
private Set<ThreePid> threePids = new HashSet<>();
|
||||||
|
|
||||||
public UserAuthResult failure() {
|
public UserAuthResult failure() {
|
||||||
success = false;
|
success = false;
|
||||||
mxid = null;
|
|
||||||
displayName = null;
|
displayName = null;
|
||||||
|
photo = null;
|
||||||
|
threePids.clear();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserAuthResult success(String mxid, String displayName) {
|
public UserAuthResult success(String displayName) {
|
||||||
setSuccess(true);
|
setSuccess(true);
|
||||||
setMxid(mxid);
|
|
||||||
setDisplayName(displayName);
|
setDisplayName(displayName);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
@@ -58,14 +57,6 @@ public class UserAuthResult {
|
|||||||
this.success = success;
|
this.success = success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMxid() {
|
|
||||||
return mxid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMxid(String mxid) {
|
|
||||||
this.mxid = mxid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
@@ -74,8 +65,12 @@ public class UserAuthResult {
|
|||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserAuthResult withThreePid(ThreePidMedium medium, String address) {
|
public String getPhoto() {
|
||||||
return withThreePid(medium.getId(), address);
|
return photo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhoto(String photo) {
|
||||||
|
this.photo = photo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserAuthResult withThreePid(String medium, String address) {
|
public UserAuthResult withThreePid(String medium, String address) {
|
||||||
@@ -84,8 +79,8 @@ public class UserAuthResult {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ThreePid> getThreePids() {
|
public Set<ThreePid> getThreePids() {
|
||||||
return Collections.unmodifiableList(threePids);
|
return Collections.unmodifiableSet(threePids);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -20,25 +20,25 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.auth.provider;
|
package io.kamax.mxisd.auth.provider;
|
||||||
|
|
||||||
import io.kamax.mxisd.ThreePid;
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.mxisd.UserID;
|
import io.kamax.mxisd.UserID;
|
||||||
import io.kamax.mxisd.UserIdType;
|
import io.kamax.mxisd.UserIdType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.Set;
|
||||||
|
|
||||||
public class BackendAuthResult {
|
public class BackendAuthResult {
|
||||||
|
|
||||||
public static class BackendAuthProfile {
|
public static class BackendAuthProfile {
|
||||||
|
|
||||||
private String displayName;
|
private String displayName;
|
||||||
private List<ThreePid> threePids = new ArrayList<>();
|
private Set<ThreePid> threePids = new HashSet<>();
|
||||||
|
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ThreePid> getThreePids() {
|
public Set<ThreePid> getThreePids() {
|
||||||
return threePids;
|
return threePids;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,20 +49,26 @@ public class BackendAuthResult {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void fail() {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
public static BackendAuthResult success(String id, UserIdType type, String displayName) {
|
public static BackendAuthResult success(String id, UserIdType type, String displayName) {
|
||||||
return success(id, type.getId(), displayName);
|
return success(id, type.getId(), displayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BackendAuthResult success(String id, String type, String displayName) {
|
public static BackendAuthResult success(String id, String type, String displayName) {
|
||||||
BackendAuthResult r = new BackendAuthResult();
|
BackendAuthResult r = new BackendAuthResult();
|
||||||
r.success = true;
|
r.succeed(id, type, displayName);
|
||||||
r.id = new UserID(type, id);
|
|
||||||
r.profile = new BackendAuthProfile();
|
|
||||||
r.profile.displayName = displayName;
|
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void succeed(String id, String type, String displayName) {
|
||||||
|
this.success = true;
|
||||||
|
this.id = new UserID(type, id);
|
||||||
|
this.profile.displayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
private Boolean success;
|
private Boolean success;
|
||||||
private UserID id;
|
private UserID id;
|
||||||
private BackendAuthProfile profile = new BackendAuthProfile();
|
private BackendAuthProfile profile = new BackendAuthProfile();
|
@@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2017 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://max.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.firebase;
|
||||||
|
|
||||||
|
import com.google.firebase.auth.UserInfo;
|
||||||
|
import com.google.i18n.phonenumbers.NumberParseException;
|
||||||
|
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.mxisd.UserIdType;
|
||||||
|
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||||
|
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class GoogleFirebaseAuthenticator extends GoogleFirebaseBackend implements AuthenticatorProvider {
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class);
|
||||||
|
|
||||||
|
private PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
||||||
|
|
||||||
|
public GoogleFirebaseAuthenticator(boolean isEnabled, String credsPath, String db) {
|
||||||
|
super(isEnabled, "AuthenticationProvider", credsPath, db);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitOnLatch(BackendAuthResult result, CountDownLatch l, String purpose) {
|
||||||
|
try {
|
||||||
|
l.await(30, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.warn("Interrupted while waiting for " + purpose);
|
||||||
|
result.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toEmail(BackendAuthResult result, String email) {
|
||||||
|
if (StringUtils.isBlank(email)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.withThreePid(new ThreePid(ThreePidMedium.Email.getId(), email));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toMsisdn(BackendAuthResult result, String phoneNumber) {
|
||||||
|
if (StringUtils.isBlank(phoneNumber)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String number = phoneUtil.format(
|
||||||
|
phoneUtil.parse(
|
||||||
|
phoneNumber,
|
||||||
|
null // No default region
|
||||||
|
),
|
||||||
|
PhoneNumberUtil.PhoneNumberFormat.E164
|
||||||
|
).substring(1); // We want without the leading +
|
||||||
|
result.withThreePid(new ThreePid(ThreePidMedium.PhoneNumber.getId(), number));
|
||||||
|
} catch (NumberParseException e) {
|
||||||
|
log.warn("Invalid phone number: {}", phoneNumber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitOnLatch(CountDownLatch l) {
|
||||||
|
try {
|
||||||
|
l.await(30, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.warn("Interrupted while waiting for Firebase auth check");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
|
||||||
|
if (!isEnabled()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Trying to authenticate {}", mxid);
|
||||||
|
|
||||||
|
final BackendAuthResult result = BackendAuthResult.failure();
|
||||||
|
|
||||||
|
String localpart = mxid.getLocalPart();
|
||||||
|
CountDownLatch l = new CountDownLatch(1);
|
||||||
|
getFirebase().verifyIdToken(password).addOnSuccessListener(token -> {
|
||||||
|
try {
|
||||||
|
if (!StringUtils.equals(localpart, token.getUid())) {
|
||||||
|
log.info("Failure to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", mxid, localpart, token.getUid());
|
||||||
|
result.fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), token.getName());
|
||||||
|
log.info("{} was successfully authenticated", mxid);
|
||||||
|
log.info("Fetching profile for {}", mxid);
|
||||||
|
CountDownLatch userRecordLatch = new CountDownLatch(1);
|
||||||
|
getFirebase().getUser(token.getUid()).addOnSuccessListener(user -> {
|
||||||
|
try {
|
||||||
|
toEmail(result, user.getEmail());
|
||||||
|
toMsisdn(result, user.getPhoneNumber());
|
||||||
|
|
||||||
|
for (UserInfo info : user.getProviderData()) {
|
||||||
|
toEmail(result, info.getEmail());
|
||||||
|
toMsisdn(result, info.getPhoneNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Got {} 3PIDs in profile", result.getProfile().getThreePids().size());
|
||||||
|
} finally {
|
||||||
|
userRecordLatch.countDown();
|
||||||
|
}
|
||||||
|
}).addOnFailureListener(e -> {
|
||||||
|
try {
|
||||||
|
log.warn("Unable to fetch Firebase user profile for {}", mxid);
|
||||||
|
result.fail();
|
||||||
|
} finally {
|
||||||
|
userRecordLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
waitOnLatch(result, userRecordLatch, "Firebase user profile");
|
||||||
|
} finally {
|
||||||
|
l.countDown();
|
||||||
|
}
|
||||||
|
}).addOnFailureListener(e -> {
|
||||||
|
try {
|
||||||
|
if (e instanceof IllegalArgumentException) {
|
||||||
|
log.info("Failure to authenticate {}: invalid firebase token", mxid);
|
||||||
|
} else {
|
||||||
|
log.info("Failure to authenticate {}: {}", mxid, e.getMessage(), e);
|
||||||
|
log.info("Exception", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.fail();
|
||||||
|
} finally {
|
||||||
|
l.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
waitOnLatch(result, l, "Firebase auth check");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -20,10 +20,17 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.backend.ldap;
|
package io.kamax.mxisd.backend.ldap;
|
||||||
|
|
||||||
|
import com.google.i18n.phonenumbers.NumberParseException;
|
||||||
|
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
import io.kamax.matrix._MatrixID;
|
import io.kamax.matrix._MatrixID;
|
||||||
import io.kamax.mxisd.UserIdType;
|
import io.kamax.mxisd.UserIdType;
|
||||||
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||||
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.ldap.LdapConfig;
|
||||||
|
import io.kamax.mxisd.util.GsonUtil;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.directory.api.ldap.model.cursor.CursorException;
|
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.CursorLdapReferralException;
|
||||||
@@ -35,17 +42,25 @@ import org.apache.directory.api.ldap.model.message.SearchScope;
|
|||||||
import org.apache.directory.ldap.client.api.LdapConnection;
|
import org.apache.directory.ldap.client.api.LdapConnection;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class LdapAuthProvider extends LdapGenericBackend implements AuthenticatorProvider {
|
public class LdapAuthProvider extends LdapBackend implements AuthenticatorProvider {
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(LdapAuthProvider.class);
|
private Logger log = LoggerFactory.getLogger(LdapAuthProvider.class);
|
||||||
|
|
||||||
private String getUidAttribute() {
|
private PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
|
||||||
return getCfg().getAttribute().getUid().getValue();
|
|
||||||
|
@Autowired
|
||||||
|
public LdapAuthProvider(LdapConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg, mxCfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -53,41 +68,57 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
|
|||||||
return getCfg().isEnabled();
|
return getCfg().isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<String> getMsisdn(String phoneNumber) {
|
||||||
|
try { // FIXME export into dedicated ThreePid class within SDK (copy from Firebase Auth)
|
||||||
|
return Optional.of(phoneUtil.format(
|
||||||
|
phoneUtil.parse(
|
||||||
|
phoneNumber,
|
||||||
|
null // No default region
|
||||||
|
),
|
||||||
|
PhoneNumberUtil.PhoneNumberFormat.E164
|
||||||
|
).substring(1)); // We want without the leading +
|
||||||
|
} catch (NumberParseException e) {
|
||||||
|
log.warn("Invalid phone number: {}", phoneNumber);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
|
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
|
||||||
log.info("Performing auth for {}", mxid);
|
log.info("Performing auth for {}", mxid);
|
||||||
|
|
||||||
LdapConnection conn = getConn();
|
|
||||||
try {
|
try (LdapConnection conn = getConn()) {
|
||||||
bind(conn);
|
bind(conn);
|
||||||
|
|
||||||
String uidType = getCfg().getAttribute().getUid().getType();
|
String uidType = getAt().getUid().getType();
|
||||||
String userFilterValue = StringUtils.equals(LdapThreePidProvider.UID, uidType) ? mxid.getLocalPart() : mxid.getId();
|
String userFilterValue = StringUtils.equals(LdapBackend.UID, uidType) ? mxid.getLocalPart() : mxid.getId();
|
||||||
if (StringUtils.isBlank(userFilterValue)) {
|
if (StringUtils.isBlank(userFilterValue)) {
|
||||||
log.warn("Username is empty, failing auth");
|
log.warn("Username is empty, failing auth");
|
||||||
return BackendAuthResult.failure();
|
return BackendAuthResult.failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
String userFilter = "(" + getCfg().getAttribute().getUid().getValue() + "=" + userFilterValue + ")";
|
String userFilter = "(" + getUidAtt() + "=" + userFilterValue + ")";
|
||||||
if (!StringUtils.isBlank(getCfg().getAuth().getFilter())) {
|
userFilter = buildWithFilter(userFilter, getCfg().getAuth().getFilter());
|
||||||
userFilter = "(&" + getCfg().getAuth().getFilter() + userFilter + ")";
|
|
||||||
}
|
Set<String> attributes = new HashSet<>();
|
||||||
EntryCursor cursor = conn.search(getCfg().getConn().getBaseDn(), userFilter, SearchScope.SUBTREE, getUidAttribute(), getCfg().getAttribute().getName());
|
attributes.add(getUidAtt());
|
||||||
try {
|
attributes.add(getAt().getName());
|
||||||
|
getAt().getThreepid().forEach((k, v) -> attributes.addAll(v));
|
||||||
|
String[] attArray = new String[attributes.size()];
|
||||||
|
attributes.toArray(attArray);
|
||||||
|
|
||||||
|
log.debug("Base DN: {}", getBaseDn());
|
||||||
|
log.debug("Query: {}", userFilter);
|
||||||
|
log.debug("Attributes: {}", GsonUtil.build().toJson(attArray));
|
||||||
|
|
||||||
|
try (EntryCursor cursor = conn.search(getBaseDn(), userFilter, SearchScope.SUBTREE, attArray)) {
|
||||||
while (cursor.next()) {
|
while (cursor.next()) {
|
||||||
Entry entry = cursor.get();
|
Entry entry = cursor.get();
|
||||||
String dn = entry.getDn().getName();
|
String dn = entry.getDn().getName();
|
||||||
log.info("Checking possible match, DN: {}", dn);
|
log.info("Checking possible match, DN: {}", dn);
|
||||||
|
|
||||||
Attribute attribute = entry.get(getUidAttribute());
|
if (!getAttribute(entry, getUidAtt()).isPresent()) {
|
||||||
if (attribute == null) {
|
|
||||||
log.info("DN {}: no attribute {}, skpping", dn, getUidAttribute());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String data = attribute.get().toString();
|
|
||||||
if (data.length() < 1) {
|
|
||||||
log.info("DN {}: empty attribute {}, skipping", getUidAttribute());
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,31 +130,40 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
|
|||||||
return BackendAuthResult.failure();
|
return BackendAuthResult.failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
Attribute nameAttribute = entry.get(getCfg().getAttribute().getName());
|
Attribute nameAttribute = entry.get(getAt().getName());
|
||||||
String name = nameAttribute != null ? nameAttribute.get().toString() : null;
|
String name = nameAttribute != null ? nameAttribute.get().toString() : null;
|
||||||
|
|
||||||
log.info("Authentication successful for {}", entry.getDn().getName());
|
log.info("Authentication successful for {}", entry.getDn().getName());
|
||||||
log.info("DN {} is a valid match", dn);
|
log.info("DN {} is a valid match", dn);
|
||||||
|
|
||||||
// TODO should we canonicalize the MXID?
|
// TODO should we canonicalize the MXID?
|
||||||
return BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, name);
|
BackendAuthResult result = BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, name);
|
||||||
|
log.info("Processing 3PIDs for profile");
|
||||||
|
getAt().getThreepid().forEach((k, v) -> {
|
||||||
|
log.info("Processing 3PID type {}", k);
|
||||||
|
v.forEach(attId -> {
|
||||||
|
List<String> values = getAttributes(entry, attId);
|
||||||
|
log.info("\tAttribute {} has {} value(s)", attId, values.size());
|
||||||
|
getAttributes(entry, attId).forEach(tpidValue -> {
|
||||||
|
if (ThreePidMedium.PhoneNumber.is(k)) {
|
||||||
|
tpidValue = getMsisdn(tpidValue).orElse(tpidValue);
|
||||||
|
}
|
||||||
|
result.withThreePid(new ThreePid(k, tpidValue));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
log.info("Found {} 3PIDs", result.getProfile().getThreePids().size());
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
} catch (CursorLdapReferralException e) {
|
} catch (CursorLdapReferralException e) {
|
||||||
log.warn("Entity for {} is only available via referral, skipping", mxid);
|
log.warn("Entity for {} is only available via referral, skipping", mxid);
|
||||||
} finally {
|
|
||||||
cursor.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("No match were found for {}", mxid);
|
log.info("No match were found for {}", mxid);
|
||||||
return BackendAuthResult.failure();
|
return BackendAuthResult.failure();
|
||||||
} catch (LdapException | IOException | CursorException e) {
|
} catch (LdapException | IOException | CursorException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
conn.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
160
src/main/java/io/kamax/mxisd/backend/ldap/LdapBackend.java
Normal file
160
src/main/java/io/kamax/mxisd/backend/ldap/LdapBackend.java
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2017 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://max.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.ldap;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.ldap.LdapConfig;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.directory.api.ldap.model.entry.Attribute;
|
||||||
|
import org.apache.directory.api.ldap.model.entry.AttributeUtils;
|
||||||
|
import org.apache.directory.api.ldap.model.entry.Entry;
|
||||||
|
import org.apache.directory.api.ldap.model.exception.LdapException;
|
||||||
|
import org.apache.directory.ldap.client.api.LdapConnection;
|
||||||
|
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public abstract class LdapBackend {
|
||||||
|
|
||||||
|
public static final String UID = "uid";
|
||||||
|
public static final String MATRIX_ID = "mxid";
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(LdapBackend.class);
|
||||||
|
|
||||||
|
private LdapConfig cfg;
|
||||||
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
|
public LdapBackend(LdapConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.mxCfg = mxCfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LdapConfig getCfg() {
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getBaseDn() {
|
||||||
|
return cfg.getConnection().getBaseDn();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected LdapConfig.Attribute getAt() {
|
||||||
|
return cfg.getAttribute();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getUidAtt() {
|
||||||
|
return getAt().getUid().getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected synchronized LdapConnection getConn() throws LdapException {
|
||||||
|
return new LdapNetworkConnection(cfg.getConnection().getHost(), cfg.getConnection().getPort(), cfg.getConnection().isTls());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void bind(LdapConnection conn) throws LdapException {
|
||||||
|
if (StringUtils.isBlank(cfg.getConnection().getBindDn()) && StringUtils.isBlank(cfg.getConnection().getBindPassword())) {
|
||||||
|
conn.anonymousBind();
|
||||||
|
} else {
|
||||||
|
conn.bind(cfg.getConnection().getBindDn(), cfg.getConnection().getBindPassword());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String buildWithFilter(String base, String filter) {
|
||||||
|
if (StringUtils.isBlank(filter)) {
|
||||||
|
return base;
|
||||||
|
} else {
|
||||||
|
return "(&" + filter + base + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String buildOrQuery(String value, List<String> attributes) {
|
||||||
|
if (attributes.size() < 1) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("(|");
|
||||||
|
attributes.forEach(s -> {
|
||||||
|
builder.append("(");
|
||||||
|
builder.append(s).append("=").append(value).append(")");
|
||||||
|
});
|
||||||
|
builder.append(")");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String buildOrQuery(String value, String... attributes) {
|
||||||
|
return buildOrQuery(value, Arrays.asList(attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildOrQueryWithFilter(String filter, String value, String... attributes) {
|
||||||
|
return buildWithFilter(buildOrQuery(value, attributes), filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildMatrixIdFromUid(String uid) {
|
||||||
|
String uidType = getCfg().getAttribute().getUid().getType();
|
||||||
|
if (StringUtils.equals(UID, uidType)) {
|
||||||
|
return "@" + uid + ":" + mxCfg.getDomain();
|
||||||
|
} else if (StringUtils.equals(MATRIX_ID, uidType)) {
|
||||||
|
return uid;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Bind type " + uidType + " is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> getAttribute(Entry entry, String attName) {
|
||||||
|
Attribute attribute = entry.get(attName);
|
||||||
|
if (attribute == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
String value = attribute.get().toString();
|
||||||
|
if (StringUtils.isBlank(value)) {
|
||||||
|
log.info("DN {}: empty attribute {}, skipping", attName);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAttributes(Entry entry, String attName) {
|
||||||
|
List<String> values = new ArrayList<>();
|
||||||
|
javax.naming.directory.Attribute att = AttributeUtils.toAttributes(entry).get(attName);
|
||||||
|
if (att == null) {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
NamingEnumeration<?> list = att.getAll();
|
||||||
|
while (list.hasMore()) {
|
||||||
|
values.add(list.next().toString());
|
||||||
|
}
|
||||||
|
} catch (NamingException e) {
|
||||||
|
log.warn("Error while processing LDAP attribute {}, result could be incomplete!", attName, e);
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2017 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://max.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.ldap;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.ldap.LdapConfig;
|
||||||
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
||||||
|
import io.kamax.mxisd.directory.IDirectoryProvider;
|
||||||
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
|
import io.kamax.mxisd.util.GsonUtil;
|
||||||
|
import org.apache.directory.api.ldap.model.cursor.CursorException;
|
||||||
|
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
|
||||||
|
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
|
||||||
|
import org.apache.directory.api.ldap.model.entry.Entry;
|
||||||
|
import org.apache.directory.api.ldap.model.exception.LdapException;
|
||||||
|
import org.apache.directory.api.ldap.model.message.SearchScope;
|
||||||
|
import org.apache.directory.ldap.client.api.LdapConnection;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class LdapDirectoryProvider extends LdapBackend implements IDirectoryProvider {
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(LdapDirectoryProvider.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public LdapDirectoryProvider(LdapConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg, mxCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return getCfg().isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected UserDirectorySearchResult search(String query, List<String> attributes) {
|
||||||
|
UserDirectorySearchResult result = new UserDirectorySearchResult();
|
||||||
|
result.setLimited(false);
|
||||||
|
|
||||||
|
try (LdapConnection conn = getConn()) {
|
||||||
|
bind(conn);
|
||||||
|
|
||||||
|
LdapConfig.Attribute atCfg = getCfg().getAttribute();
|
||||||
|
|
||||||
|
attributes = new ArrayList<>(attributes);
|
||||||
|
attributes.add(getUidAtt());
|
||||||
|
String[] attArray = new String[attributes.size()];
|
||||||
|
attributes.toArray(attArray);
|
||||||
|
String searchQuery = buildOrQueryWithFilter(getCfg().getDirectory().getFilter(), "*" + query + "*", attArray);
|
||||||
|
|
||||||
|
log.debug("Base DN: {}", getBaseDn());
|
||||||
|
log.debug("Query: {}", searchQuery);
|
||||||
|
log.debug("Attributes: {}", GsonUtil.build().toJson(attArray));
|
||||||
|
|
||||||
|
try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, attArray)) {
|
||||||
|
while (cursor.next()) {
|
||||||
|
Entry entry = cursor.get();
|
||||||
|
log.info("Found possible match, DN: {}", entry.getDn().getName());
|
||||||
|
getAttribute(entry, getUidAtt()).ifPresent(uid -> {
|
||||||
|
log.info("DN {} is a valid match", entry.getDn().getName());
|
||||||
|
try {
|
||||||
|
UserDirectorySearchResult.Result entryResult = new UserDirectorySearchResult.Result();
|
||||||
|
entryResult.setUserId(buildMatrixIdFromUid(uid));
|
||||||
|
getAttribute(entry, atCfg.getName()).ifPresent(entryResult::setDisplayName);
|
||||||
|
result.addResult(entryResult);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.warn("Bind was found but type {} is not supported", atCfg.getUid().getType());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (CursorLdapReferralException e) {
|
||||||
|
log.warn("An entry is only available via referral, skipping");
|
||||||
|
} catch (IOException | LdapException | CursorException e) {
|
||||||
|
throw new InternalServerError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchByDisplayName(String query) {
|
||||||
|
log.info("Performing LDAP directory search on display name using '{}'", query);
|
||||||
|
List<String> attributes = new ArrayList<>();
|
||||||
|
attributes.add(getAt().getName());
|
||||||
|
attributes.addAll(getCfg().getDirectory().getAttribute().getOther());
|
||||||
|
return search(query, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchBy3pid(String query) {
|
||||||
|
log.info("Performing LDAP directory search on 3PIDs using '{}'", query);
|
||||||
|
List<String> attributes = new ArrayList<>();
|
||||||
|
attributes.add(getAt().getName());
|
||||||
|
getCfg().getAttribute().getThreepid().forEach((k, v) -> attributes.addAll(v));
|
||||||
|
return search(query, attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2017 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://max.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.ldap;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.ldap.LdapConfig;
|
||||||
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||||
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
|
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||||
|
import io.kamax.mxisd.util.GsonUtil;
|
||||||
|
import org.apache.directory.api.ldap.model.cursor.CursorException;
|
||||||
|
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
|
||||||
|
import org.apache.directory.api.ldap.model.cursor.EntryCursor;
|
||||||
|
import org.apache.directory.api.ldap.model.entry.Entry;
|
||||||
|
import org.apache.directory.api.ldap.model.exception.LdapException;
|
||||||
|
import org.apache.directory.api.ldap.model.message.SearchScope;
|
||||||
|
import org.apache.directory.ldap.client.api.LdapConnection;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class LdapThreePidProvider extends LdapBackend implements IThreePidProvider {
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(LdapThreePidProvider.class);
|
||||||
|
|
||||||
|
public LdapThreePidProvider(LdapConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg, mxCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return getCfg().isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<String> lookup(LdapConnection conn, String medium, String value) {
|
||||||
|
Optional<String> tPidQueryOpt = getCfg().getIdentity().getQuery(medium);
|
||||||
|
if (!tPidQueryOpt.isPresent()) {
|
||||||
|
log.warn("{} is not a configured 3PID type for LDAP lookup", medium);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
// we merge 3PID specific query with global/specific filter, if one exists.
|
||||||
|
String tPidQuery = tPidQueryOpt.get().replaceAll(getCfg().getIdentity().getToken(), value);
|
||||||
|
String searchQuery = buildWithFilter(tPidQuery, getCfg().getIdentity().getFilter());
|
||||||
|
|
||||||
|
log.debug("Base DN: {}", getBaseDn());
|
||||||
|
log.debug("Query: {}", searchQuery);
|
||||||
|
log.debug("Attributes: {}", GsonUtil.build().toJson(getUidAtt()));
|
||||||
|
|
||||||
|
try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, getUidAtt())) {
|
||||||
|
while (cursor.next()) {
|
||||||
|
Entry entry = cursor.get();
|
||||||
|
log.info("Found possible match, DN: {}", entry.getDn().getName());
|
||||||
|
|
||||||
|
Optional<String> data = getAttribute(entry, getUidAtt());
|
||||||
|
if (!data.isPresent()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("DN {} is a valid match", entry.getDn().getName());
|
||||||
|
return Optional.of(buildMatrixIdFromUid(data.get()));
|
||||||
|
}
|
||||||
|
} catch (CursorLdapReferralException e) {
|
||||||
|
log.warn("3PID {} is only available via referral, skipping", value);
|
||||||
|
} catch (IOException | LdapException | CursorException e) {
|
||||||
|
throw new InternalServerError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||||
|
log.info("Performing LDAP lookup {} of type {}", request.getThreePid(), request.getType());
|
||||||
|
|
||||||
|
try (LdapConnection conn = getConn()) {
|
||||||
|
bind(conn);
|
||||||
|
return lookup(conn, request.getType(), request.getThreePid()).map(id -> new SingleLookupReply(request, id));
|
||||||
|
} catch (LdapException | IOException e) {
|
||||||
|
throw new InternalServerError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
||||||
|
log.info("Looking up {} mappings", mappings.size());
|
||||||
|
List<ThreePidMapping> mappingsFound = new ArrayList<>();
|
||||||
|
|
||||||
|
try (LdapConnection conn = getConn()) {
|
||||||
|
bind(conn);
|
||||||
|
|
||||||
|
for (ThreePidMapping mapping : mappings) {
|
||||||
|
try {
|
||||||
|
lookup(conn, mapping.getMedium(), mapping.getValue()).ifPresent(id -> {
|
||||||
|
mapping.setMxid(id);
|
||||||
|
mappingsFound.add(mapping);
|
||||||
|
});
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.warn("{} is not a supported 3PID type for LDAP lookup", mapping.getMedium());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (LdapException | IOException e) {
|
||||||
|
throw new InternalServerError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappingsFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.ldap.netiq;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.backend.ldap.LdapAuthProvider;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.ldap.netiq.NetIqLdapConfig;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class NetIqLdapAuthProvider extends LdapAuthProvider {
|
||||||
|
|
||||||
|
public NetIqLdapAuthProvider(NetIqLdapConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg, mxCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted
|
||||||
|
@Override
|
||||||
|
public String buildMatrixIdFromUid(String uid) {
|
||||||
|
return super.buildMatrixIdFromUid(uid).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.ldap.netiq;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.backend.ldap.LdapDirectoryProvider;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.ldap.netiq.NetIqLdapConfig;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class NetIqLdapDirectoryProvider extends LdapDirectoryProvider {
|
||||||
|
|
||||||
|
public NetIqLdapDirectoryProvider(NetIqLdapConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg, mxCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted
|
||||||
|
@Override
|
||||||
|
public String buildMatrixIdFromUid(String uid) {
|
||||||
|
return super.buildMatrixIdFromUid(uid).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.ldap.netiq;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.backend.ldap.LdapThreePidProvider;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.ldap.netiq.NetIqLdapConfig;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class NetIqLdapThreePidProvider extends LdapThreePidProvider {
|
||||||
|
|
||||||
|
public NetIqLdapThreePidProvider(NetIqLdapConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg, mxCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted
|
||||||
|
@Override
|
||||||
|
public String buildMatrixIdFromUid(String uid) {
|
||||||
|
return super.buildMatrixIdFromUid(uid).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.memory;
|
||||||
|
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.matrix._ThreePid;
|
||||||
|
import io.kamax.mxisd.UserIdType;
|
||||||
|
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||||
|
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.memory.MemoryIdentityConfig;
|
||||||
|
import io.kamax.mxisd.config.memory.MemoryStoreConfig;
|
||||||
|
import io.kamax.mxisd.config.memory.MemoryThreePid;
|
||||||
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
||||||
|
import io.kamax.mxisd.directory.IDirectoryProvider;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||||
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
|
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||||
|
import io.kamax.mxisd.profile.ProfileProvider;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class MemoryIdentityStore implements AuthenticatorProvider, IDirectoryProvider, IThreePidProvider, ProfileProvider {
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(MemoryIdentityStore.class);
|
||||||
|
|
||||||
|
private final MatrixConfig mxCfg;
|
||||||
|
private final MemoryStoreConfig cfg;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public MemoryIdentityStore(MatrixConfig mxCfg, MemoryStoreConfig cfg) {
|
||||||
|
this.mxCfg = mxCfg;
|
||||||
|
this.cfg = cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<MemoryIdentityConfig> findByUsername(String username) {
|
||||||
|
return cfg.getIdentities().stream()
|
||||||
|
.filter(id -> StringUtils.equals(id.getUsername(), username))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return cfg.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserDirectorySearchResult search(
|
||||||
|
Predicate<MemoryIdentityConfig> predicate,
|
||||||
|
Function<MemoryIdentityConfig, UserDirectorySearchResult.Result> mapper
|
||||||
|
) {
|
||||||
|
UserDirectorySearchResult search = new UserDirectorySearchResult();
|
||||||
|
cfg.getIdentities().stream().filter(predicate).map(mapper).forEach(search::addResult);
|
||||||
|
return search;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchByDisplayName(String query) {
|
||||||
|
return search(
|
||||||
|
entry -> StringUtils.containsIgnoreCase(entry.getUsername(), query),
|
||||||
|
entry -> {
|
||||||
|
UserDirectorySearchResult.Result result = new UserDirectorySearchResult.Result();
|
||||||
|
result.setUserId(MatrixID.from(entry.getUsername(), mxCfg.getDomain()).acceptable().getId());
|
||||||
|
result.setDisplayName(entry.getUsername());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchBy3pid(String query) {
|
||||||
|
return search(
|
||||||
|
entry -> entry.getThreepids().stream()
|
||||||
|
.anyMatch(tpid -> StringUtils.containsIgnoreCase(tpid.getAddress(), query)),
|
||||||
|
entry -> {
|
||||||
|
UserDirectorySearchResult.Result result = new UserDirectorySearchResult.Result();
|
||||||
|
result.setUserId(MatrixID.from(entry.getUsername(), mxCfg.getDomain()).acceptable().getId());
|
||||||
|
result.setDisplayName(entry.getUsername());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<_ThreePid> getThreepids(_MatrixID mxid) {
|
||||||
|
List<_ThreePid> l = new ArrayList<>();
|
||||||
|
findByUsername(mxid.getLocalPart()).ifPresent(c -> l.addAll(c.getThreepids()));
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getRoles(_MatrixID mxid) {
|
||||||
|
List<String> l = new ArrayList<>();
|
||||||
|
findByUsername(mxid.getLocalPart()).ifPresent(c -> l.addAll(c.getRoles()));
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return Integer.MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||||
|
logger.info("Performing lookup {} of type {}", request.getThreePid(), request.getType());
|
||||||
|
ThreePid req = new ThreePid(request.getType(), request.getThreePid());
|
||||||
|
for (MemoryIdentityConfig id : cfg.getIdentities()) {
|
||||||
|
for (MemoryThreePid threepid : id.getThreepids()) {
|
||||||
|
if (req.equals(new ThreePid(threepid.getMedium(), threepid.getAddress()))) {
|
||||||
|
return Optional.of(new SingleLookupReply(request, new MatrixID(id.getUsername(), mxCfg.getDomain())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
|
||||||
|
return findByUsername(mxid.getLocalPart()).map(id -> {
|
||||||
|
if (!StringUtils.equals(id.getUsername(), mxid.getLocalPart())) {
|
||||||
|
return BackendAuthResult.failure();
|
||||||
|
} else {
|
||||||
|
BackendAuthResult result = new BackendAuthResult();
|
||||||
|
id.getThreepids().forEach(tpid -> result.withThreePid(new ThreePid(tpid.getMedium(), tpid.getAddress())));
|
||||||
|
result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), "");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}).orElseGet(BackendAuthResult::failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2017 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://max.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.rest;
|
||||||
|
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.rest.RestBackendConfig;
|
||||||
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest;
|
||||||
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
||||||
|
import io.kamax.mxisd.directory.IDirectoryProvider;
|
||||||
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
|
import io.kamax.mxisd.util.RestClientUtils;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class RestDirectoryProvider extends RestProvider implements IDirectoryProvider {
|
||||||
|
|
||||||
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
|
public RestDirectoryProvider(RestBackendConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg);
|
||||||
|
this.mxCfg = mxCfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return cfg.isEnabled() && StringUtils.isNotBlank(cfg.getEndpoints().getDirectory());
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserDirectorySearchResult search(String by, String query) {
|
||||||
|
UserDirectorySearchRequest request = new UserDirectorySearchRequest(query);
|
||||||
|
request.setBy(by);
|
||||||
|
try (CloseableHttpResponse httpResponse = client.execute(RestClientUtils.post(cfg.getEndpoints().getDirectory(), request))) {
|
||||||
|
int status = httpResponse.getStatusLine().getStatusCode();
|
||||||
|
if (status < 200 || status >= 300) {
|
||||||
|
throw new InternalServerError("REST backend: Error: " + IOUtils.toString(httpResponse.getEntity().getContent(), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDirectorySearchResult response = parser.parse(httpResponse, UserDirectorySearchResult.class);
|
||||||
|
for (UserDirectorySearchResult.Result result : response.getResults()) {
|
||||||
|
result.setUserId(new MatrixID(result.getUserId(), mxCfg.getDomain()).getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InternalServerError("REST backend: I/O error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchByDisplayName(String query) {
|
||||||
|
return search("name", query);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchBy3pid(String query) {
|
||||||
|
return search("threepid", query);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -24,7 +24,7 @@ import io.kamax.matrix._MatrixID;
|
|||||||
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||||
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
||||||
import io.kamax.mxisd.config.ServerConfig;
|
import io.kamax.mxisd.config.ServerConfig;
|
||||||
import io.kamax.mxisd.config.sql.SqlProviderConfig;
|
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
|
||||||
import io.kamax.mxisd.invitation.InvitationManager;
|
import io.kamax.mxisd.invitation.InvitationManager;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -32,15 +32,15 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class SqlAuthProvider implements AuthenticatorProvider {
|
public class GenericSqlAuthProvider implements AuthenticatorProvider {
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(SqlAuthProvider.class);
|
private Logger log = LoggerFactory.getLogger(GenericSqlAuthProvider.class);
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private ServerConfig srvCfg;
|
private ServerConfig srvCfg;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SqlProviderConfig cfg;
|
private GenericSqlProviderConfig cfg;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private InvitationManager invMgr;
|
private InvitationManager invMgr;
|
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2017 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://max.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.sql;
|
||||||
|
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
|
||||||
|
import io.kamax.mxisd.config.sql.SqlConfig;
|
||||||
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
||||||
|
import io.kamax.mxisd.directory.IDirectoryProvider;
|
||||||
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult.Result;
|
||||||
|
|
||||||
|
public abstract class GenericSqlDirectoryProvider implements IDirectoryProvider {
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(GenericSqlDirectoryProvider.class);
|
||||||
|
|
||||||
|
protected SqlConfig cfg;
|
||||||
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
|
private SqlConnectionPool pool;
|
||||||
|
|
||||||
|
public GenericSqlDirectoryProvider(SqlConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.pool = new SqlConnectionPool(cfg);
|
||||||
|
this.mxCfg = mxCfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return cfg.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setParameters(PreparedStatement stmt, String searchTerm) throws SQLException {
|
||||||
|
for (int i = 1; i <= stmt.getParameterMetaData().getParameterCount(); i++) {
|
||||||
|
stmt.setString(i, searchTerm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Optional<Result> processRow(ResultSet rSet) throws SQLException {
|
||||||
|
Result item = new Result();
|
||||||
|
item.setUserId(rSet.getString(1));
|
||||||
|
item.setDisplayName(rSet.getString(2));
|
||||||
|
return Optional.of(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDirectorySearchResult search(String searchTerm, GenericSqlProviderConfig.Query query) {
|
||||||
|
try (Connection conn = pool.get()) {
|
||||||
|
log.info("Will execute query: {}", query.getValue());
|
||||||
|
try (PreparedStatement stmt = conn.prepareStatement(query.getValue())) {
|
||||||
|
setParameters(stmt, searchTerm);
|
||||||
|
|
||||||
|
try (ResultSet rSet = stmt.executeQuery()) {
|
||||||
|
UserDirectorySearchResult result = new UserDirectorySearchResult();
|
||||||
|
result.setLimited(false);
|
||||||
|
|
||||||
|
while (rSet.next()) {
|
||||||
|
processRow(rSet).ifPresent(e -> {
|
||||||
|
if (StringUtils.equalsIgnoreCase("localpart", query.getType())) {
|
||||||
|
e.setUserId(new MatrixID(e.getUserId(), mxCfg.getDomain()).getId());
|
||||||
|
}
|
||||||
|
result.addResult(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new InternalServerError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchByDisplayName(String searchTerm) {
|
||||||
|
log.info("Searching users by display name using '{}'", searchTerm);
|
||||||
|
return search(searchTerm, cfg.getDirectory().getQuery().getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchBy3pid(String searchTerm) {
|
||||||
|
log.info("Searching users by 3PID using '{}'", searchTerm);
|
||||||
|
return search(searchTerm, cfg.getDirectory().getQuery().getThreepid());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2017 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://max.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.sql;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class GenericSqlThreePidProvider extends SqlThreePidProvider {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public GenericSqlThreePidProvider(GenericSqlProviderConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg, mxCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2017 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://max.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.sql;
|
||||||
|
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.matrix._ThreePid;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.sql.SqlConfig;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||||
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
|
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||||
|
import io.kamax.mxisd.profile.ProfileProvider;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public abstract class SqlThreePidProvider implements IThreePidProvider, ProfileProvider {
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(SqlThreePidProvider.class);
|
||||||
|
|
||||||
|
private SqlConfig cfg;
|
||||||
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
|
private SqlConnectionPool pool;
|
||||||
|
|
||||||
|
public SqlThreePidProvider(SqlConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.pool = new SqlConnectionPool(cfg);
|
||||||
|
this.mxCfg = mxCfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return cfg.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||||
|
log.info("SQL lookup");
|
||||||
|
String stmtSql = StringUtils.defaultIfBlank(cfg.getIdentity().getMedium().get(request.getType()), cfg.getIdentity().getQuery());
|
||||||
|
log.info("SQL query: {}", stmtSql);
|
||||||
|
try (Connection conn = pool.get()) {
|
||||||
|
try (PreparedStatement stmt = conn.prepareStatement(stmtSql)) {
|
||||||
|
stmt.setString(1, request.getType().toLowerCase());
|
||||||
|
stmt.setString(2, request.getThreePid().toLowerCase());
|
||||||
|
|
||||||
|
try (ResultSet rSet = stmt.executeQuery()) {
|
||||||
|
while (rSet.next()) {
|
||||||
|
String uid = rSet.getString("uid");
|
||||||
|
log.info("Found match: {}", uid);
|
||||||
|
if (StringUtils.equals("uid", cfg.getIdentity().getType())) {
|
||||||
|
log.info("Resolving as localpart");
|
||||||
|
return Optional.of(new SingleLookupReply(request, new MatrixID(uid, mxCfg.getDomain())));
|
||||||
|
}
|
||||||
|
if (StringUtils.equals("mxid", cfg.getIdentity().getType())) {
|
||||||
|
log.info("Resolving as MXID");
|
||||||
|
return Optional.of(new SingleLookupReply(request, new MatrixID(uid)));
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Identity type is unknown, skipping");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("No match found in SQL");
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<_ThreePid> getThreepids(_MatrixID mxid) {
|
||||||
|
List<_ThreePid> threepids = new ArrayList<>();
|
||||||
|
|
||||||
|
String stmtSql = cfg.getProfile().getThreepid().getQuery();
|
||||||
|
try (Connection conn = pool.get()) {
|
||||||
|
PreparedStatement stmt = conn.prepareStatement(stmtSql);
|
||||||
|
stmt.setString(1, mxid.getId());
|
||||||
|
|
||||||
|
ResultSet rSet = stmt.executeQuery();
|
||||||
|
while (rSet.next()) {
|
||||||
|
String medium = rSet.getString("medium");
|
||||||
|
String address = rSet.getString("address");
|
||||||
|
threepids.add(new ThreePid(medium, address));
|
||||||
|
}
|
||||||
|
|
||||||
|
return threepids;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getRoles(_MatrixID mxid) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2017 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://max.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.sql;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
|
||||||
|
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
|
||||||
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SynapseSqlDirectoryProvider extends GenericSqlDirectoryProvider {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SynapseSqlDirectoryProvider(SynapseSqlProviderConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg, mxCfg);
|
||||||
|
|
||||||
|
if (StringUtils.equals("sqlite", cfg.getType())) {
|
||||||
|
String userId = "'@' || p.user_id || ':" + mxCfg.getDomain() + "'";
|
||||||
|
GenericSqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
|
||||||
|
queries.getName().setValue(
|
||||||
|
"select " + userId + ", displayname from profiles p where displayname like ?");
|
||||||
|
queries.getThreepid().setValue(
|
||||||
|
"select t.user_id, p.displayname " +
|
||||||
|
"from user_threepids t JOIN profiles p on t.user_id = " + userId + " " +
|
||||||
|
"where t.address like ?");
|
||||||
|
} else if (StringUtils.equals("postgresql", cfg.getType())) {
|
||||||
|
String userId = "concat('@',p.user_id,':" + mxCfg.getDomain() + "')";
|
||||||
|
GenericSqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
|
||||||
|
queries.getName().setValue(
|
||||||
|
"select " + userId + ", displayname from profiles p where displayname ilike ?");
|
||||||
|
queries.getThreepid().setValue(
|
||||||
|
"select t.user_id, p.displayname " +
|
||||||
|
"from user_threepids t JOIN profiles p on t.user_id = " + userId + " " +
|
||||||
|
"where t.address ilike ?");
|
||||||
|
} else {
|
||||||
|
throw new ConfigurationException("Invalid SQL type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setParameters(PreparedStatement stmt, String searchTerm) throws SQLException {
|
||||||
|
stmt.setString(1, "%" + searchTerm + "%");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2017 Maxime Dor
|
||||||
|
*
|
||||||
|
* https://max.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.sql;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class SynapseSqlThreePidProvider extends SqlThreePidProvider {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public SynapseSqlThreePidProvider(SynapseSqlProviderConfig cfg, MatrixConfig mxCfg) {
|
||||||
|
super(cfg, mxCfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.wordpress;
|
||||||
|
|
||||||
|
public class WordpressAuthData {
|
||||||
|
|
||||||
|
public String token;
|
||||||
|
private String userEmail;
|
||||||
|
private String userNicename;
|
||||||
|
private String userDisplayName;
|
||||||
|
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserEmail() {
|
||||||
|
return userEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserEmail(String userEmail) {
|
||||||
|
this.userEmail = userEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserNicename() {
|
||||||
|
return userNicename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserNicename(String userNicename) {
|
||||||
|
this.userNicename = userNicename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserDisplayName() {
|
||||||
|
return userDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserDisplayName(String userDisplayName) {
|
||||||
|
this.userDisplayName = userDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.wordpress;
|
||||||
|
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.mxisd.UserIdType;
|
||||||
|
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||||
|
import io.kamax.mxisd.auth.provider.BackendAuthResult;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WordpressAuthProvider implements AuthenticatorProvider {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(WordpressAuthProvider.class);
|
||||||
|
|
||||||
|
private WordpressRestBackend wordpress;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WordpressAuthProvider(WordpressRestBackend wordpress) {
|
||||||
|
this.wordpress = wordpress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return wordpress.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
|
||||||
|
try {
|
||||||
|
WordpressAuthData data = wordpress.authenticate(mxid.getLocalPart(), password);
|
||||||
|
BackendAuthResult result = new BackendAuthResult();
|
||||||
|
if (StringUtils.isNotBlank(data.getUserEmail())) {
|
||||||
|
result.withThreePid(new ThreePid("email", data.getUserEmail()));
|
||||||
|
}
|
||||||
|
result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), data.getUserDisplayName());
|
||||||
|
return result;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.error("Authentication failed for {}: {}", mxid.getId(), e.getMessage());
|
||||||
|
return BackendAuthResult.failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.wordpress;
|
||||||
|
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.wordpress.WordpressConfig;
|
||||||
|
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
|
||||||
|
import io.kamax.mxisd.directory.IDirectoryProvider;
|
||||||
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WordpressDirectoryProvider implements IDirectoryProvider {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(WordpressDirectoryProvider.class);
|
||||||
|
|
||||||
|
private WordpressConfig cfg;
|
||||||
|
private WordressSqlBackend wordpress;
|
||||||
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WordpressDirectoryProvider(WordpressConfig cfg, WordressSqlBackend wordpress, MatrixConfig mxCfg) {
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.wordpress = wordpress;
|
||||||
|
this.mxCfg = mxCfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return wordpress.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setParameters(PreparedStatement stmt, String searchTerm) throws SQLException {
|
||||||
|
for (int i = 1; i <= stmt.getParameterMetaData().getParameterCount(); i++) {
|
||||||
|
stmt.setString(i, "%" + searchTerm + "%");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Optional<UserDirectorySearchResult.Result> processRow(ResultSet rSet) throws SQLException {
|
||||||
|
UserDirectorySearchResult.Result item = new UserDirectorySearchResult.Result();
|
||||||
|
item.setUserId(rSet.getString(1));
|
||||||
|
item.setDisplayName(rSet.getString(2));
|
||||||
|
return Optional.of(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserDirectorySearchResult search(String searchTerm, String query) {
|
||||||
|
try (Connection conn = wordpress.getConnection()) {
|
||||||
|
log.info("Will execute query: {}", query);
|
||||||
|
try (PreparedStatement stmt = conn.prepareStatement(query)) {
|
||||||
|
setParameters(stmt, searchTerm);
|
||||||
|
|
||||||
|
try (ResultSet rSet = stmt.executeQuery()) {
|
||||||
|
UserDirectorySearchResult result = new UserDirectorySearchResult();
|
||||||
|
result.setLimited(false);
|
||||||
|
|
||||||
|
while (rSet.next()) {
|
||||||
|
processRow(rSet).ifPresent(e -> {
|
||||||
|
try {
|
||||||
|
e.setUserId(MatrixID.from(e.getUserId(), mxCfg.getDomain()).valid().getId());
|
||||||
|
result.addResult(e);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
log.warn("Ignoring result {} - Invalid characters for a Matrix ID", e.getUserId());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new InternalServerError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchByDisplayName(String searchTerm) {
|
||||||
|
log.info("Searching users by display name using '{}'", searchTerm);
|
||||||
|
return search(searchTerm, cfg.getSql().getQuery().getDirectory().get("name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDirectorySearchResult searchBy3pid(String searchTerm) {
|
||||||
|
log.info("Searching users by 3PID using '{}'", searchTerm);
|
||||||
|
return search(searchTerm, cfg.getSql().getQuery().getDirectory().get("threepid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.wordpress;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
import io.kamax.matrix.json.InvalidJsonException;
|
||||||
|
import io.kamax.mxisd.config.wordpress.WordpressConfig;
|
||||||
|
import io.kamax.mxisd.util.RestClientUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.client.methods.HttpRequestBase;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.util.EntityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WordpressRestBackend {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(WordpressRestBackend.class);
|
||||||
|
private final String jsonPath = "/wp-json";
|
||||||
|
private final String jwtPath = "/jwt-auth/v1";
|
||||||
|
|
||||||
|
private WordpressConfig cfg;
|
||||||
|
private CloseableHttpClient client;
|
||||||
|
|
||||||
|
private String jsonEndpoint;
|
||||||
|
private String jwtEndpoint;
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WordpressRestBackend(WordpressConfig cfg, CloseableHttpClient client) {
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.client = client;
|
||||||
|
|
||||||
|
if (!cfg.isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonEndpoint = cfg.getRest().getBase() + jsonPath;
|
||||||
|
jwtEndpoint = jsonEndpoint + jwtPath;
|
||||||
|
validateConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateConfig() {
|
||||||
|
log.info("Validating JWT auth endpoint");
|
||||||
|
try (CloseableHttpResponse res = client.execute(new HttpGet(jwtEndpoint))) {
|
||||||
|
int status = res.getStatusLine().getStatusCode();
|
||||||
|
if (status != 200) {
|
||||||
|
log.warn("JWT auth endpoint check failed: Got status code {}", status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String data = EntityUtils.toString(res.getEntity());
|
||||||
|
if (StringUtils.isBlank(data)) {
|
||||||
|
log.warn("JWT auth endpoint check failed: Got no/empty body data");
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject body = GsonUtil.parseObj(data);
|
||||||
|
if (!body.has("namespace")) {
|
||||||
|
log.warn("JWT auth endpoint check failed: invalid namespace");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("JWT auth endpoint check succeeded");
|
||||||
|
} catch (InvalidJsonException e) {
|
||||||
|
log.warn("JWT auth endpoint check failed: Invalid JSON response: {}", e.getMessage());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("JWT auth endpoint check failed: Could not read API endpoint: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return cfg.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WordpressAuthData authenticate(String username, String password) {
|
||||||
|
JsonObject body = new JsonObject();
|
||||||
|
body.addProperty("username", username);
|
||||||
|
body.addProperty("password", password);
|
||||||
|
HttpPost req = RestClientUtils.post(jwtEndpoint + "/token", body);
|
||||||
|
try (CloseableHttpResponse res = client.execute(req)) {
|
||||||
|
int status = res.getStatusLine().getStatusCode();
|
||||||
|
String bodyRes = EntityUtils.toString(res.getEntity());
|
||||||
|
if (status != 200) {
|
||||||
|
throw new IllegalArgumentException(bodyRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GsonUtil.get().fromJson(bodyRes, WordpressAuthData.class);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void authenticate() {
|
||||||
|
WordpressAuthData data = authenticate(
|
||||||
|
cfg.getRest().getCredential().getUsername(),
|
||||||
|
cfg.getRest().getCredential().getPassword());
|
||||||
|
log.info("Internal authentication: success, logged in as " + data.getUserNicename());
|
||||||
|
token = data.getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CloseableHttpResponse runRequest(HttpRequestBase request) throws IOException {
|
||||||
|
request.setHeader("Authorization", "Bearer " + token);
|
||||||
|
return client.execute(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CloseableHttpResponse withAuthentication(HttpRequestBase request) throws IOException {
|
||||||
|
CloseableHttpResponse response = runRequest(request);
|
||||||
|
if (response.getStatusLine().getStatusCode() == 403) { //FIXME we should check the JWT expiration time
|
||||||
|
authenticate();
|
||||||
|
response = runRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.wordpress;
|
||||||
|
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.wordpress.WordpressConfig;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||||
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
|
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WordpressThreePidProvider implements IThreePidProvider {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(WordpressThreePidProvider.class);
|
||||||
|
|
||||||
|
private MatrixConfig mxCfg;
|
||||||
|
private WordpressConfig cfg;
|
||||||
|
private WordressSqlBackend wordpress;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WordpressThreePidProvider(MatrixConfig mxCfg, WordpressConfig cfg, WordressSqlBackend wordpress) {
|
||||||
|
this.mxCfg = mxCfg;
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.wordpress = wordpress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return wordpress.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Optional<_MatrixID> find(ThreePid tpid) {
|
||||||
|
String query = cfg.getSql().getQuery().getThreepid().get(tpid.getMedium());
|
||||||
|
if (Objects.isNull(query)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection conn = wordpress.getConnection()) {
|
||||||
|
PreparedStatement stmt = conn.prepareStatement(query);
|
||||||
|
stmt.setString(1, tpid.getAddress());
|
||||||
|
|
||||||
|
try (ResultSet rSet = stmt.executeQuery()) {
|
||||||
|
while (rSet.next()) {
|
||||||
|
String uid = rSet.getString("uid");
|
||||||
|
log.info("Found match: {}", uid);
|
||||||
|
try {
|
||||||
|
return Optional.of(MatrixID.from(uid, mxCfg.getDomain()).valid());
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
log.warn("Ignoring match {} - Invalid characters for a Matrix ID", uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("No valid match found in Wordpress");
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||||
|
return find(new ThreePid(request.getType(), request.getThreePid())).map(mxid -> new SingleLookupReply(request, mxid));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
||||||
|
for (ThreePidMapping tpidMap : mappings) {
|
||||||
|
find(new ThreePid(tpidMap.getMedium(), tpidMap.getValue())).ifPresent(mxid -> tpidMap.setMxid(mxid.getId()));
|
||||||
|
}
|
||||||
|
return mappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.backend.wordpress;
|
||||||
|
|
||||||
|
import com.mchange.v2.c3p0.ComboPooledDataSource;
|
||||||
|
import io.kamax.mxisd.config.wordpress.WordpressConfig;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class WordressSqlBackend {
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(WordressSqlBackend.class);
|
||||||
|
|
||||||
|
private WordpressConfig cfg;
|
||||||
|
|
||||||
|
private ComboPooledDataSource ds;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public WordressSqlBackend(WordpressConfig cfg) {
|
||||||
|
this.cfg = cfg;
|
||||||
|
|
||||||
|
ds = new ComboPooledDataSource();
|
||||||
|
ds.setJdbcUrl("jdbc:" + cfg.getSql().getType() + ":" + cfg.getSql().getConnection());
|
||||||
|
ds.setMinPoolSize(1);
|
||||||
|
ds.setMaxPoolSize(10);
|
||||||
|
ds.setAcquireIncrement(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return cfg.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Connection getConnection() throws SQLException {
|
||||||
|
return ds.getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -20,33 +20,40 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.config;
|
package io.kamax.mxisd.config;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConfigurationProperties("dns.overwrite")
|
@ConfigurationProperties("directory")
|
||||||
public class DnsOverwrite {
|
public class DirectoryConfig {
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(DnsOverwrite.class);
|
private final transient Logger log = LoggerFactory.getLogger(DnsOverwriteConfig.class);
|
||||||
|
|
||||||
@Autowired
|
public static class Exclude {
|
||||||
private ServerConfig srvCfg;
|
|
||||||
|
|
||||||
@Autowired
|
private boolean homeserver;
|
||||||
private DnsOverwriteEntry homeserver;
|
|
||||||
|
|
||||||
public Optional<DnsOverwriteEntry> findHost(String lookup) {
|
public boolean getHomeserver() {
|
||||||
if (homeserver != null && StringUtils.equalsIgnoreCase(lookup, homeserver.getName())) {
|
return homeserver;
|
||||||
return Optional.of(homeserver);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Optional.empty();
|
public Exclude setHomeserver(boolean homeserver) {
|
||||||
|
this.homeserver = homeserver;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Exclude exclude = new Exclude();
|
||||||
|
|
||||||
|
public Exclude getExclude() {
|
||||||
|
return exclude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExclude(Exclude exclude) {
|
||||||
|
this.exclude = exclude;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
106
src/main/java/io/kamax/mxisd/config/DnsOverwriteConfig.java
Normal file
106
src/main/java/io/kamax/mxisd/config/DnsOverwriteConfig.java
Normal 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()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -71,7 +71,7 @@ public class FirebaseConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
private void postConstruct() {
|
public void build() {
|
||||||
log.info("--- Firebase configuration ---");
|
log.info("--- Firebase configuration ---");
|
||||||
log.info("Enabled: {}", isEnabled());
|
log.info("Enabled: {}", isEnabled());
|
||||||
if (isEnabled()) {
|
if (isEnabled()) {
|
||||||
@@ -82,20 +82,12 @@ public class FirebaseConfig {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public AuthenticatorProvider getAuthProvider() {
|
public AuthenticatorProvider getAuthProvider() {
|
||||||
if (!enabled) {
|
return new GoogleFirebaseAuthenticator(enabled, credentials, database);
|
||||||
return new GoogleFirebaseAuthenticator(false);
|
|
||||||
} else {
|
|
||||||
return new GoogleFirebaseAuthenticator(credentials, database, mxCfg.getDomain());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IThreePidProvider getLookupProvider() {
|
public IThreePidProvider getLookupProvider() {
|
||||||
if (!enabled) {
|
return new GoogleFirebaseProvider(enabled, credentials, database, mxCfg.getDomain());
|
||||||
return new GoogleFirebaseProvider(false);
|
|
||||||
} else {
|
|
||||||
return new GoogleFirebaseProvider(credentials, database, mxCfg.getDomain());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -18,23 +18,26 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.kamax.mxisd.config
|
package io.kamax.mxisd.config;
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConfigurationProperties(prefix = "forward")
|
@ConfigurationProperties(prefix = "forward")
|
||||||
class ForwardConfig {
|
public class ForwardConfig {
|
||||||
|
|
||||||
private List<String> servers = new ArrayList<>()
|
private List<String> servers = new ArrayList<>();
|
||||||
|
|
||||||
List<String> getServers() {
|
public List<String> getServers() {
|
||||||
return servers
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setServers(List<String> servers) {
|
public void setServers(List<String> servers) {
|
||||||
this.servers = servers
|
this.servers = servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
76
src/main/java/io/kamax/mxisd/config/InvitationConfig.java
Normal file
76
src/main/java/io/kamax/mxisd/config/InvitationConfig.java
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2018 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.config;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties("invite")
|
||||||
|
public class InvitationConfig {
|
||||||
|
|
||||||
|
private final Logger log = LoggerFactory.getLogger(InvitationConfig.class);
|
||||||
|
|
||||||
|
public static class Resolution {
|
||||||
|
|
||||||
|
private boolean recursive;
|
||||||
|
private long timer;
|
||||||
|
|
||||||
|
public boolean isRecursive() {
|
||||||
|
return recursive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRecursive(boolean recursive) {
|
||||||
|
this.recursive = recursive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimer() {
|
||||||
|
return timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTimer(long timer) {
|
||||||
|
this.timer = timer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private Resolution resolution;
|
||||||
|
|
||||||
|
public Resolution getResolution() {
|
||||||
|
return resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResolution(Resolution resolution) {
|
||||||
|
this.resolution = resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void build() {
|
||||||
|
log.info("--- Invite config ---");
|
||||||
|
log.info("Resolution: {}", GsonUtil.build().toJson(resolution));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -18,32 +18,33 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.kamax.mxisd.config
|
package io.kamax.mxisd.config;
|
||||||
|
|
||||||
import io.kamax.mxisd.exception.ConfigurationException
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
import org.apache.commons.lang.StringUtils
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.springframework.beans.factory.InitializingBean
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Configuration
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConfigurationProperties(prefix = "key")
|
@ConfigurationProperties(prefix = "key")
|
||||||
class KeyConfig implements InitializingBean {
|
public class KeyConfig {
|
||||||
|
|
||||||
private String path
|
private String path;
|
||||||
|
|
||||||
void setPath(String path) {
|
public void setPath(String path) {
|
||||||
this.path = path
|
this.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPath() {
|
public String getPath() {
|
||||||
return path
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@PostConstruct
|
||||||
void afterPropertiesSet() throws Exception {
|
public void build() {
|
||||||
if (StringUtils.isBlank(getPath())) {
|
if (StringUtils.isBlank(getPath())) {
|
||||||
throw new ConfigurationException("key.path")
|
throw new ConfigurationException("key.path");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user