diff --git a/application.example.yaml b/application.example.yaml index 4e34371..13d33a9 100644 --- a/application.example.yaml +++ b/application.example.yaml @@ -223,6 +223,21 @@ 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' +#sql.identity.type: 'mxid' +#sql.identity.query: 'SELECT user_id AS uid FROM user_threepids WHERE medium = ? AND address = ?' +#sql.identity.medium.email: "SELECT user_id AS uid FROM user_threepids WHERE medium = ? AND address = ?" + + + ####################################### # Lookup queries forward config items # ####################################### diff --git a/src/main/groovy/io/kamax/mxisd/auth/provider/SqlAuthProvider.java b/src/main/groovy/io/kamax/mxisd/auth/provider/SqlAuthProvider.java new file mode 100644 index 0000000..28b41d7 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/auth/provider/SqlAuthProvider.java @@ -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 . + */ + +package io.kamax.mxisd.auth.provider; + +import io.kamax.mxisd.auth.UserAuthResult; +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 UserAuthResult authenticate(String id, String password) { + log.info("Performing dummy authentication try to force invite mapping refresh"); + + invMgr.lookupMappingsForInvites(); + return new UserAuthResult().failure(); + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderAuthConfig.java b/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderAuthConfig.java new file mode 100644 index 0000000..4d826a7 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderAuthConfig.java @@ -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; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderConfig.java b/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderConfig.java new file mode 100644 index 0000000..2607ad0 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderConfig.java @@ -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 . + */ + +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())); + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderIdentityConfig.java b/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderIdentityConfig.java new file mode 100644 index 0000000..c55c662 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderIdentityConfig.java @@ -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 . + */ + +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 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 getMedium() { + return medium; + } + + public void setMedium(Map medium) { + this.medium = medium; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy index 41aafcd..4e430e6 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy +++ b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy @@ -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 @@ -48,6 +49,9 @@ 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() } } diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java index 4d7d683..96a0846 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java @@ -85,7 +85,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 @@ -233,7 +233,7 @@ public class InvitationManager { 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()); } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/SqlProvider.java b/src/main/groovy/io/kamax/mxisd/lookup/provider/SqlProvider.java new file mode 100644 index 0000000..c7b7225 --- /dev/null +++ b/src/main/groovy/io/kamax/mxisd/lookup/provider/SqlProvider.java @@ -0,0 +1,107 @@ +/* + * 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 . + */ + +package io.kamax.mxisd.lookup.provider; + +import io.kamax.matrix.MatrixID; +import io.kamax.mxisd.config.ServerConfig; +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 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 SqlProvider implements IThreePidProvider { + + private Logger log = LoggerFactory.getLogger(SqlProvider.class); + + @Autowired + private ServerConfig srvCfg; + + @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 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, srvCfg.getName()))); + } + 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 populate(List mappings) { + return new ArrayList<>(); + } + +}