Compare commits

..

17 Commits

Author SHA1 Message Date
Maxime Dor
7fff2448a1 Improve the docker experience
- Only one env variable to configure on first usage
- Auto-generate a default config
- Improve doc
2017-12-16 19:58:11 +01:00
Maxime Dor
6571ff76b1 Take LDAP filter into account when doing 3PID lookups 2017-12-16 19:17:22 +01:00
Maxime Dor
16690a0329 Enforce baseDn for LDAP provider 2017-12-06 20:31:06 +01:00
Maxime Dor
6ac593f0fa Fix typo 2017-12-02 20:13:25 +01:00
Maxime Dor
1581ab9e07 Better logic (cosmetic) for default 3PID notification providers 2017-11-30 02:44:21 +01:00
Maxime Dor
a1adca72e8 Properly select raw notification handler by default 2017-11-26 16:30:42 +01:00
Maxime Dor
e2b3920840 Directory: document HS exclusion config option 2017-11-22 19:40:16 +01:00
Maxime Dor
aaa742f6d2 LDAP: Properly handle multi-value attributes 2017-11-17 16:51:16 +01:00
Maxime Dor
959feb686c Improve auth doc 2017-11-16 22:35:16 +01:00
Maxime Dor
d9c5c5056a Improve getting started wording 2017-11-15 21:01:33 +01:00
Maxime Dor
83fafdcfeb Add config option to disable HS lookup for directory searches 2017-11-06 11:16:22 +01:00
Maxime Dor
e916ecd08b Properly handle Synapse as an Identity provider 2017-10-30 17:43:22 +01:00
Maxime Dor
1461d8ef6c Add fixme to prevent using mxisd DB as synapse identity store 2017-10-30 16:42:00 +01:00
Maxime Dor
19c1214e4a Fix release version extraction from git tags 2017-10-25 00:40:43 +02:00
Maxime Dor
b976f69c39 Fix systemd log format 2017-10-17 15:59:16 +02:00
Maxime Dor
3675da4a0f Specify that LDAP now supports profile auto-fill 2017-10-11 21:08:10 +02:00
Max Dor
077955d538 Set theme jekyll-theme-cayman 2017-10-09 19:18:37 +02:00
28 changed files with 366 additions and 122 deletions

View File

@@ -6,6 +6,11 @@ EXPOSE 8090
ADD build/libs/mxisd.jar /mxisd.jar ADD build/libs/mxisd.jar /mxisd.jar
ADD src/docker/start.sh /start.sh ADD src/docker/start.sh /start.sh
RUN mkdir -p /var/mxisd
ENV JAVA_OPTS="" ENV JAVA_OPTS=""
ENV CONF_FILE_PATH="/etc/mxisd/mxisd.yaml"
ENV SIGN_KEY_PATH="/var/mxisd/sign.key"
ENV SQLITE_DATABASE_PATH="/var/mxisd/mxisd.db"
CMD [ "/start.sh" ] CMD [ "/start.sh" ]

View File

