Compare commits

...

17 Commits

Author SHA1 Message Date
Max Dor
bd9161ec9b Better handle of synapse SQL connection
- Do not fail if it is not configured
- Add missing configuration step
2018-10-18 20:59:06 +02:00
Max Dor
544cab816c Use the actual NetIQ config for its profile provider 2018-10-16 21:28:38 +02:00
Max Dor
cdb56aec1f Add documentation for new AS Notification/Profile feature 2018-10-16 21:28:38 +02:00
Max Dor
407138e972 Add LDAP support Matrix ID room invites notifications 2018-10-16 21:28:38 +02:00
Max Dor
3eee4eaccf Add extra placeholders for Matrix ID room invites notifications
- Sender display name, if available
- Room name, if available
2018-10-16 21:28:38 +02:00
Max Dor
b3aefbed77 Add support for 3PID notification for Matrix ID room invites
- Experimental feature
- Via AS API
2018-10-16 21:28:38 +02:00
Max Dor
843fa04f19 Update links to new repo org 2018-10-12 16:21:29 +02:00
Max Dor
f7d1a300f1 Fix #69 2018-10-10 02:10:48 +02:00
Max Dor
f16eb264be Fix for #72 2018-10-10 01:59:15 +02:00
Max Dor
f29014be1f Fix some logging statements 2018-09-30 17:41:18 +02:00
Max Dor
0c0feab0c0 Improve docs 2018-09-19 22:29:20 +02:00
Max Dor
dd313881db Fix repositories order
Repositories are attempted in order listed. This change optimize the
order so central repos are attempting before custom ones.
2018-09-19 22:28:25 +02:00
Max Dor
feb37112b2 Add on/off switch for 3PID in directory lookups 2018-08-15 11:25:41 +02:00
Max Dor
1ab8a27fda Add on/off switch for bulk lookups 2018-08-12 02:16:14 +02:00
Max Dor
deafc420a5 Properly handle leading @ in search (Fix #79) 2018-06-22 01:42:07 +02:00
Felix Schäfer
fce15f0e29 Use server.name instead of matrix.domain in Docs (#81)
Enhance documentation to talk about server.name in DNS override for auth
2018-06-07 13:55:54 +02:00
Max Dor
5b5893f407 Fix typo in doc 2018-06-02 22:16:33 +02:00
71 changed files with 1695 additions and 259 deletions

View File

@@ -1,6 +1,6 @@
mxisd - Federated Matrix Identity Server mxisd - Federated Matrix Identity Server
---------------------------------------- ----------------------------------------
![Travis-CI build status](https://travis-ci.org/kamax-io/mxisd.svg?branch=master) ![Travis-CI build status](https://travis-ci.org/kamax-matrix/mxisd.svg?branch=master)
- [Overview](#overview) - [Overview](#overview)
- [Features](#features) - [Features](#features)

View File

@@ -1,6 +1,6 @@
# Sample configuration file explaining the minimum required keys to be set to run mxisd # Sample configuration file explaining the minimum required keys to be set to run mxisd
# #
# For a complete list of options, see https://github.com/kamax-io/mxisd # For a complete list of options, see https://github.com/kamax-matrix/mxisd
####################### #######################
# Matrix config items # # Matrix config items #
@@ -53,7 +53,7 @@ storage.provider.sqlite.database: '/path/to/mxisd.db'
# #
# Root/Central servers to be used as final fallback when performing lookups. # Root/Central servers to be used as final fallback when performing lookups.
# By default, for privacy reasons, matrix.org servers are not enabled anymore. # By default, for privacy reasons, matrix.org servers are not enabled anymore.
# See the following issue: https://github.com/kamax-io/mxisd/issues/76 # See the following issue: https://github.com/kamax-matrix/mxisd/issues/76
# #
# If you would like to use them and trade away your privacy for convenience, uncomment the following option: # If you would like to use them and trade away your privacy for convenience, uncomment the following option:
# #
@@ -64,28 +64,28 @@ storage.provider.sqlite.database: '/path/to/mxisd.db'
# LDAP Backend # # LDAP Backend #
################ ################
# If you would like to integrate with your AD/Samba/LDAP server, # 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 # see https://github.com/kamax-matrix/mxisd/blob/master/docs/backends/ldap.md
############### ###############
# SQL Backend # # SQL Backend #
############### ###############
# If you would like to integrate with a MySQL/MariaDB/PostgreQL/SQLite DB, # 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 # see https://github.com/kamax-matrix/mxisd/blob/master/docs/backends/sql.md
################ ################
# REST Backend # # REST Backend #
################ ################
# If you would like to integrate with an existing web service/webapp, # 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 # see https://github.com/kamax-matrix/mxisd/blob/master/docs/backends/rest.md
################################################# #################################################
# Notifications for invites/addition to profile # # Notifications for invites/addition to profile #
################################################# #################################################
# If you would like to change the content, # If you would like to change the content,
# see https://github.com/kamax-io/mxisd/blob/master/docs/threepids/notifications/template-generator.md # see https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/notifications/template-generator.md
# #
#### E-mail invite sender #### E-mail invite sender
# #

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -65,9 +65,9 @@ buildscript {
} }
repositories { repositories {
mavenCentral()
maven { url "https://kamax.io/maven/releases/" } maven { url "https://kamax.io/maven/releases/" }
maven { url "https://kamax.io/maven/snapshots/" } maven { url "https://kamax.io/maven/snapshots/" }
mavenCentral()
} }
dependencies { dependencies {
@@ -81,7 +81,7 @@ dependencies {
compile "org.springframework.boot:spring-boot-starter-thymeleaf:1.5.10.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.11' compile 'io.kamax:matrix-java-sdk:0.0.14-8-g0e57ec6'
// ed25519 handling // ed25519 handling
compile 'net.i2p.crypto:eddsa:0.1.0' compile 'net.i2p.crypto:eddsa:0.1.0'

View File

@@ -1,4 +1,6 @@
# Table of Contents # Table of Contents
- [Identity Concepts in Matrix](concepts.md)
- [Getting Started](getting-started.md)
- [Build from sources](build.md) (Optional) - [Build from sources](build.md) (Optional)
- Installation - Installation
- [Debian package](install/debian.md) - [Debian package](install/debian.md)
@@ -20,3 +22,4 @@
- [SendGrid](threepids/notification/sendgrid-handler.md) - [SendGrid](threepids/notification/sendgrid-handler.md)
- [Sessions](threepids/session/session.md) - [Sessions](threepids/session/session.md)
- [Views](threepids/session/session-views.md) - [Views](threepids/session/session-views.md)
- [FAQ](faq.md)

View File

@@ -12,7 +12,7 @@
### Build ### Build
```bash ```bash
git clone https://github.com/kamax-io/mxisd.git git clone https://github.com/kamax-matrix/mxisd.git
cd mxisd cd mxisd
./gradlew build ./gradlew build
``` ```

View File

@@ -148,7 +148,8 @@ dns.overwrite.homeserver.client:
value: 'http://localhost:8008' value: 'http://localhost:8008'
``` ```
`name` must be the hostname of the URL that clients use when connecting to the Homeserver. `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` You can use `${server.name}` to auto-populate the `value` using the `server.name` configuration option and avoid duplicating it.
using the `matrix.domain` configuration option and avoid duplicating it. In case the hostname is the same as your Matrix domain and `server.name` is not explicitely set in the config, `server.name` will default to
`matrix.domain` and will still probably have the correct value.
`value` is the base internal URL of the Homeserver, without any `/_matrix/..` or trailing `/`. `value` is the base internal URL of the Homeserver, without any `/_matrix/..` or trailing `/`.

View File

@@ -138,5 +138,12 @@ the `matrix.domain` configuration option and avoid duplicating it.
You can configure if the Homeserver should be queried at all when doing a directory search. You can configure if the Homeserver should be queried at all when doing a directory search.
To disable Homeserver results, set the following in mxisd configuration file: To disable Homeserver results, set the following in mxisd configuration file:
```yaml ```yaml
directory.exclude.homeserever: true directory.exclude.homeserver: true
```
### 3PID exclusion in search
You can configure if the 3PID should also be included when doing a directory search.
By default, a search is performed on the 3PIDs. If you would like to not include them:
```yaml
directory.exclude.threepid: true
``` ```

View File

@@ -0,0 +1,72 @@
# Integration as an Application Service
**WARNING:** These features are currently highly experimental. They can be removed or modified without notice.
All the features requires a Homeserver capable of connecting Application Services.
## Email notification for Room invites by Matrix ID
This feature allows for users found in Identity stores to be instantly notified about Room Invites, regardless if their
account was already provisioned on the Homeserver.
### Requirements
- [Identity store(s)](../../stores/README.md) supporting the Profile feature
- At least one email entry in the identity store for each user that could be invited.
### Configuration
In your mxisd config file:
```yaml
matrix:
listener:
url: '<URL TO THE CS API OF THE HOMESERVER>'
localpart: 'appservice-mxisd'
token:
hs: 'HS_TOKEN_CHANGE_ME'
synapseSql:
enabled: false ## Do not use this line if Synapse is used as an Identity Store
type: '<DB TYPE>'
connection: '<DB CONNECTION URL>'
```
The `synapseSql` section is used to retrieve display names which are not directly accessible in this mode.
For details about `type` and `connection`, see the [relevant documentation](../../stores/synapse.md).
If you do not configure it, some placeholders will not be available in the notification, like the Room name.
You can also change the default template of the notification using the `generic.matrixId` template option.
See [the Template generator documentation](../../threepids/notification/template-generator.md) for more info.
### Homeserver integration
#### Synapse
Create a new appservice registration file. Futher config will assume it is in `/etc/matrix-synapse/appservice-mxisd.yaml`
```yaml
id: "appservice-mxisd"
url: "http://127.0.0.1:8090"
as_token: "AS_TOKEN_CHANGE_ME"
hs_token: "HS_TOKEN_CHANGE_ME"
sender_localpart: "appservice-mxisd"
namespaces:
users:
- regex: "@*"
exclusive: false
aliases: []
rooms: []
```
`id`: An arbitrary unique string to identify the AS.
`url`: mxisd to reach mxisd. This ideally should be HTTP and not going through any reverse proxy.
`as_token`: Arbitrary value used by mxisd when talking to the HS. Not currently used.
`hs_token`: Arbitrary value used by synapse when talking to mxisd. Must match `token.hs` in mxisd config.
`sender_localpart`: Username for the mxisd itself on the HS. Default configuration should be kept.
`namespaces`: To be kept as is.
Edit your `homeserver.yaml` and add a new entry to the appservice config file, which should look something like this:
```yaml
app_service_config_files:
- '/etc/matrix-synapse/appservice-mxisd.yaml'
- ...
```
Restart synapse when done to register mxisd.
#### Others
See your Homeserver documentation on how to integrate.
### Test
Invite a user which is part of your domain while an appropriate Identity store is used.

View File

@@ -0,0 +1,12 @@
# Profile enhancement
**WARNING**: Alpha feature, not officially supported. Do not use.
This feature allows to enhance a profile query with more info than just Matrix ID and Display name, allowing for custom
applications to retrieve custom data not currently provided by synapse, per example.
## Configuration
### Reverse proxy
#### Apache
```apache
ProxyPassMatch "^/_matrix/client/r0/profile/([^/]+)$" "http://127.0.0.1:8090/_matrix/client/r0/profile/$1"
```

View File

@@ -11,7 +11,7 @@ forward.servers:
- 'matrix-org' - 'matrix-org'
``` ```
**NOTE:** You should carefully consider enabling this option, which is discouraged. **NOTE:** You should carefully consider enabling this option, which is discouraged.
For more info, see the [relevant issue](https://github.com/kamax-io/mxisd/issues/76). For more info, see the [relevant issue](https://github.com/kamax-matrix/mxisd/issues/76).
## Room Invitations ## Room Invitations
Resolution can be customized using the following configuration: Resolution can be customized using the following configuration:

View File

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

View File

@@ -12,7 +12,7 @@ This will be a good ground work for further integration with features and your e
## Preparation ## Preparation
You will need: You will need:
- Homeserver - Working Homeserver, ideally with working federation
- Reverse proxy with regular TLS/SSL certificate (Let's encrypt) for your mxisd domain - 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 As synapse requires an HTTPS connection when talking to an Identity service, **a reverse proxy is required** as mxisd does
@@ -20,7 +20,7 @@ not support HTTPS listener at this time.
For maximum integration, it is best to have your Homeserver and mxisd reachable via the same hostname. For maximum integration, it is best to have your Homeserver and mxisd reachable via the same hostname.
Be aware of a [NAT/Reverse proxy gotcha](https://github.com/kamax-io/mxisd/wiki/Gotchas#nating) if you use the same Be aware of a [NAT/Reverse proxy gotcha](https://github.com/kamax-matrix/mxisd/wiki/Gotchas#nating) if you use the same
hostname. hostname.
The following Quick Start guide assumes you will host the Homeserver and mxisd under the same hostname. The following Quick Start guide assumes you will host the Homeserver and mxisd under the same hostname.
@@ -30,10 +30,11 @@ If you would like a high-level view of the infrastructure and how each feature i
## Install ## Install
Install via: Install via:
- [Debian package](install/debian.md) - [Debian package](install/debian.md)
- [ArchLinux](install/archlinux.md)
- [Docker image](install/docker.md) - [Docker image](install/docker.md)
- [Sources](build.md) - [Sources](build.md)
See the [Latest release](https://github.com/kamax-io/mxisd/releases/latest) for links to each. See the [Latest release](https://github.com/kamax-matrix/mxisd/releases/latest) for links to each.
## Configure ## Configure
**NOTE**: please view the install instruction for your platform, as this step might be optional or already handled for you. **NOTE**: please view the install instruction for your platform, as this step might be optional or already handled for you.
@@ -118,10 +119,15 @@ It is recommended to remove `matrix.org` and `vector.im` (or any other default e
your own Identity server is authoritative for your HS. your own Identity server is authoritative for your HS.
## Validate ## Validate
Log in using your Matrix client and set `https://example.org` as your Identity server URL, replacing `example.org` by **NOTE:** In case your homeserver has no working federation, step 5 will not happen. If step 4 took place, consider
your installation validated.
1. 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. the relevant hostname which you configured in your reverse proxy.
Invite `mxisd-federation-test@kamax.io` to a room, which should be turned into a Matrix invite to `@mxisd-lookup-test:kamax.io`. 2. Create a new empty room. All further actions will take place in this room.
At this point, the test user will join the room, send a congratulation message and leave. 3. Invite `mxisd-federation-test@kamax.io`
4. The 3PID invite should be turned into a Matrix invite to `@mxisd-lookup-test:kamax.io`.
5. The invited test user will join the room, send a congratulation message and leave.
**NOTE:** You might not see a suggestion for the e-mail address, which is normal. Still proceed with the invite. **NOTE:** You might not see a suggestion for the e-mail address, which is normal. Still proceed with the invite.
If it worked, it means you are up and running and can enjoy mxisd in its basic mode! Congratulations! If it worked, it means you are up and running and can enjoy mxisd in its basic mode! Congratulations!

View File

@@ -1,6 +1,6 @@
# Debian package # Debian package
## Install ## Install
1. Download the [latest release](https://github.com/kamax-io/mxisd/releases/latest) 1. Download the [latest release](https://github.com/kamax-matrix/mxisd/releases/latest)
2. Run: 2. Run:
```bash ```bash
dpkg -i /path/to/downloaded/mxisd.deb dpkg -i /path/to/downloaded/mxisd.deb

View File

@@ -7,6 +7,7 @@ https://firebase.google.com/
| Authentication | Yes | | Authentication | Yes |
| Directory | No | | Directory | No |
| Identity | Yes | | Identity | Yes |
| Profile | No |
## Requirements ## Requirements
This backend requires a suitable Matrix client capable of performing Firebase authentication and passing the following This backend requires a suitable Matrix client capable of performing Firebase authentication and passing the following

View File

@@ -13,6 +13,7 @@ For NetIQ, replace all the `ldap` prefix in the configuration by `netiq`.
| Authentication | Yes | | Authentication | Yes |
| Directory | Yes | | Directory | Yes |
| Identity | Yes | | Identity | Yes |
| Profile | Yes |
## Getting started ## Getting started
### Base ### Base

View File

@@ -13,6 +13,7 @@ To integrate this backend with your webapp, you will need to implement three spe
| Authentication | Yes | | Authentication | Yes |
| Directory | Yes | | Directory | Yes |
| Identity | Yes | | Identity | Yes |
| Profile | No |
## Configuration ## Configuration
| Key | Default | Description | | Key | Default | Description |

View File

@@ -11,6 +11,7 @@
| Authentication | No | | Authentication | No |
| Directory | Yes | | Directory | Yes |
| Identity | Yes | | Identity | Yes |
| Profile | Yes |
Due to the implementation complexity of supporting arbitrary hashing/encoding mechanisms or auth flow, Authentication Due to the implementation complexity of supporting arbitrary hashing/encoding mechanisms or auth flow, Authentication
will be out of scope of SQL Identity stores and should be done via one of the other identity stores, typically will be out of scope of SQL Identity stores and should be done via one of the other identity stores, typically

View File

@@ -7,6 +7,7 @@ Synapse's Database itself can be used as an Identity store.
| Authentication | No | | Authentication | No |
| Directory | Yes | | Directory | Yes |
| Identity | Yes | | Identity | Yes |
| Profile | Yes |
Authentication is done by Synapse itself. Authentication is done by Synapse itself.

View File

@@ -10,6 +10,7 @@ Two types of connections are required for full support:
| Authentication | Yes | | Authentication | Yes |
| Directory | Yes | | Directory | Yes |
| Identity | Yes | | Identity | Yes |
| Profile | No |
## Requirements ## Requirements
- [Wordpress](https://wordpress.org/download/) >= 4.4 - [Wordpress](https://wordpress.org/download/) >= 4.4
@@ -55,3 +56,11 @@ With possible values:
- `mariadb` - `mariadb`
- `postgresql` - `postgresql`
- `sqlite` - `sqlite`
---
To configure the tables prefix for default queries, in case a custom value was set during Wordpress install:
```yaml
wordpress.sql.tablePrefix: <string>
```
By default, the value is set to `wp_`.

View File

@@ -1,5 +1,5 @@
# SendGrid Notification handler # 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). To be completed. See [raw possible configuration items](https://github.com/kamax-matrix/mxisd/blob/master/src/main/resources/application.yaml#L172).
Enabled with: Enabled with:
```yaml ```yaml

View File

@@ -19,6 +19,8 @@ threepid.medium.<YOUR 3PID MEDIUM HERE>:
validation: validation:
local: '/path/to/validate-local-template.eml' local: '/path/to/validate-local-template.eml'
remote: 'path/to/validate-remote-template.eml' remote: 'path/to/validate-remote-template.eml'
generic:
matrixId: '/path/to/mxid-invite-template.eml'
``` ```
The `template` generator is usually the default, so no further configuration is needed. The `template` generator is usually the default, so no further configuration is needed.

View File

@@ -1,6 +1,6 @@
Package: mxisd Package: mxisd
Maintainer: Kamax.io <foss@kamax.io> Maintainer: Kamax.io <foss@kamax.io>
Homepage: https://github.com/kamax-io/mxisd Homepage: https://github.com/kamax-matrix/mxisd
Description: Federated Matrix Identity Server Description: Federated Matrix Identity Server
Architecture: all Architecture: all
Depends: openjdk-8-jre | openjdk-8-jre-headless | openjdk-8-jdk | openjdk-8-jdk-headless Depends: openjdk-8-jre | openjdk-8-jre-headless | openjdk-8-jdk | openjdk-8-jdk-headless

View File

@@ -0,0 +1,112 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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.as;
import com.google.gson.JsonObject;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix._MatrixID;
import io.kamax.matrix._ThreePid;
import io.kamax.matrix.event.EventKey;
import io.kamax.matrix.json.GsonUtil;
import io.kamax.mxisd.backend.sql.synapse.Synapse;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.notification.NotificationManager;
import io.kamax.mxisd.profile.ProfileManager;
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.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class AppServiceHandler {
private final Logger log = LoggerFactory.getLogger(AppServiceHandler.class);
private MatrixConfig cfg;
private ProfileManager profiler;
private NotificationManager notif;
private Synapse synapse;
@Autowired
public AppServiceHandler(MatrixConfig cfg, ProfileManager profiler, NotificationManager notif, Synapse synapse) {
this.cfg = cfg;
this.profiler = profiler;
this.notif = notif;
this.synapse = synapse;
}
public void processTransaction(List<JsonObject> eventsJson) {
eventsJson.forEach(ev -> {
if (!StringUtils.equals("m.room.member", GsonUtil.getStringOrNull(ev, "type"))) {
return;
}
if (!StringUtils.equals("invite", GsonUtil.getStringOrNull(ev, "membership"))) {
return;
}
String roomId = GsonUtil.getStringOrNull(ev, "room_id");
_MatrixID sender = MatrixID.asAcceptable(GsonUtil.getStringOrNull(ev, "sender"));
EventKey.StateKey.findString(ev).ifPresent(id -> {
_MatrixID mxid = MatrixID.asAcceptable(id);
if (!StringUtils.equals(mxid.getDomain(), cfg.getDomain())) {
log.debug("Ignoring invite for {}: not a local user");
return;
}
log.info("Got invite for {}", id);
boolean wasSent = false;
List<_ThreePid> tpids = profiler.getThreepids(mxid);
if (tpids.isEmpty()) {
log.info("No email found in identity stores for {}", id);
}
for (_ThreePid tpid : tpids) {
if (!StringUtils.equals("email", tpid.getMedium())) {
continue;
}
log.info("Found an email address to notify about room invitation: {}", tpid.getAddress());
Map<String, String> properties = new HashMap<>();
profiler.getDisplayName(sender).ifPresent(name -> properties.put("sender_display_name", name));
try {
synapse.getRoomName(roomId).ifPresent(name -> properties.put("room_name", name));
} catch (RuntimeException e) {
log.warn("Unable to fetch room name - Did you provide synapse DB information as documented?");
log.warn("Underlying error:", e);
}
IMatrixIdInvite inv = new MatrixIdInvite(roomId, sender, mxid, tpid.getMedium(), tpid.getAddress(), properties);
notif.sendForInvite(inv);
wasSent = true;
}
log.info("Was notification sent? {}", wasSent);
});
});
}
}

View File

@@ -0,0 +1,30 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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.as;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.invitation.IThreePidInvite;
public interface IMatrixIdInvite extends IThreePidInvite {
_MatrixID getInvitee();
}

View File

@@ -0,0 +1,77 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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.as;
import io.kamax.matrix._MatrixID;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class MatrixIdInvite implements IMatrixIdInvite {
private String roomId;
private _MatrixID sender;
private _MatrixID invitee;
private String medium;
private String address;
private Map<String, String> properties;
public MatrixIdInvite(String roomId, _MatrixID sender, _MatrixID invitee, String medium, String address, Map<String, String> properties) {
this.roomId = Objects.requireNonNull(roomId);
this.sender = Objects.requireNonNull(sender);
this.invitee = Objects.requireNonNull(invitee);
this.medium = Objects.requireNonNull(medium);
this.address = Objects.requireNonNull(address);
this.properties = new HashMap<>(Objects.requireNonNull(properties));
}
@Override
public _MatrixID getSender() {
return sender;
}
@Override
public String getMedium() {
return medium;
}
@Override
public String getAddress() {
return address;
}
@Override
public _MatrixID getInvitee() {
return invitee;
}
@Override
public String getRoomId() {
return roomId;
}
@Override
public Map<String, String> getProperties() {
return properties;
}
}

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -20,6 +20,7 @@
package io.kamax.mxisd.backend.ldap; package io.kamax.mxisd.backend.ldap;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.LdapConfig; import io.kamax.mxisd.config.ldap.LdapConfig;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@@ -70,7 +71,7 @@ public abstract class LdapBackend {
return getAt().getUid().getValue(); return getAt().getUid().getValue();
} }
protected synchronized LdapConnection getConn() throws LdapException { protected synchronized LdapConnection getConn() {
return new LdapNetworkConnection(cfg.getConnection().getHost(), cfg.getConnection().getPort(), cfg.getConnection().isTls()); return new LdapNetworkConnection(cfg.getConnection().getHost(), cfg.getConnection().getPort(), cfg.getConnection().isTls());
} }
@@ -124,6 +125,17 @@ public abstract class LdapBackend {
} }
} }
public String buildUidFromMatrixId(_MatrixID mxId) {
String uidType = getCfg().getAttribute().getUid().getType();
if (StringUtils.equals(UID, uidType)) {
return mxId.getLocalPart();
} else if (StringUtils.equals(MATRIX_ID, uidType)) {
return mxId.getId();
} else {
throw new IllegalArgumentException("Bind type " + uidType + " is not supported");
}
}
public Optional<String> getAttribute(Entry entry, String attName) { public Optional<String> getAttribute(Entry entry, String attName) {
Attribute attribute = entry.get(attName); Attribute attribute = entry.get(attName);
if (attribute == null) { if (attribute == null) {

View File

@@ -0,0 +1,157 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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;
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.ldap.LdapConfig;
import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.profile.ProfileProvider;
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.Collections;
import java.util.List;
import java.util.Optional;
@Component
public class LdapProfileProvider extends LdapBackend implements ProfileProvider {
private transient Logger log = LoggerFactory.getLogger(LdapProfileProvider.class);
@Autowired
public LdapProfileProvider(LdapConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
@Override
public boolean isEnabled() {
return getCfg().isEnabled();
}
@Override
public Optional<String> getDisplayName(_MatrixID userId) {
String uid = buildUidFromMatrixId(userId);
log.info("Searching for display name of {}:", uid);
try (LdapConnection conn = getConn()) {
bind(conn);
String searchQuery = buildOrQueryWithFilter(getCfg().getProfile().getFilter(), uid, getUidAtt());
log.debug("Base DN: {}", getBaseDn());
log.debug("Query: {}", searchQuery);
try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, getAt().getName())) {
while (cursor.next()) {
Entry entry = cursor.get();
log.info("Found possible match, DN: {}", entry.getDn().getName());
Optional<String> v = getAttribute(entry, getAt().getName()).flatMap(id -> {
log.info("DN {} is a valid match", entry.getDn().getName());
try {
return getAttribute(entry, getAt().getName());
} catch (IllegalArgumentException e) {
log.warn("Bind was found but type {} is not supported", getAt().getUid().getType());
return Optional.empty();
}
});
if (v.isPresent()) {
log.info("DN {} is the final match", entry.getDn().getName());
return v;
}
}
}
} catch (CursorLdapReferralException e) {
log.warn("An entry is only available via referral, skipping");
} catch (IOException | LdapException | CursorException e) {
throw new InternalServerError(e);
}
return Optional.empty();
}
@Override
public List<_ThreePid> getThreepids(_MatrixID userId) {
String uid = buildUidFromMatrixId(userId);
log.info("Searching for 3PIDs of {}:", uid);
List<_ThreePid> threePids = new ArrayList<>();
try (LdapConnection conn = getConn()) {
bind(conn);
log.debug("Base DN: {}", getBaseDn());
getCfg().getAttribute().getThreepid().forEach((medium, attributes) -> {
String[] attArray = new String[attributes.size()];
attributes.toArray(attArray);
String searchQuery = buildOrQueryWithFilter(getCfg().getProfile().getFilter(), uid, getUidAtt());
log.debug("Query for 3PID {}: {}", medium, searchQuery);
try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, attArray)) {
while (cursor.next()) {
Entry entry = cursor.get();
log.info("Found possible match, DN: {}", entry.getDn().getName());
try {
attributes.stream()
.flatMap(at -> getAttributes(entry, at).stream())
.forEach(address -> {
log.info("Found 3PID: {} - {}", medium, address);
threePids.add(new ThreePid(medium, address));
});
} catch (IllegalArgumentException e) {
log.warn("Bind was found but type {} is not supported", getAt().getUid().getType());
}
}
} catch (CursorLdapReferralException e) {
log.warn("An entry is only available via referral, skipping");
} catch (IOException | LdapException | CursorException e) {
throw new InternalServerError(e);
}
});
} catch (IOException | LdapException e) {
throw new InternalServerError(e);
}
return threePids;
}
@Override
public List<String> getRoles(_MatrixID userId) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,50 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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.matrix._MatrixID;
import io.kamax.mxisd.backend.ldap.LdapProfileProvider;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ldap.netiq.NetIqLdapConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class NetIqLdapProfileProvider extends LdapProfileProvider {
@Autowired
public NetIqLdapProfileProvider(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();
}
// FIXME this is duplicated in the other NetIQ classes, due to the Matrix ID generation code that was not abstracted
@Override
public String buildUidFromMatrixId(_MatrixID mxid) {
return super.buildUidFromMatrixId(mxid).toLowerCase();
}
}

View File

@@ -1,6 +1,6 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Maxime Dor * Copyright (C) 2018 Kamax Sarl
* *
* https://www.kamax.io/ * https://www.kamax.io/
* *
@@ -76,6 +76,11 @@ public class MemoryIdentityStore implements AuthenticatorProvider, IDirectoryPro
return cfg.isEnabled(); return cfg.isEnabled();
} }
@Override
public Optional<String> getDisplayName(_MatrixID mxid) {
return findByUsername(mxid.getLocalPart()).map(MemoryIdentityConfig::getDisplayName);
}
private UserDirectorySearchResult search( private UserDirectorySearchResult search(
Predicate<MemoryIdentityConfig> predicate, Predicate<MemoryIdentityConfig> predicate,
Function<MemoryIdentityConfig, UserDirectorySearchResult.Result> mapper Function<MemoryIdentityConfig, UserDirectorySearchResult.Result> mapper

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -28,6 +28,12 @@ import java.sql.SQLException;
public class SqlConnectionPool { public class SqlConnectionPool {
public interface SqlFunction<T, R> {
R run(T connection) throws SQLException;
}
private ComboPooledDataSource ds; private ComboPooledDataSource ds;
public SqlConnectionPool(SqlConfig cfg) { public SqlConnectionPool(SqlConfig cfg) {
@@ -42,4 +48,12 @@ public class SqlConnectionPool {
return ds.getConnection(); return ds.getConnection();
} }
public <T> T withConnFunction(SqlFunction<Connection, T> function) {
try (Connection conn = get()) {
return function.run(conn);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
} }

View File

@@ -0,0 +1,105 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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.sql;
import io.kamax.matrix.ThreePid;
import io.kamax.matrix._MatrixID;
import io.kamax.matrix._ThreePid;
import io.kamax.mxisd.config.sql.SqlConfig;
import io.kamax.mxisd.profile.ProfileProvider;
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 SqlProfileProvider implements ProfileProvider {
private Logger log = LoggerFactory.getLogger(SqlProfileProvider.class);
private SqlConfig.Profile cfg;
private SqlConnectionPool pool;
public SqlProfileProvider(SqlConfig cfg) {
this.cfg = cfg.getProfile();
this.pool = new SqlConnectionPool(cfg);
}
@Override
public boolean isEnabled() {
return cfg.isEnabled();
}
@Override
public Optional<String> getDisplayName(_MatrixID user) {
String stmtSql = cfg.getDisplayName().getQuery();
try (Connection conn = pool.get()) {
try (PreparedStatement stmt = conn.prepareStatement(stmtSql)) {
stmt.setString(1, user.getId());
try (ResultSet rSet = stmt.executeQuery()) {
if (!rSet.next()) {
return Optional.empty();
}
return Optional.ofNullable(rSet.getString(1));
}
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public List<_ThreePid> getThreepids(_MatrixID user) {
List<_ThreePid> threepids = new ArrayList<>();
String stmtSql = cfg.getThreepid().getQuery();
try (Connection conn = pool.get()) {
PreparedStatement stmt = conn.prepareStatement(stmtSql);
stmt.setString(1, user.getId());
ResultSet rSet = stmt.executeQuery();
while (rSet.next()) {
String medium = rSet.getString(1);
String address = rSet.getString(2);
threepids.add(new ThreePid(medium, address));
}
return threepids;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@Override
public List<String> getRoles(_MatrixID user) {
return Collections.emptyList();
}
}

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -21,16 +21,12 @@
package io.kamax.mxisd.backend.sql; package io.kamax.mxisd.backend.sql;
import io.kamax.matrix.MatrixID; 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.MatrixConfig;
import io.kamax.mxisd.config.sql.SqlConfig; import io.kamax.mxisd.config.sql.SqlConfig;
import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.provider.IThreePidProvider; import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import io.kamax.mxisd.profile.ProfileProvider;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -40,11 +36,10 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
public abstract class SqlThreePidProvider implements IThreePidProvider, ProfileProvider { public abstract class SqlThreePidProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(SqlThreePidProvider.class); private Logger log = LoggerFactory.getLogger(SqlThreePidProvider.class);
@@ -114,31 +109,4 @@ public abstract class SqlThreePidProvider implements IThreePidProvider, ProfileP
return new ArrayList<>(); 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();
}
} }

View File

@@ -1,69 +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.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
import io.kamax.mxisd.exception.ConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@Component
public class SynapseSqlDirectoryProvider extends GenericSqlDirectoryProvider {
@Autowired
public SynapseSqlDirectoryProvider(SynapseSqlProviderConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
if (StringUtils.equals("sqlite", cfg.getType())) {
String userId = "'@' || p.user_id || ':" + mxCfg.getDomain() + "'";
GenericSqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
queries.getName().setValue(
"select " + userId + ", displayname from profiles p where displayname like ?");
queries.getThreepid().setValue(
"select t.user_id, p.displayname " +
"from user_threepids t JOIN profiles p on t.user_id = " + userId + " " +
"where t.address like ?");
} else if (StringUtils.equals("postgresql", cfg.getType())) {
String userId = "concat('@',p.user_id,':" + mxCfg.getDomain() + "')";
GenericSqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
queries.getName().setValue(
"select " + userId + ", displayname from profiles p where displayname ilike ?");
queries.getThreepid().setValue(
"select t.user_id, p.displayname " +
"from user_threepids t JOIN profiles p on t.user_id = " + userId + " " +
"where t.address ilike ?");
} else {
throw new ConfigurationException("Invalid SQL type");
}
}
@Override
protected void setParameters(PreparedStatement stmt, String searchTerm) throws SQLException {
stmt.setString(1, "%" + searchTerm + "%");
}
}

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -18,13 +18,12 @@
* 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.backend.sql; package io.kamax.mxisd.backend.sql.generic;
import io.kamax.matrix._MatrixID; 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.sql.generic.GenericSqlProviderConfig;
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;
@@ -36,9 +35,6 @@ public class GenericSqlAuthProvider implements AuthenticatorProvider {
private Logger log = LoggerFactory.getLogger(GenericSqlAuthProvider.class); private Logger log = LoggerFactory.getLogger(GenericSqlAuthProvider.class);
@Autowired
private ServerConfig srvCfg;
@Autowired @Autowired
private GenericSqlProviderConfig cfg; private GenericSqlProviderConfig cfg;
@@ -47,7 +43,7 @@ public class GenericSqlAuthProvider implements AuthenticatorProvider {
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return cfg.isEnabled(); return cfg.getAuth().isEnabled();
} }
@Override @Override

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -18,12 +18,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package io.kamax.mxisd.backend.sql; package io.kamax.mxisd.backend.sql.generic;
import io.kamax.matrix.MatrixID; import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.backend.sql.SqlConnectionPool;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
import io.kamax.mxisd.config.sql.SqlConfig; import io.kamax.mxisd.config.sql.SqlConfig;
import io.kamax.mxisd.config.sql.generic.GenericSqlProviderConfig;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult; import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.directory.IDirectoryProvider; import io.kamax.mxisd.directory.IDirectoryProvider;
import io.kamax.mxisd.exception.InternalServerError; import io.kamax.mxisd.exception.InternalServerError;
@@ -44,7 +45,7 @@ public abstract class GenericSqlDirectoryProvider implements IDirectoryProvider
private Logger log = LoggerFactory.getLogger(GenericSqlDirectoryProvider.class); private Logger log = LoggerFactory.getLogger(GenericSqlDirectoryProvider.class);
protected SqlConfig cfg; protected SqlConfig cfg;
private MatrixConfig mxCfg; protected MatrixConfig mxCfg;
private SqlConnectionPool pool; private SqlConnectionPool pool;
@@ -56,7 +57,7 @@ public abstract class GenericSqlDirectoryProvider implements IDirectoryProvider
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return cfg.isEnabled(); return cfg.getDirectory().isEnabled();
} }
protected void setParameters(PreparedStatement stmt, String searchTerm) throws SQLException { protected void setParameters(PreparedStatement stmt, String searchTerm) throws SQLException {

View File

@@ -0,0 +1,34 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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.sql.generic;
import io.kamax.mxisd.backend.sql.SqlProfileProvider;
import io.kamax.mxisd.config.sql.generic.GenericSqlProviderConfig;
import org.springframework.stereotype.Component;
@Component
public class GenericSqlProfileProvider extends SqlProfileProvider {
public GenericSqlProfileProvider(GenericSqlProviderConfig cfg) {
super(cfg);
}
}

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -18,10 +18,11 @@
* 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.backend.sql; package io.kamax.mxisd.backend.sql.generic;
import io.kamax.mxisd.backend.sql.SqlThreePidProvider;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig; import io.kamax.mxisd.config.sql.generic.GenericSqlProviderConfig;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;

View File

@@ -0,0 +1,55 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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.sql.synapse;
import io.kamax.mxisd.backend.sql.SqlConnectionPool;
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Optional;
@Component
public class Synapse {
private SqlConnectionPool pool;
@Autowired
public Synapse(SynapseSqlProviderConfig sqlCfg) {
this.pool = new SqlConnectionPool(sqlCfg);
}
public Optional<String> getRoomName(String id) {
return pool.withConnFunction(conn -> {
PreparedStatement stmt = conn.prepareStatement(SynapseQueries.getRoomName());
stmt.setString(1, id);
ResultSet rSet = stmt.executeQuery();
if (!rSet.next()) {
return Optional.empty();
}
return Optional.ofNullable(rSet.getString(1));
});
}
}

View File

@@ -0,0 +1,74 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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.sql.synapse;
import io.kamax.mxisd.exception.ConfigurationException;
import org.apache.commons.lang.StringUtils;
public class SynapseQueries {
public static String getUserId(String type, String domain) {
if (StringUtils.equals("sqlite", type)) {
return "'@' || p.user_id || ':" + domain + "'";
} else if (StringUtils.equals("postgresql", type)) {
return "concat('@',p.user_id,':" + domain + "')";
} else {
throw new ConfigurationException("Invalid Synapse SQL type: " + type);
}
}
public static String getDisplayName() {
return "SELECT displayname FROM profiles WHERE user_id = ?";
}
public static String getThreepids() {
return "SELECT medium, address FROM user_threepids WHERE user_id = ?";
}
public static String findByDisplayName(String type, String domain) {
if (StringUtils.equals("sqlite", type)) {
return "select " + getUserId(type, domain) + ", displayname from profiles p where displayname like ?";
} else if (StringUtils.equals("postgresql", type)) {
return "select " + getUserId(type, domain) + ", displayname from profiles p where displayname ilike ?";
} else {
throw new ConfigurationException("Invalid Synapse SQL type: " + type);
}
}
public static String findByThreePidAddress(String type, String domain) {
if (StringUtils.equals("sqlite", type)) {
return "select t.user_id, p.displayname " +
"from user_threepids t JOIN profiles p on t.user_id = " + getUserId(type, domain) + " " +
"where t.address like ?";
} else if (StringUtils.equals("postgresql", type)) {
return "select t.user_id, p.displayname " +
"from user_threepids t JOIN profiles p on t.user_id = " + getUserId(type, domain) + " " +
"where t.address ilike ?";
} else {
throw new ConfigurationException("Invalid Synapse SQL type: " + type);
}
}
public static String getRoomName() {
return "select r.name from room_names r, events e, (select r1.room_id,max(e1.origin_server_ts) ts from room_names r1, events e1 where r1.event_id = e1.event_id group by r1.room_id) rle where e.origin_server_ts = rle.ts and r.event_id = e.event_id and r.room_id = ?";
}
}

View File

@@ -0,0 +1,63 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Kamax Sarl
*
* 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.sql.synapse;
import io.kamax.mxisd.backend.sql.generic.GenericSqlDirectoryProvider;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.generic.GenericSqlProviderConfig;
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Objects;
@Component
public class SynapseSqlDirectoryProvider extends GenericSqlDirectoryProvider {
@Autowired
public SynapseSqlDirectoryProvider(SynapseSqlProviderConfig cfg, MatrixConfig mxCfg) {
super(cfg, mxCfg);
}
@Override
protected void setParameters(PreparedStatement stmt, String searchTerm) throws SQLException {
stmt.setString(1, "%" + searchTerm + "%");
}
@PostConstruct
public void build() {
if (!isEnabled()) {
return;
}
GenericSqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
if (Objects.isNull(queries.getName().getValue())) {
queries.getName().setValue(SynapseQueries.findByDisplayName(cfg.getType(), mxCfg.getDomain()));
}
if (Objects.isNull(queries.getThreepid().getValue())) {
queries.getThreepid().setValue(SynapseQueries.findByThreePidAddress(cfg.getType(), mxCfg.getDomain()));
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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.sql.synapse;
import io.kamax.mxisd.backend.sql.SqlProfileProvider;
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SynapseSqlProfileProvider extends SqlProfileProvider {
@Autowired
public SynapseSqlProfileProvider(SynapseSqlProviderConfig cfg) {
super(cfg);
}
}

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -18,8 +18,9 @@
* 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.backend.sql; package io.kamax.mxisd.backend.sql.synapse;
import io.kamax.mxisd.backend.sql.SqlThreePidProvider;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig; import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;

View File

@@ -0,0 +1,49 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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 org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.Objects;
@Configuration
@ConfigurationProperties(prefix = "lookup.bulk")
public class BulkLookupConfig {
private Boolean enabled;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public void build() {
if (Objects.isNull(enabled)) {
enabled = true;
}
}
}

View File

@@ -25,6 +25,8 @@ import org.slf4j.LoggerFactory;
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("directory") @ConfigurationProperties("directory")
public class DirectoryConfig { public class DirectoryConfig {
@@ -34,6 +36,7 @@ public class DirectoryConfig {
public static class Exclude { public static class Exclude {
private boolean homeserver; private boolean homeserver;
private boolean threepid;
public boolean getHomeserver() { public boolean getHomeserver() {
return homeserver; return homeserver;
@@ -44,6 +47,14 @@ public class DirectoryConfig {
return this; return this;
} }
public boolean getThreepid() {
return threepid;
}
public void setThreepid(boolean threepid) {
this.threepid = threepid;
}
} }
private Exclude exclude = new Exclude(); private Exclude exclude = new Exclude();
@@ -56,4 +67,12 @@ public class DirectoryConfig {
this.exclude = exclude; this.exclude = exclude;
} }
@PostConstruct
public void buid() {
log.info("--- Directory config ---");
log.info("Exclude:");
log.info("\tHomeserver: {}", getExclude().getHomeserver());
log.info("\t3PID: {}", getExclude().getThreepid());
}
} }

View File

@@ -0,0 +1,108 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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 org.apache.commons.lang.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.net.MalformedURLException;
import java.net.URL;
@Configuration
@ConfigurationProperties("matrix.listener")
public class ListenerConfig {
public static class Token {
private String as;
private String hs;
public String getAs() {
return as;
}
public void setAs(String as) {
this.as = as;
}
public String getHs() {
return hs;
}
public void setHs(String hs) {
this.hs = hs;
}
}
private transient URL csUrl;
private String url;
private String localpart;
private Token token = new Token();
public URL getUrl() {
return csUrl;
}
public void setUrl(String url) {
this.url = url;
}
public String getLocalpart() {
return localpart;
}
public void setLocalpart(String localpart) {
this.localpart = localpart;
}
public Token getToken() {
return token;
}
public void setToken(Token token) {
this.token = token;
}
@PostConstruct
public void build() throws MalformedURLException {
if (StringUtils.isBlank(url)) {
return;
}
csUrl = new URL(url);
if (StringUtils.isBlank(getLocalpart())) {
throw new IllegalArgumentException("localpart for matrix listener is not set");
}
if (StringUtils.isBlank(getToken().getAs())) {
throw new IllegalArgumentException("AS token is not set");
}
if (StringUtils.isBlank(getToken().getHs())) {
throw new IllegalArgumentException("HS token is not set");
}
}
}

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -234,6 +234,19 @@ public abstract class LdapConfig {
} }
public static class Profile {
private String filter;
public String getFilter() {
return filter;
}
public void setFilter(String filter) {
this.filter = filter;
}
}
private Logger log = LoggerFactory.getLogger(LdapConfig.class); private Logger log = LoggerFactory.getLogger(LdapConfig.class);
@@ -245,6 +258,7 @@ public abstract class LdapConfig {
private Auth auth; private Auth auth;
private Directory directory; private Directory directory;
private Identity identity; private Identity identity;
private Profile profile = new Profile();
protected abstract String getConfigName(); protected abstract String getConfigName();
@@ -304,6 +318,14 @@ public abstract class LdapConfig {
this.identity = identity; this.identity = identity;
} }
public Profile getProfile() {
return profile;
}
public void setProfile(Profile profile) {
this.profile = profile;
}
@PostConstruct @PostConstruct
public void build() { public void build() {
log.info("--- " + getConfigName() + " Config ---"); log.info("--- " + getConfigName() + " Config ---");
@@ -356,6 +378,9 @@ public abstract class LdapConfig {
getAuth().setFilter(StringUtils.defaultIfBlank(getAuth().getFilter(), getFilter())); getAuth().setFilter(StringUtils.defaultIfBlank(getAuth().getFilter(), getFilter()));
getDirectory().setFilter(StringUtils.defaultIfBlank(getDirectory().getFilter(), getFilter())); getDirectory().setFilter(StringUtils.defaultIfBlank(getDirectory().getFilter(), getFilter()));
getIdentity().setFilter(StringUtils.defaultIfBlank(getIdentity().getFilter(), getFilter())); getIdentity().setFilter(StringUtils.defaultIfBlank(getIdentity().getFilter(), getFilter()));
if (StringUtils.isBlank(getProfile().getFilter())) {
getProfile().setFilter(getFilter());
}
log.info("Host: {}", connection.getHost()); log.info("Host: {}", connection.getHost());
log.info("Port: {}", connection.getPort()); log.info("Port: {}", connection.getPort());
@@ -367,6 +392,7 @@ public abstract class LdapConfig {
log.info("Auth: {}", GsonUtil.get().toJson(auth)); log.info("Auth: {}", GsonUtil.get().toJson(auth));
log.info("Directory: {}", GsonUtil.get().toJson(directory)); log.info("Directory: {}", GsonUtil.get().toJson(directory));
log.info("Identity: {}", GsonUtil.get().toJson(identity)); log.info("Identity: {}", GsonUtil.get().toJson(identity));
log.info("Profile: {}", GsonUtil.get().toJson(profile));
} }
} }

View File

@@ -30,6 +30,7 @@ public class MemoryIdentityConfig {
private String username; private String username;
private String password; private String password;
private String displayName;
private List<MemoryThreePid> threepids = new ArrayList<>(); private List<MemoryThreePid> threepids = new ArrayList<>();
private List<String> roles = new ArrayList<>(); private List<String> roles = new ArrayList<>();
@@ -49,6 +50,14 @@ public class MemoryIdentityConfig {
this.password = password; this.password = password;
} }
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public List<MemoryThreePid> getThreepids() { public List<MemoryThreePid> getThreepids() {
return threepids; return threepids;
} }

View File

@@ -1,3 +1,23 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Kamax Sarl
*
* 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.sql; package io.kamax.mxisd.config.sql;
import io.kamax.mxisd.util.GsonUtil; import io.kamax.mxisd.util.GsonUtil;
@@ -7,10 +27,11 @@ import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
public abstract class SqlConfig { public abstract class SqlConfig {
private Logger log = LoggerFactory.getLogger(SqlConfig.class); private transient Logger log = LoggerFactory.getLogger(SqlConfig.class);
public static class Query { public static class Query {
@@ -136,6 +157,20 @@ public abstract class SqlConfig {
} }
public static class ProfileDisplayName {
private String query;
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
}
public static class ProfileThreepids { public static class ProfileThreepids {
private String query; private String query;
@@ -152,8 +187,26 @@ public abstract class SqlConfig {
public static class Profile { public static class Profile {
private Boolean enabled;
private ProfileDisplayName displayName = new ProfileDisplayName();
private ProfileThreepids threepid = new ProfileThreepids(); private ProfileThreepids threepid = new ProfileThreepids();
public Boolean isEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public ProfileDisplayName getDisplayName() {
return displayName;
}
public void setDisplayName(ProfileDisplayName displayName) {
this.displayName = displayName;
}
public ProfileThreepids getThreepid() { public ProfileThreepids getThreepid() {
return threepid; return threepid;
} }
@@ -230,7 +283,8 @@ public abstract class SqlConfig {
protected abstract String getProviderName(); protected abstract String getProviderName();
protected void doBuild() { @PostConstruct
public void build() {
if (getAuth().isEnabled() == null) { if (getAuth().isEnabled() == null) {
getAuth().setEnabled(isEnabled()); getAuth().setEnabled(isEnabled());
} }
@@ -242,14 +296,15 @@ public abstract class SqlConfig {
if (getIdentity().isEnabled() == null) { if (getIdentity().isEnabled() == null) {
getIdentity().setEnabled(isEnabled()); getIdentity().setEnabled(isEnabled());
} }
if (Objects.isNull(getProfile().isEnabled())) {
getProfile().setEnabled(isEnabled());
}
} }
@PostConstruct protected void printConfig() {
public void build() {
log.info("--- " + getProviderName() + " Provider config ---"); log.info("--- " + getProviderName() + " Provider config ---");
doBuild();
log.info("Enabled: {}", isEnabled()); log.info("Enabled: {}", isEnabled());
if (isEnabled()) { if (isEnabled()) {
log.info("Type: {}", getType()); log.info("Type: {}", getType());
@@ -259,7 +314,12 @@ public abstract class SqlConfig {
log.info("Identity type: {}", getIdentity().getType()); log.info("Identity type: {}", getIdentity().getType());
log.info("3PID mapping query: {}", getIdentity().getQuery()); log.info("3PID mapping query: {}", getIdentity().getQuery());
log.info("Identity medium queries: {}", GsonUtil.build().toJson(getIdentity().getMedium())); log.info("Identity medium queries: {}", GsonUtil.build().toJson(getIdentity().getMedium()));
log.info("Profile 3PID query: {}", getProfile().getThreepid().getQuery()); log.info("Profile:");
log.info("\tEnabled: {}", getProfile().isEnabled());
if (getProfile().isEnabled()) {
log.info("\tDisplay name query: {}", getProfile().getDisplayName().getQuery());
log.info("\tProfile 3PID query: {}", getProfile().getThreepid().getQuery());
}
} }
} }

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -18,8 +18,9 @@
* 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.sql; package io.kamax.mxisd.config.sql.generic;
import io.kamax.mxisd.config.sql.SqlConfig;
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 org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -20,6 +20,7 @@
package io.kamax.mxisd.config.sql.synapse; package io.kamax.mxisd.config.sql.synapse;
import io.kamax.mxisd.backend.sql.synapse.SynapseQueries;
import io.kamax.mxisd.config.sql.SqlConfig; import io.kamax.mxisd.config.sql.SqlConfig;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
@@ -38,22 +39,26 @@ public class SynapseSqlProviderConfig extends SqlConfig {
@PostConstruct @PostConstruct
public void doBuild() { public void doBuild() {
super.doBuild(); getAuth().setEnabled(false); // Synapse does the auth, we only act as a directory/identity service.
// FIXME check that the DB is not the mxisd one // FIXME check that the DB is not the mxisd one
// See https://matrix.to/#/!NPRUEisLjcaMtHIzDr:kamax.io/$1509377583327omXkC:kamax.io // See https://matrix.to/#/!NPRUEisLjcaMtHIzDr:kamax.io/$1509377583327omXkC:kamax.io
getAuth().setEnabled(false); // Synapse does the auth, we only act as a directory/identity service. if (getIdentity().isEnabled() && StringUtils.isBlank(getIdentity().getType())) {
if (getDirectory().isEnabled()) {
//FIXME set default queries for name and threepid
}
if (getIdentity().isEnabled()) {
if (StringUtils.isBlank(getIdentity().getType())) {
getIdentity().setType("mxid"); getIdentity().setType("mxid");
getIdentity().setQuery("SELECT user_id AS uid FROM user_threepids WHERE medium = ? AND address = ?"); getIdentity().setQuery("SELECT user_id AS uid FROM user_threepids WHERE medium = ? AND address = ?");
} }
if (getProfile().isEnabled()) {
if (StringUtils.isBlank(getProfile().getDisplayName().getQuery())) {
getProfile().getDisplayName().setQuery(SynapseQueries.getDisplayName());
}
if (StringUtils.isBlank(getProfile().getThreepid().getQuery())) {
getProfile().getThreepid().setQuery(SynapseQueries.getThreepids());
} }
} }
printConfig();
}
} }

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -28,6 +28,8 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
@Configuration @Configuration
@ConfigurationProperties("notification.handlers.sendgrid") @ConfigurationProperties("notification.handlers.sendgrid")
@@ -142,6 +144,15 @@ public class EmailSendGridConfig {
private EmailTemplate invite = new EmailTemplate(); private EmailTemplate invite = new EmailTemplate();
private TemplateSession session = new TemplateSession(); private TemplateSession session = new TemplateSession();
private Map<String, EmailTemplate> generic = new HashMap<>();
public Map<String, EmailTemplate> getGeneric() {
return generic;
}
public void setGeneric(Map<String, EmailTemplate> generic) {
this.generic = generic;
}
public EmailTemplate getInvite() { public EmailTemplate getInvite() {
return invite; return invite;

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -22,6 +22,9 @@ package io.kamax.mxisd.config.threepid.medium;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import java.util.HashMap;
import java.util.Map;
public class GenericTemplateConfig { public class GenericTemplateConfig {
private static final String classpathPrefix = "classpath:"; private static final String classpathPrefix = "classpath:";
@@ -73,6 +76,7 @@ public class GenericTemplateConfig {
private String invite; private String invite;
private Session session = new Session(); private Session session = new Session();
private Map<String, String> generic = new HashMap<>();
public String getInvite() { public String getInvite() {
return invite; return invite;
@@ -86,4 +90,12 @@ public class GenericTemplateConfig {
return session; return session;
} }
public Map<String, String> getGeneric() {
return generic;
}
public void setGeneric(Map<String, String> generic) {
this.generic = generic;
}
} }

View File

@@ -105,6 +105,7 @@ public class WordpressConfig {
private String type; private String type;
private String connection; private String connection;
private String tablePrefix;
private Query query; private Query query;
public String getType() { public String getType() {
@@ -123,6 +124,14 @@ public class WordpressConfig {
this.connection = connection; this.connection = connection;
} }
public String getTablePrefix() {
return tablePrefix;
}
public void setTablePrefix(String tablePrefix) {
this.tablePrefix = tablePrefix;
}
public Query getQuery() { public Query getQuery() {
return query; return query;
} }

View File

@@ -0,0 +1,110 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* 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.controller.app.v1;
import com.google.gson.JsonObject;
import io.kamax.matrix.json.GsonUtil;
import io.kamax.mxisd.as.AppServiceHandler;
import io.kamax.mxisd.config.ListenerConfig;
import io.kamax.mxisd.exception.HttpMatrixException;
import io.kamax.mxisd.exception.NotAllowedException;
import io.kamax.mxisd.util.GsonParser;
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.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import static org.springframework.web.bind.annotation.RequestMethod.GET;
import static org.springframework.web.bind.annotation.RequestMethod.PUT;
@RestController
@CrossOrigin
@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public class AppServiceController {
private final Logger log = LoggerFactory.getLogger(AppServiceController.class);
private final ListenerConfig cfg;
private final String notFoundBody;
private final GsonParser parser;
private final AppServiceHandler handler;
@Autowired
public AppServiceController(ListenerConfig cfg, AppServiceHandler handler) {
this.notFoundBody = GsonUtil.get().toJson(GsonUtil.makeObj("errcode", "io.kamax.mxisd.AS_NOT_FOUND"));
this.parser = new GsonParser();
this.cfg = cfg;
this.handler = handler;
}
private void validateToken(String token) {
if (StringUtils.isBlank(token)) {
throw new HttpMatrixException(401, "M_UNAUTHORIZED", "No HS token");
}
if (!StringUtils.equals(cfg.getToken().getHs(), token)) {
throw new NotAllowedException("Invalid HS token");
}
}
@RequestMapping(value = "/rooms/**", method = GET)
public String getRoom(HttpServletResponse res, @RequestParam(name = "access_token", required = false) String token) {
validateToken(token);
res.setStatus(404);
return notFoundBody;
}
@RequestMapping(value = "/users/**", method = GET)
public String getUser(HttpServletResponse res, @RequestParam(name = "access_token", required = false) String token) {
validateToken(token);
res.setStatus(404);
return notFoundBody;
}
@RequestMapping(value = "/transactions/{txnId:.+}", method = PUT)
public Object getTransaction(
HttpServletRequest request,
@RequestParam(name = "access_token", required = false) String token,
@PathVariable String txnId) {
try {
validateToken(token);
log.info("Processing transaction {}", txnId);
List<JsonObject> events = GsonUtil.asList(GsonUtil.getArray(parser.parse(request.getInputStream()), "events"), JsonObject.class);
handler.processTransaction(events);
return "{}";
} catch (Throwable e) {
log.warn("Unable to properly process transaction", e);
}
return "{}";
}
}

View File

@@ -75,6 +75,10 @@ public class DirectoryManager {
} }
public UserDirectorySearchResult search(URI target, String accessToken, String query) { public UserDirectorySearchResult search(URI target, String accessToken, String query) {
if (StringUtils.startsWith(query, "@")) {
query = query.substring(1);
}
log.info("Performing search for '{}'", query); log.info("Performing search for '{}'", query);
log.info("Original request URL: {}", target); log.info("Original request URL: {}", target);
UserDirectorySearchResult result = new UserDirectorySearchResult(); UserDirectorySearchResult result = new UserDirectorySearchResult();
@@ -125,6 +129,9 @@ public class DirectoryManager {
result.setLimited(true); result.setLimited(true);
} }
if (cfg.getExclude().getThreepid()) {
log.info("Skipping 3PID data, disabled in config");
} else {
resultProvider = provider.searchBy3pid(query); resultProvider = provider.searchBy3pid(query);
log.info("Threepid: found {} match(es) for '{}'", resultProvider.getResults().size(), query); log.info("Threepid: found {} match(es) for '{}'", resultProvider.getResults().size(), query);
result.getResults().addAll(resultProvider.getResults()); result.getResults().addAll(resultProvider.getResults());
@@ -132,6 +139,7 @@ public class DirectoryManager {
result.setLimited(true); result.setLimited(true);
} }
} }
}
log.info("Total matches: {} - limited? {}", result.getResults().size(), result.isLimited()); log.info("Total matches: {} - limited? {}", result.getResults().size(), result.isLimited());
return result; return result;

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -228,7 +228,7 @@ public class InvitationManager {
log.info("Invite is already pending for {}:{}, returning data", invitation.getMedium(), invitation.getAddress()); log.info("Invite is already pending for {}:{}, returning data", invitation.getMedium(), invitation.getAddress());
if (!StringUtils.equals(invitation.getRoomId(), reply.getInvite().getRoomId())) { if (!StringUtils.equals(invitation.getRoomId(), reply.getInvite().getRoomId())) {
log.info("Sending new notification as new invite room {} is different from the original {}", invitation.getRoomId(), reply.getInvite().getRoomId()); log.info("Sending new notification as new invite room {} is different from the original {}", invitation.getRoomId(), reply.getInvite().getRoomId());
notifMgr.sendForInvite(new ThreePidInviteReply(reply.getId(), invitation, reply.getToken(), reply.getDisplayName())); notifMgr.sendForReply(new ThreePidInviteReply(reply.getId(), invitation, reply.getToken(), reply.getDisplayName()));
} else { } else {
// FIXME we should check attempt and send if bigger // FIXME we should check attempt and send if bigger
} }
@@ -247,7 +247,7 @@ public class InvitationManager {
reply = new ThreePidInviteReply(invId, invitation, token, displayName); reply = new ThreePidInviteReply(invId, invitation, token, displayName);
log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress()); log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress());
notifMgr.sendForInvite(reply); notifMgr.sendForReply(reply);
log.info("Storing invite under ID {}", invId); log.info("Storing invite under ID {}", invId);
storage.insertInvite(reply); storage.insertInvite(reply);
@@ -319,9 +319,13 @@ public class InvitationManager {
CloseableHttpResponse response = client.execute(req); CloseableHttpResponse response = client.execute(req);
int statusCode = response.getStatusLine().getStatusCode(); int statusCode = response.getStatusLine().getStatusCode();
log.info("Answer code: {}", statusCode); log.info("Answer code: {}", statusCode);
if (statusCode >= 300) { if (statusCode >= 300 && statusCode != 403) {
log.warn("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8)); log.warn("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
} else { } else {
if (statusCode == 403) {
log.info("Invite was obsolete");
}
invitations.remove(getId(reply.getInvite())); invitations.remove(getId(reply.getInvite()));
storage.deleteInvite(reply.getId()); storage.deleteInvite(reply.getId());
log.info("Removed invite from internal store"); log.info("Removed invite from internal store");

View File

@@ -21,6 +21,7 @@
package io.kamax.mxisd.lookup.strategy; package io.kamax.mxisd.lookup.strategy;
import edazdarevic.commons.net.CIDRUtils; import edazdarevic.commons.net.CIDRUtils;
import io.kamax.mxisd.config.BulkLookupConfig;
import io.kamax.mxisd.config.RecursiveLookupConfig; import io.kamax.mxisd.config.RecursiveLookupConfig;
import io.kamax.mxisd.exception.ConfigurationException; import io.kamax.mxisd.exception.ConfigurationException;
import io.kamax.mxisd.lookup.*; import io.kamax.mxisd.lookup.*;
@@ -34,6 +35,7 @@ import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -44,14 +46,16 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
private Logger log = LoggerFactory.getLogger(RecursivePriorityLookupStrategy.class); private Logger log = LoggerFactory.getLogger(RecursivePriorityLookupStrategy.class);
private RecursiveLookupConfig cfg; private RecursiveLookupConfig cfg;
private BulkLookupConfig bulkCfg;
private List<IThreePidProvider> providers; private List<IThreePidProvider> providers;
private IBridgeFetcher bridge; private IBridgeFetcher bridge;
private List<CIDRUtils> allowedCidr = new ArrayList<>(); private List<CIDRUtils> allowedCidr = new ArrayList<>();
@Autowired @Autowired
public RecursivePriorityLookupStrategy(RecursiveLookupConfig cfg, List<IThreePidProvider> providers, IBridgeFetcher bridge) { public RecursivePriorityLookupStrategy(RecursiveLookupConfig cfg, BulkLookupConfig bulkCfg, List<IThreePidProvider> providers, IBridgeFetcher bridge) {
this.cfg = cfg; this.cfg = cfg;
this.bulkCfg = bulkCfg;
this.bridge = bridge; this.bridge = bridge;
this.providers = providers.stream().filter(p -> { this.providers = providers.stream().filter(p -> {
log.info("3PID Provider {} is enabled: {}", p.getClass().getSimpleName(), p.isEnabled()); log.info("3PID Provider {} is enabled: {}", p.getClass().getSimpleName(), p.isEnabled());
@@ -193,6 +197,10 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
@Override @Override
public List<ThreePidMapping> find(BulkLookupRequest request) { public List<ThreePidMapping> find(BulkLookupRequest request) {
if (!bulkCfg.getEnabled()) {
return Collections.emptyList();
}
List<ThreePidMapping> mapToDo = new ArrayList<>(request.getMappings()); List<ThreePidMapping> mapToDo = new ArrayList<>(request.getMappings());
List<ThreePidMapping> mapFoundAll = new ArrayList<>(); List<ThreePidMapping> mapFoundAll = new ArrayList<>();

View File

@@ -67,7 +67,7 @@ public class IdentityServerUtils {
log.info("{} is not an URL, using as-is", domainOrUrl); log.info("{} is not an URL, using as-is", domainOrUrl);
} }
log.info("Discovery Identity Server for {}", domainOrUrl); log.info("Discovering Identity Server for {}", domainOrUrl);
log.info("Performing SRV lookup"); log.info("Performing SRV lookup");
String lookupDns = getSrvRecordName(domainOrUrl); String lookupDns = getSrvRecordName(domainOrUrl);
log.info("Lookup name: {}", lookupDns); log.info("Lookup name: {}", lookupDns);
@@ -98,10 +98,12 @@ public class IdentityServerUtils {
if (isUsable(baseUrl)) { if (isUsable(baseUrl)) {
log.info("Found Identity Server for domain {} at {}", domainOrUrl, baseUrl); log.info("Found Identity Server for domain {} at {}", domainOrUrl, baseUrl);
return Optional.of(baseUrl); return Optional.of(baseUrl);
} else {
log.info("Found no Identity server for domain {} at {}", domainOrUrl, baseUrl);
} }
} }
log.info("Found no Identity server for domain {} at {}"); log.info("Found no Identity server for domain {}", domainOrUrl);
return Optional.empty(); return Optional.empty();
} catch (TextParseException e) { } catch (TextParseException e) {
log.warn(domainOrUrl + " is not a valid domain name"); log.warn(domainOrUrl + " is not a valid domain name");

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -20,6 +20,7 @@
package io.kamax.mxisd.notification; package io.kamax.mxisd.notification;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession; import io.kamax.mxisd.threepid.session.IThreePidSession;
@@ -29,7 +30,9 @@ public interface INotificationHandler {
String getMedium(); String getMedium();
void sendForInvite(IThreePidInviteReply invite); void sendForInvite(IMatrixIdInvite invite);
void sendForReply(IThreePidInviteReply invite);
void sendForValidation(IThreePidSession session); void sendForValidation(IThreePidSession session);

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -20,6 +20,7 @@
package io.kamax.mxisd.notification; package io.kamax.mxisd.notification;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.config.threepid.notification.NotificationConfig; import io.kamax.mxisd.config.threepid.notification.NotificationConfig;
import io.kamax.mxisd.exception.NotImplementedException; import io.kamax.mxisd.exception.NotImplementedException;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;
@@ -68,8 +69,12 @@ public class NotificationManager {
return handlers.containsKey(medium); return handlers.containsKey(medium);
} }
public void sendForInvite(IThreePidInviteReply invite) { public void sendForInvite(IMatrixIdInvite invite) {
ensureMedium(invite.getInvite().getMedium()).sendForInvite(invite); ensureMedium(invite.getMedium()).sendForInvite(invite);
}
public void sendForReply(IThreePidInviteReply invite) {
ensureMedium(invite.getInvite().getMedium()).sendForReply(invite);
} }
public void sendForValidation(IThreePidSession session) { public void sendForValidation(IThreePidSession session) {

View File

@@ -22,37 +22,66 @@ package io.kamax.mxisd.profile;
import io.kamax.matrix._MatrixID; import io.kamax.matrix._MatrixID;
import io.kamax.matrix._ThreePid; import io.kamax.matrix._ThreePid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Component @Component
public class ProfileManager { public class ProfileManager {
private final Logger log = LoggerFactory.getLogger(ProfileManager.class);
private List<ProfileProvider> providers; private List<ProfileProvider> providers;
@Autowired
public ProfileManager(List<ProfileProvider> providers) { public ProfileManager(List<ProfileProvider> providers) {
this.providers = providers.stream() this.providers = providers;
.filter(ProfileProvider::isEnabled) }
@PostConstruct
public void build() {
log.info("--- Profile providers ---");
providers = providers.stream()
.filter(pp -> {
log.info("\t- {} - Is enabled? {}", pp.getClass().getSimpleName(), pp.isEnabled());
return pp.isEnabled();
})
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public <T> List<T> get(Function<ProfileProvider, List<T>> function) { public <T> List<T> getList(Function<ProfileProvider, List<T>> function) {
return providers.stream() return providers.stream()
.map(function) .map(function)
.flatMap(Collection::stream) .flatMap(Collection::stream)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public List<_ThreePid> getThreepids(_MatrixID mxid) { public <T> Optional<T> getOpt(Function<ProfileProvider, Optional<T>> function) {
return get(p -> p.getThreepids(mxid)); return providers.stream()
.map(function)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
} }
public List<String> getRoles(_MatrixID mxid) { public Optional<String> getDisplayName(_MatrixID user) {
return get(p -> p.getRoles(mxid)); return getOpt(p -> p.getDisplayName(user));
}
public List<_ThreePid> getThreepids(_MatrixID user) {
return getList(p -> p.getThreepids(user));
}
public List<String> getRoles(_MatrixID user) {
return getList(p -> p.getRoles(user));
} }
} }

View File

@@ -24,13 +24,16 @@ import io.kamax.matrix._MatrixID;
import io.kamax.matrix._ThreePid; import io.kamax.matrix._ThreePid;
import java.util.List; import java.util.List;
import java.util.Optional;
public interface ProfileProvider { public interface ProfileProvider {
boolean isEnabled(); boolean isEnabled();
List<_ThreePid> getThreepids(_MatrixID mxid); Optional<String> getDisplayName(_MatrixID userId);
List<String> getRoles(_MatrixID mxid); List<_ThreePid> getThreepids(_MatrixID userId);
List<String> getRoles(_MatrixID userId);
} }

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -23,6 +23,7 @@ package io.kamax.mxisd.threepid.connector.email;
import com.sendgrid.SendGrid; import com.sendgrid.SendGrid;
import com.sendgrid.SendGridException; import com.sendgrid.SendGridException;
import io.kamax.matrix.ThreePidMedium; import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.threepid.connector.EmailSendGridConfig; import io.kamax.mxisd.config.threepid.connector.EmailSendGridConfig;
@@ -87,13 +88,25 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
} }
@Override @Override
public void sendForInvite(IThreePidInviteReply invite) { public void sendForInvite(IMatrixIdInvite invite) {
EmailTemplate template = cfg.getTemplates().getInvite(); EmailTemplate template = cfg.getTemplates().getGeneric().get("matrixId");
Email email = getEmail(); Email email = getEmail();
email.setSubject(populateForInvite(invite, template.getSubject())); email.setSubject(populateForInvite(invite, template.getSubject()));
email.setText(populateForInvite(invite, getFromFile(template.getBody().getText()))); email.setText(populateForInvite(invite, getFromFile(template.getBody().getText())));
email.setHtml(populateForInvite(invite, getFromFile(template.getBody().getHtml()))); email.setHtml(populateForInvite(invite, getFromFile(template.getBody().getHtml())));
send(invite.getAddress(), email);
}
@Override
public void sendForReply(IThreePidInviteReply invite) {
EmailTemplate template = cfg.getTemplates().getInvite();
Email email = getEmail();
email.setSubject(populateForReply(invite, template.getSubject()));
email.setText(populateForReply(invite, getFromFile(template.getBody().getText())));
email.setHtml(populateForReply(invite, getFromFile(template.getBody().getHtml())));
send(invite.getInvite().getAddress(), email); send(invite.getInvite().getAddress(), email);
} }

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -20,6 +20,7 @@
package io.kamax.mxisd.threepid.notification; package io.kamax.mxisd.threepid.notification;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.exception.ConfigurationException; import io.kamax.mxisd.exception.ConfigurationException;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.notification.INotificationHandler; import io.kamax.mxisd.notification.INotificationHandler;
@@ -55,8 +56,13 @@ public abstract class GenericNotificationHandler<A extends IThreePidConnector, B
} }
@Override @Override
public void sendForInvite(IThreePidInviteReply invite) { public void sendForInvite(IMatrixIdInvite invite) {
send(connector, invite.getInvite().getAddress(), generator.getForInvite(invite)); send(connector, invite.getAddress(), generator.getForInvite(invite));
}
@Override
public void sendForReply(IThreePidInviteReply invite) {
send(connector, invite.getInvite().getAddress(), generator.getForReply(invite));
} }
@Override @Override

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -20,6 +20,7 @@
package io.kamax.mxisd.threepid.notification; package io.kamax.mxisd.threepid.notification;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.threepid.medium.GenericTemplateConfig; import io.kamax.mxisd.config.threepid.medium.GenericTemplateConfig;
@@ -65,9 +66,20 @@ public abstract class GenericTemplateNotificationGenerator extends PlaceholderNo
} }
@Override @Override
public String getForInvite(IThreePidInviteReply invite) { public String getForInvite(IMatrixIdInvite invite) {
String template = cfg.getGeneric().get("matrixId");
if (StringUtils.isBlank(template)) {
throw new InternalServerError("No " + invite.getMedium() + " template configured for Matrix ID invites");
}
log.info("Generating notification content for Matrix ID invite");
return populateForInvite(invite, getTemplateContent(template));
}
@Override
public String getForReply(IThreePidInviteReply invite) {
log.info("Generating notification content for 3PID invite"); log.info("Generating notification content for 3PID invite");
return populateForInvite(invite, getTemplateContent(cfg.getInvite())); return populateForReply(invite, getTemplateContent(cfg.getInvite()));
} }
@Override @Override

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -20,6 +20,7 @@
package io.kamax.mxisd.threepid.notification; package io.kamax.mxisd.threepid.notification;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession; import io.kamax.mxisd.threepid.session.IThreePidSession;
@@ -29,7 +30,9 @@ public interface INotificationGenerator {
String getMedium(); String getMedium();
String getForInvite(IThreePidInviteReply invite); String getForInvite(IMatrixIdInvite invite);
String getForReply(IThreePidInviteReply invite);
String getForValidation(IThreePidSession session); String getForValidation(IThreePidSession session);

View File

@@ -1,8 +1,8 @@
/* /*
* mxisd - Matrix Identity Server Daemon * mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor * Copyright (C) 2017 Kamax Sarl
* *
* https://max.kamax.io/ * https://www.kamax.io/
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@@ -21,6 +21,7 @@
package io.kamax.mxisd.threepid.notification; package io.kamax.mxisd.threepid.notification;
import io.kamax.matrix.ThreePid; import io.kamax.matrix.ThreePid;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.controller.identity.v1.IdentityAPIv1; import io.kamax.mxisd.controller.identity.v1.IdentityAPIv1;
@@ -39,7 +40,7 @@ public abstract class PlaceholderNotificationGenerator {
this.srvCfg = srvCfg; this.srvCfg = srvCfg;
} }
protected String populateForCommon(String input, ThreePid recipient) { protected String populateForCommon(ThreePid recipient, String input) {
String domainPretty = WordUtils.capitalizeFully(mxCfg.getDomain()); String domainPretty = WordUtils.capitalizeFully(mxCfg.getDomain());
return input return input
@@ -49,7 +50,23 @@ public abstract class PlaceholderNotificationGenerator {
.replace("%RECIPIENT_ADDRESS%", recipient.getAddress()); .replace("%RECIPIENT_ADDRESS%", recipient.getAddress());
} }
protected String populateForInvite(IThreePidInviteReply invite, String input) { protected String populateForInvite(IMatrixIdInvite invite, String input) {
String senderName = invite.getProperties().getOrDefault("sender_display_name", "");
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getSender().getId());
String roomName = invite.getProperties().getOrDefault("room_name", "");
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getRoomId());
return populateForCommon(new ThreePid(invite.getMedium(), invite.getAddress()), input)
.replace("%SENDER_ID%", invite.getSender().getId())
.replace("%SENDER_NAME%", senderName)
.replace("%SENDER_NAME_OR_ID%", senderNameOrId)
.replace("%RECIPIENT_ID%", invite.getInvitee().getId())
.replace("%ROOM_ID%", invite.getRoomId())
.replace("%ROOM_NAME%", roomName)
.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
}
protected String populateForReply(IThreePidInviteReply invite, String input) {
ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress()); ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress());
String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", ""); String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", "");
@@ -57,7 +74,7 @@ public abstract class PlaceholderNotificationGenerator {
String roomName = invite.getInvite().getProperties().getOrDefault("room_name", ""); String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId()); String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
return populateForCommon(input, tpid) return populateForCommon(tpid, input)
.replace("%SENDER_ID%", invite.getInvite().getSender().getId()) .replace("%SENDER_ID%", invite.getInvite().getSender().getId())
.replace("%SENDER_NAME%", senderName) .replace("%SENDER_NAME%", senderName)
.replace("%SENDER_NAME_OR_ID%", senderNameOrId) .replace("%SENDER_NAME_OR_ID%", senderNameOrId)
@@ -76,7 +93,7 @@ public abstract class PlaceholderNotificationGenerator {
session.getToken() session.getToken()
); );
return populateForCommon(input, session.getThreePid()) return populateForCommon(session.getThreePid(), input)
.replace("%VALIDATION_LINK%", validationLink) .replace("%VALIDATION_LINK%", validationLink)
.replace("%VALIDATION_TOKEN%", session.getToken()) .replace("%VALIDATION_TOKEN%", session.getToken())
.replace("%NEXT_URL%", validationLink); .replace("%NEXT_URL%", validationLink);

View File

@@ -46,8 +46,8 @@ public class EmailNotificationGenerator extends GenericTemplateNotificationGener
} }
@Override @Override
protected String populateForCommon(String body, ThreePid recipient) { protected String populateForCommon(ThreePid recipient, String body) {
body = super.populateForCommon(body, recipient); body = super.populateForCommon(recipient, body);
body = body.replace("%FROM_EMAIL%", cfg.getIdentity().getFrom()); body = body.replace("%FROM_EMAIL%", cfg.getIdentity().getFrom());
body = body.replace("%FROM_NAME%", cfg.getIdentity().getName()); body = body.replace("%FROM_NAME%", cfg.getIdentity().getName());
return body; return body;

View File

@@ -1,7 +1,7 @@
# DO NOT USE THIS FILE AS-IS FOR YOUR INITIAL CONFIGURATION # DO NOT USE THIS FILE AS-IS FOR YOUR INITIAL CONFIGURATION
# ONLY TAKE THE SPECIFIC SECTION YOU WANT TO CONFIGURE # ONLY TAKE THE SPECIFIC SECTION YOU WANT TO CONFIGURE
# #
# For more information about configuration, visit https://github.com/kamax-io/mxisd/blob/master/docs/configure.md # For more information about configuration, visit https://github.com/kamax-matrix/mxisd/blob/master/docs/configure.md
spring: spring:
main: main:
@@ -26,6 +26,12 @@ matrix:
servers: servers:
matrix-org: matrix-org:
- 'https://matrix.org' - 'https://matrix.org'
listener:
url: ''
localpart: ''
token:
as: ''
hs: ''
lookup: lookup:
recursive: recursive:
@@ -132,7 +138,6 @@ netiq:
firebase: firebase:
enabled: false enabled: false
sql: sql:
enabled: false enabled: false
type: 'sqlite' type: 'sqlite'
@@ -155,23 +160,17 @@ sql:
threepid: threepid:
query: 'SELECT medium, address FROM user_threepids WHERE user_id = ?' query: 'SELECT medium, address FROM user_threepids WHERE user_id = ?'
synapseSql:
enabled: false
type: 'sqlite'
profile:
threepid:
query: 'SELECT medium, address FROM user_threepids WHERE user_id = ?'
wordpress: wordpress:
enabled: false enabled: false
sql: sql:
type: 'mysql' type: 'mysql'
tablePrefix: 'wp_'
query: query:
threepid: threepid:
email: 'SELECT user_login as uid FROM wp_users WHERE user_email = ?' email: 'SELECT user_login as uid FROM ${wordpress.sql.tablePrefix}users WHERE user_email = ?'
directory: directory:
name: "SELECT DISTINCT user_login, display_name FROM wp_users u LEFT JOIN wp_usermeta m ON m.user_id = u.id WHERE u.display_name LIKE ? OR (m.meta_key = 'nickname' AND m.meta_value = ?) OR (m.meta_key = 'first_name' AND m.meta_value = ?) OR (m.meta_key = 'last_name' AND m.meta_value = ?);" name: "SELECT DISTINCT user_login, display_name FROM ${wordpress.sql.tablePrefix}users u LEFT JOIN ${wordpress.sql.tablePrefix}usermeta m ON m.user_id = u.id WHERE u.display_name LIKE ? OR (m.meta_key = 'nickname' AND m.meta_value = ?) OR (m.meta_key = 'first_name' AND m.meta_value = ?) OR (m.meta_key = 'last_name' AND m.meta_value = ?);"
threepid: 'SELECT DISTINCT user_login, display_name FROM wp_users WHERE user_email LIKE ?' threepid: 'SELECT DISTINCT user_login, display_name FROM ${wordpress.sql.tablePrefix}users WHERE user_email LIKE ?'
forward: forward:
servers: [] servers: []
@@ -194,6 +193,8 @@ threepid:
generators: generators:
template: template:
invite: 'classpath:threepids/email/invite-template.eml' invite: 'classpath:threepids/email/invite-template.eml'
generic:
matrixId: 'classpath:threepids/email/mxid-template.eml'
session: session:
validation: validation:
local: 'classpath:threepids/email/validate-local-template.eml' local: 'classpath:threepids/email/validate-local-template.eml'

View File

@@ -0,0 +1,73 @@
Subject: You have been invited to a room
MIME-Version: 1.0
Content-Type: multipart/alternative;
boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
Content-Type: text/plain; charset=UTF-8
Content-Disposition: inline
Hi,
%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on Matrix.
Thanks,
%DOMAIN_PRETTY% Admins
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
Content-Type: multipart/related;
boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
type="text/html"
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR
Content-Type: text/html; charset=UTF-8
Content-Disposition: inline
<!doctype html>
<html lang="en">
<head>
<style type="text/css">
body {
margin: 0px;
}
pre, code {
word-break: break-word;
white-space: pre-wrap;
}
#page {
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
font-color: #454545;
font-size: 12pt;
width: 100%%;
padding: 20px;
}
#inner {
width: 640px;
}
</style>
</head>
<body>
<table id="page">
<tr>
<td> </td>
<td id="inner">
<p>Hi,</p>
<p>%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on Matrix.</p>
<p>Thanks,</p>
<p>%DOMAIN_PRETTY% Admins</p>
</td>
<td> </td>
</tr>
</table>
</body>
</html>
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR--
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--