Compare commits

...

27 Commits

Author SHA1 Message Date
Maxime Dor
9babad6b33 Add push of latest dev docker image tag to push target 2017-09-18 02:29:58 +02:00
Maxime Dor
00896ab280 Add PostgreSQL support for SQL Backend 2017-09-18 02:24:46 +02:00
Max Dor
f03cd76f52 Merge pull request #30 from kamax-io/rest-backend
REST backend
2017-09-18 01:05:31 +02:00
Maxime Dor
0453c1db30 Add tests for REST backend implementation 2017-09-18 01:00:17 +02:00
Maxime Dor
013be139c9 Fix bad copy/paste and bad method scope 2017-09-17 23:26:22 +02:00
Maxime Dor
317fc367f8 Identity lookup implementation for REST backend 2017-09-17 22:34:48 +02:00
Maxime Dor
efc54e73f2 Streamline Backend auth mechanism/return values 2017-09-17 21:19:29 +02:00
Maxime Dor
0182ec7251 Streamline JSON requests/answers 2017-09-17 15:48:33 +02:00
Maxime Dor
640ccb7ef8 Improve docker build process and doc 2017-09-17 15:12:39 +02:00
Maxime Dor
2e694349c9 Add default type for SQL Backend 2017-09-17 14:47:48 +02:00
Maxime Dor
9328fa1eb4 Update link for REST auth module in sample config 2017-09-17 14:17:50 +02:00
Maxime Dor
221d823f3b Update LDAP library to fix auth filter bug 2017-09-17 14:01:38 +02:00
Maxime Dor
8b6eadb9ab Auth endpoint implementation 2017-09-17 05:17:00 +02:00
Maxime Dor
22d8380bce Configuration management
- Default values
- Compute values
2017-09-17 02:03:45 +02:00
Maxime Dor
0cbf1a83a5 First skeleton for REST backend 2017-09-17 01:06:43 +02:00
Maxime Dor
23f717579e Refactor backend packages 2017-09-16 22:56:53 +02:00
Maxime Dor
4eb8c95c3a Handle anonymous bind in LDAP backend (Fix #27) 2017-09-16 18:11:39 +02:00
Maxime Dor
0fa04e4a54 Better wording 2017-09-16 14:19:26 +02:00
Maxime Dor
f97aa8cbef Cosmetic refactoring 2017-09-16 13:46:15 +02:00
Maxime Dor
37b0f29184 Cosmetic refactoring 2017-09-16 13:01:37 +02:00
Maxime Dor
2befdbb54f Improve network discovery explanation 2017-09-16 04:34:16 +02:00
Maxime Dor
d1a6c84e6b Properly split authoritative domain and public IS host 2017-09-16 04:29:01 +02:00
Maxime Dor
e8229b867a Add docker build targets 2017-09-16 02:07:10 +02:00
Maxime Dor
6fb18d5827 Remove problematic handling of multiple validation requests for same 3PID 2017-09-16 01:34:31 +02:00
Maxime Dor
a8488a0745 Add ability to overwrite DNS when trying to contact the related homeserver 2017-09-14 23:10:23 +02:00
Maxime Dor
89a7416367 Add prototype support for SQL auth/directory backends 2017-09-14 20:59:35 +02:00
Maxime Dor
068c6b8555 Use proper object for key validity json answer 2017-09-14 18:34:02 +02:00
51 changed files with 2273 additions and 157 deletions

View File

@@ -7,7 +7,7 @@ mxisd - Federated Matrix Identity Server Daemon
[Integration](#integration) | [Support](#support)
# Overview
mxisd is a Federated Matrix Identity server aimed to self-hosted Matrix infrastructures.
mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures.
mxisd uses a cascading lookup model which performs lookup from a more authoritative to a less authoritative source, usually doing:
- Local identity stores: LDAP, etc.
@@ -80,11 +80,11 @@ For more info, see [the public repository](https://hub.docker.com/r/kamax/mxisd/
### From source
[Build mxisd](#build) then build the docker image:
```
docker build -t your-org/mxisd:$(git describe --tags --always --dirty) .
./gradlew dockerBuild
```
You can run a container of the given image and test it with the following command (adapt volumes host paths):
```
docker run -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t your-org/mxisd:$(git describe --tags --always --dirty)
docker run -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t kamax/mxisd:latest-dev
```
# From Source
@@ -169,11 +169,14 @@ systemctl start mxisd
# Configuration
After following the specific instructions to create a config file from the sample:
1. Set the `server.name` value to the domain value used in your Home Server configuration
1. Set the `matrix.domain` value to the domain value used in your Home Server configuration
2. Set an absolute location for the signing keys using `key.path`
3. Set a location for the default SQLite persistence using `storage.provider.sqlite.database`
4. Configure the E-mail invite sender with items starting in `invite.sender.email`
In case your IS public domain does not match your Matrix domain, see `server.name` and `server.publicUrl`
config items.
If you want to use the LDAP backend:
1. Enable it with `ldap.enabled`
2. Configure connection options using items starting in `ldap.connection`
@@ -181,10 +184,15 @@ If you want to use the LDAP backend:
# Network Discovery
To allow other federated Identity Server to reach yours, configure the following DNS SRV entry (adapt to your values):
To allow other federated Identity Server to reach yours, the same algorithm used for Homeservers takes place:
1. Check for the appropriate DNS SRV record
2. If not found, use the base domain
If your Identity Server public hostname does not match your Matrix domain, configure the following DNS SRV entry
and replace `matrix.example.com` by your Identity server public hostname - **Make sure to end with a final dot!**
```
_matrix-identity._tcp.example.com. 3600 IN SRV 10 0 443 matrix.example.com.
```
```
This would only apply for 3PID that are DNS-based, like e-mails. For anything else, like phone numbers, no federation
is currently possible.

View File

@@ -7,6 +7,15 @@
# Any mandatory configuration item will not be prefixed by # and will also contain a value as example that must be
# changed. It is advised to re-create a clean config file with only the required configuration item.
#######################
# Matrix config items #
#######################
# Matrix domain, same as the domain configure in your Homeserver configuration.
#
# This is used to build the various identifiers for identity, auth and directory.
matrix.domain: ''
#######################
# Server config items #
@@ -19,21 +28,33 @@
#server.port: 8090
# Realm under which this Identity Server is authoritative.
# Public hostname of this identity server.
#
# This is used to avoid unnecessary connections and endless recursive lookup.
# e.g. domain name in e-mails.
server.name: 'example.org'
# This would be typically be the same as your Matrix domain.
# In case it is not, set this value.
#
# This value is used in various signatures within the Matrix protocol and should be a reachable hostname.
# You can validate by ensuring you see a JSON answer when calling (replace the domain):
# https://example.org/_matrix/identity/status
#
#server.name: 'example.org'
# Public URL to reach this identity server
#
# This is used with 3PID invites in room and other Homeserver key verification workflow.
# If left unconfigured, it will be generated from the server name
# If left unconfigured, it will be generated from the server name.
#
# You should typically set this value if you want to change the public port under which
# this Identity server is reachable.
#
# %SERVER_NAME% placeholder is available to avoid configuration duplication.
# e.g. 'https://%SERVER_NAME%:8443'
#
#server.publicUrl: 'https://example.org'
#############################
# Signing keys config items #
#############################
@@ -47,6 +68,7 @@ server.name: 'example.org'
key.path: '/path/to/sign.key'
#################################
# Recurisve lookup config items #
#################################
@@ -125,14 +147,15 @@ key.path: '/path/to/sign.key'
#lookup.recursive.bridge.mappings.msisdn: ''
#####################
# LDAP config items #
#####################
# Global enable/disable switch
#
#ldap.enabled: false
#### Connection related config items
# If the connection should be secure
#
@@ -149,12 +172,14 @@ key.path: '/path/to/sign.key'
#ldap.connection.port: 389
# Bind DN to use when performing lookups
# Bind DN for the connection.
#
# If Bind DN and password are empty, anonymous authentication is performed
#
#ldap.connection.bindDn: 'CN=Matrix Identity Server,CN=Users,DC=example,DC=org'
# Bind password to use
# Bind password for the connection.
#
#ldap.connection.bindPassword: 'password'
@@ -195,15 +220,13 @@ key.path: '/path/to/sign.key'
#### Configuration section relating the authentication of users performed via LDAP.
#
# This can be done using the REST Auth module for synapse and pointing it to the identity server.
# See https://github.com/maxidor/matrix-synapse-rest-auth
# See https://github.com/kamax-io/matrix-synapse-rest-auth
#
# During authentication, What to filter potential users by, typically by using a dedicated group.
# If this value is not set, login check will be performed for all entities within the LDAP
#
# Example: (memberOf=CN=Matrix Users,CN=Users,DC=example,DC=org)
#
# /!\ Currently NOT supported due to a possible bug in LDAP library /!\
#
#ldap.auth.filter: ''
@@ -223,6 +246,47 @@ key.path: '/path/to/sign.key'
#ldap.identity.medium.msisdn: "(|(telephoneNumber=+%3pid)(mobile=+%3pid)(homePhone=+%3pid)(otherTelephone=+%3pid)(otherMobile=+%3pid)(otherHomePhone=+%3pid))"
############################
# SQL Provider config item #
############################
#
# Example configuration to integrate with synapse SQLite DB (default configuration)
#
#sql.enabled: true
#sql.type: 'sqlite'
#sql.connection: '/var/lib/matrix-synapse/homeserver.db'
#
# Example configuration to integrate with synapse PostgreSQL DB
#sql.enabled: true
#sql.type: 'postgresql'
#sql.connection: '//dnsOrIpToServer/dbName?user=synapseDbUser&password=synapseDbPassword'
#
# Configuration for an arbitrary server with arbitrary driver
#
# sql.identity.type possible values:
# - uid Returned value is the localpart of the Matrix ID
# - mxid Full Matrix ID, including domain
#
# sql.identity.query MUST contain a column with label 'uid'
#
# If you would like to overwrite the global lookup query for specific medium type,
# add a config item (see below for example) in the following format
# sql.identity.medium.theMediumIdYouWant: 'the query'
#sql.enabled: true
#sql.type: 'jdbcDriverName'
#sql.connection: '//dnsOrIpToServer/dbName?user=synapseDbUser&password=synapseDbPassword'
#sql.identity.type: 'mxid'
#sql.identity.query: 'SELECT raw AS uid FROM table WHERE medium = ? AND address = ?'
#sql.identity.medium.email: 'SELECT raw AS uid FROM emailTable WHERE address = ?'
#######################################
# Lookup queries forward config items #
#######################################
@@ -236,6 +300,7 @@ key.path: '/path/to/sign.key'
# - "https://vector.im"
#############################
# 3PID invites config items #
#############################
@@ -304,6 +369,7 @@ invite.sender.email.email: "matrix-identity@example.org"
#invite.sender.email.template: "/absolute/path/to/file"
############################
# Persistence config items #
############################
@@ -314,6 +380,7 @@ invite.sender.email.email: "matrix-identity@example.org"
#
#storage.backend: 'sqlite'
#### Generic SQLite provider config
#
# Path to the SQLite DB file, required if SQLite backend is chosen
@@ -324,3 +391,24 @@ invite.sender.email.email: "matrix-identity@example.org"
# - /var/lib/mxisd/mxisd.db
#
storage.provider.sqlite.database: '/path/to/mxisd.db'
######################
# DNS-related config #
######################
# The domain to overwrite
#
#dns.overwrite.homeserver.name: 'example.org'
# - 'env' from environment variable specified by value
# - any other value will use the value as-is as host
#
#dns.overwrite.homeserver.type: 'raw'
# The value to use, depending on the type.
# Protocol will always be HTTPS
#
#dns.overwrite.homeserver.value: 'localhost:8448'

View File

@@ -40,6 +40,9 @@ def debBuildConfPath = "${debBuildBasePath}${debConfPath}"
def debBuildDataPath = "${debBuildBasePath}${debDataPath}"
def debBuildSystemdPath = "${debBuildBasePath}${debSystemdPath}"
def dockerImageName = "kamax/mxisd"
def dockerImageTag = "${dockerImageName}:${gitVersion()}"
String gitVersion() {
def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?")
ByteArrayOutputStream out = new ByteArrayOutputStream()
@@ -83,7 +86,7 @@ dependencies {
compile 'net.i2p.crypto:eddsa:0.1.0'
// LDAP connector
compile 'org.apache.directory.api:api-all:1.0.0-RC2'
compile 'org.apache.directory.api:api-all:1.0.0'
// DNS lookups
compile 'dnsjava:dnsjava:2.1.8'
@@ -110,7 +113,11 @@ dependencies {
// SQLite
compile 'org.xerial:sqlite-jdbc:3.20.0'
// PostgreSQL
compile 'org.postgresql:postgresql:42.1.4'
testCompile 'junit:junit:4.12'
testCompile 'com.github.tomakehurst:wiremock:2.8.0'
}
springBoot {
@@ -216,3 +223,23 @@ task buildDeb(dependsOn: build) {
}
}
}
task dockerBuild(type: Exec, dependsOn: build) {
commandLine 'docker', 'build', '-t', dockerImageTag, project.rootDir
doLast {
exec {
commandLine 'docker', 'tag', dockerImageTag, "${dockerImageName}:latest-dev"
}
}
}
task dockerPush(type: Exec) {
commandLine 'docker', 'push', dockerImageTag
doLast {
exec {
commandLine 'docker', 'push', "${dockerImageName}:latest-dev"
}
}
}

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

@@ -0,0 +1,169 @@
# REST backend
The REST backend allows you to query arbitrary REST JSON endpoints as backends for the following flows:
- Identity lookup
- Authentication
## Configuration
| Key | Default | Description |
---------------------------------|---------------------------------------|------------------------------------------------------|
| rest.enabled | false | Globally enable/disable the REST backend |
| rest.host | *empty* | Default base URL to use for the different endpoints. |
| rest.endpoints.auth | /_mxisd/identity/api/v1/auth | Endpoint to validate credentials |
| rest.endpoints.identity.single | /_mxisd/identity/api/v1/lookup/single | Endpoint to lookup a single 3PID |
| rest.endpoints.identity.bulk | /_mxisd/identity/api/v1/lookup/bulk | Endpoint to lookup a list of 3PID |
Endpoint values can handle two formats:
- URL Path starting with `/` that gets happened to the `rest.host`
- Full URL, if you want each endpoint to go to a specific server/protocol/port
`rest.host` is only mandatory if at least one endpoint is not a full URL.
## Endpoints
### Authenticate
Configured with `rest.endpoints.auth`
HTTP method: `POST`
Encoding: JSON UTF-8
#### Request Body
```
{
"auth": {
"mxid": "@john.doe:example.org",
"localpart": "john.doe",
"domain": "example.org",
"password": "passwordOfTheUser"
}
}
```
#### Response Body
If the authentication fails:
```
{
"auth": {
"success": false
}
}
```
If the authentication succeed:
- `auth.id` supported values: `localpart`, `mxid`
- `auth.profile` and any sub-member are all optional
```
{
"auth": {
"success": true,
"id": {
"type": "localpart",
"value": "john"
},
"profile": {
"display_name": "John Doe",
"three_pids": [
{
"medium": "email",
"address": "john.doe@example.org"
},
{
"medium": "msisdn",
"address": "123456789"
}
]
}
}
}
```
### Lookup
#### Single
Configured with `rest.endpoints.identity.single`
HTTP method: `POST`
Encoding: JSON UTF-8
#### Request Body
```
{
"lookup": {
"medium": "email",
"address": "john.doe@example.org"
}
}
```
#### Response Body
If a match was found:
- `lookup.id.type` supported values: `localpart`, `mxid`
```
{
"lookup": {
"medium": "email",
"address": "john.doe@example.org",
"id": {
"type": "mxid",
"value": "@john:example.org"
}
}
}
```
If no match was found:
```
{}
```
#### Bulk
Configured with `rest.endpoints.identity.bulk`
HTTP method: `POST`
Encoding: JSON UTF-8
#### Request Body
```
{
"lookup": [
{
"medium": "email",
"address": "john.doe@example.org"
},
{
"medium": "msisdn",
"address": "123456789"
}
]
}
```
#### Response Body
For all entries where a match was found:
- `lookup[].id.type` supported values: `localpart`, `mxid`
```
{
"lookup": [
{
"medium": "email",
"address": "john.doe@example.org",
"id": {
"type": "localpart",
"value": "john"
}
},
{
"medium": "msisdn",
"address": "123456789",
"id": {
"type": "mxid",
"value": "@jane:example.org"
}
}
]
}
```
If no match was found:
```
{
"lookup": []
}
```

View File

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

View File

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

View File

@@ -20,8 +20,13 @@
package io.kamax.mxisd.auth;
import io.kamax.matrix.MatrixID;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.ThreePid;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.invitation.InvitationManager;
import io.kamax.mxisd.lookup.ThreePidMapping;
import org.slf4j.Logger;
@@ -40,26 +45,45 @@ public class AuthManager {
@Autowired
private List<AuthenticatorProvider> providers = new ArrayList<>();
@Autowired
private MatrixConfig mxCfg;
@Autowired
private InvitationManager invMgr;
public UserAuthResult authenticate(String id, String password) {
_MatrixID mxid = new MatrixID(id);
for (AuthenticatorProvider provider : providers) {
if (!provider.isEnabled()) {
continue;
}
UserAuthResult result = provider.authenticate(id, password);
BackendAuthResult result = provider.authenticate(mxid, password);
if (result.isSuccess()) {
String mxId;
if (UserIdType.Localpart.is(result.getId().getType())) {
mxId = new MatrixID(result.getId().getValue(), mxCfg.getDomain()).getId();
} else if (UserIdType.MatrixID.is(result.getId().getType())) {
mxId = new MatrixID(result.getId().getValue()).getId();
} else {
log.warn("Unsupported User ID type {} for backend {}", result.getId().getType(), provider.getClass().getSimpleName());
continue;
}
UserAuthResult authResult = new UserAuthResult().success(mxId, result.getProfile().getDisplayName());
for (ThreePid pid : result.getProfile().getThreePids()) {
authResult.withThreePid(pid.getMedium(), pid.getAddress());
}
log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName());
for (ThreePid pid : result.getThreePids()) {
for (ThreePid pid : authResult.getThreePids()) {
log.info("Processing {} for {}", pid, id);
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, result.getMxid()));
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, authResult.getMxid()));
}
invMgr.lookupMappingsForInvites();
return result;
return authResult;
}
}

View File

@@ -20,12 +20,12 @@
package io.kamax.mxisd.auth.provider;
import io.kamax.mxisd.auth.UserAuthResult;
import io.kamax.matrix._MatrixID;
public interface AuthenticatorProvider {
boolean isEnabled();
UserAuthResult authenticate(String id, String password);
BackendAuthResult authenticate(_MatrixID mxid, String password);
}

View File

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

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.auth.provider
package io.kamax.mxisd.backend.firebase
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
@@ -27,14 +27,17 @@ import com.google.firebase.internal.NonNull
import com.google.firebase.tasks.OnFailureListener
import com.google.firebase.tasks.OnSuccessListener
import io.kamax.matrix.ThreePidMedium
import io.kamax.mxisd.auth.UserAuthResult
import io.kamax.matrix._MatrixID
import io.kamax.mxisd.ThreePid
import io.kamax.mxisd.UserIdType
import io.kamax.mxisd.auth.provider.AuthenticatorProvider
import io.kamax.mxisd.auth.provider.BackendAuthResult
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.regex.Matcher
import java.util.regex.Pattern
public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
@@ -48,7 +51,7 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
private FirebaseApp fbApp;
private FirebaseAuth fbAuth;
private void waitOnLatch(UserAuthResult result, CountDownLatch l, long timeout, TimeUnit unit, String purpose) {
private void waitOnLatch(BackendAuthResult result, CountDownLatch l, long timeout, TimeUnit unit, String purpose) {
try {
l.await(timeout, unit);
} catch (InterruptedException e) {
@@ -107,22 +110,16 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
}
@Override
public UserAuthResult authenticate(String id, String password) {
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
if (!isEnabled()) {
throw new IllegalStateException();
}
final UserAuthResult result = new UserAuthResult();
log.info("Trying to authenticate {}", mxid);
log.info("Trying to authenticate {}", id);
Matcher m = matrixIdLaxPattern.matcher(id);
if (!m.matches()) {
log.warn("Could not validate {} as a Matrix ID", id);
result.failure();
}
BackendAuthResult result = BackendAuthResult.failure();
String localpart = m.group(1);
CountDownLatch l = new CountDownLatch(1);
fbAuth.verifyIdToken(password).addOnSuccessListener(new OnSuccessListener<FirebaseToken>() {
@Override
@@ -130,26 +127,26 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
try {
if (!StringUtils.equals(localpart, token.getUid())) {
log.info("Failture to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", id, localpart, token.getUid());
result.failure();
result = BackendAuthResult.failure();
return;
}
log.info("{} was successfully authenticated", id);
result.success(id, token.getName());
log.info("Fetching profile for {}", id);
result = BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, token.getName());
log.info("{} was successfully authenticated", mxid);
log.info("Fetching profile for {}", mxid);
CountDownLatch userRecordLatch = new CountDownLatch(1);
fbAuth.getUser(token.getUid()).addOnSuccessListener(new OnSuccessListener<UserRecord>() {
@Override
void onSuccess(UserRecord user) {
try {
if (StringUtils.isNotBlank(user.getEmail())) {
result.withThreePid(ThreePidMedium.Email, user.getEmail());
result.withThreePid(new ThreePid(ThreePidMedium.Email.getId(), user.getEmail()));
}
if (StringUtils.isNotBlank(user.getPhoneNumber())) {
result.withThreePid(ThreePidMedium.PhoneNumber, user.getPhoneNumber());
result.withThreePid(new ThreePid(ThreePidMedium.PhoneNumber.getId(), user.getPhoneNumber()));
}
} finally {
userRecordLatch.countDown();
}
@@ -158,8 +155,8 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
@Override
void onFailure(@NonNull Exception e) {
try {
log.warn("Unable to fetch Firebase user profile for {}", id);
result.failure();
log.warn("Unable to fetch Firebase user profile for {}", mxid);
result = BackendAuthResult.failure();
} finally {
userRecordLatch.countDown();
}
@@ -176,13 +173,13 @@ public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
void onFailure(@NonNull Exception e) {
try {
if (e instanceof IllegalArgumentException) {
log.info("Failure to authenticate {}: invalid firebase token", id);
log.info("Failure to authenticate {}: invalid firebase token", mxid);
} else {
log.info("Failure to authenticate {}: {}", id, e.getMessage(), e);
log.info("Exception", e);
}
result.failure();
result = BackendAuthResult.failure();
} finally {
l.countDown()
}

View File

@@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.lookup.provider
package io.kamax.mxisd.backend.firebase
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
@@ -33,6 +33,7 @@ import io.kamax.matrix.ThreePidMedium
import io.kamax.mxisd.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.provider.IThreePidProvider
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory

View File

@@ -18,12 +18,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.auth.provider;
package io.kamax.mxisd.backend.ldap;
import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.auth.UserAuthResult;
import io.kamax.mxisd.config.ldap.LdapConfig;
import io.kamax.mxisd.lookup.provider.LdapProvider;
import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.UserIdType;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult;
import org.apache.commons.lang.StringUtils;
import org.apache.directory.api.ldap.model.cursor.CursorException;
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException;
@@ -33,52 +33,41 @@ 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.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class LdapAuthProvider implements AuthenticatorProvider {
public class LdapAuthProvider extends LdapGenericBackend implements AuthenticatorProvider {
private Logger log = LoggerFactory.getLogger(LdapAuthProvider.class);
@Autowired
private LdapConfig ldapCfg;
private LdapConnection getConn() {
return new LdapNetworkConnection(ldapCfg.getConn().getHost(), ldapCfg.getConn().getPort(), ldapCfg.getConn().isTls());
}
private void bind(LdapConnection conn) throws LdapException {
conn.bind(ldapCfg.getConn().getBindDn(), ldapCfg.getConn().getBindPassword());
}
private String getUidAttribute() {
return ldapCfg.getAttribute().getUid().getValue();
return getCfg().getAttribute().getUid().getValue();
}
@Override
public boolean isEnabled() {
return ldapCfg.isEnabled();
return getCfg().isEnabled();
}
@Override
public UserAuthResult authenticate(String id, String password) {
log.info("Performing auth for {}", id);
public BackendAuthResult authenticate(_MatrixID mxid, String password) {
log.info("Performing auth for {}", mxid);
LdapConnection conn = getConn();
try {
bind(conn);
String uidType = ldapCfg.getAttribute().getUid().getType();
MatrixID mxIdExt = new MatrixID(id);
String userFilterValue = StringUtils.equals(LdapProvider.UID, uidType) ? mxIdExt.getLocalPart() : mxIdExt.getId();
String userFilter = "(" + ldapCfg.getAttribute().getUid().getValue() + "=" + userFilterValue + ")";
EntryCursor cursor = conn.search(ldapCfg.getConn().getBaseDn(), userFilter, SearchScope.SUBTREE, getUidAttribute(), ldapCfg.getAttribute().getName());
String uidType = getCfg().getAttribute().getUid().getType();
String userFilterValue = StringUtils.equals(LdapThreePidProvider.UID, uidType) ? mxid.getLocalPart() : mxid.getId();
String userFilter = "(" + getCfg().getAttribute().getUid().getValue() + "=" + userFilterValue + ")";
if (!StringUtils.isBlank(getCfg().getAuth().getFilter())) {
userFilter = "(&" + getCfg().getAuth().getFilter() + userFilter + ")";
}
EntryCursor cursor = conn.search(getCfg().getConn().getBaseDn(), userFilter, SearchScope.SUBTREE, getUidAttribute(), getCfg().getAttribute().getName());
try {
while (cursor.next()) {
Entry entry = cursor.get();
@@ -102,25 +91,26 @@ public class LdapAuthProvider implements AuthenticatorProvider {
conn.bind(entry.getDn(), password);
} catch (LdapException e) {
log.info("Unable to bind using {} because {}", entry.getDn().getName(), e.getMessage());
return new UserAuthResult().failure();
return BackendAuthResult.failure();
}
Attribute nameAttribute = entry.get(ldapCfg.getAttribute().getName());
Attribute nameAttribute = entry.get(getCfg().getAttribute().getName());
String name = nameAttribute != null ? nameAttribute.get().toString() : null;
log.info("Authentication successful for {}", entry.getDn().getName());
log.info("DN {} is a valid match", dn);
return new UserAuthResult().success(mxIdExt.getId(), name);
// TODO should we canonicalize the MXID?
return BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, name);
}
} catch (CursorLdapReferralException e) {
log.warn("Entity for {} is only available via referral, skipping", mxIdExt);
log.warn("Entity for {} is only available via referral, skipping", mxid);
} finally {
cursor.close();
}
log.info("No match were found for {}", id);
return new UserAuthResult().failure();
log.info("No match were found for {}", mxid);
return BackendAuthResult.failure();
} catch (LdapException | IOException | CursorException e) {
throw new RuntimeException(e);
} finally {

View File

@@ -0,0 +1,57 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.backend.ldap;
import io.kamax.mxisd.config.ldap.LdapConfig;
import org.apache.commons.lang.StringUtils;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class LdapGenericBackend {
private Logger log = LoggerFactory.getLogger(LdapGenericBackend.class);
@Autowired
private LdapConfig ldapCfg;
protected LdapConnection getConn() {
return new LdapNetworkConnection(ldapCfg.getConn().getHost(), ldapCfg.getConn().getPort(), ldapCfg.getConn().isTls());
}
protected void bind(LdapConnection conn) throws LdapException {
if (StringUtils.isBlank(ldapCfg.getConn().getBindDn()) && StringUtils.isBlank(ldapCfg.getConn().getBindPassword())) {
conn.anonymousBind();
} else {
conn.bind(ldapCfg.getConn().getBindDn(), ldapCfg.getConn().getBindPassword());
}
}
protected LdapConfig getCfg() {
return ldapCfg;
}
}

View File

@@ -18,13 +18,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.lookup.provider
package io.kamax.mxisd.backend.ldap
import io.kamax.mxisd.config.ServerConfig
import io.kamax.mxisd.config.ldap.LdapConfig
import io.kamax.mxisd.config.MatrixConfig
import io.kamax.mxisd.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping
import io.kamax.mxisd.lookup.provider.IThreePidProvider
import org.apache.commons.lang.StringUtils
import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException
import org.apache.directory.api.ldap.model.cursor.EntryCursor
@@ -32,41 +32,29 @@ import org.apache.directory.api.ldap.model.entry.Attribute
import org.apache.directory.api.ldap.model.entry.Entry
import org.apache.directory.api.ldap.model.message.SearchScope
import org.apache.directory.ldap.client.api.LdapConnection
import org.apache.directory.ldap.client.api.LdapNetworkConnection
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
@Component
class LdapProvider implements IThreePidProvider {
class LdapThreePidProvider extends LdapGenericBackend implements IThreePidProvider {
public static final String UID = "uid"
public static final String MATRIX_ID = "mxid"
private Logger log = LoggerFactory.getLogger(LdapProvider.class)
private Logger log = LoggerFactory.getLogger(LdapThreePidProvider.class)
@Autowired
private ServerConfig srvCfg
@Autowired
private LdapConfig ldapCfg
private MatrixConfig mxCfg
@Override
boolean isEnabled() {
return ldapCfg.isEnabled()
}
private LdapConnection getConn() {
return new LdapNetworkConnection(ldapCfg.getConn().getHost(), ldapCfg.getConn().getPort(), ldapCfg.getConn().isTls())
}
private void bind(LdapConnection conn) {
conn.bind(ldapCfg.getConn().getBindDn(), ldapCfg.getConn().getBindPassword())
return getCfg().isEnabled()
}
private String getUidAttribute() {
return ldapCfg.getAttribute().getUid().getValue();
return getCfg().getAttribute().getUid().getValue();
}
@Override
@@ -82,14 +70,14 @@ class LdapProvider implements IThreePidProvider {
Optional<String> lookup(LdapConnection conn, String medium, String value) {
String uidAttribute = getUidAttribute()
Optional<String> queryOpt = ldapCfg.getIdentity().getQuery(medium)
Optional<String> queryOpt = getCfg().getIdentity().getQuery(medium)
if (!queryOpt.isPresent()) {
log.warn("{} is not a configured 3PID type for LDAP lookup", medium)
return Optional.empty()
}
String searchQuery = queryOpt.get().replaceAll("%3pid", value)
EntryCursor cursor = conn.search(ldapCfg.getConn().getBaseDn(), searchQuery, SearchScope.SUBTREE, uidAttribute)
EntryCursor cursor = conn.search(getCfg().getConn().getBaseDn(), searchQuery, SearchScope.SUBTREE, uidAttribute)
try {
while (cursor.next()) {
Entry entry = cursor.get()
@@ -97,21 +85,21 @@ class LdapProvider implements IThreePidProvider {
Attribute attribute = entry.get(uidAttribute)
if (attribute == null) {
log.info("DN {}: no attribute {}, skpping", entry.getDn(), ldapCfg.getAttribute())
log.info("DN {}: no attribute {}, skpping", entry.getDn(), getCfg().getAttribute())
continue
}
String data = attribute.get().toString()
if (data.length() < 1) {
log.info("DN {}: empty attribute {}, skipping", ldapCfg.getAttribute())
log.info("DN {}: empty attribute {}, skipping", getCfg().getAttribute())
continue
}
StringBuilder matrixId = new StringBuilder()
// TODO Should we turn this block into a map of functions?
String uidType = ldapCfg.getAttribute().getUid().getType()
String uidType = getCfg().getAttribute().getUid().getType()
if (StringUtils.equals(UID, uidType)) {
matrixId.append("@").append(data).append(":").append(srvCfg.getName())
matrixId.append("@").append(data).append(":").append(mxCfg.getDomain())
} else if (StringUtils.equals(MATRIX_ID, uidType)) {
matrixId.append(data)
} else {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,52 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.Optional;
@Configuration
@ConfigurationProperties("dns.overwrite")
public class DnsOverwrite {
private Logger log = LoggerFactory.getLogger(DnsOverwrite.class);
@Autowired
private ServerConfig srvCfg;
@Autowired
private DnsOverwriteEntry homeserver;
public Optional<DnsOverwriteEntry> findHost(String lookup) {
if (homeserver != null && StringUtils.equalsIgnoreCase(lookup, homeserver.getName())) {
return Optional.of(homeserver);
}
return Optional.empty();
}
}

View File

@@ -0,0 +1,67 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties("dns.overwrite.homeserver")
public class DnsOverwriteEntry {
private String name;
private String type;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getTarget() {
if (StringUtils.equals("env", getType())) {
return System.getenv(getValue());
} else {
return getValue();
}
}
}

View File

@@ -21,8 +21,8 @@
package io.kamax.mxisd.config;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.GoogleFirebaseAuthenticator;
import io.kamax.mxisd.lookup.provider.GoogleFirebaseProvider;
import io.kamax.mxisd.backend.firebase.GoogleFirebaseAuthenticator;
import io.kamax.mxisd.backend.firebase.GoogleFirebaseProvider;
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -40,7 +40,7 @@ public class FirebaseConfig {
private Logger log = LoggerFactory.getLogger(FirebaseConfig.class);
@Autowired
private ServerConfig srvCfg;
private MatrixConfig mxCfg;
private boolean enabled;
private String credentials;
@@ -85,7 +85,7 @@ public class FirebaseConfig {
if (!enabled) {
return new GoogleFirebaseAuthenticator(false);
} else {
return new GoogleFirebaseAuthenticator(credentials, database, srvCfg.getName());
return new GoogleFirebaseAuthenticator(credentials, database, mxCfg.getDomain());
}
}
@@ -94,7 +94,7 @@ public class FirebaseConfig {
if (!enabled) {
return new GoogleFirebaseProvider(false);
} else {
return new GoogleFirebaseProvider(credentials, database, srvCfg.getName());
return new GoogleFirebaseProvider(credentials, database, mxCfg.getDomain());
}
}

View File

@@ -0,0 +1,59 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config;
import io.kamax.mxisd.exception.ConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@ConfigurationProperties("matrix")
public class MatrixConfig {
private Logger log = LoggerFactory.getLogger(MatrixConfig.class);
private String domain;
public String getDomain() {
return domain;
}
public void setDomain(String domain) {
this.domain = domain;
}
@PostConstruct
public void build() {
log.info("--- Matrix config ---");
if (StringUtils.isBlank(domain)) {
throw new ConfigurationException("matrix.domain");
}
log.info("Domain: {}", getDomain());
}
}

View File

@@ -20,11 +20,11 @@
package io.kamax.mxisd.config
import io.kamax.mxisd.exception.ConfigurationException
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.InitializingBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
@@ -34,6 +34,9 @@ class ServerConfig implements InitializingBean {
private Logger log = LoggerFactory.getLogger(ServerConfig.class);
@Autowired
private MatrixConfig mxCfg;
private String name
private int port
private String publicUrl
@@ -64,13 +67,18 @@ class ServerConfig implements InitializingBean {
@Override
void afterPropertiesSet() throws Exception {
log.info("--- Server config ---")
if (StringUtils.isBlank(getName())) {
throw new ConfigurationException("server.name")
setName(mxCfg.getDomain());
log.debug("server.name is empty, using matrix.domain");
}
if (StringUtils.isBlank(getPublicUrl())) {
log.warn("Public URL is empty, generating from name {}", getName())
publicUrl = "https://${getName()}"
setPublicUrl("https://${getName()}");
log.debug("Public URL is empty, generating from name");
} else {
setPublicUrl(StringUtils.replace(getPublicUrl(), "%SERVER_NAME%", getName()));
}
try {
@@ -79,7 +87,6 @@ class ServerConfig implements InitializingBean {
log.warn("Public URL is not valid: {}", StringUtils.defaultIfBlank(e.getMessage(), "<no reason provided>"))
}
log.info("--- Server config ---")
log.info("Name: {}", getName())
log.info("Port: {}", getPort())
log.info("Public URL: {}", getPublicUrl())

View File

@@ -21,7 +21,7 @@
package io.kamax.mxisd.config.ldap
import groovy.json.JsonOutput
import io.kamax.mxisd.lookup.provider.LdapProvider
import io.kamax.mxisd.backend.ldap.LdapThreePidProvider
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@@ -112,7 +112,7 @@ class LdapConfig {
}
String uidType = attribute.getUid().getType();
if (!StringUtils.equals(LdapProvider.UID, uidType) && !StringUtils.equals(LdapProvider.MATRIX_ID, uidType)) {
if (!StringUtils.equals(LdapThreePidProvider.UID, uidType) && !StringUtils.equals(LdapThreePidProvider.MATRIX_ID, uidType)) {
throw new IllegalArgumentException("Unsupported LDAP UID type: " + uidType)
}

View File

@@ -0,0 +1,149 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config.rest;
import io.kamax.mxisd.exception.ConfigurationException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.net.MalformedURLException;
import java.net.URL;
@Configuration
@ConfigurationProperties("rest")
public class RestBackendConfig {
public static class IdentityEndpoints {
private String single;
private String bulk;
public String getSingle() {
return single;
}
public void setSingle(String single) {
this.single = single;
}
public String getBulk() {
return bulk;
}
public void setBulk(String bulk) {
this.bulk = bulk;
}
}
public static class Endpoints {
private IdentityEndpoints identity = new IdentityEndpoints();
private String auth;
public IdentityEndpoints getIdentity() {
return identity;
}
public void setIdentity(IdentityEndpoints identity) {
this.identity = identity;
}
public String getAuth() {
return auth;
}
public void setAuth(String auth) {
this.auth = auth;
}
}
private Logger log = LoggerFactory.getLogger(RestBackendConfig.class);
private boolean enabled;
private String host;
private Endpoints endpoints = new Endpoints();
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Endpoints getEndpoints() {
return endpoints;
}
public void setEndpoints(Endpoints endpoints) {
this.endpoints = endpoints;
}
private String buildEndpointUrl(String endpoint) {
if (StringUtils.startsWith(endpoint, "/")) {
if (StringUtils.isBlank(getHost())) {
throw new ConfigurationException("rest.host");
}
try {
new URL(getHost());
} catch (MalformedURLException e) {
throw new ConfigurationException("rest.host", e.getMessage());
}
return getHost() + endpoint;
} else {
return endpoint;
}
}
@PostConstruct
public void build() {
log.info("--- REST backend config ---");
log.info("Enabled: {}", isEnabled());
if (isEnabled()) {
endpoints.setAuth(buildEndpointUrl(endpoints.getAuth()));
endpoints.identity.setSingle(buildEndpointUrl(endpoints.identity.getSingle()));
endpoints.identity.setBulk(buildEndpointUrl(endpoints.identity.getBulk()));
log.info("Host: {}", getHost());
log.info("Auth endpoint: {}", endpoints.getAuth());
log.info("Identity Single endpoint: {}", endpoints.identity.getSingle());
log.info("Identity Bulk endpoint: {}", endpoints.identity.getBulk());
}
}
}

View File

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

View File

@@ -0,0 +1,96 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config.sql;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
@ConfigurationProperties("sql")
public class SqlProviderConfig {
private Logger log = LoggerFactory.getLogger(SqlProviderConfig.class);
private boolean enabled;
private String type;
private String connection;
private SqlProviderAuthConfig auth;
private SqlProviderIdentityConfig identity;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getConnection() {
return connection;
}
public void setConnection(String connection) {
this.connection = connection;
}
public SqlProviderAuthConfig getAuth() {
return auth;
}
public void setAuth(SqlProviderAuthConfig auth) {
this.auth = auth;
}
public SqlProviderIdentityConfig getIdentity() {
return identity;
}
public void setIdentity(SqlProviderIdentityConfig identity) {
this.identity = identity;
}
@PostConstruct
private void postConstruct() {
log.info("--- SQL Provider config ---");
log.info("Enabled: {}", isEnabled());
if (isEnabled()) {
log.info("Type: {}", getType());
log.info("Connection: {}", getConnection());
log.info("Auth enabled: {}", getAuth().isEnabled());
log.info("Identy type: {}", getIdentity().getType());
log.info("Identity medium queries: {}", new Gson().toJson(getIdentity().getMedium()));
}
}
}

View File

@@ -0,0 +1,61 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.config.sql;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@ConfigurationProperties("sql.identity")
public class SqlProviderIdentityConfig {
private String type;
private String query;
private Map<String, String> medium = new HashMap<>();
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public Map<String, String> getMedium() {
return medium;
}
public void setMedium(Map<String, String> medium) {
this.medium = medium;
}
}

View File

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

View File

@@ -44,7 +44,7 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST
@RestController
@CrossOrigin
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
class InvitationController {
private Logger log = LoggerFactory.getLogger(InvitationController.class)

View File

@@ -20,9 +20,10 @@
package io.kamax.mxisd.controller.v1
import com.google.gson.Gson
import groovy.json.JsonOutput
import io.kamax.mxisd.controller.v1.io.KeyValidityJson
import io.kamax.mxisd.exception.BadRequestException
import io.kamax.mxisd.exception.NotImplementedException
import io.kamax.mxisd.key.KeyManager
import org.apache.commons.lang.StringUtils
import org.slf4j.Logger
@@ -37,7 +38,7 @@ import static org.springframework.web.bind.annotation.RequestMethod.GET
@RestController
@CrossOrigin
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
class KeyController {
private Logger log = LoggerFactory.getLogger(KeyController.class)
@@ -45,6 +46,10 @@ class KeyController {
@Autowired
private KeyManager keyMgr
private Gson gson = new Gson();
private String validKey = gson.toJson(new KeyValidityJson(true));
private String invalidKey = gson.toJson(new KeyValidityJson(false));
@RequestMapping(value = "/pubkey/{keyType}:{keyId}", method = GET)
String getKey(@PathVariable String keyType, @PathVariable int keyId) {
if (!"ed25519".contentEquals(keyType)) {
@@ -59,9 +64,9 @@ class KeyController {
@RequestMapping(value = "/pubkey/ephemeral/isvalid", method = GET)
String checkEphemeralKeyValidity(HttpServletRequest request) {
log.error("{} was requested but not implemented", request.getRequestURL())
log.warn("Ephemeral key was request but no ephemeral key are generated, replying not valid")
throw new NotImplementedException()
return invalidKey
}
@RequestMapping(value = "/pubkey/isvalid", method = GET)
@@ -70,9 +75,7 @@ class KeyController {
// TODO do in manager
boolean valid = StringUtils.equals(pubKey, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()))
return JsonOutput.toJson(
valid: valid
)
return valid ? validKey : invalidKey
}
}

View File

@@ -45,7 +45,7 @@ import static org.springframework.web.bind.annotation.RequestMethod.POST
@RestController
@CrossOrigin
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
class MappingController {
private Logger log = LoggerFactory.getLogger(MappingController.class)

View File

@@ -25,6 +25,7 @@ import com.google.gson.JsonObject
import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson
import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson
import io.kamax.mxisd.exception.BadRequestException
import io.kamax.mxisd.invitation.InvitationManager
import io.kamax.mxisd.lookup.ThreePidValidation
import io.kamax.mxisd.mapping.MappingManager
import org.apache.commons.io.IOUtils
@@ -42,12 +43,15 @@ import java.nio.charset.StandardCharsets
@RestController
@CrossOrigin
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
class SessionController {
@Autowired
private MappingManager mgr
@Autowired
private InvitationManager invMgr;
private Gson gson = new Gson()
private Logger log = LoggerFactory.getLogger(SessionController.class)
@@ -131,6 +135,10 @@ class SessionController {
obj.addProperty("error", e.getMessage())
response.setStatus(HttpStatus.SC_BAD_REQUEST)
return gson.toJson(obj)
} finally {
// If a user registers, there is no standard login event. Instead, this is the only way to trigger
// resolution at an appropriate time. Meh at synapse/Riot!
invMgr.lookupMappingsForInvites()
}
}

View File

@@ -20,6 +20,8 @@
package io.kamax.mxisd.controller.v1;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -30,10 +32,18 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class StatusController {
private Gson gson = new Gson();
@RequestMapping(value = "/_matrix/identity/status")
public String getStatus() {
// TODO link to backend
return "{\"status\":{\"health\":\"OK\"}}";
JsonObject status = new JsonObject();
status.addProperty("health", "OK");
JsonObject obj = new JsonObject();
obj.add("status", status);
return gson.toJson(obj);
}
}

View File

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

View File

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

View File

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

View File

@@ -22,6 +22,8 @@ package io.kamax.mxisd.invitation;
import com.google.gson.Gson;
import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.config.DnsOverwrite;
import io.kamax.mxisd.config.DnsOverwriteEntry;
import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
import io.kamax.mxisd.invitation.sender.IInviteSender;
@@ -78,6 +80,9 @@ public class InvitationManager {
@Autowired
private SignatureManager signMgr;
@Autowired
private DnsOverwrite dns;
private Map<String, IInviteSender> senders;
private CloseableHttpClient client;
@@ -85,7 +90,7 @@ public class InvitationManager {
private Timer refreshTimer;
private String getId(IThreePidInvite invite) {
return invite.getSender().getDomain() + invite.getMedium() + invite.getAddress();
return invite.getSender().getDomain().toLowerCase() + invite.getMedium().toLowerCase() + invite.getAddress().toLowerCase();
}
@PostConstruct
@@ -149,6 +154,13 @@ public class InvitationManager {
// TODO use caching mechanism
// TODO export in matrix-java-sdk
String findHomeserverForDomain(String domain) {
Optional<DnsOverwriteEntry> entryOpt = dns.findHost(domain);
if (entryOpt.isPresent()) {
DnsOverwriteEntry entry = entryOpt.get();
log.info("Found DNS overwrite for {} to {}", entry.getName(), entry.getTarget());
return "https://" + entry.getTarget();
}
log.debug("Performing SRV lookup for {}", domain);
String lookupDns = getSrvRecordName(domain);
log.info("Lookup name: {}", lookupDns);
@@ -223,17 +235,19 @@ public class InvitationManager {
}
public void lookupMappingsForInvites() {
log.info("Checking for existing mapping for pending invites");
for (IThreePidInviteReply reply : invitations.values()) {
log.info("Processing invite {}", getIdForLog(reply));
ForkJoinPool.commonPool().submit(new MappingChecker(reply));
if (!invitations.isEmpty()) {
log.info("Checking for existing mapping for pending invites");
for (IThreePidInviteReply reply : invitations.values()) {
log.info("Processing invite {}", getIdForLog(reply));
ForkJoinPool.commonPool().submit(new MappingChecker(reply));
}
}
}
public void publishMappingIfInvited(ThreePidMapping threePid) {
log.info("Looking up possible pending invites for {}:{}", threePid.getMedium(), threePid.getValue());
for (IThreePidInviteReply reply : invitations.values()) {
if (StringUtils.equals(reply.getInvite().getMedium(), threePid.getMedium()) && StringUtils.equals(reply.getInvite().getAddress(), threePid.getValue())) {
if (StringUtils.equalsIgnoreCase(reply.getInvite().getMedium(), threePid.getMedium()) && StringUtils.equalsIgnoreCase(reply.getInvite().getAddress(), threePid.getValue())) {
log.info("{}:{} has an invite pending on HS {}, publishing mapping", threePid.getMedium(), threePid.getValue(), reply.getInvite().getSender().getDomain());
publishMapping(reply, threePid.getMxid());
}

View File

@@ -22,7 +22,7 @@ package io.kamax.mxisd.invitation.sender;
import com.sun.mail.smtp.SMTPTransport;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.invite.sender.EmailSenderConfig;
import io.kamax.mxisd.exception.ConfigurationException;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
@@ -56,7 +56,7 @@ public class EmailInviteSender implements IInviteSender {
private EmailSenderConfig cfg;
@Autowired
private ServerConfig srvCfg;
private MatrixConfig mxCfg;
@Autowired
private ApplicationContext app;
@@ -87,7 +87,7 @@ public class EmailInviteSender implements IInviteSender {
}
try {
String domainPretty = WordUtils.capitalizeFully(srvCfg.getName());
String domainPretty = WordUtils.capitalizeFully(mxCfg.getDomain());
String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", "");
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
@@ -97,7 +97,7 @@ public class EmailInviteSender implements IInviteSender {
StringUtils.startsWith(cfg.getTemplate(), "classpath:") ?
app.getResource(cfg.getTemplate()).getInputStream() : new FileInputStream(cfg.getTemplate()),
StandardCharsets.UTF_8);
templateBody = templateBody.replace("%DOMAIN%", srvCfg.getName());
templateBody = templateBody.replace("%DOMAIN%", mxCfg.getDomain());
templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty);
templateBody = templateBody.replace("%FROM_EMAIL%", cfg.getEmail());
templateBody = templateBody.replace("%FROM_NAME%", cfg.getName());

View File

@@ -20,7 +20,7 @@
package io.kamax.mxisd.lookup.provider
import io.kamax.mxisd.config.ServerConfig
import io.kamax.mxisd.config.MatrixConfig
import io.kamax.mxisd.lookup.SingleLookupReply
import io.kamax.mxisd.lookup.SingleLookupRequest
import io.kamax.mxisd.lookup.ThreePidMapping
@@ -44,7 +44,7 @@ class DnsLookupProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(DnsLookupProvider.class)
@Autowired
private ServerConfig srvCfg
private MatrixConfig mxCfg
@Autowired
private IRemoteIdentityServerFetcher fetcher
@@ -79,7 +79,7 @@ class DnsLookupProvider implements IThreePidProvider {
// TODO use caching mechanism
Optional<String> findIdentityServerForDomain(String domain) {
if (StringUtils.equals(srvCfg.getName(), domain)) {
if (StringUtils.equals(mxCfg.getDomain(), domain)) {
log.info("We are authoritative for {}, no remote lookup", domain)
return Optional.empty()
}

View File

@@ -36,7 +36,6 @@ public class MappingManager {
private Logger log = LoggerFactory.getLogger(MappingManager.class);
private Map<String, Session> threePidLookups = new WeakHashMap<>();
private Map<String, Session> sessions = new HashMap<>();
private Timer cleaner;
@@ -51,7 +50,6 @@ public class MappingManager {
log.info("Session {} is obsolete, removing", s.sid);
sessions.remove(s.sid);
threePidLookups.remove(s.hash);
}
}
}
@@ -65,16 +63,9 @@ public class MappingManager {
} while (sessions.containsKey(sid));
String threePidHash = data.getMedium() + data.getValue();
Session session = threePidLookups.get(threePidHash);
if (session != null) {
sid = session.sid;
} else {
// TODO perform some kind of validation
session = new Session(sid, threePidHash, data);
sessions.put(sid, session);
threePidLookups.put(threePidHash, session);
}
// TODO think how to handle different requests for the same e-mail
Session session = new Session(sid, threePidHash, data);
sessions.put(sid, session);
log.info("Created new session {} to validate {} {}", sid, session.medium, session.address);
return sid;

View File

@@ -0,0 +1,91 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.util;
import com.google.gson.*;
import io.kamax.mxisd.exception.InvalidResponseJsonException;
import io.kamax.mxisd.exception.JsonMemberNotFoundException;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
public class GsonParser {
private JsonParser parser = new JsonParser();
private Gson gson;
public GsonParser() {
this(new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create());
}
public GsonParser(Gson gson) {
this.gson = gson;
}
public JsonObject parse(InputStream stream) throws IOException {
JsonElement el = parser.parse(IOUtils.toString(stream, StandardCharsets.UTF_8));
if (!el.isJsonObject()) {
throw new InvalidResponseJsonException("Response body is not a JSON object");
}
return el.getAsJsonObject();
}
public <T> T parse(HttpResponse res, Class<T> type) throws IOException {
return gson.fromJson(parse(res.getEntity().getContent()), type);
}
public JsonObject parse(InputStream stream, String property) throws IOException {
JsonObject obj = parse(stream);
if (!obj.has(property)) {
throw new JsonMemberNotFoundException("Member " + property + " does not exist");
}
JsonElement el = obj.get(property);
if (!el.isJsonObject()) {
throw new InvalidResponseJsonException("Member " + property + " is not a JSON object");
}
return el.getAsJsonObject();
}
public <T> T parse(InputStream stream, String memberName, Class<T> type) throws IOException {
JsonObject obj = parse(stream, memberName);
return gson.fromJson(obj, type);
}
public <T> T parse(HttpResponse res, String memberName, Class<T> type) throws IOException {
return parse(res.getEntity().getContent(), memberName, type);
}
public <T> Optional<T> parseOptional(HttpResponse res, String memberName, Class<T> type) throws IOException {
try {
return Optional.of(parse(res.getEntity().getContent(), memberName, type));
} catch (JsonMemberNotFoundException e) {
return Optional.empty();
}
}
}

View File

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

View File

@@ -0,0 +1,48 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.util;
import com.google.gson.Gson;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import java.nio.charset.StandardCharsets;
public class RestClientUtils {
public static HttpPost post(String url, String body) {
StringEntity entity = new StringEntity(body, StandardCharsets.UTF_8);
entity.setContentType(ContentType.APPLICATION_JSON.toString());
HttpPost req = new HttpPost(url);
req.setEntity(entity);
return req;
}
public static HttpPost post(String url, Gson gson, String member, Object o) {
return post(url, JsonUtils.getObjAsString(gson, member, o));
}
public static HttpPost post(String url, Gson gson, Object o) {
return post(url, gson.toJson(o));
}
}

View File

@@ -23,6 +23,13 @@ lookup:
enabled: false
recursiveOnly: true
rest:
endpoints:
auth: "/_mxisd/identity/api/v1/auth"
identity:
single: "/_mxisd/identity/api/v1/lookup/single"
bulk: "/_mxisd/identity/api/v1/lookup/bulk"
ldap:
enabled: false
connection:
@@ -41,6 +48,9 @@ ldap:
firebase:
enabled: false
sql:
type: 'sqlite'
forward:
servers:
- "https://matrix.org"

View File

@@ -0,0 +1,147 @@
package io.kamax.mxisd.backend.rest;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.rest.RestBackendConfig;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
import org.apache.commons.lang.StringUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class RestThreePidProviderTest {
@Rule
public WireMockRule wireMockRule = new WireMockRule(65000);
private RestThreePidProvider p;
private String lookupSinglePath = "/lookup/single";
private SingleLookupRequest lookupSingleRequest;
private String lookupSingleRequestBody = "{\"lookup\":{\"medium\":\"email\",\"address\":\"john.doe@example.org\"}}";
private String lookupSingleFoundBody = "{\"lookup\":{\"medium\":\"email\",\"address\":\"john.doe@example.org\"" +
",\"id\":{\"type\":\"mxid\",\"value\":\"@john:example.org\"}}}";
private String lookupSingleNotFoundBody = "{}";
private String lookupBulkPath = "/lookup/bulk";
private List<ThreePidMapping> lookupBulkList;
private String lookupBulkRequestBody = "{\"lookup\":[{\"medium\":\"email\",\"address\":\"john.doe@example.org\"}," +
"{\"medium\":\"msisdn\",\"address\":\"123456789\"}]}";
private String lookupBulkFoundBody = "{\"lookup\":[{\"medium\":\"email\",\"address\":\"john.doe@example.org\"," +
"\"id\":{\"type\":\"localpart\",\"value\":\"john\"}},{\"medium\":\"msisdn\",\"address\":\"123456789\"," +
"\"id\":{\"type\":\"mxid\",\"value\":\"@jane:example.org\"}}]}";
private String lookupBulkNotFoundBody = "{\"lookup\":[]}";
@Before
public void before() {
MatrixConfig mxCfg = new MatrixConfig();
mxCfg.setDomain("example.org");
mxCfg.build();
RestBackendConfig cfg = new RestBackendConfig();
cfg.setEnabled(true);
cfg.setHost("http://localhost:65000");
cfg.getEndpoints().getIdentity().setSingle(lookupSinglePath);
cfg.getEndpoints().getIdentity().setBulk("/lookup/bulk");
cfg.build();
p = new RestThreePidProvider(cfg, mxCfg);
lookupSingleRequest = new SingleLookupRequest();
lookupSingleRequest.setType(ThreePidMedium.Email.getId());
lookupSingleRequest.setThreePid("john.doe@example.org");
ThreePidMapping m1 = new ThreePidMapping();
m1.setMedium(ThreePidMedium.Email.getId());
m1.setValue("john.doe@example.org");
ThreePidMapping m2 = new ThreePidMapping();
m1.setMedium(ThreePidMedium.PhoneNumber.getId());
m1.setValue("123456789");
lookupBulkList = new ArrayList<>();
lookupBulkList.add(m1);
lookupBulkList.add(m2);
}
@Test
public void lookupSingleFound() {
stubFor(post(urlEqualTo(lookupSinglePath))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(lookupSingleFoundBody)
)
);
Optional<SingleLookupReply> rep = p.find(lookupSingleRequest);
assertTrue(rep.isPresent());
rep.ifPresent(data -> {
assertNotNull(data.getMxid());
assertTrue(data.getMxid().getId(), StringUtils.equals(data.getMxid().getId(), "@john:example.org"));
});
verify(postRequestedFor(urlMatching("/lookup/single"))
.withHeader("Content-Type", containing("application/json"))
.withRequestBody(equalTo(lookupSingleRequestBody))
);
}
@Test
public void lookupSingleNotFound() {
stubFor(post(urlEqualTo(lookupSinglePath))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(lookupSingleNotFoundBody)
)
);
Optional<SingleLookupReply> rep = p.find(lookupSingleRequest);
assertTrue(!rep.isPresent());
verify(postRequestedFor(urlMatching("/lookup/single"))
.withHeader("Content-Type", containing("application/json"))
.withRequestBody(equalTo(lookupSingleRequestBody))
);
}
@Test
public void lookupBulkFound() {
stubFor(post(urlEqualTo(lookupBulkPath))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(lookupBulkFoundBody)
)
);
List<ThreePidMapping> mappings = p.populate(lookupBulkList);
assertNotNull(mappings);
assertTrue(mappings.size() == 2);
assertTrue(StringUtils.equals(mappings.get(0).getMxid(), "@john:example.org"));
assertTrue(StringUtils.equals(mappings.get(1).getMxid(), "@jane:example.org"));
}
@Test
public void lookupBulkNotFound() {
stubFor(post(urlEqualTo(lookupBulkPath))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(lookupBulkNotFoundBody)
)
);
List<ThreePidMapping> mappings = p.populate(lookupBulkList);
assertNotNull(mappings);
assertTrue(mappings.size() == 0);
}
}