@@ -18,7 +18,7 @@ It is specifically designed to connect to an Identity store (AD/Samba/LDAP, SQL
and ease the integration of the Matrix ecosystem with an existing infrastructure, or to build a new one using lasting and ease the integration of the Matrix ecosystem with an existing infrastructure, or to build a new one using lasting
tools. tools.
The core principle of mxisd is to map between Matrix IDs and 3PIDs (Thrid-party Identifiers) for the Homeserver and its The core principle of mxisd is to map between Matrix IDs and 3PIDs (Third-party Identifiers) for the Homeserver and its
users. 3PIDs can be anything that identify a user, like: users. 3PIDs can be anything that identify a user, like:
- Full name - Full name
- Email address - Email address

View File

@@ -47,7 +47,7 @@ String gitVersion() {
def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?") def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?")
ByteArrayOutputStream out = new ByteArrayOutputStream() ByteArrayOutputStream out = new ByteArrayOutputStream()
exec { exec {
commandLine = ['git', 'describe', '--always', '--dirty'] commandLine = ['git', 'describe', '--tags', '--always', '--dirty']
standardOutput = out standardOutput = out
} }
def v = out.toString().replace(System.lineSeparator(), '') def v = out.toString().replace(System.lineSeparator(), '')

1
docs/_config.yml Normal file
View File

@@ -0,0 +1 @@
theme: jekyll-theme-cayman

View File

@@ -43,7 +43,8 @@ ldap.attribute.name: 'cn'
``` ```
You can also change the attribute lists for 3PID, like email or phone numbers. You can also change the attribute lists for 3PID, like email or phone numbers.
The following example would overwrite the [default list of attributes](../../src/main/resources/application.yaml#L67) for emails and phone number: The following example would overwrite the [default list of attributes](../../src/main/resources/application.yaml#L67)
for emails and phone number:
``` ```
ldap.attribute.threepid.email: ldap.attribute.threepid.email:
- 'mail' - 'mail'
@@ -65,7 +66,8 @@ of the Configuration below.
No further configuration is needed to enable authentication with LDAP once globally enabled and configured. No further configuration is needed to enable authentication with LDAP once globally enabled and configured.
You have the possiblity to use a different query filter if you wish, see Configuration below. You have the possiblity to use a different query filter if you wish, see Configuration below.
Profile auto-fill is not yet supported but is a top priority. Profile auto-fill is enabled by default. It will use the `name` and `threepid` configuration options to get a lit of
attributes to be used to build the user profile to pass on to synapse during authentication.
## Directory ## Directory
No further configuration is needed to enable directory with LDAP once globally enabled and configured. No further configuration is needed to enable directory with LDAP once globally enabled and configured.

View File

@@ -27,22 +27,27 @@ It allows to use Identity stores configured in mxisd to authenticate users on yo
Performed on [synapse with REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth/blob/master/README.md) Performed on [synapse with REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth/blob/master/README.md)
## Getting started ## Getting started
Authentication is possible by linking synapse and mxisd together using the REST auth module
(also known as password provider).
### Synapse ### Synapse
You will need: - Install the [REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth).
- Edit your synapse configuration:
- As described by the auth module documentation
- Set `endpoint` to `http://mxisdAddress:8090` - Replace `mxisdAddress` by an IP/host name that provides a direct
connection to mxisd.
This **MUST NOT** be a public address, and SHOULD NOT go through a reverse proxy.
- Restart synapse
### mxisd
- Configure and enable at least one [Identity store](../backends/) - Configure and enable at least one [Identity store](../backends/)
- Install the [REST auth module](https://github.com/kamax-io/matrix-synapse-rest-auth) - Restart mxisd
Once installed, edit your synapse configuration as described for the auth module: ### Validate
- Set `endpoint` to `http://mxisdAddress:8090` - Replace `mxisdAddress` to an internal IP/Hostname. Login on the Homeserver using credentials present in your backend.
- If you want to avoid [known issues](https://github.com/matrix-org/matrix-doc/issues/586) with lower/upper case
usernames, set `enforceLowercase` in the REST config to `true`.
**IMPORTANT**: if this is a new installation, it is highly recommended to enforce lowercase, as it is not possible to ## Next steps
workaround the bug at a later date and will cause issues with invites, searches, authentication. ### Profile auto-fill
Restart synapse and login on the Homeserver using credentials present in your backend.
## Profile auto-fill
Auto-filling user profile depends on two conditions: Auto-filling user profile depends on two conditions:
- The REST auth module is configured for it, which is the case by default - The REST auth module is configured for it, which is the case by default
- Your Identity store is configured to provide profile data. See your Identity store [documentation](../backends/) on - Your Identity store is configured to provide profile data. See your Identity store [documentation](../backends/) on

View File

@@ -9,6 +9,7 @@
- [LDAP](#ldap) - [LDAP](#ldap)
- [SQL](#sql) - [SQL](#sql)
- [REST](#rest) - [REST](#rest)
- [Next steps](#next-steps)
## Description ## Description
This feature allows you to search for existing and/or potential users that are already present in your Identity backend This feature allows you to search for existing and/or potential users that are already present in your Identity backend
@@ -165,3 +166,11 @@ For each query, `type` can be used to tell mxisd how to process the ID column:
#### REST #### REST
See the [dedicated document](../backends/rest.md) See the [dedicated document](../backends/rest.md)
## Next steps
### Homeserver results
You can configure if the Homeserver should be queried at all when doing a directory search.
To disable Homeserver results, set the following in mxisd config file:
```
directory.exclude.homeserever: true
```

View File

@@ -37,6 +37,8 @@ Install via:
See the [Latest release](https://github.com/kamax-io/mxisd/releases/latest) for links to each. See the [Latest release](https://github.com/kamax-io/mxisd/releases/latest) for links to each.
## Configure ## Configure
**NOTE**: please view the install instruction for your platform, as this step might be optional/handled for you.
Create/edit a minimal configuration (see installer doc for the location): Create/edit a minimal configuration (see installer doc for the location):
``` ```
matrix.domain: 'MyMatrixDomain.org' matrix.domain: 'MyMatrixDomain.org'
@@ -54,7 +56,8 @@ Complete configuration guide is available [here](configure.md).
For an overview of a typical mxisd infrastructure, see the [dedicated document](architecture.md) For an overview of a typical mxisd infrastructure, see the [dedicated document](architecture.md)
### Reverse proxy ### Reverse proxy
#### Apache2 #### Apache2
In the VirtualHost handling the domain with SSL, add the following line and replace `0.0.0.0` by the right address/host. In the VirtualHost handling the domain with SSL, add the following line and replace `0.0.0.0` by the internal IP/hostname
pointing to mxisd.
**This line MUST be present before the one for the homeserver!** **This line MUST be present before the one for the homeserver!**
``` ```
ProxyPass /_matrix/identity/ http://0.0.0.0:8090/_matrix/identity/ ProxyPass /_matrix/identity/ http://0.0.0.0:8090/_matrix/identity/
@@ -82,7 +85,7 @@ trusted_third_party_id_servers:
- vector.im - vector.im
- example.org - example.org
``` ```
It is recommended to remove `matrix.org` and `vector.im` so only your own Identity server is allowed by synapse. It is recommended to remove `matrix.org` and `vector.im` so only your own Identity server is authoritative for your HS.
## Validate ## Validate
Log in using your Matrix client and set `https://example.org` as your Identity server URL, replacing `example.org` by Log in using your Matrix client and set `https://example.org` as your Identity server URL, replacing `example.org` by

View File

@@ -5,10 +5,18 @@ Pull the latest stable image:
docker pull kamax/mxisd docker pull kamax/mxisd
``` ```
## Configure
On first run, simply using `MATRIX_DOMAIN` as an environment variable will create a default config for you.
You can also provide a configuration file named `mxisd.yaml` in the volume mapped to `/etc/mxisd` before starting your
container.
## Run ## Run
Run it (adapt volume paths to your host): Use the following command after adapting to your needs:
- The `MATRIX_DOMAIN` environment variable to yours
- The volumes host paths
``` ```
docker run --rm -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t kamax/mxisd docker run --rm -e MATRIX_DOMAIN=example.org -v /data/mxisd/etc:/etc/mxisd -v /data/mxisd/var:/var/mxisd -p 8090:8090 -t kamax/mxisd
``` ```
For more info, including the list of possible tags, see [the public repository](https://hub.docker.com/r/kamax/mxisd/) For more info, including the list of possible tags, see [the public repository](https://hub.docker.com/r/kamax/mxisd/)

View File

@@ -1,2 +1,26 @@
#!/bin/sh #!/bin/sh
if ! [ -z "$CONF_FILE_PATH" ] && ! [ -f "CONF_FILE_PATH" ]; then
echo "Generating config file $CONF_FILE_PATH"
touch "CONF_FILE_PATH"
if ! [ -z "$MATRIX_DOMAIN" ]; then
echo "Setting matrix domain to $MATRIX_DOMAIN"
echo "matrix.domain: $MATRIX_DOMAIN" >> "$CONF_FILE_PATH"
fi
if ! [ -z "$SIGN_KEY_PATH" ]; then
echo "Setting signing key path to $SIGN_KEY_PATH"
echo "key.path: $SIGN_KEY_PATH" >> "$CONF_FILE_PATH"
fi
if ! [ -z "$SQLITE_DATABASE_PATH" ]; then
echo "Setting SQLite DB path to $SQLITE_DATABASE_PATH"
echo "storage.provider.sqlite.database: $SQLITE_DATABASE_PATH" >> "$CONF_FILE_PATH"
fi
echo "Starting mxisd..."
echo
fi
exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dspring.config.location=/etc/mxisd/ -Dspring.config.name=mxisd -jar /mxisd.jar exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -Dspring.config.location=/etc/mxisd/ -Dspring.config.name=mxisd -jar /mxisd.jar

View File

@@ -46,6 +46,7 @@ import org.springframework.stereotype.Component;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@@ -133,14 +134,20 @@ public class LdapAuthProvider extends LdapGenericBackend implements Authenticato
// TODO should we canonicalize the MXID? // TODO should we canonicalize the MXID?
BackendAuthResult result = BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, name); BackendAuthResult result = BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, name);
log.info("Processing 3PIDs for profile"); log.info("Processing 3PIDs for profile");
getAt().getThreepid().forEach((k, v) -> v.forEach(attId -> { getAt().getThreepid().forEach((k, v) -> {
getAttribute(entry, attId).ifPresent(tpidValue -> { log.info("Processing 3PID type {}", k);
if (ThreePidMedium.PhoneNumber.is(k)) { v.forEach(attId -> {
tpidValue = getMsisdn(tpidValue).orElse(tpidValue); List<String> values = getAttributes(entry, attId);
} log.info("\tAttribute {} has {} value(s)", attId, values.size());
result.withThreePid(new ThreePid(k, tpidValue)); getAttributes(entry, attId).forEach(tpidValue -> {
if (ThreePidMedium.PhoneNumber.is(k)) {
tpidValue = getMsisdn(tpidValue).orElse(tpidValue);
}
result.withThreePid(new ThreePid(k, tpidValue));
});
}); });
})); });
log.info("Found {} 3PIDs", result.getProfile().getThreePids().size()); log.info("Found {} 3PIDs", result.getProfile().getThreePids().size());
return result; return result;
} }

View File

@@ -25,6 +25,7 @@ import io.kamax.mxisd.config.ldap.LdapAttributeConfig;
import io.kamax.mxisd.config.ldap.LdapConfig; import io.kamax.mxisd.config.ldap.LdapConfig;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.directory.api.ldap.model.entry.Attribute; import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.AttributeUtils;
import org.apache.directory.api.ldap.model.entry.Entry; import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException; import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.ldap.client.api.LdapConnection; import org.apache.directory.ldap.client.api.LdapConnection;
@@ -32,6 +33,9 @@ import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -124,7 +128,6 @@ public abstract class LdapGenericBackend {
public Optional<String> getAttribute(Entry entry, String attName) { public Optional<String> getAttribute(Entry entry, String attName) {
Attribute attribute = entry.get(attName); Attribute attribute = entry.get(attName);
if (attribute == null) { if (attribute == null) {
log.info("DN {}: no attribute {}, skipping", entry.getDn(), attName);
return Optional.empty(); return Optional.empty();
} }
@@ -137,4 +140,22 @@ public abstract class LdapGenericBackend {
return Optional.of(value); return Optional.of(value);
} }
public List<String> getAttributes(Entry entry, String attName) {
List<String> values = new ArrayList<>();
javax.naming.directory.Attribute att = AttributeUtils.toAttributes(entry).get(attName);
if (att == null) {
return values;
}
try {
NamingEnumeration<?> list = att.getAll();
while (list.hasMore()) {
values.add(list.next().toString());
}
} catch (NamingException e) {
log.warn("Error while processing LDAP attribute {}, result could be incomplete!", attName, e);
}
return values;
}
} }

View File

@@ -68,13 +68,16 @@ public class LdapThreePidProvider extends LdapGenericBackend implements IThreePi
} }
private Optional<String> lookup(LdapConnection conn, String medium, String value) { private Optional<String> lookup(LdapConnection conn, String medium, String value) {
Optional<String> queryOpt = getCfg().getIdentity().getQuery(medium); Optional<String> tPidQueryOpt = getCfg().getIdentity().getQuery(medium);
if (!queryOpt.isPresent()) { if (!tPidQueryOpt.isPresent()) {
log.warn("{} is not a configured 3PID type for LDAP lookup", medium); log.warn("{} is not a configured 3PID type for LDAP lookup", medium);
return Optional.empty(); return Optional.empty();
} }
String searchQuery = queryOpt.get().replaceAll(getCfg().getIdentity().getToken(), value); // we merge 3PID specific query with global/specific filter, if one exists.
String tPidQuery = tPidQueryOpt.get().replaceAll(getCfg().getIdentity().getToken(), value);
String searchQuery = buildWithFilter(tPidQuery, getCfg().getIdentity().getFilter());
try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, getUidAtt())) { try (EntryCursor cursor = conn.search(getBaseDn(), searchQuery, SearchScope.SUBTREE, getUidAtt())) {
while (cursor.next()) { while (cursor.next()) {
Entry entry = cursor.get(); Entry entry = cursor.get();

View File

@@ -24,7 +24,7 @@ import io.kamax.matrix._MatrixID;
import io.kamax.mxisd.auth.provider.AuthenticatorProvider; import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
import io.kamax.mxisd.auth.provider.BackendAuthResult; import io.kamax.mxisd.auth.provider.BackendAuthResult;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.config.sql.SqlProviderConfig; import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
import io.kamax.mxisd.invitation.InvitationManager; import io.kamax.mxisd.invitation.InvitationManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -32,15 +32,15 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class SqlAuthProvider implements AuthenticatorProvider { public class GenericSqlAuthProvider implements AuthenticatorProvider {
private Logger log = LoggerFactory.getLogger(SqlAuthProvider.class); private Logger log = LoggerFactory.getLogger(GenericSqlAuthProvider.class);
@Autowired @Autowired
private ServerConfig srvCfg; private ServerConfig srvCfg;
@Autowired @Autowired
private SqlProviderConfig cfg; private GenericSqlProviderConfig cfg;
@Autowired @Autowired
private InvitationManager invMgr; private InvitationManager invMgr;

View File

@@ -22,8 +22,8 @@ package io.kamax.mxisd.backend.sql;
import io.kamax.matrix.MatrixID; import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
import io.kamax.mxisd.config.sql.SqlConfig; import io.kamax.mxisd.config.sql.SqlConfig;
import io.kamax.mxisd.config.sql.SqlProviderConfig;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult; import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.directory.IDirectoryProvider; import io.kamax.mxisd.directory.IDirectoryProvider;
import io.kamax.mxisd.exception.InternalServerError; import io.kamax.mxisd.exception.InternalServerError;
@@ -39,16 +39,16 @@ import java.util.Optional;
import static io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult.Result; import static io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult.Result;
public abstract class SqlDirectoryProvider implements IDirectoryProvider { public abstract class GenericSqlDirectoryProvider implements IDirectoryProvider {
private Logger log = LoggerFactory.getLogger(SqlDirectoryProvider.class); private Logger log = LoggerFactory.getLogger(GenericSqlDirectoryProvider.class);
protected SqlConfig cfg; protected SqlConfig cfg;
private MatrixConfig mxCfg; private MatrixConfig mxCfg;
private SqlConnectionPool pool; private SqlConnectionPool pool;
public SqlDirectoryProvider(SqlConfig cfg, MatrixConfig mxCfg) { public GenericSqlDirectoryProvider(SqlConfig cfg, MatrixConfig mxCfg) {
this.cfg = cfg; this.cfg = cfg;
this.pool = new SqlConnectionPool(cfg); this.pool = new SqlConnectionPool(cfg);
this.mxCfg = mxCfg; this.mxCfg = mxCfg;
@@ -72,7 +72,7 @@ public abstract class SqlDirectoryProvider implements IDirectoryProvider {
return Optional.of(item); return Optional.of(item);
} }
public UserDirectorySearchResult search(String searchTerm, SqlProviderConfig.Query query) { public UserDirectorySearchResult search(String searchTerm, GenericSqlProviderConfig.Query query) {
try (Connection conn = pool.get()) { try (Connection conn = pool.get()) {
log.info("Will execute query: {}", query.getValue()); log.info("Will execute query: {}", query.getValue());
try (PreparedStatement stmt = conn.prepareStatement(query.getValue())) { try (PreparedStatement stmt = conn.prepareStatement(query.getValue())) {

View File

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

View File

@@ -22,7 +22,7 @@ package io.kamax.mxisd.backend.sql;
import io.kamax.matrix.MatrixID; import io.kamax.matrix.MatrixID;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.SqlProviderConfig; import io.kamax.mxisd.config.sql.SqlConfig;
import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.ThreePidMapping;
@@ -30,8 +30,6 @@ import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
@@ -41,18 +39,16 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@Component public abstract class SqlThreePidProvider implements IThreePidProvider {
public class SqlThreePidProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(SqlThreePidProvider.class); private Logger log = LoggerFactory.getLogger(SqlThreePidProvider.class);
private SqlProviderConfig cfg; private SqlConfig cfg;
private MatrixConfig mxCfg; private MatrixConfig mxCfg;
private SqlConnectionPool pool; private SqlConnectionPool pool;
@Autowired public SqlThreePidProvider(SqlConfig cfg, MatrixConfig mxCfg) {
public SqlThreePidProvider(SqlProviderConfig cfg, MatrixConfig mxCfg) {
this.cfg = cfg; this.cfg = cfg;
this.pool = new SqlConnectionPool(cfg); this.pool = new SqlConnectionPool(cfg);
this.mxCfg = mxCfg; this.mxCfg = mxCfg;

View File

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

View File

@@ -21,7 +21,7 @@
package io.kamax.mxisd.backend.sql; package io.kamax.mxisd.backend.sql;
import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.sql.SqlProviderConfig; import io.kamax.mxisd.config.sql.GenericSqlProviderConfig;
import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig; import io.kamax.mxisd.config.sql.synapse.SynapseSqlProviderConfig;
import io.kamax.mxisd.exception.ConfigurationException; import io.kamax.mxisd.exception.ConfigurationException;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@@ -32,9 +32,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
@Component @Component
public class SynapseSqliteDirectoryProvider extends SqlDirectoryProvider { public class SynapseSqliteDirectoryProvider extends GenericSqlDirectoryProvider {
private SynapseSqlProviderConfig cfg;
@Autowired @Autowired
public SynapseSqliteDirectoryProvider(SynapseSqlProviderConfig cfg, MatrixConfig mxCfg) { public SynapseSqliteDirectoryProvider(SynapseSqlProviderConfig cfg, MatrixConfig mxCfg) {
@@ -42,7 +40,7 @@ public class SynapseSqliteDirectoryProvider extends SqlDirectoryProvider {
if (StringUtils.equals("sqlite", cfg.getType())) { if (StringUtils.equals("sqlite", cfg.getType())) {
String userId = "'@' || p.user_id || ':" + mxCfg.getDomain() + "'"; String userId = "'@' || p.user_id || ':" + mxCfg.getDomain() + "'";
SqlProviderConfig.Type queries = cfg.getDirectory().getQuery(); GenericSqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
queries.getName().setValue( queries.getName().setValue(
"select " + userId + ", displayname from profiles p where displayname like ?"); "select " + userId + ", displayname from profiles p where displayname like ?");
queries.getThreepid().setValue( queries.getThreepid().setValue(
@@ -51,7 +49,7 @@ public class SynapseSqliteDirectoryProvider extends SqlDirectoryProvider {
"where t.address like ?"); "where t.address like ?");
} else if (StringUtils.equals("postgresql", cfg.getType())) { } else if (StringUtils.equals("postgresql", cfg.getType())) {
String userId = "concat('@',p.user_id,':" + mxCfg.getDomain() + "')"; String userId = "concat('@',p.user_id,':" + mxCfg.getDomain() + "')";
SqlProviderConfig.Type queries = cfg.getDirectory().getQuery(); GenericSqlProviderConfig.Type queries = cfg.getDirectory().getQuery();
queries.getName().setValue( queries.getName().setValue(
"select " + userId + ", displayname from profiles p where displayname ilike ?"); "select " + userId + ", displayname from profiles p where displayname ilike ?");
queries.getThreepid().setValue( queries.getThreepid().setValue(

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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties("directory")
public class DirectoryConfig {
private final transient Logger log = LoggerFactory.getLogger(DnsOverwriteConfig.class);
public static class Exclude {
private boolean homeserver;
public boolean getHomeserver() {
return homeserver;
}
public Exclude setHomeserver(boolean homeserver) {
this.homeserver = homeserver;
return this;
}
}
private Exclude exclude = new Exclude();
public Exclude getExclude() {
return exclude;
}
public void setExclude(Exclude exclude) {
this.exclude = exclude;
}
}

View File

@@ -162,11 +162,14 @@ public class LdapConfig {
throw new IllegalStateException("LDAP port is not valid"); throw new IllegalStateException("LDAP port is not valid");
} }
if (StringUtils.isBlank(conn.getBaseDn())) {
throw new ConfigurationException("ldap.connection.baseDn");
}
if (StringUtils.isBlank(attribute.getUid().getType())) { if (StringUtils.isBlank(attribute.getUid().getType())) {
throw new IllegalStateException("Attribute UID Type cannot be empty"); throw new IllegalStateException("Attribute UID Type cannot be empty");
} }
if (StringUtils.isBlank(attribute.getUid().getValue())) { if (StringUtils.isBlank(attribute.getUid().getValue())) {
throw new IllegalStateException("Attribute UID value cannot be empty"); throw new IllegalStateException("Attribute UID value cannot be empty");
} }

View File

@@ -24,21 +24,14 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import javax.annotation.PostConstruct;
@Configuration @Configuration
@ConfigurationProperties("sql") @ConfigurationProperties("sql")
@Primary @Primary
public class SqlProviderConfig extends SqlConfig { public class GenericSqlProviderConfig extends SqlConfig {
@Override @Override
protected String getProviderName() { protected String getProviderName() {
return "Generic SQL"; return "Generic SQL";
} }
@PostConstruct
public void build() {
super.build();
}
} }

View File

@@ -4,6 +4,7 @@ import io.kamax.mxisd.util.GsonUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -36,22 +37,22 @@ public abstract class SqlConfig {
public static class Type { public static class Type {
private SqlProviderConfig.Query name = new SqlProviderConfig.Query(); private GenericSqlProviderConfig.Query name = new GenericSqlProviderConfig.Query();
private SqlProviderConfig.Query threepid = new SqlProviderConfig.Query(); private GenericSqlProviderConfig.Query threepid = new GenericSqlProviderConfig.Query();
public SqlProviderConfig.Query getName() { public GenericSqlProviderConfig.Query getName() {
return name; return name;
} }
public void setName(SqlProviderConfig.Query name) { public void setName(GenericSqlProviderConfig.Query name) {
this.name = name; this.name = name;
} }
public SqlProviderConfig.Query getThreepid() { public GenericSqlProviderConfig.Query getThreepid() {
return threepid; return threepid;
} }
public void setThreepid(SqlProviderConfig.Query threepid) { public void setThreepid(GenericSqlProviderConfig.Query threepid) {
this.threepid = threepid; this.threepid = threepid;
} }
@@ -74,7 +75,7 @@ public abstract class SqlConfig {
public static class Directory { public static class Directory {
private Boolean enabled; private Boolean enabled;
private SqlProviderConfig.Type query = new SqlProviderConfig.Type(); private GenericSqlProviderConfig.Type query = new GenericSqlProviderConfig.Type();
public Boolean isEnabled() { public Boolean isEnabled() {
return enabled; return enabled;
@@ -84,11 +85,11 @@ public abstract class SqlConfig {
this.enabled = enabled; this.enabled = enabled;
} }
public SqlProviderConfig.Type getQuery() { public GenericSqlProviderConfig.Type getQuery() {
return query; return query;
} }
public void setQuery(SqlProviderConfig.Type query) { public void setQuery(GenericSqlProviderConfig.Type query) {
this.query = query; this.query = query;
} }
@@ -138,9 +139,9 @@ public abstract class SqlConfig {
private boolean enabled; private boolean enabled;
private String type; private String type;
private String connection; private String connection;
private SqlProviderConfig.Auth auth = new SqlProviderConfig.Auth(); private GenericSqlProviderConfig.Auth auth = new GenericSqlProviderConfig.Auth();
private SqlProviderConfig.Directory directory = new SqlProviderConfig.Directory(); private GenericSqlProviderConfig.Directory directory = new GenericSqlProviderConfig.Directory();
private SqlProviderConfig.Identity identity = new SqlProviderConfig.Identity(); private GenericSqlProviderConfig.Identity identity = new GenericSqlProviderConfig.Identity();
public boolean isEnabled() { public boolean isEnabled() {
return enabled; return enabled;
@@ -166,35 +167,33 @@ public abstract class SqlConfig {
this.connection = connection; this.connection = connection;
} }
public SqlProviderConfig.Auth getAuth() { public GenericSqlProviderConfig.Auth getAuth() {
return auth; return auth;
} }
public void setAuth(SqlProviderConfig.Auth auth) { public void setAuth(GenericSqlProviderConfig.Auth auth) {
this.auth = auth; this.auth = auth;
} }
public SqlProviderConfig.Directory getDirectory() { public GenericSqlProviderConfig.Directory getDirectory() {
return directory; return directory;
} }
public void setDirectory(SqlProviderConfig.Directory directory) { public void setDirectory(GenericSqlProviderConfig.Directory directory) {
this.directory = directory; this.directory = directory;
} }
public SqlProviderConfig.Identity getIdentity() { public GenericSqlProviderConfig.Identity getIdentity() {
return identity; return identity;
} }
public void setIdentity(SqlProviderConfig.Identity identity) { public void setIdentity(GenericSqlProviderConfig.Identity identity) {
this.identity = identity; this.identity = identity;
} }
protected abstract String getProviderName(); protected abstract String getProviderName();
public void build() { protected void doBuild() {
log.info("--- " + getProviderName() + " Provider config ---");
if (getAuth().isEnabled() == null) { if (getAuth().isEnabled() == null) {
getAuth().setEnabled(isEnabled()); getAuth().setEnabled(isEnabled());
} }
@@ -206,6 +205,13 @@ public abstract class SqlConfig {
if (getIdentity().isEnabled() == null) { if (getIdentity().isEnabled() == null) {
getIdentity().setEnabled(isEnabled()); getIdentity().setEnabled(isEnabled());
} }
}
@PostConstruct
public void build() {
log.info("--- " + getProviderName() + " Provider config ---");
doBuild();
log.info("Enabled: {}", isEnabled()); log.info("Enabled: {}", isEnabled());
if (isEnabled()) { if (isEnabled()) {
@@ -214,6 +220,7 @@ public abstract class SqlConfig {
log.info("Auth enabled: {}", getAuth().isEnabled()); log.info("Auth enabled: {}", getAuth().isEnabled());
log.info("Directory queries: {}", GsonUtil.build().toJson(getDirectory().getQuery())); log.info("Directory queries: {}", GsonUtil.build().toJson(getDirectory().getQuery()));
log.info("Identity type: {}", getIdentity().getType()); log.info("Identity type: {}", getIdentity().getType());
log.info("3PID mapping query: {}", getIdentity().getQuery());
log.info("Identity medium queries: {}", GsonUtil.build().toJson(getIdentity().getMedium())); log.info("Identity medium queries: {}", GsonUtil.build().toJson(getIdentity().getMedium()));
} }
} }

View File

@@ -21,6 +21,7 @@
package io.kamax.mxisd.config.sql.synapse; package io.kamax.mxisd.config.sql.synapse;
import io.kamax.mxisd.config.sql.SqlConfig; import io.kamax.mxisd.config.sql.SqlConfig;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -36,8 +37,23 @@ public class SynapseSqlProviderConfig extends SqlConfig {
} }
@PostConstruct @PostConstruct
public void build() { public void doBuild() {
super.build(); super.doBuild();
// FIXME check that the DB is not the mxisd one
// See https://matrix.to/#/!NPRUEisLjcaMtHIzDr:kamax.io/$1509377583327omXkC:kamax.io
getAuth().setEnabled(false); // Synapse does the auth, we only act as a directory/identity service.
if (getDirectory().isEnabled()) {
//FIXME set default queries for name and threepid
}
if (getIdentity().isEnabled()) {
if (StringUtils.isBlank(getIdentity().getType())) {
getIdentity().setType("mxid");
getIdentity().setQuery("SELECT user_id AS uid FROM user_threepids WHERE medium = ? AND address = ?");
}
}
} }
} }

View File

@@ -23,6 +23,7 @@ package io.kamax.mxisd.directory;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import io.kamax.matrix.MatrixErrorInfo; import io.kamax.matrix.MatrixErrorInfo;
import io.kamax.mxisd.config.DirectoryConfig;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest; import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult; import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.dns.ClientDnsOverwrite; import io.kamax.mxisd.dns.ClientDnsOverwrite;
@@ -54,6 +55,7 @@ public class DirectoryManager {
private Logger log = LoggerFactory.getLogger(DirectoryManager.class); private Logger log = LoggerFactory.getLogger(DirectoryManager.class);
private DirectoryConfig cfg;
private List<IDirectoryProvider> providers; private List<IDirectoryProvider> providers;
private ClientDnsOverwrite dns; private ClientDnsOverwrite dns;
@@ -61,7 +63,8 @@ public class DirectoryManager {
private Gson gson; private Gson gson;
@Autowired @Autowired
public DirectoryManager(List<IDirectoryProvider> providers, ClientDnsOverwrite dns) { public DirectoryManager(DirectoryConfig cfg, List<IDirectoryProvider> providers, ClientDnsOverwrite dns) {
this.cfg = cfg;
this.dns = dns; this.dns = dns;
this.client = HttpClients.custom().setUserAgent("mxisd").build(); //FIXME centralize this.client = HttpClients.custom().setUserAgent("mxisd").build(); //FIXME centralize
this.gson = GsonUtil.build(); this.gson = GsonUtil.build();
@@ -76,37 +79,41 @@ public class DirectoryManager {
log.info("Original request URL: {}", target); log.info("Original request URL: {}", target);
UserDirectorySearchResult result = new UserDirectorySearchResult(); UserDirectorySearchResult result = new UserDirectorySearchResult();
URIBuilder builder = dns.transform(target); if (cfg.getExclude().getHomeserver()) {
log.info("Querying HS at {}", builder); log.info("Skipping HS directory data, disabled in config");
builder.setParameter("access_token", accessToken); } else {
HttpPost req = RestClientUtils.post( URIBuilder builder = dns.transform(target);
builder.toString(), log.info("Querying HS at {}", builder);
new UserDirectorySearchRequest(query)); builder.setParameter("access_token", accessToken);
try (CloseableHttpResponse res = client.execute(req)) { HttpPost req = RestClientUtils.post(
int status = res.getStatusLine().getStatusCode(); builder.toString(),
Charset charset = ContentType.getOrDefault(res.getEntity()).getCharset(); new UserDirectorySearchRequest(query));
String body = IOUtils.toString(res.getEntity().getContent(), charset); try (CloseableHttpResponse res = client.execute(req)) {
int status = res.getStatusLine().getStatusCode();
Charset charset = ContentType.getOrDefault(res.getEntity()).getCharset();
String body = IOUtils.toString(res.getEntity().getContent(), charset);
if (status != 200) { if (status != 200) {
MatrixErrorInfo info = gson.fromJson(body, MatrixErrorInfo.class); MatrixErrorInfo info = gson.fromJson(body, MatrixErrorInfo.class);
if (StringUtils.equals("M_UNRECOGNIZED", info.getErrcode())) { // FIXME no hardcoding, use Enum if (StringUtils.equals("M_UNRECOGNIZED", info.getErrcode())) { // FIXME no hardcoding, use Enum
log.warn("Homeserver does not support Directory feature, skipping"); log.warn("Homeserver does not support Directory feature, skipping");
} else { } else {
log.error("Homeserver returned an error while performing directory search"); log.error("Homeserver returned an error while performing directory search");
throw new MatrixException(status, info.getErrcode(), info.getError()); throw new MatrixException(status, info.getErrcode(), info.getError());
}
} }
}
UserDirectorySearchResult resultHs = gson.fromJson(body, UserDirectorySearchResult.class); UserDirectorySearchResult resultHs = gson.fromJson(body, UserDirectorySearchResult.class);
log.info("Found {} match(es) in HS for '{}'", resultHs.getResults().size(), query); log.info("Found {} match(es) in HS for '{}'", resultHs.getResults().size(), query);
result.getResults().addAll(resultHs.getResults()); result.getResults().addAll(resultHs.getResults());
if (resultHs.isLimited()) { if (resultHs.isLimited()) {
result.setLimited(true); result.setLimited(true);
}
} catch (JsonSyntaxException e) {
throw new InternalServerError("Invalid JSON reply from the HS: " + e.getMessage());
} catch (IOException e) {
throw new InternalServerError("Unable to query the HS: I/O error: " + e.getMessage());
} }
} catch (JsonSyntaxException e) {
throw new InternalServerError("Invalid JSON reply from the HS: " + e.getMessage());
} catch (IOException e) {
throw new InternalServerError("Unable to query the HS: I/O error: " + e.getMessage());
} }
for (IDirectoryProvider provider : providers) { for (IDirectoryProvider provider : providers) {

View File

@@ -53,7 +53,10 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
public RecursivePriorityLookupStrategy(RecursiveLookupConfig cfg, List<IThreePidProvider> providers, IBridgeFetcher bridge) { public RecursivePriorityLookupStrategy(RecursiveLookupConfig cfg, List<IThreePidProvider> providers, IBridgeFetcher bridge) {
this.cfg = cfg; this.cfg = cfg;
this.bridge = bridge; this.bridge = bridge;
this.providers = providers.stream().filter(IThreePidProvider::isEnabled).collect(Collectors.toList()); this.providers = providers.stream().filter(p -> {
log.info("3PID Provider {} is enabled: {}", p.getClass().getSimpleName(), p.isEnabled());
return p.isEnabled();
}).collect(Collectors.toList());
} }
@PostConstruct @PostConstruct

View File

@@ -46,16 +46,14 @@ public class NotificationManager {
this.handlers = new HashMap<>(); this.handlers = new HashMap<>();
handlers.forEach(h -> { handlers.forEach(h -> {
log.info("Found handler {} for medium {}", h.getId(), h.getMedium()); log.info("Found handler {} for medium {}", h.getId(), h.getMedium());
String handlerId = cfg.getHandler().get(h.getMedium()); String handlerId = cfg.getHandler().getOrDefault(h.getMedium(), "raw");
if (StringUtils.isBlank(handlerId) || StringUtils.equals(handlerId, h.getId())) { if (StringUtils.equals(handlerId, h.getId())) {
this.handlers.put(h.getMedium(), h); this.handlers.put(h.getMedium(), h);
} }
}); });
log.info("--- Notification handler ---"); log.info("--- Notification handler ---");
this.handlers.forEach((k, v) -> { this.handlers.forEach((k, v) -> log.info("\tHandler for {}: {}", k, v.getId()));
log.info("\tHandler for {}: {}", k, v.getId());
});
} }
private INotificationHandler ensureMedium(String medium) { private INotificationHandler ensureMedium(String medium) {

View File

@@ -227,10 +227,14 @@ view:
storage: storage:
backend: 'sqlite' backend: 'sqlite'
directory:
exclude:
homeserver: false
--- ---
spring: spring:
profiles: systemd profiles: systemd
logging: logging:
pattern: pattern:
console: '%d{.SSS}${LOG_LEVEL_PATTERN:%5p} [%15.15t] %35.35logger{34} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}' console: '%d{.SSS} ${LOG_LEVEL_PATTERN:%5p} [%15.15t] %35.35logger{34} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}'