diff --git a/build.gradle b/build.gradle index 053837f..362d260 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,3 @@ -import java.util.regex.Pattern - /* * mxisd - Matrix Identity Server Daemon * Copyright (C) 2017 Maxime Dor @@ -20,7 +18,9 @@ import java.util.regex.Pattern * along with this program. If not, see . */ -apply plugin: 'groovy' +import java.util.regex.Pattern + +apply plugin: 'java' apply plugin: 'org.springframework.boot' def confFileName = "application.example.yaml" @@ -70,9 +70,6 @@ repositories { } dependencies { - // We are a groovy project - compile 'org.codehaus.groovy:groovy-all:2.4.7' - // Easy file management compile 'commons-io:commons-io:2.5' diff --git a/src/main/groovy/io/kamax/mxisd/backend/firebase/GoogleFirebaseAuthenticator.groovy b/src/main/groovy/io/kamax/mxisd/backend/firebase/GoogleFirebaseAuthenticator.groovy deleted file mode 100644 index 4533f3a..0000000 --- a/src/main/groovy/io/kamax/mxisd/backend/firebase/GoogleFirebaseAuthenticator.groovy +++ /dev/null @@ -1,193 +0,0 @@ -/* - * mxisd - Matrix Identity Server Daemon - * Copyright (C) 2017 Maxime Dor - * - * https://max.kamax.io/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.kamax.mxisd.backend.firebase - -import com.google.firebase.FirebaseApp -import com.google.firebase.FirebaseOptions -import com.google.firebase.auth.* -import com.google.firebase.internal.NonNull -import com.google.firebase.tasks.OnFailureListener -import com.google.firebase.tasks.OnSuccessListener -import io.kamax.matrix.ThreePidMedium -import io.kamax.matrix._MatrixID -import io.kamax.mxisd.ThreePid -import io.kamax.mxisd.UserIdType -import io.kamax.mxisd.auth.provider.AuthenticatorProvider -import io.kamax.mxisd.auth.provider.BackendAuthResult -import org.apache.commons.lang.StringUtils -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import java.util.regex.Pattern - -public class GoogleFirebaseAuthenticator implements AuthenticatorProvider { - - private Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class); - - private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)"); // FIXME use matrix-java-sdk - - private boolean isEnabled; - private String domain; - private FirebaseApp fbApp; - private FirebaseAuth fbAuth; - - private void waitOnLatch(BackendAuthResult result, CountDownLatch l, long timeout, TimeUnit unit, String purpose) { - try { - l.await(timeout, unit); - } catch (InterruptedException e) { - log.warn("Interrupted while waiting for " + purpose); - result.failure(); - } - } - - public GoogleFirebaseAuthenticator(boolean isEnabled) { - this.isEnabled = isEnabled; - } - - public GoogleFirebaseAuthenticator(String credsPath, String db, String domain) { - this(true); - this.domain = domain; - try { - fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "AuthenticationProvider"); - fbAuth = FirebaseAuth.getInstance(fbApp); - - log.info("Google Firebase Authentication is ready"); - } catch (IOException e) { - throw new RuntimeException("Error when initializing Firebase", e); - } - } - - private FirebaseCredential getCreds(String credsPath) throws IOException { - if (StringUtils.isNotBlank(credsPath)) { - return FirebaseCredentials.fromCertificate(new FileInputStream(credsPath)); - } else { - return FirebaseCredentials.applicationDefault(); - } - } - - private FirebaseOptions getOpts(String credsPath, String db) throws IOException { - if (StringUtils.isBlank(db)) { - throw new IllegalArgumentException("Firebase database is not configured"); - } - - return new FirebaseOptions.Builder() - .setCredential(getCreds(credsPath)) - .setDatabaseUrl(db) - .build(); - } - - @Override - public boolean isEnabled() { - return isEnabled; - } - - private void waitOnLatch(CountDownLatch l) { - try { - l.await(30, TimeUnit.SECONDS); - } catch (InterruptedException e) { - log.warn("Interrupted while waiting for Firebase auth check"); - } - } - - @Override - public BackendAuthResult authenticate(_MatrixID mxid, String password) { - if (!isEnabled()) { - throw new IllegalStateException(); - } - - log.info("Trying to authenticate {}", mxid); - - BackendAuthResult result = BackendAuthResult.failure(); - - String localpart = m.group(1); - CountDownLatch l = new CountDownLatch(1); - fbAuth.verifyIdToken(password).addOnSuccessListener(new OnSuccessListener() { - @Override - void onSuccess(FirebaseToken token) { - try { - if (!StringUtils.equals(localpart, token.getUid())) { - log.info("Failture to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", id, localpart, token.getUid()); - result = BackendAuthResult.failure(); - return; - } - - result = BackendAuthResult.success(mxid.getId(), UserIdType.MatrixID, token.getName()); - log.info("{} was successfully authenticated", mxid); - log.info("Fetching profile for {}", mxid); - CountDownLatch userRecordLatch = new CountDownLatch(1); - fbAuth.getUser(token.getUid()).addOnSuccessListener(new OnSuccessListener() { - @Override - void onSuccess(UserRecord user) { - try { - if (StringUtils.isNotBlank(user.getEmail())) { - result.withThreePid(new ThreePid(ThreePidMedium.Email.getId(), user.getEmail())); - } - - if (StringUtils.isNotBlank(user.getPhoneNumber())) { - result.withThreePid(new ThreePid(ThreePidMedium.PhoneNumber.getId(), user.getPhoneNumber())); - } - - } finally { - userRecordLatch.countDown(); - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - void onFailure(@NonNull Exception e) { - try { - log.warn("Unable to fetch Firebase user profile for {}", mxid); - result = BackendAuthResult.failure(); - } finally { - userRecordLatch.countDown(); - } - } - }); - - waitOnLatch(result, userRecordLatch, 30, TimeUnit.SECONDS, "Firebase user profile"); - } finally { - l.countDown() - } - } - }).addOnFailureListener(new OnFailureListener() { - @Override - void onFailure(@NonNull Exception e) { - try { - if (e instanceof IllegalArgumentException) { - log.info("Failure to authenticate {}: invalid firebase token", mxid); - } else { - log.info("Failure to authenticate {}: {}", id, e.getMessage(), e); - log.info("Exception", e); - } - - result = BackendAuthResult.failure(); - } finally { - l.countDown() - } - } - }); - - waitOnLatch(result, l, 30, TimeUnit.SECONDS, "Firebase auth check"); - return result; - } - -} diff --git a/src/main/groovy/io/kamax/mxisd/backend/ldap/LdapThreePidProvider.groovy b/src/main/groovy/io/kamax/mxisd/backend/ldap/LdapThreePidProvider.groovy deleted file mode 100644 index d30c7f9..0000000 --- a/src/main/groovy/io/kamax/mxisd/backend/ldap/LdapThreePidProvider.groovy +++ /dev/null @@ -1,169 +0,0 @@ -/* - * mxisd - Matrix Identity Server Daemon - * Copyright (C) 2017 Maxime Dor - * - * https://max.kamax.io/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.kamax.mxisd.backend.ldap - -import io.kamax.mxisd.config.MatrixConfig -import io.kamax.mxisd.lookup.SingleLookupReply -import io.kamax.mxisd.lookup.SingleLookupRequest -import io.kamax.mxisd.lookup.ThreePidMapping -import io.kamax.mxisd.lookup.provider.IThreePidProvider -import org.apache.commons.lang.StringUtils -import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException -import org.apache.directory.api.ldap.model.cursor.EntryCursor -import org.apache.directory.api.ldap.model.entry.Attribute -import org.apache.directory.api.ldap.model.entry.Entry -import org.apache.directory.api.ldap.model.message.SearchScope -import org.apache.directory.ldap.client.api.LdapConnection -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component - -@Component -class LdapThreePidProvider extends LdapGenericBackend implements IThreePidProvider { - - public static final String UID = "uid" - public static final String MATRIX_ID = "mxid" - - private Logger log = LoggerFactory.getLogger(LdapThreePidProvider.class) - - @Autowired - private MatrixConfig mxCfg - - @Override - boolean isEnabled() { - return getCfg().isEnabled() - } - - private String getUidAttribute() { - return getCfg().getAttribute().getUid().getValue(); - } - - @Override - boolean isLocal() { - return true - } - - @Override - int getPriority() { - return 20 - } - - Optional lookup(LdapConnection conn, String medium, String value) { - String uidAttribute = getUidAttribute() - - Optional queryOpt = getCfg().getIdentity().getQuery(medium) - if (!queryOpt.isPresent()) { - log.warn("{} is not a configured 3PID type for LDAP lookup", medium) - return Optional.empty() - } - - String searchQuery = queryOpt.get().replaceAll("%3pid", value) - EntryCursor cursor = conn.search(getCfg().getConn().getBaseDn(), searchQuery, SearchScope.SUBTREE, uidAttribute) - try { - while (cursor.next()) { - Entry entry = cursor.get() - log.info("Found possible match, DN: {}", entry.getDn().getName()) - - Attribute attribute = entry.get(uidAttribute) - if (attribute == null) { - log.info("DN {}: no attribute {}, skpping", entry.getDn(), getCfg().getAttribute()) - continue - } - - String data = attribute.get().toString() - if (data.length() < 1) { - log.info("DN {}: empty attribute {}, skipping", getCfg().getAttribute()) - continue - } - - StringBuilder matrixId = new StringBuilder() - // TODO Should we turn this block into a map of functions? - String uidType = getCfg().getAttribute().getUid().getType() - if (StringUtils.equals(UID, uidType)) { - matrixId.append("@").append(data).append(":").append(mxCfg.getDomain()) - } else if (StringUtils.equals(MATRIX_ID, uidType)) { - matrixId.append(data) - } else { - log.warn("Bind was found but type {} is not supported", uidType) - continue - } - - log.info("DN {} is a valid match", entry.getDn().getName()) - return Optional.of(matrixId.toString()) - } - } catch (CursorLdapReferralException e) { - log.warn("3PID {} is only available via referral, skipping", value) - } finally { - cursor.close() - } - - return Optional.empty() - } - - @Override - Optional find(SingleLookupRequest request) { - log.info("Performing LDAP lookup ${request.getThreePid()} of type ${request.getType()}") - - LdapConnection conn = getConn() - try { - bind(conn) - - Optional mxid = lookup(conn, request.getType(), request.getThreePid()) - if (mxid.isPresent()) { - return Optional.of(new SingleLookupReply(request, mxid.get())); - } - } finally { - conn.close() - } - - log.info("No match found") - return Optional.empty() - } - - @Override - List populate(List mappings) { - log.info("Looking up {} mappings", mappings.size()) - List mappingsFound = new ArrayList<>() - - LdapConnection conn = getConn() - try { - bind(conn) - - for (ThreePidMapping mapping : mappings) { - try { - Optional mxid = lookup(conn, mapping.getMedium(), mapping.getValue()) - if (mxid.isPresent()) { - mapping.setMxid(mxid.get()) - mappingsFound.add(mapping) - } - } catch (IllegalArgumentException e) { - log.warn("{} is not a supported 3PID type for LDAP lookup", mapping.getMedium()) - } - } - } finally { - conn.close() - } - - return mappingsFound - } - -} diff --git a/src/main/groovy/io/kamax/mxisd/config/RecursiveLookupBridgeConfig.groovy b/src/main/groovy/io/kamax/mxisd/config/RecursiveLookupBridgeConfig.groovy deleted file mode 100644 index 9830376..0000000 --- a/src/main/groovy/io/kamax/mxisd/config/RecursiveLookupBridgeConfig.groovy +++ /dev/null @@ -1,83 +0,0 @@ -/* - * mxisd - Matrix Identity Server Daemon - * Copyright (C) 2017 Maxime Dor - * - * https://max.kamax.io/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.kamax.mxisd.config - -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.InitializingBean -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.context.annotation.Configuration - -@Configuration -@ConfigurationProperties(prefix = "lookup.recursive.bridge") -class RecursiveLookupBridgeConfig implements InitializingBean { - - private Logger log = LoggerFactory.getLogger(RecursiveLookupBridgeConfig.class) - - private boolean enabled - private boolean recursiveOnly - private String server - private Map mappings = new HashMap<>() - - boolean getEnabled() { - return enabled - } - - void setEnabled(boolean enabled) { - this.enabled = enabled - } - - boolean getRecursiveOnly() { - return recursiveOnly - } - - void setRecursiveOnly(boolean recursiveOnly) { - this.recursiveOnly = recursiveOnly - } - - String getServer() { - return server - } - - void setServer(String server) { - this.server = server - } - - Map getMappings() { - return mappings - } - - void setMappings(Map mappings) { - this.mappings = mappings - } - - @Override - void afterPropertiesSet() throws Exception { - log.info("--- Bridge integration lookups config ---") - log.info("Enabled: {}", getEnabled()) - if (getEnabled()) { - log.info("Recursive only: {}", getRecursiveOnly()) - log.info("Fallback Server: {}", getServer()) - log.info("Mappings: {}", mappings.size()) - } - } - -} diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy b/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy deleted file mode 100644 index 0f9428d..0000000 --- a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConfig.groovy +++ /dev/null @@ -1,129 +0,0 @@ -/* - * mxisd - Matrix Identity Server Daemon - * Copyright (C) 2017 Maxime Dor - * - * https://max.kamax.io/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.kamax.mxisd.config.ldap - -import groovy.json.JsonOutput -import io.kamax.mxisd.backend.ldap.LdapThreePidProvider -import org.apache.commons.lang.StringUtils -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.context.annotation.Configuration - -import javax.annotation.PostConstruct - -@Configuration -@ConfigurationProperties(prefix = "ldap") -class LdapConfig { - - private Logger log = LoggerFactory.getLogger(LdapConfig.class) - - private boolean enabled - - @Autowired - private LdapConnectionConfig conn - private LdapAttributeConfig attribute - private LdapAuthConfig auth - private LdapIdentityConfig identity - - boolean isEnabled() { - return enabled - } - - void setEnabled(boolean enabled) { - this.enabled = enabled - } - - LdapConnectionConfig getConn() { - return conn - } - - void setConn(LdapConnectionConfig conn) { - this.conn = conn - } - - LdapAttributeConfig getAttribute() { - return attribute - } - - void setAttribute(LdapAttributeConfig attribute) { - this.attribute = attribute - } - - LdapAuthConfig getAuth() { - return auth - } - - void setAuth(LdapAuthConfig auth) { - this.auth = auth - } - - LdapIdentityConfig getIdentity() { - return identity - } - - void setIdentity(LdapIdentityConfig identity) { - this.identity = identity - } - - @PostConstruct - void afterPropertiesSet() { - log.info("--- LDAP Config ---") - log.info("Enabled: {}", isEnabled()) - - if (!isEnabled()) { - return - } - - if (StringUtils.isBlank(conn.getHost())) { - throw new IllegalStateException("LDAP Host must be configured!") - } - - if (1 > conn.getPort() || 65535 < conn.getPort()) { - throw new IllegalStateException("LDAP port is not valid") - } - - if (StringUtils.isBlank(attribute.getUid().getType())) { - throw new IllegalStateException("Attribute UID Type cannot be empty") - } - - - if (StringUtils.isBlank(attribute.getUid().getValue())) { - throw new IllegalStateException("Attribute UID value cannot be empty") - } - - String uidType = attribute.getUid().getType(); - if (!StringUtils.equals(LdapThreePidProvider.UID, uidType) && !StringUtils.equals(LdapThreePidProvider.MATRIX_ID, uidType)) { - throw new IllegalArgumentException("Unsupported LDAP UID type: " + uidType) - } - - log.info("Host: {}", conn.getHost()) - log.info("Port: {}", conn.getPort()) - log.info("Bind DN: {}", conn.getBindDn()) - log.info("Base DN: {}", conn.getBaseDn()) - - log.info("Attribute: {}", JsonOutput.toJson(attribute)) - log.info("Auth: {}", JsonOutput.toJson(auth)) - log.info("Identity: {}", JsonOutput.toJson(identity)) - } - -} diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/MappingController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/MappingController.groovy deleted file mode 100644 index 945a282..0000000 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/MappingController.groovy +++ /dev/null @@ -1,121 +0,0 @@ -/* - * mxisd - Matrix Identity Server Daemon - * Copyright (C) 2017 Maxime Dor - * - * https://max.kamax.io/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.kamax.mxisd.controller.v1 - -import com.google.gson.Gson -import com.google.gson.JsonObject -import groovy.json.JsonOutput -import groovy.json.JsonSlurper -import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson -import io.kamax.mxisd.lookup.* -import io.kamax.mxisd.lookup.strategy.LookupStrategy -import io.kamax.mxisd.signature.SignatureManager -import org.apache.commons.lang.StringUtils -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.MediaType -import org.springframework.web.bind.annotation.CrossOrigin -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController - -import javax.servlet.http.HttpServletRequest - -import static org.springframework.web.bind.annotation.RequestMethod.GET -import static org.springframework.web.bind.annotation.RequestMethod.POST - -@RestController -@CrossOrigin -@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) -class MappingController { - - private Logger log = LoggerFactory.getLogger(MappingController.class) - private JsonSlurper json = new JsonSlurper() - private Gson gson = new Gson() - - @Autowired - private LookupStrategy strategy - - @Autowired - private SignatureManager signMgr - - private void setRequesterInfo(ALookupRequest lookupReq, HttpServletRequest req) { - lookupReq.setRequester(req.getRemoteAddr()) - String xff = req.getHeader("X-FORWARDED-FOR") - lookupReq.setRecursive(StringUtils.isNotBlank(xff)) - if (lookupReq.isRecursive()) { - lookupReq.setRecurseHosts(Arrays.asList(xff.split(","))) - } - - lookupReq.setUserAgent(req.getHeader("USER-AGENT")) - } - - @RequestMapping(value = "/lookup", method = GET) - String lookup(HttpServletRequest request, @RequestParam String medium, @RequestParam String address) { - SingleLookupRequest lookupRequest = new SingleLookupRequest() - setRequesterInfo(lookupRequest, request) - lookupRequest.setType(medium) - lookupRequest.setThreePid(address) - - log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive()) - - Optional lookupOpt = strategy.find(lookupRequest) - if (!lookupOpt.isPresent()) { - log.info("No mapping was found, return empty JSON object") - return "{}" - } - - SingleLookupReply lookup = lookupOpt.get() - if (lookup.isSigned()) { - log.info("Lookup is already signed, sending as-is") - return lookup.getBody(); - } else { - log.info("Lookup is not signed, signing") - JsonObject obj = new Gson().toJsonTree(new SingeLookupReplyJson(lookup)).getAsJsonObject() - obj.add("signatures", signMgr.signMessageGson(gson.toJson(obj))) - - return gson.toJson(obj) - } - } - - @RequestMapping(value = "/bulk_lookup", method = POST) - String bulkLookup(HttpServletRequest request) { - BulkLookupRequest lookupRequest = new BulkLookupRequest() - setRequesterInfo(lookupRequest, request) - log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive()) - - ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(request.getInputStream().getText()) - List mappings = new ArrayList<>() - for (List mappingRaw : input.getThreepids()) { - ThreePidMapping mapping = new ThreePidMapping() - mapping.setMedium(mappingRaw.get(0)) - mapping.setValue(mappingRaw.get(1)) - mappings.add(mapping) - } - lookupRequest.setMappings(mappings) - - ClientBulkLookupAnswer answer = new ClientBulkLookupAnswer() - answer.addAll(strategy.find(lookupRequest)) - return JsonOutput.toJson(answer) - } - -} diff --git a/src/main/groovy/io/kamax/mxisd/key/KeyManager.groovy b/src/main/groovy/io/kamax/mxisd/key/KeyManager.groovy deleted file mode 100644 index 6be9227..0000000 --- a/src/main/groovy/io/kamax/mxisd/key/KeyManager.groovy +++ /dev/null @@ -1,106 +0,0 @@ -/* - * mxisd - Matrix Identity Server Daemon - * Copyright (C) 2017 Maxime Dor - * - * https://max.kamax.io/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.kamax.mxisd.key - -import io.kamax.mxisd.config.KeyConfig -import net.i2p.crypto.eddsa.EdDSAEngine -import net.i2p.crypto.eddsa.EdDSAPrivateKey -import net.i2p.crypto.eddsa.EdDSAPublicKey -import net.i2p.crypto.eddsa.KeyPairGenerator -import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable -import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec -import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec -import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec -import org.apache.commons.io.FileUtils -import org.springframework.beans.factory.InitializingBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component - -import java.nio.charset.StandardCharsets -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.Paths -import java.security.KeyPair -import java.security.MessageDigest -import java.security.PrivateKey - -@Component -class KeyManager implements InitializingBean { - - @Autowired - private KeyConfig keyCfg - - private EdDSAParameterSpec keySpecs - private EdDSAEngine signEngine - private List keys - - @Override - void afterPropertiesSet() throws Exception { - keySpecs = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512) - signEngine = new EdDSAEngine(MessageDigest.getInstance(keySpecs.getHashAlgorithm())) - keys = new ArrayList<>() - - Path privKey = Paths.get(keyCfg.getPath()) - - if (!Files.exists(privKey)) { - KeyPair pair = (new KeyPairGenerator()).generateKeyPair() - String keyEncoded = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded()) - FileUtils.writeStringToFile(privKey.toFile(), keyEncoded, StandardCharsets.ISO_8859_1) - keys.add(pair) - } else { - if (Files.isDirectory(privKey)) { - throw new RuntimeException("Invalid path for private key: ${privKey.toString()}") - } - - if (Files.isReadable(privKey)) { - byte[] seed = Base64.getDecoder().decode(FileUtils.readFileToString(privKey.toFile(), StandardCharsets.ISO_8859_1)) - EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(seed, keySpecs) - EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKeySpec.getA(), keySpecs) - keys.add(new KeyPair(new EdDSAPublicKey(pubKeySpec), new EdDSAPrivateKey(privKeySpec))) - } - } - } - - int getCurrentIndex() { - return 0 - } - - KeyPair getKeys(int index) { - return keys.get(index) - } - - PrivateKey getPrivateKey(int index) { - return getKeys(index).getPrivate() - } - - EdDSAPublicKey getPublicKey(int index) { - return (EdDSAPublicKey) getKeys(index).getPublic() - } - - EdDSAParameterSpec getSpecs() { - return keySpecs - } - - String getPublicKeyBase64(int index) { - return Base64.getEncoder().encodeToString(getPublicKey(index).getAbyte()) - } - -} diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/RemoteIdentityServerFetcher.groovy b/src/main/groovy/io/kamax/mxisd/lookup/provider/RemoteIdentityServerFetcher.groovy deleted file mode 100644 index 5844d9c..0000000 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/RemoteIdentityServerFetcher.groovy +++ /dev/null @@ -1,135 +0,0 @@ -/* - * mxisd - Matrix Identity Server Daemon - * Copyright (C) 2017 Maxime Dor - * - * https://max.kamax.io/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.kamax.mxisd.lookup.provider - -import groovy.json.JsonException -import groovy.json.JsonOutput -import groovy.json.JsonSlurper -import io.kamax.mxisd.controller.v1.ClientBulkLookupRequest -import io.kamax.mxisd.lookup.SingleLookupReply -import io.kamax.mxisd.lookup.SingleLookupRequest -import io.kamax.mxisd.lookup.ThreePidMapping -import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher -import io.kamax.mxisd.matrix.IdentityServerUtils -import org.apache.http.HttpEntity -import org.apache.http.HttpResponse -import org.apache.http.client.HttpClient -import org.apache.http.client.entity.EntityBuilder -import org.apache.http.client.methods.HttpPost -import org.apache.http.entity.ContentType -import org.apache.http.impl.client.HttpClients -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.context.annotation.Lazy -import org.springframework.context.annotation.Scope -import org.springframework.stereotype.Component - -@Component -@Scope("prototype") -@Lazy -public class RemoteIdentityServerFetcher implements IRemoteIdentityServerFetcher { - - private Logger log = LoggerFactory.getLogger(RemoteIdentityServerFetcher.class) - - private JsonSlurper json = new JsonSlurper() - - @Override - boolean isUsable(String remote) { - return IdentityServerUtils.isUsable(remote) - } - - @Override - Optional find(String remote, SingleLookupRequest request) { - log.info("Looking up {} 3PID {} using {}", request.getType(), request.getThreePid(), remote) - - HttpURLConnection rootSrvConn = (HttpURLConnection) new URL( - "${remote}/_matrix/identity/api/v1/lookup?medium=${request.getType()}&address=${request.getThreePid()}" - ).openConnection() - - try { - String outputRaw = rootSrvConn.getInputStream().getText() - def output = json.parseText(outputRaw) - if (output['address']) { - log.info("Found 3PID mapping: {}", output) - - return Optional.of(SingleLookupReply.fromRecursive(request, outputRaw)) - } - - log.info("Empty 3PID mapping from {}", remote) - return Optional.empty() - } catch (IOException e) { - log.warn("Error looking up 3PID mapping {}: {}", request.getThreePid(), e.getMessage()) - return Optional.empty() - } catch (JsonException e) { - log.warn("Invalid JSON answer from {}", remote) - return Optional.empty() - } - } - - @Override - List find(String remote, List mappings) { - List mappingsFound = new ArrayList<>() - - ClientBulkLookupRequest mappingRequest = new ClientBulkLookupRequest() - mappingRequest.setMappings(mappings) - - String url = "${remote}/_matrix/identity/api/v1/bulk_lookup" - HttpClient client = HttpClients.createDefault() - try { - HttpPost request = new HttpPost(url) - request.setEntity( - EntityBuilder.create() - .setText(JsonOutput.toJson(mappingRequest)) - .setContentType(ContentType.APPLICATION_JSON) - .build() - ) - - HttpResponse response = client.execute(request) - try { - if (response.getStatusLine().getStatusCode() != 200) { - log.info("Could not perform lookup at {} due to HTTP return code: {}", url, response.getStatusLine().getStatusCode()) - return mappingsFound - } - - HttpEntity entity = response.getEntity() - if (entity != null) { - ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(entity.getContent().getText()) - for (List mappingRaw : input.getThreepids()) { - ThreePidMapping mapping = new ThreePidMapping() - mapping.setMedium(mappingRaw.get(0)) - mapping.setValue(mappingRaw.get(1)) - mapping.setMxid(mappingRaw.get(2)) - mappingsFound.add(mapping) - } - } else { - log.info("HTTP response from {} was empty", remote) - } - - return mappingsFound - } finally { - response.close() - } - } finally { - client.close() - } - } - -} diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy deleted file mode 100644 index 048dd21..0000000 --- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy +++ /dev/null @@ -1,210 +0,0 @@ -/* - * mxisd - Matrix Identity Server Daemon - * Copyright (C) 2017 Maxime Dor - * - * https://max.kamax.io/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.kamax.mxisd.lookup.strategy - -import edazdarevic.commons.net.CIDRUtils -import io.kamax.mxisd.config.RecursiveLookupConfig -import io.kamax.mxisd.lookup.* -import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher -import io.kamax.mxisd.lookup.provider.IThreePidProvider -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.InitializingBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component - -import java.util.function.Predicate -import java.util.stream.Collectors - -@Component -class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBean { - - private Logger log = LoggerFactory.getLogger(RecursivePriorityLookupStrategy.class) - - @Autowired - private RecursiveLookupConfig recursiveCfg - - @Autowired - private List providers - - @Autowired - private IBridgeFetcher bridge - - private List allowedCidr = new ArrayList<>() - - @Override - void afterPropertiesSet() throws Exception { - log.info("Found ${providers.size()} providers") - - providers.sort(new Comparator() { - - @Override - int compare(IThreePidProvider o1, IThreePidProvider o2) { - return Integer.compare(o2.getPriority(), o1.getPriority()) - } - - }) - - log.info("Recursive lookup enabled: {}", recursiveCfg.isEnabled()) - for (String cidr : recursiveCfg.getAllowedCidr()) { - log.info("{} is allowed for recursion", cidr) - allowedCidr.add(new CIDRUtils(cidr)) - } - } - - boolean isAllowedForRecursive(String source) { - boolean canRecurse = false - - if (recursiveCfg.isEnabled()) { - log.debug("Checking {} CIDRs for recursion", allowedCidr.size()) - for (CIDRUtils cidr : allowedCidr) { - if (cidr.isInRange(source)) { - log.debug("{} is in range {}, allowing recursion", source, cidr.getNetworkAddress()) - canRecurse = true - break - } else { - log.debug("{} is not in range {}", source, cidr.getNetworkAddress()) - } - } - } - - return canRecurse - } - - List listUsableProviders(ALookupRequest request) { - return listUsableProviders(request, false); - } - - List listUsableProviders(ALookupRequest request, boolean forceRecursive) { - List usableProviders = new ArrayList<>() - - boolean canRecurse = forceRecursive || isAllowedForRecursive(request.getRequester()) - - log.info("Host {} allowed for recursion: {}", request.getRequester(), canRecurse) - for (IThreePidProvider provider : providers) { - if (provider.isEnabled() && (provider.isLocal() || canRecurse || forceRecursive)) { - usableProviders.add(provider) - } - } - - return usableProviders - } - - @Override - List getLocalProviders() { - return providers.stream().filter(new Predicate() { - @Override - boolean test(IThreePidProvider iThreePidProvider) { - return iThreePidProvider.isEnabled() && iThreePidProvider.isLocal() - } - }).collect(Collectors.toList()) - } - - List getRemoteProviders() { - return providers.stream().filter(new Predicate() { - @Override - boolean test(IThreePidProvider iThreePidProvider) { - return iThreePidProvider.isEnabled() && !iThreePidProvider.isLocal() - } - }).collect(Collectors.toList()) - } - - private static SingleLookupRequest build(String medium, String address) { - SingleLookupRequest req = new SingleLookupRequest(); - req.setType(medium) - req.setThreePid(address) - req.setRequester("Internal") - return req; - } - - @Override - Optional find(String medium, String address, boolean recursive) { - return find(build(medium, address), recursive) - } - - @Override - Optional findLocal(String medium, String address) { - return find(build(medium, address), getLocalProviders()) - } - - @Override - Optional findRemote(String medium, String address) { - return find(build(medium, address), getRemoteProviders()) - } - - Optional find(SingleLookupRequest request, boolean forceRecursive) { - return find(request, listUsableProviders(request, forceRecursive)); - } - - Optional find(SingleLookupRequest request, List providers) { - for (IThreePidProvider provider : providers) { - Optional lookupDataOpt = provider.find(request) - if (lookupDataOpt.isPresent()) { - return lookupDataOpt - } - } - - if ( - recursiveCfg.getBridge() != null && - recursiveCfg.getBridge().getEnabled() && - (!recursiveCfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester())) - ) { - log.info("Using bridge failover for lookup") - return bridge.find(request) - } - - return Optional.empty() - } - - @Override - Optional find(SingleLookupRequest request) { - return find(request, false) - } - - @Override - Optional findRecursive(SingleLookupRequest request) { - return find(request, true) - } - - @Override - List find(BulkLookupRequest request) { - List mapToDo = new ArrayList<>(request.getMappings()) - List mapFoundAll = new ArrayList<>() - - for (IThreePidProvider provider : listUsableProviders(request)) { - if (mapToDo.isEmpty()) { - log.info("No more mappings to lookup") - break - } else { - log.info("{} mappings remaining overall", mapToDo.size()) - } - - log.info("Using provider {} for remaining mappings", provider.getClass().getSimpleName()) - List mapFound = provider.populate(mapToDo) - log.info("Provider {} returned {} mappings", provider.getClass().getSimpleName(), mapFound.size()) - mapFoundAll.addAll(mapFound) - mapToDo.removeAll(mapFound) - } - - return mapFoundAll - } - -} diff --git a/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy b/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy deleted file mode 100644 index 84c4ad9..0000000 --- a/src/main/groovy/io/kamax/mxisd/signature/SignatureManager.groovy +++ /dev/null @@ -1,78 +0,0 @@ -/* - * mxisd - Matrix Identity Server Daemon - * Copyright (C) 2017 Maxime Dor - * - * https://max.kamax.io/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package io.kamax.mxisd.signature - -import com.google.gson.JsonObject -import io.kamax.mxisd.config.ServerConfig -import io.kamax.mxisd.key.KeyManager -import net.i2p.crypto.eddsa.EdDSAEngine -import org.json.JSONObject -import org.springframework.beans.factory.InitializingBean -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component - -import java.security.MessageDigest - -@Component -class SignatureManager implements InitializingBean { - - @Autowired - private KeyManager keyMgr - - @Autowired - private ServerConfig srvCfg - - private EdDSAEngine signEngine - - private String sign(String message) { - byte[] signRaw = signEngine.signOneShot(message.getBytes()) - return Base64.getEncoder().encodeToString(signRaw) - } - - JSONObject signMessageJson(String message) { - String sign = sign(message) - - JSONObject keySignature = new JSONObject() - keySignature.put("ed25519:${keyMgr.getCurrentIndex()}", sign) - JSONObject signature = new JSONObject() - signature.put("${srvCfg.getName()}", keySignature) - - return signature - } - - JsonObject signMessageGson(String message) { - String sign = sign(message) - - JsonObject keySignature = new JsonObject() - keySignature.addProperty("ed25519:${keyMgr.getCurrentIndex()}", sign) - JsonObject signature = new JsonObject() - signature.add("${srvCfg.getName()}", keySignature); - - return signature - } - - @Override - void afterPropertiesSet() throws Exception { - signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getSpecs().getHashAlgorithm())) - signEngine.initSign(keyMgr.getPrivateKey(keyMgr.getCurrentIndex())) - } - -} diff --git a/src/main/groovy/io/kamax/mxisd/MatrixIdentityServerApplication.groovy b/src/main/java/io/kamax/mxisd/MatrixIdentityServerApplication.java similarity index 87% rename from src/main/groovy/io/kamax/mxisd/MatrixIdentityServerApplication.groovy rename to src/main/java/io/kamax/mxisd/MatrixIdentityServerApplication.java index a83015b..4de33e6 100644 --- a/src/main/groovy/io/kamax/mxisd/MatrixIdentityServerApplication.groovy +++ b/src/main/java/io/kamax/mxisd/MatrixIdentityServerApplication.java @@ -18,16 +18,16 @@ * along with this program. If not, see . */ -package io.kamax.mxisd +package io.kamax.mxisd; -import org.springframework.boot.SpringApplication -import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication class MatrixIdentityServerApplication { - static void main(String[] args) throws Exception { - SpringApplication.run(MatrixIdentityServerApplication.class, args) + public static void main(String[] args) { + SpringApplication.run(MatrixIdentityServerApplication.class, args); } } diff --git a/src/main/groovy/io/kamax/mxisd/ThreePid.java b/src/main/java/io/kamax/mxisd/ThreePid.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/ThreePid.java rename to src/main/java/io/kamax/mxisd/ThreePid.java diff --git a/src/main/groovy/io/kamax/mxisd/UserID.java b/src/main/java/io/kamax/mxisd/UserID.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/UserID.java rename to src/main/java/io/kamax/mxisd/UserID.java diff --git a/src/main/groovy/io/kamax/mxisd/UserIdType.java b/src/main/java/io/kamax/mxisd/UserIdType.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/UserIdType.java rename to src/main/java/io/kamax/mxisd/UserIdType.java diff --git a/src/main/groovy/io/kamax/mxisd/auth/AuthManager.java b/src/main/java/io/kamax/mxisd/auth/AuthManager.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/auth/AuthManager.java rename to src/main/java/io/kamax/mxisd/auth/AuthManager.java diff --git a/src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java b/src/main/java/io/kamax/mxisd/auth/UserAuthResult.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/auth/UserAuthResult.java rename to src/main/java/io/kamax/mxisd/auth/UserAuthResult.java diff --git a/src/main/groovy/io/kamax/mxisd/auth/provider/AuthenticatorProvider.java b/src/main/java/io/kamax/mxisd/auth/provider/AuthenticatorProvider.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/auth/provider/AuthenticatorProvider.java rename to src/main/java/io/kamax/mxisd/auth/provider/AuthenticatorProvider.java diff --git a/src/main/groovy/io/kamax/mxisd/auth/provider/BackendAuthResult.java b/src/main/java/io/kamax/mxisd/auth/provider/BackendAuthResult.java similarity index 86% rename from src/main/groovy/io/kamax/mxisd/auth/provider/BackendAuthResult.java rename to src/main/java/io/kamax/mxisd/auth/provider/BackendAuthResult.java index 79c1036..7eb658f 100644 --- a/src/main/groovy/io/kamax/mxisd/auth/provider/BackendAuthResult.java +++ b/src/main/java/io/kamax/mxisd/auth/provider/BackendAuthResult.java @@ -49,20 +49,27 @@ public class BackendAuthResult { return r; } + public void fail() { + success = false; + } + 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; - + r.succeed(id, type, displayName); return r; } + public void succeed(String id, String type, String displayName) { + this.success = true; + this.id = new UserID(type, id); + this.profile = new BackendAuthProfile(); + this.profile.displayName = displayName; + } + private Boolean success; private UserID id; private BackendAuthProfile profile = new BackendAuthProfile(); diff --git a/src/main/java/io/kamax/mxisd/backend/firebase/GoogleFirebaseAuthenticator.java b/src/main/java/io/kamax/mxisd/backend/firebase/GoogleFirebaseAuthenticator.java new file mode 100644 index 0000000..00bcd66 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/backend/firebase/GoogleFirebaseAuthenticator.java @@ -0,0 +1,177 @@ +/* + * 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.backend.firebase; + +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseCredential; +import com.google.firebase.auth.FirebaseCredentials; +import io.kamax.matrix.ThreePidMedium; +import io.kamax.matrix._MatrixID; +import io.kamax.mxisd.ThreePid; +import io.kamax.mxisd.UserIdType; +import io.kamax.mxisd.auth.provider.AuthenticatorProvider; +import io.kamax.mxisd.auth.provider.BackendAuthResult; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class GoogleFirebaseAuthenticator implements AuthenticatorProvider { + + private Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class); + + private boolean isEnabled; + private FirebaseApp fbApp; + private FirebaseAuth fbAuth; + + private void waitOnLatch(BackendAuthResult result, CountDownLatch l, String purpose) { + try { + l.await(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.warn("Interrupted while waiting for " + purpose); + result.fail(); + } + } + + public GoogleFirebaseAuthenticator(boolean isEnabled) { + this.isEnabled = isEnabled; + } + + public GoogleFirebaseAuthenticator(String credsPath, String db) { + this(true); + try { + fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "AuthenticationProvider"); + fbAuth = FirebaseAuth.getInstance(fbApp); + + log.info("Google Firebase Authentication is ready"); + } catch (IOException e) { + throw new RuntimeException("Error when initializing Firebase", e); + } + } + + private FirebaseCredential getCreds(String credsPath) throws IOException { + if (StringUtils.isNotBlank(credsPath)) { + return FirebaseCredentials.fromCertificate(new FileInputStream(credsPath)); + } else { + return FirebaseCredentials.applicationDefault(); + } + } + + private FirebaseOptions getOpts(String credsPath, String db) throws IOException { + if (StringUtils.isBlank(db)) { + throw new IllegalArgumentException("Firebase database is not configured"); + } + + return new FirebaseOptions.Builder() + .setCredential(getCreds(credsPath)) + .setDatabaseUrl(db) + .build(); + } + + @Override + public boolean isEnabled() { + return isEnabled; + } + + private void waitOnLatch(CountDownLatch l) { + try { + l.await(30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.warn("Interrupted while waiting for Firebase auth check"); + } + } + + @Override + public BackendAuthResult authenticate(_MatrixID mxid, String password) { + if (!isEnabled()) { + throw new IllegalStateException(); + } + + log.info("Trying to authenticate {}", mxid); + + final BackendAuthResult result = BackendAuthResult.failure(); + + String localpart = mxid.getLocalPart(); + CountDownLatch l = new CountDownLatch(1); + fbAuth.verifyIdToken(password).addOnSuccessListener(token -> { + try { + if (!StringUtils.equals(localpart, token.getUid())) { + log.info("Failure to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", mxid, localpart, token.getUid()); + result.fail(); + return; + } + + result.succeed(mxid.getId(), UserIdType.MatrixID.getId(), token.getName()); + log.info("{} was successfully authenticated", mxid); + log.info("Fetching profile for {}", mxid); + CountDownLatch userRecordLatch = new CountDownLatch(1); + fbAuth.getUser(token.getUid()).addOnSuccessListener(user -> { + try { + if (StringUtils.isNotBlank(user.getEmail())) { + result.withThreePid(new ThreePid(ThreePidMedium.Email.getId(), user.getEmail())); + } + + if (StringUtils.isNotBlank(user.getPhoneNumber())) { + result.withThreePid(new ThreePid(ThreePidMedium.PhoneNumber.getId(), user.getPhoneNumber())); + } + + } finally { + userRecordLatch.countDown(); + } + }).addOnFailureListener(e -> { + try { + log.warn("Unable to fetch Firebase user profile for {}", mxid); + result.fail(); + } finally { + userRecordLatch.countDown(); + } + }); + + waitOnLatch(result, userRecordLatch, "Firebase user profile"); + } finally { + l.countDown(); + } + }).addOnFailureListener(e -> { + try { + if (e instanceof IllegalArgumentException) { + log.info("Failure to authenticate {}: invalid firebase token", mxid); + } else { + log.info("Failure to authenticate {}: {}", mxid, e.getMessage(), e); + log.info("Exception", e); + } + + result.fail(); + } finally { + l.countDown(); + } + }); + + waitOnLatch(result, l, "Firebase auth check"); + return result; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/backend/firebase/GoogleFirebaseProvider.groovy b/src/main/java/io/kamax/mxisd/backend/firebase/GoogleFirebaseProvider.java similarity index 59% rename from src/main/groovy/io/kamax/mxisd/backend/firebase/GoogleFirebaseProvider.groovy rename to src/main/java/io/kamax/mxisd/backend/firebase/GoogleFirebaseProvider.java index 95eac0a..e37ff21 100644 --- a/src/main/groovy/io/kamax/mxisd/backend/firebase/GoogleFirebaseProvider.groovy +++ b/src/main/java/io/kamax/mxisd/backend/firebase/GoogleFirebaseProvider.java @@ -18,40 +18,40 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.backend.firebase +package io.kamax.mxisd.backend.firebase; -import com.google.firebase.FirebaseApp -import com.google.firebase.FirebaseOptions -import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.auth.FirebaseCredential -import com.google.firebase.auth.FirebaseCredentials -import com.google.firebase.auth.UserRecord -import com.google.firebase.internal.NonNull -import com.google.firebase.tasks.OnFailureListener -import com.google.firebase.tasks.OnSuccessListener -import io.kamax.matrix.ThreePidMedium -import io.kamax.mxisd.lookup.SingleLookupReply -import io.kamax.mxisd.lookup.SingleLookupRequest -import io.kamax.mxisd.lookup.ThreePidMapping -import io.kamax.mxisd.lookup.provider.IThreePidProvider -import org.apache.commons.lang.StringUtils -import org.slf4j.Logger -import org.slf4j.LoggerFactory +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseCredential; +import com.google.firebase.auth.FirebaseCredentials; +import com.google.firebase.auth.UserRecord; +import com.google.firebase.tasks.OnFailureListener; +import com.google.firebase.tasks.OnSuccessListener; +import io.kamax.matrix.MatrixID; +import io.kamax.matrix.ThreePidMedium; +import io.kamax.mxisd.lookup.SingleLookupReply; +import io.kamax.mxisd.lookup.SingleLookupRequest; +import io.kamax.mxisd.lookup.ThreePidMapping; +import io.kamax.mxisd.lookup.provider.IThreePidProvider; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import java.util.function.Consumer -import java.util.regex.Pattern +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; public class GoogleFirebaseProvider implements IThreePidProvider { private Logger log = LoggerFactory.getLogger(GoogleFirebaseProvider.class); - private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)"); - private boolean isEnabled; private String domain; - private FirebaseApp fbApp; private FirebaseAuth fbAuth; public GoogleFirebaseProvider(boolean isEnabled) { @@ -61,8 +61,9 @@ public class GoogleFirebaseProvider implements IThreePidProvider { public GoogleFirebaseProvider(String credsPath, String db, String domain) { this(true); this.domain = domain; + try { - fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "ThreePidProvider"); + FirebaseApp fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "ThreePidProvider"); fbAuth = FirebaseAuth.getInstance(fbApp); log.info("Google Firebase Authentication is ready"); @@ -91,7 +92,7 @@ public class GoogleFirebaseProvider implements IThreePidProvider { } private String getMxid(UserRecord record) { - return "@${record.getUid()}:${domain}"; + return new MatrixID(record.getUid(), domain).getId(); } @Override @@ -118,71 +119,59 @@ public class GoogleFirebaseProvider implements IThreePidProvider { } private Optional findInternal(String medium, String address) { - UserRecord r; + final UserRecord[] r = new UserRecord[1]; CountDownLatch l = new CountDownLatch(1); - OnSuccessListener success = new OnSuccessListener() { - @Override - void onSuccess(UserRecord result) { - log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid()) - r = result; - l.countDown() - } + OnSuccessListener success = result -> { + log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid()); + r[0] = result; + l.countDown(); }; - OnFailureListener failure = new OnFailureListener() { - @Override - void onFailure(@NonNull Exception e) { - log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage()) - r = null; - l.countDown() - } + OnFailureListener failure = e -> { + log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage()); + r[0] = null; + l.countDown(); }; if (ThreePidMedium.Email.is(medium)) { - log.info("Performing E-mail 3PID lookup for {}", address) + log.info("Performing E-mail 3PID lookup for {}", address); fbAuth.getUserByEmail(address) .addOnSuccessListener(success) .addOnFailureListener(failure); waitOnLatch(l); } else if (ThreePidMedium.PhoneNumber.is(medium)) { - log.info("Performing msisdn 3PID lookup for {}", address) + log.info("Performing msisdn 3PID lookup for {}", address); fbAuth.getUserByPhoneNumber(address) .addOnSuccessListener(success) .addOnFailureListener(failure); waitOnLatch(l); } else { log.info("{} is not a supported 3PID medium", medium); - r = null; + r[0] = null; } - return Optional.ofNullable(r); + return Optional.ofNullable(r[0]); } @Override public Optional find(SingleLookupRequest request) { - Optional urOpt = findInternal(request.getType(), request.getThreePid()) - if (urOpt.isPresent()) { - return Optional.of(new SingleLookupReply(request, getMxid(urOpt.get()))); - } + Optional urOpt = findInternal(request.getType(), request.getThreePid()); + return urOpt.map(userRecord -> new SingleLookupReply(request, getMxid(userRecord))); - return Optional.empty(); } @Override public List populate(List mappings) { List results = new ArrayList<>(); - mappings.parallelStream().forEach(new Consumer() { - @Override - void accept(ThreePidMapping o) { - Optional urOpt = findInternal(o.getMedium(), o.getValue()); - if (urOpt.isPresent()) { - ThreePidMapping result = new ThreePidMapping(); - result.setMedium(o.getMedium()) - result.setValue(o.getValue()) - result.setMxid(getMxid(urOpt.get())) - results.add(result) - } + mappings.parallelStream().forEach(o -> { + Optional urOpt = findInternal(o.getMedium(), o.getValue()); + if (urOpt.isPresent()) { + ThreePidMapping result = new ThreePidMapping(); + result.setMedium(o.getMedium()); + result.setValue(o.getValue()); + result.setMxid(getMxid(urOpt.get())); + results.add(result); } }); return results; diff --git a/src/main/groovy/io/kamax/mxisd/backend/ldap/LdapAuthProvider.java b/src/main/java/io/kamax/mxisd/backend/ldap/LdapAuthProvider.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/backend/ldap/LdapAuthProvider.java rename to src/main/java/io/kamax/mxisd/backend/ldap/LdapAuthProvider.java diff --git a/src/main/groovy/io/kamax/mxisd/backend/ldap/LdapGenericBackend.java b/src/main/java/io/kamax/mxisd/backend/ldap/LdapGenericBackend.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/backend/ldap/LdapGenericBackend.java rename to src/main/java/io/kamax/mxisd/backend/ldap/LdapGenericBackend.java diff --git a/src/main/java/io/kamax/mxisd/backend/ldap/LdapThreePidProvider.java b/src/main/java/io/kamax/mxisd/backend/ldap/LdapThreePidProvider.java new file mode 100644 index 0000000..5dbf139 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/backend/ldap/LdapThreePidProvider.java @@ -0,0 +1,174 @@ +/* + * 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.backend.ldap; + +import io.kamax.mxisd.config.MatrixConfig; +import io.kamax.mxisd.exception.InternalServerError; +import io.kamax.mxisd.lookup.SingleLookupReply; +import io.kamax.mxisd.lookup.SingleLookupRequest; +import io.kamax.mxisd.lookup.ThreePidMapping; +import io.kamax.mxisd.lookup.provider.IThreePidProvider; +import org.apache.commons.lang.StringUtils; +import org.apache.directory.api.ldap.model.cursor.CursorException; +import org.apache.directory.api.ldap.model.cursor.CursorLdapReferralException; +import org.apache.directory.api.ldap.model.cursor.EntryCursor; +import org.apache.directory.api.ldap.model.entry.Attribute; +import org.apache.directory.api.ldap.model.entry.Entry; +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.message.SearchScope; +import org.apache.directory.ldap.client.api.LdapConnection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Component +public class LdapThreePidProvider extends LdapGenericBackend implements IThreePidProvider { + + public static final String UID = "uid"; + public static final String MATRIX_ID = "mxid"; + + private Logger log = LoggerFactory.getLogger(LdapThreePidProvider.class); + + @Autowired + private MatrixConfig mxCfg; + + @Override + public boolean isEnabled() { + return getCfg().isEnabled(); + } + + private String getUidAttribute() { + return getCfg().getAttribute().getUid().getValue(); + } + + @Override + public boolean isLocal() { + return true; + } + + @Override + public int getPriority() { + return 20; + } + + private Optional lookup(LdapConnection conn, String medium, String value) { + String uidAttribute = getUidAttribute(); + + Optional 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); + try (EntryCursor cursor = conn.search(getCfg().getConn().getBaseDn(), searchQuery, SearchScope.SUBTREE, uidAttribute)) { + while (cursor.next()) { + Entry entry = cursor.get(); + log.info("Found possible match, DN: {}", entry.getDn().getName()); + + Attribute attribute = entry.get(uidAttribute); + if (attribute == null) { + log.info("DN {}: no attribute {}, skpping", entry.getDn(), getCfg().getAttribute()); + continue; + } + + String data = attribute.get().toString(); + if (data.length() < 1) { + log.info("DN {}: empty attribute {}, skipping", getCfg().getAttribute()); + continue; + } + + StringBuilder matrixId = new StringBuilder(); + // TODO Should we turn this block into a map of functions? + String uidType = getCfg().getAttribute().getUid().getType(); + if (StringUtils.equals(UID, uidType)) { + matrixId.append("@").append(data).append(":").append(mxCfg.getDomain()); + } else if (StringUtils.equals(MATRIX_ID, uidType)) { + matrixId.append(data); + } else { + log.warn("Bind was found but type {} is not supported", uidType); + continue; + } + + log.info("DN {} is a valid match", entry.getDn().getName()); + return Optional.of(matrixId.toString()); + } + } catch (CursorLdapReferralException e) { + log.warn("3PID {} is only available via referral, skipping", value); + } catch (IOException | LdapException | CursorException e) { + throw new InternalServerError(e); + } + + return Optional.empty(); + } + + @Override + public Optional find(SingleLookupRequest request) { + log.info("Performing LDAP lookup ${request.getThreePid()} of type ${request.getType()}"); + + try (LdapConnection conn = getConn()) { + bind(conn); + + Optional mxid = lookup(conn, request.getType(), request.getThreePid()); + if (mxid.isPresent()) { + return Optional.of(new SingleLookupReply(request, mxid.get())); + } + } catch (LdapException | IOException e) { + throw new InternalServerError(e); + } + + log.info("No match found"); + return Optional.empty(); + } + + @Override + public List populate(List mappings) { + log.info("Looking up {} mappings", mappings.size()); + List mappingsFound = new ArrayList<>(); + + try (LdapConnection conn = getConn()) { + bind(conn); + + for (ThreePidMapping mapping : mappings) { + try { + Optional mxid = lookup(conn, mapping.getMedium(), mapping.getValue()); + if (mxid.isPresent()) { + mapping.setMxid(mxid.get()); + mappingsFound.add(mapping); + } + } catch (IllegalArgumentException e) { + log.warn("{} is not a supported 3PID type for LDAP lookup", mapping.getMedium()); + } + } + } catch (LdapException | IOException e) { + throw new InternalServerError(e); + } + + return mappingsFound; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/backend/rest/LookupBulkResponseJson.java b/src/main/java/io/kamax/mxisd/backend/rest/LookupBulkResponseJson.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/backend/rest/LookupBulkResponseJson.java rename to src/main/java/io/kamax/mxisd/backend/rest/LookupBulkResponseJson.java diff --git a/src/main/groovy/io/kamax/mxisd/backend/rest/LookupSingleRequestJson.java b/src/main/java/io/kamax/mxisd/backend/rest/LookupSingleRequestJson.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/backend/rest/LookupSingleRequestJson.java rename to src/main/java/io/kamax/mxisd/backend/rest/LookupSingleRequestJson.java diff --git a/src/main/groovy/io/kamax/mxisd/backend/rest/LookupSingleResponseJson.java b/src/main/java/io/kamax/mxisd/backend/rest/LookupSingleResponseJson.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/backend/rest/LookupSingleResponseJson.java rename to src/main/java/io/kamax/mxisd/backend/rest/LookupSingleResponseJson.java diff --git a/src/main/groovy/io/kamax/mxisd/backend/rest/RestAuthProvider.java b/src/main/java/io/kamax/mxisd/backend/rest/RestAuthProvider.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/backend/rest/RestAuthProvider.java rename to src/main/java/io/kamax/mxisd/backend/rest/RestAuthProvider.java diff --git a/src/main/groovy/io/kamax/mxisd/backend/rest/RestAuthRequestJson.java b/src/main/java/io/kamax/mxisd/backend/rest/RestAuthRequestJson.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/backend/rest/RestAuthRequestJson.java rename to src/main/java/io/kamax/mxisd/backend/rest/RestAuthRequestJson.java diff --git a/src/main/groovy/io/kamax/mxisd/backend/rest/RestProvider.java b/src/main/java/io/kamax/mxisd/backend/rest/RestProvider.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/backend/rest/RestProvider.java rename to src/main/java/io/kamax/mxisd/backend/rest/RestProvider.java diff --git a/src/main/groovy/io/kamax/mxisd/backend/rest/RestThreePidProvider.java b/src/main/java/io/kamax/mxisd/backend/rest/RestThreePidProvider.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/backend/rest/RestThreePidProvider.java rename to src/main/java/io/kamax/mxisd/backend/rest/RestThreePidProvider.java diff --git a/src/main/groovy/io/kamax/mxisd/backend/sql/SqlAuthProvider.java b/src/main/java/io/kamax/mxisd/backend/sql/SqlAuthProvider.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/backend/sql/SqlAuthProvider.java rename to src/main/java/io/kamax/mxisd/backend/sql/SqlAuthProvider.java diff --git a/src/main/groovy/io/kamax/mxisd/backend/sql/SqlThreePidProvider.java b/src/main/java/io/kamax/mxisd/backend/sql/SqlThreePidProvider.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/backend/sql/SqlThreePidProvider.java rename to src/main/java/io/kamax/mxisd/backend/sql/SqlThreePidProvider.java diff --git a/src/main/groovy/io/kamax/mxisd/config/DnsOverwrite.java b/src/main/java/io/kamax/mxisd/config/DnsOverwrite.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/DnsOverwrite.java rename to src/main/java/io/kamax/mxisd/config/DnsOverwrite.java diff --git a/src/main/groovy/io/kamax/mxisd/config/DnsOverwriteEntry.java b/src/main/java/io/kamax/mxisd/config/DnsOverwriteEntry.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/DnsOverwriteEntry.java rename to src/main/java/io/kamax/mxisd/config/DnsOverwriteEntry.java diff --git a/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java b/src/main/java/io/kamax/mxisd/config/FirebaseConfig.java similarity index 99% rename from src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java rename to src/main/java/io/kamax/mxisd/config/FirebaseConfig.java index 6dc522d..03408be 100644 --- a/src/main/groovy/io/kamax/mxisd/config/FirebaseConfig.java +++ b/src/main/java/io/kamax/mxisd/config/FirebaseConfig.java @@ -85,7 +85,7 @@ public class FirebaseConfig { if (!enabled) { return new GoogleFirebaseAuthenticator(false); } else { - return new GoogleFirebaseAuthenticator(credentials, database, mxCfg.getDomain()); + return new GoogleFirebaseAuthenticator(credentials, database); } } diff --git a/src/main/groovy/io/kamax/mxisd/config/ForwardConfig.groovy b/src/main/java/io/kamax/mxisd/config/ForwardConfig.java similarity index 70% rename from src/main/groovy/io/kamax/mxisd/config/ForwardConfig.groovy rename to src/main/java/io/kamax/mxisd/config/ForwardConfig.java index 2134c3a..77cc491 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ForwardConfig.groovy +++ b/src/main/java/io/kamax/mxisd/config/ForwardConfig.java @@ -18,23 +18,26 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.config +package io.kamax.mxisd.config; -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.context.annotation.Configuration +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.ArrayList; +import java.util.List; @Configuration @ConfigurationProperties(prefix = "forward") -class ForwardConfig { +public class ForwardConfig { - private List servers = new ArrayList<>() + private List servers = new ArrayList<>(); - List getServers() { - return servers + public List getServers() { + return servers; } - void setServers(List servers) { - this.servers = servers + public void setServers(List servers) { + this.servers = servers; } } diff --git a/src/main/groovy/io/kamax/mxisd/config/KeyConfig.groovy b/src/main/java/io/kamax/mxisd/config/KeyConfig.java similarity index 64% rename from src/main/groovy/io/kamax/mxisd/config/KeyConfig.groovy rename to src/main/java/io/kamax/mxisd/config/KeyConfig.java index 70e6fb1..3874991 100644 --- a/src/main/groovy/io/kamax/mxisd/config/KeyConfig.groovy +++ b/src/main/java/io/kamax/mxisd/config/KeyConfig.java @@ -18,32 +18,33 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.config +package io.kamax.mxisd.config; -import io.kamax.mxisd.exception.ConfigurationException -import org.apache.commons.lang.StringUtils -import org.springframework.beans.factory.InitializingBean -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.context.annotation.Configuration +import io.kamax.mxisd.exception.ConfigurationException; +import org.apache.commons.lang.StringUtils; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; @Configuration @ConfigurationProperties(prefix = "key") -class KeyConfig implements InitializingBean { +public class KeyConfig { - private String path + private String path; - void setPath(String path) { - this.path = path + public void setPath(String path) { + this.path = path; } - String getPath() { - return path + public String getPath() { + return path; } - @Override - void afterPropertiesSet() throws Exception { + @PostConstruct + public void build() { if (StringUtils.isBlank(getPath())) { - throw new ConfigurationException("key.path") + throw new ConfigurationException("key.path"); } } diff --git a/src/main/groovy/io/kamax/mxisd/config/MatrixConfig.java b/src/main/java/io/kamax/mxisd/config/MatrixConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/MatrixConfig.java rename to src/main/java/io/kamax/mxisd/config/MatrixConfig.java diff --git a/src/main/java/io/kamax/mxisd/config/RecursiveLookupBridgeConfig.java b/src/main/java/io/kamax/mxisd/config/RecursiveLookupBridgeConfig.java new file mode 100644 index 0000000..b14951b --- /dev/null +++ b/src/main/java/io/kamax/mxisd/config/RecursiveLookupBridgeConfig.java @@ -0,0 +1,86 @@ +/* + * 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; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.util.HashMap; +import java.util.Map; + +@Configuration +@ConfigurationProperties(prefix = "lookup.recursive.bridge") +public class RecursiveLookupBridgeConfig { + + private Logger log = LoggerFactory.getLogger(RecursiveLookupBridgeConfig.class); + + private boolean enabled; + private boolean recursiveOnly; + private String server; + private Map mappings = new HashMap<>(); + + public boolean getEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean getRecursiveOnly() { + return recursiveOnly; + } + + public void setRecursiveOnly(boolean recursiveOnly) { + this.recursiveOnly = recursiveOnly; + } + + public String getServer() { + return server; + } + + public void setServer(String server) { + this.server = server; + } + + public Map getMappings() { + return mappings; + } + + public void setMappings(Map mappings) { + this.mappings = mappings; + } + + @PostConstruct + public void build() { + log.info("--- Bridge integration lookups config ---"); + log.info("Enabled: {}", getEnabled()); + if (getEnabled()) { + log.info("Recursive only: {}", getRecursiveOnly()); + log.info("Fallback Server: {}", getServer()); + log.info("Mappings: {}", mappings.size()); + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/config/RecursiveLookupConfig.groovy b/src/main/java/io/kamax/mxisd/config/RecursiveLookupConfig.java similarity index 56% rename from src/main/groovy/io/kamax/mxisd/config/RecursiveLookupConfig.groovy rename to src/main/java/io/kamax/mxisd/config/RecursiveLookupConfig.java index 5c6a878..d6c6c13 100644 --- a/src/main/groovy/io/kamax/mxisd/config/RecursiveLookupConfig.groovy +++ b/src/main/java/io/kamax/mxisd/config/RecursiveLookupConfig.java @@ -18,41 +18,43 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.config +package io.kamax.mxisd.config; -import org.springframework.boot.context.properties.ConfigurationProperties -import org.springframework.context.annotation.Configuration +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; @Configuration @ConfigurationProperties(prefix = "lookup.recursive") -class RecursiveLookupConfig { +public class RecursiveLookupConfig { - private boolean enabled - private List allowedCidr - private RecursiveLookupBridgeConfig bridge + private boolean enabled; + private List allowedCidr; + private RecursiveLookupBridgeConfig bridge; - boolean isEnabled() { - return enabled + public boolean isEnabled() { + return enabled; } - void setEnabled(boolean enabled) { - this.enabled = enabled + public void setEnabled(boolean enabled) { + this.enabled = enabled; } - List getAllowedCidr() { - return allowedCidr + public List getAllowedCidr() { + return allowedCidr; } - void setAllowedCidr(List allowedCidr) { - this.allowedCidr = allowedCidr + public void setAllowedCidr(List allowedCidr) { + this.allowedCidr = allowedCidr; } - RecursiveLookupBridgeConfig getBridge() { - return bridge + public RecursiveLookupBridgeConfig getBridge() { + return bridge; } - void setBridge(RecursiveLookupBridgeConfig bridge) { - this.bridge = bridge + public void setBridge(RecursiveLookupBridgeConfig bridge) { + this.bridge = bridge; } } diff --git a/src/main/groovy/io/kamax/mxisd/config/SQLiteStorageConfig.java b/src/main/java/io/kamax/mxisd/config/SQLiteStorageConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/SQLiteStorageConfig.java rename to src/main/java/io/kamax/mxisd/config/SQLiteStorageConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/ServerConfig.groovy b/src/main/java/io/kamax/mxisd/config/ServerConfig.java similarity index 57% rename from src/main/groovy/io/kamax/mxisd/config/ServerConfig.groovy rename to src/main/java/io/kamax/mxisd/config/ServerConfig.java index 0db57b2..15c1300 100644 --- a/src/main/groovy/io/kamax/mxisd/config/ServerConfig.groovy +++ b/src/main/java/io/kamax/mxisd/config/ServerConfig.java @@ -18,56 +18,59 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.config +package io.kamax.mxisd.config; -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 +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; +import java.net.MalformedURLException; +import java.net.URL; @Configuration @ConfigurationProperties(prefix = "server") -class ServerConfig implements InitializingBean { +public class ServerConfig { private Logger log = LoggerFactory.getLogger(ServerConfig.class); @Autowired private MatrixConfig mxCfg; - private String name - private int port - private String publicUrl + private String name; + private int port; + private String publicUrl; - String getName() { - return name + public String getName() { + return name; } - void setName(String name) { - this.name = name + public void setName(String name) { + this.name = name; } - int getPort() { - return port + public int getPort() { + return port; } - void setPort(int port) { - this.port = port + public void setPort(int port) { + this.port = port; } - String getPublicUrl() { - return publicUrl + public String getPublicUrl() { + return publicUrl; } - void setPublicUrl(String publicUrl) { - this.publicUrl = publicUrl + public void setPublicUrl(String publicUrl) { + this.publicUrl = publicUrl; } - @Override - void afterPropertiesSet() throws Exception { - log.info("--- Server config ---") + @PostConstruct + public void build() { + log.info("--- Server config ---"); if (StringUtils.isBlank(getName())) { setName(mxCfg.getDomain()); @@ -75,21 +78,21 @@ class ServerConfig implements InitializingBean { } if (StringUtils.isBlank(getPublicUrl())) { - setPublicUrl("https://${getName()}"); + setPublicUrl("https://" + getName()); log.debug("Public URL is empty, generating from name"); } else { setPublicUrl(StringUtils.replace(getPublicUrl(), "%SERVER_NAME%", getName())); } try { - new URL(getPublicUrl()) + new URL(getPublicUrl()); } catch (MalformedURLException e) { - log.warn("Public URL is not valid: {}", StringUtils.defaultIfBlank(e.getMessage(), "")) + log.warn("Public URL is not valid: {}", StringUtils.defaultIfBlank(e.getMessage(), "")); } - log.info("Name: {}", getName()) - log.info("Port: {}", getPort()) - log.info("Public URL: {}", getPublicUrl()) + log.info("Name: {}", getName()); + log.info("Port: {}", getPort()); + log.info("Public URL: {}", getPublicUrl()); } } diff --git a/src/main/groovy/io/kamax/mxisd/config/SessionConfig.java b/src/main/java/io/kamax/mxisd/config/SessionConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/SessionConfig.java rename to src/main/java/io/kamax/mxisd/config/SessionConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/StorageConfig.java b/src/main/java/io/kamax/mxisd/config/StorageConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/StorageConfig.java rename to src/main/java/io/kamax/mxisd/config/StorageConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/ThymeleafConfig.java b/src/main/java/io/kamax/mxisd/config/ThymeleafConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/ThymeleafConfig.java rename to src/main/java/io/kamax/mxisd/config/ThymeleafConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/ViewConfig.java b/src/main/java/io/kamax/mxisd/config/ViewConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/ViewConfig.java rename to src/main/java/io/kamax/mxisd/config/ViewConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeConfig.java b/src/main/java/io/kamax/mxisd/config/ldap/LdapAttributeConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeConfig.java rename to src/main/java/io/kamax/mxisd/config/ldap/LdapAttributeConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java b/src/main/java/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java rename to src/main/java/io/kamax/mxisd/config/ldap/LdapAttributeUidConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapAuthConfig.java b/src/main/java/io/kamax/mxisd/config/ldap/LdapAuthConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/ldap/LdapAuthConfig.java rename to src/main/java/io/kamax/mxisd/config/ldap/LdapAuthConfig.java diff --git a/src/main/java/io/kamax/mxisd/config/ldap/LdapConfig.java b/src/main/java/io/kamax/mxisd/config/ldap/LdapConfig.java new file mode 100644 index 0000000..fa9c72c --- /dev/null +++ b/src/main/java/io/kamax/mxisd/config/ldap/LdapConfig.java @@ -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 . + */ + +package io.kamax.mxisd.config.ldap; + +import com.google.gson.Gson; +import io.kamax.mxisd.backend.ldap.LdapThreePidProvider; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +@Configuration +@ConfigurationProperties(prefix = "ldap") +public class LdapConfig { + + private static Gson gson = new Gson(); + + private Logger log = LoggerFactory.getLogger(LdapConfig.class); + + private boolean enabled; + + @Autowired + private LdapConnectionConfig conn; + private LdapAttributeConfig attribute; + private LdapAuthConfig auth; + private LdapIdentityConfig identity; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public LdapConnectionConfig getConn() { + return conn; + } + + public void setConn(LdapConnectionConfig conn) { + this.conn = conn; + } + + public LdapAttributeConfig getAttribute() { + return attribute; + } + + public void setAttribute(LdapAttributeConfig attribute) { + this.attribute = attribute; + } + + public LdapAuthConfig getAuth() { + return auth; + } + + public void setAuth(LdapAuthConfig auth) { + this.auth = auth; + } + + public LdapIdentityConfig getIdentity() { + return identity; + } + + public void setIdentity(LdapIdentityConfig identity) { + this.identity = identity; + } + + @PostConstruct + public void build() { + log.info("--- LDAP Config ---"); + log.info("Enabled: {}", isEnabled()); + + if (!isEnabled()) { + return; + } + + if (StringUtils.isBlank(conn.getHost())) { + throw new IllegalStateException("LDAP Host must be configured!"); + } + + if (1 > conn.getPort() || 65535 < conn.getPort()) { + throw new IllegalStateException("LDAP port is not valid"); + } + + if (StringUtils.isBlank(attribute.getUid().getType())) { + throw new IllegalStateException("Attribute UID Type cannot be empty"); + } + + + if (StringUtils.isBlank(attribute.getUid().getValue())) { + throw new IllegalStateException("Attribute UID value cannot be empty"); + } + + String uidType = attribute.getUid().getType(); + if (!StringUtils.equals(LdapThreePidProvider.UID, uidType) && !StringUtils.equals(LdapThreePidProvider.MATRIX_ID, uidType)) { + throw new IllegalArgumentException("Unsupported LDAP UID type: " + uidType); + } + + log.info("Host: {}", conn.getHost()); + log.info("Port: {}", conn.getPort()); + log.info("Bind DN: {}", conn.getBindDn()); + log.info("Base DN: {}", conn.getBaseDn()); + + log.info("Attribute: {}", gson.toJson(attribute)); + log.info("Auth: {}", gson.toJson(auth)); + log.info("Identity: {}", gson.toJson(identity)); + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapConnectionConfig.java b/src/main/java/io/kamax/mxisd/config/ldap/LdapConnectionConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/ldap/LdapConnectionConfig.java rename to src/main/java/io/kamax/mxisd/config/ldap/LdapConnectionConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/ldap/LdapIdentityConfig.java b/src/main/java/io/kamax/mxisd/config/ldap/LdapIdentityConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/ldap/LdapIdentityConfig.java rename to src/main/java/io/kamax/mxisd/config/ldap/LdapIdentityConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/rest/RestBackendConfig.java b/src/main/java/io/kamax/mxisd/config/rest/RestBackendConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/rest/RestBackendConfig.java rename to src/main/java/io/kamax/mxisd/config/rest/RestBackendConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderAuthConfig.java b/src/main/java/io/kamax/mxisd/config/sql/SqlProviderAuthConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderAuthConfig.java rename to src/main/java/io/kamax/mxisd/config/sql/SqlProviderAuthConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderConfig.java b/src/main/java/io/kamax/mxisd/config/sql/SqlProviderConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderConfig.java rename to src/main/java/io/kamax/mxisd/config/sql/SqlProviderConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderIdentityConfig.java b/src/main/java/io/kamax/mxisd/config/sql/SqlProviderIdentityConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/sql/SqlProviderIdentityConfig.java rename to src/main/java/io/kamax/mxisd/config/sql/SqlProviderIdentityConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/threepid/connector/EmailSmtpConfig.java b/src/main/java/io/kamax/mxisd/config/threepid/connector/EmailSmtpConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/threepid/connector/EmailSmtpConfig.java rename to src/main/java/io/kamax/mxisd/config/threepid/connector/EmailSmtpConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/threepid/medium/EmailConfig.java b/src/main/java/io/kamax/mxisd/config/threepid/medium/EmailConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/threepid/medium/EmailConfig.java rename to src/main/java/io/kamax/mxisd/config/threepid/medium/EmailConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/config/threepid/medium/EmailTemplateConfig.java b/src/main/java/io/kamax/mxisd/config/threepid/medium/EmailTemplateConfig.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/config/threepid/medium/EmailTemplateConfig.java rename to src/main/java/io/kamax/mxisd/config/threepid/medium/EmailTemplateConfig.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/AuthController.java b/src/main/java/io/kamax/mxisd/controller/v1/AuthController.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/AuthController.java rename to src/main/java/io/kamax/mxisd/controller/v1/AuthController.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/ClientBulkLookupAnswer.java b/src/main/java/io/kamax/mxisd/controller/v1/ClientBulkLookupAnswer.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/ClientBulkLookupAnswer.java rename to src/main/java/io/kamax/mxisd/controller/v1/ClientBulkLookupAnswer.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/ClientBulkLookupRequest.groovy b/src/main/java/io/kamax/mxisd/controller/v1/ClientBulkLookupRequest.java similarity index 57% rename from src/main/groovy/io/kamax/mxisd/controller/v1/ClientBulkLookupRequest.groovy rename to src/main/java/io/kamax/mxisd/controller/v1/ClientBulkLookupRequest.java index 7109c5c..eb7ab73 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/ClientBulkLookupRequest.groovy +++ b/src/main/java/io/kamax/mxisd/controller/v1/ClientBulkLookupRequest.java @@ -18,28 +18,31 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.controller.v1 +package io.kamax.mxisd.controller.v1; -import io.kamax.mxisd.lookup.ThreePidMapping +import io.kamax.mxisd.lookup.ThreePidMapping; -class ClientBulkLookupRequest { +import java.util.ArrayList; +import java.util.List; - private List> threepids = new ArrayList<>() +public class ClientBulkLookupRequest { - List> getThreepids() { - return threepids + private List> threepids = new ArrayList<>(); + + public List> getThreepids() { + return threepids; } - void setThreepids(List> threepids) { - this.threepids = threepids + public void setThreepids(List> threepids) { + this.threepids = threepids; } - void setMappings(List mappings) { + public void setMappings(List mappings) { for (ThreePidMapping mapping : mappings) { - List threepid = new ArrayList<>() - threepid.add(mapping.getMedium()) - threepid.add(mapping.getValue()) - threepids.add(threepid) + List threepid = new ArrayList<>(); + threepid.add(mapping.getMedium()); + threepid.add(mapping.getValue()); + threepids.add(threepid); } } diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java b/src/main/java/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java rename to src/main/java/io/kamax/mxisd/controller/v1/DefaultExceptionHandler.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/IdentityAPIv1.java b/src/main/java/io/kamax/mxisd/controller/v1/IdentityAPIv1.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/IdentityAPIv1.java rename to src/main/java/io/kamax/mxisd/controller/v1/IdentityAPIv1.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy b/src/main/java/io/kamax/mxisd/controller/v1/InvitationController.java similarity index 57% rename from src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy rename to src/main/java/io/kamax/mxisd/controller/v1/InvitationController.java index 9683da1..fe8541d 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/InvitationController.groovy +++ b/src/main/java/io/kamax/mxisd/controller/v1/InvitationController.java @@ -18,47 +18,49 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.controller.v1 +package io.kamax.mxisd.controller.v1; -import com.google.gson.Gson -import io.kamax.matrix.MatrixID -import io.kamax.mxisd.config.ServerConfig -import io.kamax.mxisd.controller.v1.io.ThreePidInviteReplyIO -import io.kamax.mxisd.invitation.IThreePidInvite -import io.kamax.mxisd.invitation.IThreePidInviteReply -import io.kamax.mxisd.invitation.InvitationManager -import io.kamax.mxisd.invitation.ThreePidInvite -import io.kamax.mxisd.key.KeyManager -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.MediaType -import org.springframework.web.bind.annotation.CrossOrigin -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam -import org.springframework.web.bind.annotation.RestController +import com.google.gson.Gson; +import io.kamax.matrix.MatrixID; +import io.kamax.mxisd.config.ServerConfig; +import io.kamax.mxisd.controller.v1.io.ThreePidInviteReplyIO; +import io.kamax.mxisd.invitation.IThreePidInvite; +import io.kamax.mxisd.invitation.IThreePidInviteReply; +import io.kamax.mxisd.invitation.InvitationManager; +import io.kamax.mxisd.invitation.ThreePidInvite; +import io.kamax.mxisd.key.KeyManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; -import static org.springframework.web.bind.annotation.RequestMethod.POST +import static org.springframework.web.bind.annotation.RequestMethod.POST; @RestController @CrossOrigin @RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) class InvitationController { - private Logger log = LoggerFactory.getLogger(InvitationController.class) + private Logger log = LoggerFactory.getLogger(InvitationController.class); @Autowired - private InvitationManager mgr + private InvitationManager mgr; @Autowired - private KeyManager keyMgr + private KeyManager keyMgr; @Autowired - private ServerConfig srvCfg + private ServerConfig srvCfg; - private Gson gson = new Gson() + private Gson gson = new Gson(); @RequestMapping(value = "/store-invite", method = POST) String store( @@ -67,14 +69,14 @@ class InvitationController { @RequestParam String medium, @RequestParam String address, @RequestParam("room_id") String roomId) { - Map parameters = new HashMap<>() + Map parameters = new HashMap<>(); for (String key : request.getParameterMap().keySet()) { parameters.put(key, request.getParameter(key)); } - IThreePidInvite invite = new ThreePidInvite(new MatrixID(sender), medium, address, roomId, parameters) - IThreePidInviteReply reply = mgr.storeInvite(invite) + IThreePidInvite invite = new ThreePidInvite(new MatrixID(sender), medium, address, roomId, parameters); + IThreePidInviteReply reply = mgr.storeInvite(invite); - return gson.toJson(new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), srvCfg.getPublicUrl())) + return gson.toJson(new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), srvCfg.getPublicUrl())); } } diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/KeyController.groovy b/src/main/java/io/kamax/mxisd/controller/v1/KeyController.java similarity index 56% rename from src/main/groovy/io/kamax/mxisd/controller/v1/KeyController.groovy rename to src/main/java/io/kamax/mxisd/controller/v1/KeyController.java index 6a4e7f1..bac51af 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/KeyController.groovy +++ b/src/main/java/io/kamax/mxisd/controller/v1/KeyController.java @@ -18,64 +18,64 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.controller.v1 +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.key.KeyManager -import org.apache.commons.lang.StringUtils -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.MediaType -import org.springframework.web.bind.annotation.* +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import io.kamax.mxisd.controller.v1.io.KeyValidityJson; +import io.kamax.mxisd.exception.BadRequestException; +import io.kamax.mxisd.key.KeyManager; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; -import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletRequest; -import static org.springframework.web.bind.annotation.RequestMethod.GET +import static org.springframework.web.bind.annotation.RequestMethod.GET; @RestController @CrossOrigin @RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) -class KeyController { +public class KeyController { - private Logger log = LoggerFactory.getLogger(KeyController.class) + private Logger log = LoggerFactory.getLogger(KeyController.class); @Autowired - private KeyManager keyMgr + 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) { + public String getKey(@PathVariable String keyType, @PathVariable int keyId) { if (!"ed25519".contentEquals(keyType)) { - throw new BadRequestException("Invalid algorithm: " + keyType) + throw new BadRequestException("Invalid algorithm: " + keyType); } - log.info("Key {}:{} was requested", keyType, keyId) - return JsonOutput.toJson([ - public_key: keyMgr.getPublicKeyBase64(keyId) - ]) + log.info("Key {}:{} was requested", keyType, keyId); + JsonObject obj = new JsonObject(); + obj.addProperty("public_key", keyMgr.getPublicKeyBase64(keyId)); + return gson.toJson(obj); } @RequestMapping(value = "/pubkey/ephemeral/isvalid", method = GET) - String checkEphemeralKeyValidity(HttpServletRequest request) { - log.warn("Ephemeral key was request but no ephemeral key are generated, replying not valid") + public String checkEphemeralKeyValidity(HttpServletRequest request) { + log.warn("Ephemeral key was request but no ephemeral key are generated, replying not valid"); - return invalidKey + return invalidKey; } @RequestMapping(value = "/pubkey/isvalid", method = GET) - String checkKeyValidity(HttpServletRequest request, @RequestParam("public_key") String pubKey) { - log.info("Validating public key {}", pubKey) + public String checkKeyValidity(HttpServletRequest request, @RequestParam("public_key") String pubKey) { + log.info("Validating public key {}", pubKey); // TODO do in manager - boolean valid = StringUtils.equals(pubKey, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex())) - return valid ? validKey : invalidKey + boolean valid = StringUtils.equals(pubKey, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex())); + return valid ? validKey : invalidKey; } } diff --git a/src/main/java/io/kamax/mxisd/controller/v1/MappingController.java b/src/main/java/io/kamax/mxisd/controller/v1/MappingController.java new file mode 100644 index 0000000..c665729 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/controller/v1/MappingController.java @@ -0,0 +1,130 @@ +/* + * 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.controller.v1; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson; +import io.kamax.mxisd.exception.InternalServerError; +import io.kamax.mxisd.lookup.*; +import io.kamax.mxisd.lookup.strategy.LookupStrategy; +import io.kamax.mxisd.signature.SignatureManager; +import io.kamax.mxisd.util.GsonParser; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static org.springframework.web.bind.annotation.RequestMethod.GET; +import static org.springframework.web.bind.annotation.RequestMethod.POST; + +@RestController +@CrossOrigin +@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) +public class MappingController { + + private Logger log = LoggerFactory.getLogger(MappingController.class); + private Gson gson = new Gson(); + private GsonParser parser = new GsonParser(gson); + + @Autowired + private LookupStrategy strategy; + + @Autowired + private SignatureManager signMgr; + + private void setRequesterInfo(ALookupRequest lookupReq, HttpServletRequest req) { + lookupReq.setRequester(req.getRemoteAddr()); + String xff = req.getHeader("X-FORWARDED-FOR"); + lookupReq.setRecursive(StringUtils.isNotBlank(xff)); + if (lookupReq.isRecursive()) { + lookupReq.setRecurseHosts(Arrays.asList(xff.split(","))); + } + + lookupReq.setUserAgent(req.getHeader("USER-AGENT")); + } + + @RequestMapping(value = "/lookup", method = GET) + String lookup(HttpServletRequest request, @RequestParam String medium, @RequestParam String address) { + SingleLookupRequest lookupRequest = new SingleLookupRequest(); + setRequesterInfo(lookupRequest, request); + lookupRequest.setType(medium); + lookupRequest.setThreePid(address); + + log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive()); + + Optional lookupOpt = strategy.find(lookupRequest); + if (!lookupOpt.isPresent()) { + log.info("No mapping was found, return empty JSON object"); + return "{}"; + } + + SingleLookupReply lookup = lookupOpt.get(); + if (lookup.isSigned()) { + log.info("Lookup is already signed, sending as-is"); + return lookup.getBody(); + } else { + log.info("Lookup is not signed, signing"); + JsonObject obj = gson.toJsonTree(new SingeLookupReplyJson(lookup)).getAsJsonObject(); + obj.add("signatures", signMgr.signMessageGson(gson.toJson(obj))); + + return gson.toJson(obj); + } + } + + @RequestMapping(value = "/bulk_lookup", method = POST) + String bulkLookup(HttpServletRequest request) { + BulkLookupRequest lookupRequest = new BulkLookupRequest(); + setRequesterInfo(lookupRequest, request); + log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive()); + + try { + ClientBulkLookupRequest input = parser.parse(request, ClientBulkLookupRequest.class); + List mappings = new ArrayList<>(); + for (List mappingRaw : input.getThreepids()) { + ThreePidMapping mapping = new ThreePidMapping(); + mapping.setMedium(mappingRaw.get(0)); + mapping.setValue(mappingRaw.get(1)); + mappings.add(mapping); + } + lookupRequest.setMappings(mappings); + + ClientBulkLookupAnswer answer = new ClientBulkLookupAnswer(); + answer.addAll(strategy.find(lookupRequest)); + return gson.toJson(answer); + } catch (IOException e) { + throw new InternalServerError(e); + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy b/src/main/java/io/kamax/mxisd/controller/v1/SessionController.java similarity index 58% rename from src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy rename to src/main/java/io/kamax/mxisd/controller/v1/SessionController.java index 718d671..03ca111 100644 --- a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy +++ b/src/main/java/io/kamax/mxisd/controller/v1/SessionController.java @@ -18,41 +18,44 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.controller.v1 +package io.kamax.mxisd.controller.v1; -import io.kamax.mxisd.config.ServerConfig -import io.kamax.mxisd.config.ViewConfig -import io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1 -import io.kamax.mxisd.session.SessionMananger -import io.kamax.mxisd.session.ValidationResult -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Controller -import org.springframework.ui.Model -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam +import io.kamax.mxisd.config.ServerConfig; +import io.kamax.mxisd.config.ViewConfig; +import io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1; +import io.kamax.mxisd.exception.InternalServerError; +import io.kamax.mxisd.session.SessionMananger; +import io.kamax.mxisd.session.ValidationResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; @Controller @RequestMapping(path = IdentityAPIv1.BASE) class SessionController { - private Logger log = LoggerFactory.getLogger(SessionController.class) + private Logger log = LoggerFactory.getLogger(SessionController.class); @Autowired private ServerConfig srvCfg; @Autowired - private SessionMananger mgr + private SessionMananger mgr; @Autowired private ViewConfig viewCfg; + ; @RequestMapping(value = "/validate/{medium}/submitToken") - String validate( + public String validate( HttpServletRequest request, HttpServletResponse response, @RequestParam String sid, @@ -60,21 +63,27 @@ class SessionController { @RequestParam String token, Model model ) { - log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString()) + log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString()); - ValidationResult r = mgr.validate(sid, secret, token) - log.info("Session {} was validated", sid) + ValidationResult r = mgr.validate(sid, secret, token); + log.info("Session {} was validated", sid); if (r.getNextUrl().isPresent()) { - String url = srvCfg.getPublicUrl() + r.getNextUrl().get() - log.info("Session {} validation: next URL is present, redirecting to {}", sid, url) - response.sendRedirect(url) + String url = srvCfg.getPublicUrl() + r.getNextUrl().get(); + log.info("Session {} validation: next URL is present, redirecting to {}", sid, url); + try { + response.sendRedirect(url); + return ""; + } catch (IOException e) { + log.warn("Unable to redirect user to {}", url); + throw new InternalServerError(e); + } } else { if (r.isCanRemote()) { String url = srvCfg.getPublicUrl() + RemoteIdentityAPIv1.getRequestToken(r.getSession().getId(), r.getSession().getSecret()); - model.addAttribute("remoteSessionLink", url) - return viewCfg.getSession().getLocalRemote().getOnTokenSubmit().getSuccess() + model.addAttribute("remoteSessionLink", url); + return viewCfg.getSession().getLocalRemote().getOnTokenSubmit().getSuccess(); } else { - return viewCfg.getSession().getLocal().getOnTokenSubmit().getSuccess() + return viewCfg.getSession().getLocal().getOnTokenSubmit().getSuccess(); } } } diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionRestController.java b/src/main/java/io/kamax/mxisd/controller/v1/SessionRestController.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/SessionRestController.java rename to src/main/java/io/kamax/mxisd/controller/v1/SessionRestController.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/StatusController.java b/src/main/java/io/kamax/mxisd/controller/v1/StatusController.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/StatusController.java rename to src/main/java/io/kamax/mxisd/controller/v1/StatusController.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java b/src/main/java/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java rename to src/main/java/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/KeyValidityJson.java b/src/main/java/io/kamax/mxisd/controller/v1/io/KeyValidityJson.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/io/KeyValidityJson.java rename to src/main/java/io/kamax/mxisd/controller/v1/io/KeyValidityJson.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/RequestTokenResponse.java b/src/main/java/io/kamax/mxisd/controller/v1/io/RequestTokenResponse.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/io/RequestTokenResponse.java rename to src/main/java/io/kamax/mxisd/controller/v1/io/RequestTokenResponse.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java b/src/main/java/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java rename to src/main/java/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java b/src/main/java/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java rename to src/main/java/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SingeLookupReplyJson.java b/src/main/java/io/kamax/mxisd/controller/v1/io/SingeLookupReplyJson.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/io/SingeLookupReplyJson.java rename to src/main/java/io/kamax/mxisd/controller/v1/io/SingeLookupReplyJson.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java b/src/main/java/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java rename to src/main/java/io/kamax/mxisd/controller/v1/io/ThreePidInviteReplyIO.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteIdentityAPIv1.java b/src/main/java/io/kamax/mxisd/controller/v1/remote/RemoteIdentityAPIv1.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteIdentityAPIv1.java rename to src/main/java/io/kamax/mxisd/controller/v1/remote/RemoteIdentityAPIv1.java diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteSessionController.java b/src/main/java/io/kamax/mxisd/controller/v1/remote/RemoteSessionController.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/controller/v1/remote/RemoteSessionController.java rename to src/main/java/io/kamax/mxisd/controller/v1/remote/RemoteSessionController.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/BadRequestException.groovy b/src/main/java/io/kamax/mxisd/exception/BadRequestException.java similarity index 76% rename from src/main/groovy/io/kamax/mxisd/exception/BadRequestException.groovy rename to src/main/java/io/kamax/mxisd/exception/BadRequestException.java index a1caa26..b373e08 100644 --- a/src/main/groovy/io/kamax/mxisd/exception/BadRequestException.groovy +++ b/src/main/java/io/kamax/mxisd/exception/BadRequestException.java @@ -18,16 +18,16 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.exception +package io.kamax.mxisd.exception; -import org.springframework.http.HttpStatus -import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(value = HttpStatus.BAD_REQUEST) -class BadRequestException extends RuntimeException { +public class BadRequestException extends RuntimeException { - BadRequestException(String s) { - super(s) + public BadRequestException(String s) { + super(s); } } diff --git a/src/main/groovy/io/kamax/mxisd/exception/ConfigurationException.java b/src/main/java/io/kamax/mxisd/exception/ConfigurationException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/ConfigurationException.java rename to src/main/java/io/kamax/mxisd/exception/ConfigurationException.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/InternalServerError.java b/src/main/java/io/kamax/mxisd/exception/InternalServerError.java similarity index 95% rename from src/main/groovy/io/kamax/mxisd/exception/InternalServerError.java rename to src/main/java/io/kamax/mxisd/exception/InternalServerError.java index 5680075..80c83ac 100644 --- a/src/main/groovy/io/kamax/mxisd/exception/InternalServerError.java +++ b/src/main/java/io/kamax/mxisd/exception/InternalServerError.java @@ -43,6 +43,10 @@ public class InternalServerError extends MatrixException { this.internalReason = internalReason; } + public InternalServerError(Throwable t) { + this(t.getMessage()); + } + public String getReference() { return reference; } diff --git a/src/main/groovy/io/kamax/mxisd/exception/InvalidCredentialsException.java b/src/main/java/io/kamax/mxisd/exception/InvalidCredentialsException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/InvalidCredentialsException.java rename to src/main/java/io/kamax/mxisd/exception/InvalidCredentialsException.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/InvalidResponseJsonException.java b/src/main/java/io/kamax/mxisd/exception/InvalidResponseJsonException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/InvalidResponseJsonException.java rename to src/main/java/io/kamax/mxisd/exception/InvalidResponseJsonException.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/JsonMemberNotFoundException.java b/src/main/java/io/kamax/mxisd/exception/JsonMemberNotFoundException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/JsonMemberNotFoundException.java rename to src/main/java/io/kamax/mxisd/exception/JsonMemberNotFoundException.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/MappingAlreadyExistsException.java b/src/main/java/io/kamax/mxisd/exception/MappingAlreadyExistsException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/MappingAlreadyExistsException.java rename to src/main/java/io/kamax/mxisd/exception/MappingAlreadyExistsException.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/MatrixException.java b/src/main/java/io/kamax/mxisd/exception/MatrixException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/MatrixException.java rename to src/main/java/io/kamax/mxisd/exception/MatrixException.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/MxisdException.java b/src/main/java/io/kamax/mxisd/exception/MxisdException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/MxisdException.java rename to src/main/java/io/kamax/mxisd/exception/MxisdException.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/NotAllowedException.java b/src/main/java/io/kamax/mxisd/exception/NotAllowedException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/NotAllowedException.java rename to src/main/java/io/kamax/mxisd/exception/NotAllowedException.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/NotImplementedException.groovy b/src/main/java/io/kamax/mxisd/exception/NotImplementedException.java similarity index 87% rename from src/main/groovy/io/kamax/mxisd/exception/NotImplementedException.groovy rename to src/main/java/io/kamax/mxisd/exception/NotImplementedException.java index 5912136..7a3ea63 100644 --- a/src/main/groovy/io/kamax/mxisd/exception/NotImplementedException.groovy +++ b/src/main/java/io/kamax/mxisd/exception/NotImplementedException.java @@ -18,10 +18,10 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.exception +package io.kamax.mxisd.exception; -import org.springframework.http.HttpStatus -import org.springframework.web.bind.annotation.ResponseStatus +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(value = HttpStatus.NOT_IMPLEMENTED) public class NotImplementedException extends RuntimeException { diff --git a/src/main/groovy/io/kamax/mxisd/exception/ObjectNotFoundException.java b/src/main/java/io/kamax/mxisd/exception/ObjectNotFoundException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/ObjectNotFoundException.java rename to src/main/java/io/kamax/mxisd/exception/ObjectNotFoundException.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/RemoteIdentityServerException.java b/src/main/java/io/kamax/mxisd/exception/RemoteIdentityServerException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/RemoteIdentityServerException.java rename to src/main/java/io/kamax/mxisd/exception/RemoteIdentityServerException.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/SessionNotValidatedException.java b/src/main/java/io/kamax/mxisd/exception/SessionNotValidatedException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/SessionNotValidatedException.java rename to src/main/java/io/kamax/mxisd/exception/SessionNotValidatedException.java diff --git a/src/main/groovy/io/kamax/mxisd/exception/SessionUnknownException.java b/src/main/java/io/kamax/mxisd/exception/SessionUnknownException.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/exception/SessionUnknownException.java rename to src/main/java/io/kamax/mxisd/exception/SessionUnknownException.java diff --git a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java b/src/main/java/io/kamax/mxisd/invitation/IThreePidInvite.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/invitation/IThreePidInvite.java rename to src/main/java/io/kamax/mxisd/invitation/IThreePidInvite.java diff --git a/src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java b/src/main/java/io/kamax/mxisd/invitation/IThreePidInviteReply.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/invitation/IThreePidInviteReply.java rename to src/main/java/io/kamax/mxisd/invitation/IThreePidInviteReply.java diff --git a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java similarity index 91% rename from src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java rename to src/main/java/io/kamax/mxisd/invitation/InvitationManager.java index 06a0d39..3f1ebc5 100644 --- a/src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java @@ -21,6 +21,8 @@ package io.kamax.mxisd.invitation; import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; import io.kamax.matrix.MatrixID; import io.kamax.mxisd.config.DnsOverwrite; import io.kamax.mxisd.config.DnsOverwriteEntry; @@ -45,8 +47,6 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContextBuilder; -import org.json.JSONArray; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -153,13 +153,13 @@ public class InvitationManager { return reply.getInvite().getSender().getId() + ":" + reply.getInvite().getRoomId() + ":" + reply.getInvite().getMedium() + ":" + reply.getInvite().getAddress(); } - String getSrvRecordName(String domain) { + private String getSrvRecordName(String domain) { return "_matrix._tcp." + domain; } // TODO use caching mechanism // TODO export in matrix-java-sdk - String findHomeserverForDomain(String domain) { + private String findHomeserverForDomain(String domain) { Optional entryOpt = dns.findHost(domain); if (entryOpt.isPresent()) { DnsOverwriteEntry entry = entryOpt.get(); @@ -264,28 +264,28 @@ public class InvitationManager { new Thread(() -> { // FIXME need to make this retry-able and within a general background working pool HttpPost req = new HttpPost(hsUrlOpt + "/_matrix/federation/v1/3pid/onbind"); // Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr - JSONObject obj = new JSONObject(); // TODO use Gson instead - obj.put("mxid", mxid); - obj.put("token", reply.getToken()); - obj.put("signatures", signMgr.signMessageJson(obj.toString())); + JsonObject obj = new JsonObject(); + obj.addProperty("mxid", mxid); + obj.addProperty("token", reply.getToken()); + obj.add("signatures", signMgr.signMessageGson(obj.toString())); - JSONObject objUp = new JSONObject(); - objUp.put("mxid", mxid); - objUp.put("medium", medium); - objUp.put("address", address); - objUp.put("sender", reply.getInvite().getSender().getId()); - objUp.put("room_id", reply.getInvite().getRoomId()); - objUp.put("signed", obj); + JsonObject objUp = new JsonObject(); + objUp.addProperty("mxid", mxid); + objUp.addProperty("medium", medium); + objUp.addProperty("address", address); + objUp.addProperty("sender", reply.getInvite().getSender().getId()); + objUp.addProperty("room_id", reply.getInvite().getRoomId()); + objUp.add("signed", obj); - JSONObject content = new JSONObject(); // TODO use Gson instead - JSONArray invites = new JSONArray(); - invites.put(objUp); - content.put("invites", invites); - content.put("medium", medium); - content.put("address", address); - content.put("mxid", mxid); + JsonObject content = new JsonObject(); + JsonArray invites = new JsonArray(); + invites.add(objUp); + content.add("invites", invites); + content.addProperty("medium", medium); + content.addProperty("address", address); + content.addProperty("mxid", mxid); - content.put("signatures", signMgr.signMessageJson(content.toString())); + content.add("signatures", signMgr.signMessageGson(content.toString())); StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8); entity.setContentType("application/json"); @@ -313,7 +313,7 @@ public class InvitationManager { private IThreePidInviteReply reply; - public MappingChecker(IThreePidInviteReply reply) { + MappingChecker(IThreePidInviteReply reply) { this.reply = reply; } diff --git a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java b/src/main/java/io/kamax/mxisd/invitation/ThreePidInvite.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/invitation/ThreePidInvite.java rename to src/main/java/io/kamax/mxisd/invitation/ThreePidInvite.java diff --git a/src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java b/src/main/java/io/kamax/mxisd/invitation/ThreePidInviteReply.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/invitation/ThreePidInviteReply.java rename to src/main/java/io/kamax/mxisd/invitation/ThreePidInviteReply.java diff --git a/src/main/java/io/kamax/mxisd/key/KeyManager.java b/src/main/java/io/kamax/mxisd/key/KeyManager.java new file mode 100644 index 0000000..bb72a46 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/key/KeyManager.java @@ -0,0 +1,115 @@ +/* + * 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.key; + +import io.kamax.mxisd.config.KeyConfig; +import net.i2p.crypto.eddsa.EdDSAEngine; +import net.i2p.crypto.eddsa.EdDSAPrivateKey; +import net.i2p.crypto.eddsa.EdDSAPublicKey; +import net.i2p.crypto.eddsa.KeyPairGenerator; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; +import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec; +import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; +import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; +import org.apache.commons.io.FileUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyPair; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +@Component +public class KeyManager { + + @Autowired + private KeyConfig keyCfg; + + private EdDSAParameterSpec keySpecs; + private EdDSAEngine signEngine; + private List keys; + + @PostConstruct + public void build() { + try { + keySpecs = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512); + signEngine = new EdDSAEngine(MessageDigest.getInstance(keySpecs.getHashAlgorithm())); + keys = new ArrayList<>(); + + Path privKey = Paths.get(keyCfg.getPath()); + + if (!Files.exists(privKey)) { + KeyPair pair = (new KeyPairGenerator()).generateKeyPair(); + String keyEncoded = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded()); + FileUtils.writeStringToFile(privKey.toFile(), keyEncoded, StandardCharsets.ISO_8859_1); + keys.add(pair); + } else { + if (Files.isDirectory(privKey)) { + throw new RuntimeException("Invalid path for private key: " + privKey.toString()); + } + + if (Files.isReadable(privKey)) { + byte[] seed = Base64.getDecoder().decode(FileUtils.readFileToString(privKey.toFile(), StandardCharsets.ISO_8859_1)); + EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(seed, keySpecs); + EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKeySpec.getA(), keySpecs); + keys.add(new KeyPair(new EdDSAPublicKey(pubKeySpec), new EdDSAPrivateKey(privKeySpec))); + } + } + } catch (NoSuchAlgorithmException | IOException e) { + throw new RuntimeException(e); + } + } + + public int getCurrentIndex() { + return 0; + } + + public KeyPair getKeys(int index) { + return keys.get(index); + } + + public PrivateKey getPrivateKey(int index) { + return getKeys(index).getPrivate(); + } + + public EdDSAPublicKey getPublicKey(int index) { + return (EdDSAPublicKey) getKeys(index).getPublic(); + } + + public EdDSAParameterSpec getSpecs() { + return keySpecs; + } + + public String getPublicKeyBase64(int index) { + return Base64.getEncoder().encodeToString(getPublicKey(index).getAbyte()); + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/lookup/ALookupRequest.java b/src/main/java/io/kamax/mxisd/lookup/ALookupRequest.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/lookup/ALookupRequest.java rename to src/main/java/io/kamax/mxisd/lookup/ALookupRequest.java diff --git a/src/main/groovy/io/kamax/mxisd/lookup/BulkLookupRequest.java b/src/main/java/io/kamax/mxisd/lookup/BulkLookupRequest.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/lookup/BulkLookupRequest.java rename to src/main/java/io/kamax/mxisd/lookup/BulkLookupRequest.java diff --git a/src/main/groovy/io/kamax/mxisd/lookup/SingleLookupReply.java b/src/main/java/io/kamax/mxisd/lookup/SingleLookupReply.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/lookup/SingleLookupReply.java rename to src/main/java/io/kamax/mxisd/lookup/SingleLookupReply.java diff --git a/src/main/groovy/io/kamax/mxisd/lookup/SingleLookupRequest.groovy b/src/main/java/io/kamax/mxisd/lookup/SingleLookupRequest.java similarity index 67% rename from src/main/groovy/io/kamax/mxisd/lookup/SingleLookupRequest.groovy rename to src/main/java/io/kamax/mxisd/lookup/SingleLookupRequest.java index 3f36ae6..e0013d2 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/SingleLookupRequest.groovy +++ b/src/main/java/io/kamax/mxisd/lookup/SingleLookupRequest.java @@ -18,27 +18,27 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.lookup +package io.kamax.mxisd.lookup; -class SingleLookupRequest extends ALookupRequest { +public class SingleLookupRequest extends ALookupRequest { - private String type - private String threePid + private String type; + private String threePid; - String getType() { - return type + public String getType() { + return type; } - void setType(String type) { - this.type = type + public void setType(String type) { + this.type = type; } - String getThreePid() { - return threePid + public String getThreePid() { + return threePid; } - void setThreePid(String threePid) { - this.threePid = threePid + public void setThreePid(String threePid) { + this.threePid = threePid; } } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidMapping.java b/src/main/java/io/kamax/mxisd/lookup/ThreePidMapping.java similarity index 95% rename from src/main/groovy/io/kamax/mxisd/lookup/ThreePidMapping.java rename to src/main/java/io/kamax/mxisd/lookup/ThreePidMapping.java index 5777ad7..26b15ec 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidMapping.java +++ b/src/main/java/io/kamax/mxisd/lookup/ThreePidMapping.java @@ -20,11 +20,13 @@ package io.kamax.mxisd.lookup; -import groovy.json.JsonOutput; +import com.google.gson.Gson; import io.kamax.mxisd.ThreePid; public class ThreePidMapping { + private static Gson gson = new Gson(); + private String medium; private String value; private String mxid; @@ -87,7 +89,7 @@ public class ThreePidMapping { @Override public String toString() { - return JsonOutput.toJson(this); + return gson.toJson(this); } } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java b/src/main/java/io/kamax/mxisd/lookup/ThreePidValidation.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java rename to src/main/java/io/kamax/mxisd/lookup/ThreePidValidation.java diff --git a/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IBridgeFetcher.groovy b/src/main/java/io/kamax/mxisd/lookup/fetcher/IBridgeFetcher.java similarity index 76% rename from src/main/groovy/io/kamax/mxisd/lookup/fetcher/IBridgeFetcher.groovy rename to src/main/java/io/kamax/mxisd/lookup/fetcher/IBridgeFetcher.java index bd445bb..bace0fe 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IBridgeFetcher.groovy +++ b/src/main/java/io/kamax/mxisd/lookup/fetcher/IBridgeFetcher.java @@ -18,16 +18,19 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.lookup.fetcher +package io.kamax.mxisd.lookup.fetcher; -import io.kamax.mxisd.lookup.SingleLookupReply -import io.kamax.mxisd.lookup.SingleLookupRequest -import io.kamax.mxisd.lookup.ThreePidMapping +import io.kamax.mxisd.lookup.SingleLookupReply; +import io.kamax.mxisd.lookup.SingleLookupRequest; +import io.kamax.mxisd.lookup.ThreePidMapping; -interface IBridgeFetcher { +import java.util.List; +import java.util.Optional; - Optional find(SingleLookupRequest request) +public interface IBridgeFetcher { - List populate(List mappings) + Optional find(SingleLookupRequest request); + + List populate(List mappings); } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IRemoteIdentityServerFetcher.groovy b/src/main/java/io/kamax/mxisd/lookup/fetcher/IRemoteIdentityServerFetcher.java similarity index 72% rename from src/main/groovy/io/kamax/mxisd/lookup/fetcher/IRemoteIdentityServerFetcher.groovy rename to src/main/java/io/kamax/mxisd/lookup/fetcher/IRemoteIdentityServerFetcher.java index aeb81d5..bfd62e8 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/fetcher/IRemoteIdentityServerFetcher.groovy +++ b/src/main/java/io/kamax/mxisd/lookup/fetcher/IRemoteIdentityServerFetcher.java @@ -18,18 +18,21 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.lookup.fetcher +package io.kamax.mxisd.lookup.fetcher; -import io.kamax.mxisd.lookup.SingleLookupReply -import io.kamax.mxisd.lookup.SingleLookupRequest -import io.kamax.mxisd.lookup.ThreePidMapping +import io.kamax.mxisd.lookup.SingleLookupReply; +import io.kamax.mxisd.lookup.SingleLookupRequest; +import io.kamax.mxisd.lookup.ThreePidMapping; -interface IRemoteIdentityServerFetcher { +import java.util.List; +import java.util.Optional; - boolean isUsable(String remote) +public interface IRemoteIdentityServerFetcher { - Optional find(String remote, SingleLookupRequest request) + boolean isUsable(String remote); - List find(String remote, List mappings) + Optional find(String remote, SingleLookupRequest request); + + List find(String remote, List mappings); } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/BridgeFetcher.java b/src/main/java/io/kamax/mxisd/lookup/provider/BridgeFetcher.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/lookup/provider/BridgeFetcher.java rename to src/main/java/io/kamax/mxisd/lookup/provider/BridgeFetcher.java diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/DnsLookupProvider.groovy b/src/main/java/io/kamax/mxisd/lookup/provider/DnsLookupProvider.java similarity index 57% rename from src/main/groovy/io/kamax/mxisd/lookup/provider/DnsLookupProvider.groovy rename to src/main/java/io/kamax/mxisd/lookup/provider/DnsLookupProvider.java index 28ef1a8..89e1f77 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/DnsLookupProvider.groovy +++ b/src/main/java/io/kamax/mxisd/lookup/provider/DnsLookupProvider.java @@ -18,170 +18,163 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.lookup.provider +package io.kamax.mxisd.lookup.provider; -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.fetcher.IRemoteIdentityServerFetcher -import io.kamax.mxisd.matrix.IdentityServerUtils -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 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.fetcher.IRemoteIdentityServerFetcher; +import io.kamax.mxisd.matrix.IdentityServerUtils; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; -import java.util.concurrent.ForkJoinPool -import java.util.concurrent.RecursiveTask -import java.util.function.Function +import java.util.*; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.RecursiveTask; @Component class DnsLookupProvider implements IThreePidProvider { - private Logger log = LoggerFactory.getLogger(DnsLookupProvider.class) + private Logger log = LoggerFactory.getLogger(DnsLookupProvider.class); @Autowired - private MatrixConfig mxCfg + private MatrixConfig mxCfg; @Autowired - private IRemoteIdentityServerFetcher fetcher + private IRemoteIdentityServerFetcher fetcher; @Override - boolean isEnabled() { - return true + public boolean isEnabled() { + return true; } @Override - boolean isLocal() { - return false + public boolean isLocal() { + return false; } @Override - int getPriority() { - return 10 + public int getPriority() { + return 10; } - Optional getDomain(String email) { - int atIndex = email.lastIndexOf("@") + private Optional getDomain(String email) { + int atIndex = email.lastIndexOf("@"); if (atIndex == -1) { - return Optional.empty() + return Optional.empty(); } - return Optional.of(email.substring(atIndex + 1)) + return Optional.of(email.substring(atIndex + 1)); } // TODO use caching mechanism - Optional findIdentityServerForDomain(String domain) { + private Optional findIdentityServerForDomain(String domain) { if (StringUtils.equals(mxCfg.getDomain(), domain)) { - log.info("We are authoritative for {}, no remote lookup", domain) - return Optional.empty() + log.info("We are authoritative for {}, no remote lookup", domain); + return Optional.empty(); } - return IdentityServerUtils.findIsUrlForDomain(domain) + return IdentityServerUtils.findIsUrlForDomain(domain); } @Override - Optional find(SingleLookupRequest request) { + public Optional find(SingleLookupRequest request) { if (!StringUtils.equals("email", request.getType())) { // TODO use enum - log.info("Skipping unsupported type {} for {}", request.getType(), request.getThreePid()) - return Optional.empty() + log.info("Skipping unsupported type {} for {}", request.getType(), request.getThreePid()); + return Optional.empty(); } - log.info("Performing DNS lookup for {}", request.getThreePid()) + log.info("Performing DNS lookup for {}", request.getThreePid()); - String domain = request.getThreePid().substring(request.getThreePid().lastIndexOf("@") + 1) - log.info("Domain name for {}: {}", request.getThreePid(), domain) - Optional baseUrl = findIdentityServerForDomain(domain) + String domain = request.getThreePid().substring(request.getThreePid().lastIndexOf("@") + 1); + log.info("Domain name for {}: {}", request.getThreePid(), domain); + Optional baseUrl = findIdentityServerForDomain(domain); if (baseUrl.isPresent()) { - return fetcher.find(baseUrl.get(), request) + return fetcher.find(baseUrl.get(), request); } - return Optional.empty() + return Optional.empty(); } @Override - List populate(List mappings) { - Map> domains = new HashMap<>() + public List populate(List mappings) { + Map> domains = new HashMap<>(); for (ThreePidMapping mapping : mappings) { if (!StringUtils.equals("email", mapping.getMedium())) { - log.info("Skipping unsupported type {} for {}", mapping.getMedium(), mapping.getValue()) - continue + log.info("Skipping unsupported type {} for {}", mapping.getMedium(), mapping.getValue()); + continue; } - Optional domainOpt = getDomain(mapping.getValue()) + Optional domainOpt = getDomain(mapping.getValue()); if (!domainOpt.isPresent()) { - log.warn("No domain for 3PID {}", mapping.getValue()) - continue + log.warn("No domain for 3PID {}", mapping.getValue()); + continue; } - String domain = domainOpt.get() - List domainMappings = domains.computeIfAbsent(domain, new Function>() { - - @Override - List apply(String s) { - return new ArrayList<>() - } - - }) - domainMappings.add(mapping) + String domain = domainOpt.get(); + List domainMappings = domains.computeIfAbsent(domain, s -> new ArrayList<>()); + domainMappings.add(mapping); } - log.info("Looking mappings across {} domains", domains.keySet().size()) - ForkJoinPool pool = new ForkJoinPool() + log.info("Looking mappings across {} domains", domains.keySet().size()); + ForkJoinPool pool = ForkJoinPool.commonPool(); RecursiveTask> task = new RecursiveTask>() { @Override protected List compute() { - List mappingsFound = new ArrayList<>() - List tasks = new ArrayList<>() + List mappingsFound = new ArrayList<>(); + List tasks = new ArrayList<>(); for (String domain : domains.keySet()) { - DomainBulkLookupTask domainTask = new DomainBulkLookupTask(domain, domains.get(domain)) - domainTask.fork() - tasks.add(domainTask) + DomainBulkLookupTask domainTask = new DomainBulkLookupTask(domain, domains.get(domain)); + domainTask.fork(); + tasks.add(domainTask); } for (DomainBulkLookupTask task : tasks) { - mappingsFound.addAll(task.join()) + mappingsFound.addAll(task.join()); } - return mappingsFound + return mappingsFound; } - } - pool.submit(task) - pool.shutdown() + }; + pool.submit(task); + pool.shutdown(); - List mappingsFound = task.join() - log.info("Found {} mappings overall", mappingsFound.size()) - return mappingsFound + List mappingsFound = task.join(); + log.info("Found {} mappings overall", mappingsFound.size()); + return mappingsFound; } private class DomainBulkLookupTask extends RecursiveTask> { - private String domain - private List mappings + private String domain; + private List mappings; DomainBulkLookupTask(String domain, List mappings) { - this.domain = domain - this.mappings = mappings + this.domain = domain; + this.mappings = mappings; } @Override protected List compute() { - List domainMappings = new ArrayList<>() + List domainMappings = new ArrayList<>(); - Optional baseUrl = findIdentityServerForDomain(domain) + Optional baseUrl = findIdentityServerForDomain(domain); if (!baseUrl.isPresent()) { - log.info("No usable Identity server for domain {}", domain) + log.info("No usable Identity server for domain {}", domain); } else { - domainMappings.addAll(fetcher.find(baseUrl.get(), mappings)) - log.info("Found {} mappings in domain {}", domainMappings.size(), domain) + domainMappings.addAll(fetcher.find(baseUrl.get(), mappings)); + log.info("Found {} mappings in domain {}", domainMappings.size(), domain); } - return domainMappings + return domainMappings; } } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/ForwarderProvider.groovy b/src/main/java/io/kamax/mxisd/lookup/provider/ForwarderProvider.java similarity index 57% rename from src/main/groovy/io/kamax/mxisd/lookup/provider/ForwarderProvider.groovy rename to src/main/java/io/kamax/mxisd/lookup/provider/ForwarderProvider.java index 1ef903a..badaa1a 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/ForwarderProvider.groovy +++ b/src/main/java/io/kamax/mxisd/lookup/provider/ForwarderProvider.java @@ -18,71 +18,75 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.lookup.provider +package io.kamax.mxisd.lookup.provider; -import io.kamax.mxisd.config.ForwardConfig -import io.kamax.mxisd.lookup.SingleLookupReply -import io.kamax.mxisd.lookup.SingleLookupRequest -import io.kamax.mxisd.lookup.ThreePidMapping -import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher -import org.slf4j.Logger -import org.slf4j.LoggerFactory -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.stereotype.Component +import io.kamax.mxisd.config.ForwardConfig; +import io.kamax.mxisd.lookup.SingleLookupReply; +import io.kamax.mxisd.lookup.SingleLookupRequest; +import io.kamax.mxisd.lookup.ThreePidMapping; +import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; @Component class ForwarderProvider implements IThreePidProvider { - private Logger log = LoggerFactory.getLogger(ForwarderProvider.class) + private Logger log = LoggerFactory.getLogger(ForwarderProvider.class); @Autowired - private ForwardConfig cfg + private ForwardConfig cfg; @Autowired - private IRemoteIdentityServerFetcher fetcher + private IRemoteIdentityServerFetcher fetcher; @Override - boolean isEnabled() { - return true + public boolean isEnabled() { + return true; } @Override - boolean isLocal() { - return false + public boolean isLocal() { + return false; } @Override - int getPriority() { - return 0 + public int getPriority() { + return 0; } @Override - Optional find(SingleLookupRequest request) { + public Optional find(SingleLookupRequest request) { for (String root : cfg.getServers()) { - Optional answer = fetcher.find(root, request) + Optional answer = fetcher.find(root, request); if (answer.isPresent()) { - return answer + return answer; } } - return Optional.empty() + return Optional.empty(); } @Override - List populate(List mappings) { - List mappingsToDo = new ArrayList<>(mappings) - List mappingsFoundGlobal = new ArrayList<>() + public List populate(List mappings) { + List mappingsToDo = new ArrayList<>(mappings); + List mappingsFoundGlobal = new ArrayList<>(); for (String root : cfg.getServers()) { - log.info("{} mappings remaining: {}", mappingsToDo.size(), mappingsToDo) - log.info("Querying {}", root) - List mappingsFound = fetcher.find(root, mappingsToDo) - log.info("{} returned {} mappings", root, mappingsFound.size()) - mappingsFoundGlobal.addAll(mappingsFound) - mappingsToDo.removeAll(mappingsFound) + log.info("{} mappings remaining: {}", mappingsToDo.size(), mappingsToDo); + log.info("Querying {}", root); + List mappingsFound = fetcher.find(root, mappingsToDo); + log.info("{} returned {} mappings", root, mappingsFound.size()); + mappingsFoundGlobal.addAll(mappingsFound); + mappingsToDo.removeAll(mappingsFound); } - return mappingsFoundGlobal + return mappingsFoundGlobal; } } diff --git a/src/main/groovy/io/kamax/mxisd/lookup/provider/IThreePidProvider.groovy b/src/main/java/io/kamax/mxisd/lookup/provider/IThreePidProvider.java similarity index 70% rename from src/main/groovy/io/kamax/mxisd/lookup/provider/IThreePidProvider.groovy rename to src/main/java/io/kamax/mxisd/lookup/provider/IThreePidProvider.java index 614cc04..204a813 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/provider/IThreePidProvider.groovy +++ b/src/main/java/io/kamax/mxisd/lookup/provider/IThreePidProvider.java @@ -18,25 +18,28 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.lookup.provider +package io.kamax.mxisd.lookup.provider; -import io.kamax.mxisd.lookup.SingleLookupReply -import io.kamax.mxisd.lookup.SingleLookupRequest -import io.kamax.mxisd.lookup.ThreePidMapping +import io.kamax.mxisd.lookup.SingleLookupReply; +import io.kamax.mxisd.lookup.SingleLookupRequest; +import io.kamax.mxisd.lookup.ThreePidMapping; -interface IThreePidProvider { +import java.util.List; +import java.util.Optional; - boolean isEnabled() +public interface IThreePidProvider { - boolean isLocal() + boolean isEnabled(); + + boolean isLocal(); /** * Higher has more priority */ - int getPriority() // Should not be here but let's KISS for now + int getPriority(); // Should not be here but let's KISS for now - Optional find(SingleLookupRequest request) + Optional find(SingleLookupRequest request); - List populate(List mappings) + List populate(List mappings); } diff --git a/src/main/java/io/kamax/mxisd/lookup/provider/RemoteIdentityServerFetcher.java b/src/main/java/io/kamax/mxisd/lookup/provider/RemoteIdentityServerFetcher.java new file mode 100644 index 0000000..bec8bd3 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/lookup/provider/RemoteIdentityServerFetcher.java @@ -0,0 +1,128 @@ +/* + * 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 com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import io.kamax.mxisd.controller.v1.ClientBulkLookupRequest; +import io.kamax.mxisd.exception.InvalidResponseJsonException; +import io.kamax.mxisd.lookup.SingleLookupReply; +import io.kamax.mxisd.lookup.SingleLookupRequest; +import io.kamax.mxisd.lookup.ThreePidMapping; +import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher; +import io.kamax.mxisd.matrix.IdentityServerUtils; +import io.kamax.mxisd.util.GsonParser; +import io.kamax.mxisd.util.RestClientUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Component +@Scope("prototype") +@Lazy +public class RemoteIdentityServerFetcher implements IRemoteIdentityServerFetcher { + + private Logger log = LoggerFactory.getLogger(RemoteIdentityServerFetcher.class); + + private Gson gson = new Gson(); + private GsonParser parser = new GsonParser(gson); + + @Override + public boolean isUsable(String remote) { + return IdentityServerUtils.isUsable(remote); + } + + @Override + public Optional find(String remote, SingleLookupRequest request) { + log.info("Looking up {} 3PID {} using {}", request.getType(), request.getThreePid(), remote); + + try { + HttpURLConnection rootSrvConn = (HttpURLConnection) new URL( + remote + "/_matrix/identity/api/v1/lookup?medium=" + request.getType() + "&address=" + request.getThreePid() + ).openConnection(); + JsonObject obj = parser.parse(rootSrvConn.getInputStream()); + if (obj.has("address")) { + log.info("Found 3PID mapping: {}", gson.toJson(obj)); + + return Optional.of(SingleLookupReply.fromRecursive(request, gson.toJson(obj))); + } + + log.info("Empty 3PID mapping from {}", remote); + return Optional.empty(); + } catch (IOException e) { + log.warn("Error looking up 3PID mapping {}: {}", request.getThreePid(), e.getMessage()); + return Optional.empty(); + } catch (JsonParseException e) { + log.warn("Invalid JSON answer from {}", remote); + return Optional.empty(); + } + } + + @Override + public List find(String remote, List mappings) { + List mappingsFound = new ArrayList<>(); + + ClientBulkLookupRequest mappingRequest = new ClientBulkLookupRequest(); + mappingRequest.setMappings(mappings); + + String url = remote + "/_matrix/identity/api/v1/bulk_lookup"; + CloseableHttpClient client = HttpClients.createDefault(); + try { + HttpPost request = RestClientUtils.post(url, mappingRequest); + try (CloseableHttpResponse response = client.execute(request)) { + if (response.getStatusLine().getStatusCode() != 200) { + log.info("Could not perform lookup at {} due to HTTP return code: {}", url, response.getStatusLine().getStatusCode()); + return mappingsFound; + } + + ClientBulkLookupRequest input = parser.parse(response, ClientBulkLookupRequest.class); + for (List mappingRaw : input.getThreepids()) { + ThreePidMapping mapping = new ThreePidMapping(); + mapping.setMedium(mappingRaw.get(0)); + mapping.setValue(mappingRaw.get(1)); + mapping.setMxid(mappingRaw.get(2)); + mappingsFound.add(mapping); + } + } + } catch (IOException e) { + log.warn("Unable to fetch remote lookup data: {}", e.getMessage()); + } catch (InvalidResponseJsonException e) { + log.info("HTTP response from {} was empty/invalid", remote); + } + + return mappingsFound; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy b/src/main/java/io/kamax/mxisd/lookup/strategy/LookupStrategy.java similarity index 68% rename from src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy rename to src/main/java/io/kamax/mxisd/lookup/strategy/LookupStrategy.java index 44b67f0..66d3d84 100644 --- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy +++ b/src/main/java/io/kamax/mxisd/lookup/strategy/LookupStrategy.java @@ -18,28 +18,31 @@ * along with this program. If not, see . */ -package io.kamax.mxisd.lookup.strategy +package io.kamax.mxisd.lookup.strategy; -import io.kamax.mxisd.lookup.BulkLookupRequest -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.lookup.BulkLookupRequest; +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; -interface LookupStrategy { +import java.util.List; +import java.util.Optional; - List getLocalProviders() +public interface LookupStrategy { - Optional find(String medium, String address, boolean recursive) + List getLocalProviders(); + + Optional find(String medium, String address, boolean recursive); Optional findLocal(String medium, String address); Optional findRemote(String medium, String address); - Optional find(SingleLookupRequest request) + Optional find(SingleLookupRequest request); - Optional findRecursive(SingleLookupRequest request) + Optional findRecursive(SingleLookupRequest request); - List find(BulkLookupRequest requests) + List find(BulkLookupRequest requests); } diff --git a/src/main/java/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.java b/src/main/java/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.java new file mode 100644 index 0000000..7b17378 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.java @@ -0,0 +1,206 @@ +/* + * 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.strategy; + +import edazdarevic.commons.net.CIDRUtils; +import io.kamax.mxisd.config.RecursiveLookupConfig; +import io.kamax.mxisd.exception.ConfigurationException; +import io.kamax.mxisd.lookup.*; +import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher; +import io.kamax.mxisd.lookup.provider.IThreePidProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Component +public class RecursivePriorityLookupStrategy implements LookupStrategy { + + private Logger log = LoggerFactory.getLogger(RecursivePriorityLookupStrategy.class); + + @Autowired + private RecursiveLookupConfig recursiveCfg; + + @Autowired + private List providers; + + @Autowired + private IBridgeFetcher bridge; + + private List allowedCidr = new ArrayList<>(); + + @PostConstruct + private void build() throws UnknownHostException { + try { + log.info("Found {} providers", providers.size()); + + providers.sort((o1, o2) -> Integer.compare(o2.getPriority(), o1.getPriority())); + + log.info("Recursive lookup enabled: {}", recursiveCfg.isEnabled()); + for (String cidr : recursiveCfg.getAllowedCidr()) { + log.info("{} is allowed for recursion", cidr); + allowedCidr.add(new CIDRUtils(cidr)); + } + } catch (UnknownHostException e) { + throw new ConfigurationException("lookup.recursive.allowedCidrs", "Allowed CIDRs"); + } + } + + private boolean isAllowedForRecursive(String source) { + boolean canRecurse = false; + + try { + if (recursiveCfg.isEnabled()) { + log.debug("Checking {} CIDRs for recursion", allowedCidr.size()); + for (CIDRUtils cidr : allowedCidr) { + if (cidr.isInRange(source)) { + log.debug("{} is in range {}, allowing recursion", source, cidr.getNetworkAddress()); + canRecurse = true; + break; + } else { + log.debug("{} is not in range {}", source, cidr.getNetworkAddress()); + } + } + } + } catch (UnknownHostException e) { + // this should never happened as we should have only IP ranges! + log.error("Unexpected {} exception: {}", e.getClass().getSimpleName(), e.getMessage()); + } + + return canRecurse; + } + + private List listUsableProviders(ALookupRequest request) { + return listUsableProviders(request, false); + } + + private List listUsableProviders(ALookupRequest request, boolean forceRecursive) { + List usableProviders = new ArrayList<>(); + + boolean canRecurse = forceRecursive || isAllowedForRecursive(request.getRequester()); + + log.info("Host {} allowed for recursion: {}", request.getRequester(), canRecurse); + for (IThreePidProvider provider : providers) { + if (provider.isEnabled() && (provider.isLocal() || canRecurse || forceRecursive)) { + usableProviders.add(provider); + } + } + + return usableProviders; + } + + @Override + public List getLocalProviders() { + return providers.stream().filter(iThreePidProvider -> iThreePidProvider.isEnabled() && iThreePidProvider.isLocal()).collect(Collectors.toList()); + } + + public List getRemoteProviders() { + return providers.stream().filter(iThreePidProvider -> iThreePidProvider.isEnabled() && !iThreePidProvider.isLocal()).collect(Collectors.toList()); + } + + private static SingleLookupRequest build(String medium, String address) { + SingleLookupRequest req = new SingleLookupRequest(); + req.setType(medium); + req.setThreePid(address); + req.setRequester("Internal"); + return req; + } + + @Override + public Optional find(String medium, String address, boolean recursive) { + return find(build(medium, address), recursive); + } + + @Override + public Optional findLocal(String medium, String address) { + return find(build(medium, address), getLocalProviders()); + } + + @Override + public Optional findRemote(String medium, String address) { + return find(build(medium, address), getRemoteProviders()); + } + + public Optional find(SingleLookupRequest request, boolean forceRecursive) { + return find(request, listUsableProviders(request, forceRecursive)); + } + + public Optional find(SingleLookupRequest request, List providers) { + for (IThreePidProvider provider : providers) { + Optional lookupDataOpt = provider.find(request); + if (lookupDataOpt.isPresent()) { + return lookupDataOpt; + } + } + + if ( + recursiveCfg.getBridge() != null && + recursiveCfg.getBridge().getEnabled() && + (!recursiveCfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester())) + ) { + log.info("Using bridge failover for lookup"); + return bridge.find(request); + } + + return Optional.empty(); + } + + @Override + public Optional find(SingleLookupRequest request) { + return find(request, false); + } + + @Override + public Optional findRecursive(SingleLookupRequest request) { + return find(request, true); + } + + @Override + public List find(BulkLookupRequest request) { + List mapToDo = new ArrayList<>(request.getMappings()); + List mapFoundAll = new ArrayList<>(); + + for (IThreePidProvider provider : listUsableProviders(request)) { + if (mapToDo.isEmpty()) { + log.info("No more mappings to lookup"); + break; + } else { + log.info("{} mappings remaining overall", mapToDo.size()); + } + + log.info("Using provider {} for remaining mappings", provider.getClass().getSimpleName()); + List mapFound = provider.populate(mapToDo); + log.info("Provider {} returned {} mappings", provider.getClass().getSimpleName(), mapFound.size()); + mapFoundAll.addAll(mapFound); + mapToDo.removeAll(mapFound); + } + + return mapFoundAll; + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/matrix/IdentityServerUtils.java b/src/main/java/io/kamax/mxisd/matrix/IdentityServerUtils.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/matrix/IdentityServerUtils.java rename to src/main/java/io/kamax/mxisd/matrix/IdentityServerUtils.java diff --git a/src/main/groovy/io/kamax/mxisd/notification/INotificationHandler.java b/src/main/java/io/kamax/mxisd/notification/INotificationHandler.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/notification/INotificationHandler.java rename to src/main/java/io/kamax/mxisd/notification/INotificationHandler.java diff --git a/src/main/groovy/io/kamax/mxisd/notification/NotificationManager.java b/src/main/java/io/kamax/mxisd/notification/NotificationManager.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/notification/NotificationManager.java rename to src/main/java/io/kamax/mxisd/notification/NotificationManager.java diff --git a/src/main/groovy/io/kamax/mxisd/session/SessionMananger.java b/src/main/java/io/kamax/mxisd/session/SessionMananger.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/session/SessionMananger.java rename to src/main/java/io/kamax/mxisd/session/SessionMananger.java diff --git a/src/main/groovy/io/kamax/mxisd/session/ValidationResult.java b/src/main/java/io/kamax/mxisd/session/ValidationResult.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/session/ValidationResult.java rename to src/main/java/io/kamax/mxisd/session/ValidationResult.java diff --git a/src/main/java/io/kamax/mxisd/signature/SignatureManager.java b/src/main/java/io/kamax/mxisd/signature/SignatureManager.java new file mode 100644 index 0000000..6c45803 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/signature/SignatureManager.java @@ -0,0 +1,79 @@ +/* + * 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.signature; + +import com.google.gson.JsonObject; +import io.kamax.mxisd.config.ServerConfig; +import io.kamax.mxisd.exception.InternalServerError; +import io.kamax.mxisd.key.KeyManager; +import net.i2p.crypto.eddsa.EdDSAEngine; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; +import java.util.Base64; + +@Component +public class SignatureManager { + + @Autowired + private KeyManager keyMgr; + + @Autowired + private ServerConfig srvCfg; + + private EdDSAEngine signEngine; + + private String sign(String message) { + try { + byte[] signRaw = signEngine.signOneShot(message.getBytes()); + return Base64.getEncoder().encodeToString(signRaw); + } catch (SignatureException e) { + throw new InternalServerError(e); + } + } + + public JsonObject signMessageGson(String message) { + String sign = sign(message); + + JsonObject keySignature = new JsonObject(); + keySignature.addProperty("ed25519:" + keyMgr.getCurrentIndex(), sign); + JsonObject signature = new JsonObject(); + signature.add(srvCfg.getName(), keySignature); + + return signature; + } + + @PostConstruct + public void build() { + try { + signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getSpecs().getHashAlgorithm())); + signEngine.initSign(keyMgr.getPrivateKey(keyMgr.getCurrentIndex())); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/groovy/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java b/src/main/java/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java rename to src/main/java/io/kamax/mxisd/spring/ConfigurationFailureAnalyzer.java diff --git a/src/main/groovy/io/kamax/mxisd/storage/IStorage.java b/src/main/java/io/kamax/mxisd/storage/IStorage.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/storage/IStorage.java rename to src/main/java/io/kamax/mxisd/storage/IStorage.java diff --git a/src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java b/src/main/java/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java rename to src/main/java/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java diff --git a/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java b/src/main/java/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java rename to src/main/java/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java diff --git a/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorageBeanFactory.java b/src/main/java/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorageBeanFactory.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorageBeanFactory.java rename to src/main/java/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorageBeanFactory.java diff --git a/src/main/groovy/io/kamax/mxisd/storage/ormlite/ThreePidInviteIO.java b/src/main/java/io/kamax/mxisd/storage/ormlite/ThreePidInviteIO.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/storage/ormlite/ThreePidInviteIO.java rename to src/main/java/io/kamax/mxisd/storage/ormlite/ThreePidInviteIO.java diff --git a/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java b/src/main/java/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java rename to src/main/java/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java diff --git a/src/main/groovy/io/kamax/mxisd/threepid/connector/IThreePidConnector.java b/src/main/java/io/kamax/mxisd/threepid/connector/IThreePidConnector.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/threepid/connector/IThreePidConnector.java rename to src/main/java/io/kamax/mxisd/threepid/connector/IThreePidConnector.java diff --git a/src/main/groovy/io/kamax/mxisd/threepid/connector/email/EmailSmtpConnector.java b/src/main/java/io/kamax/mxisd/threepid/connector/email/EmailSmtpConnector.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/threepid/connector/email/EmailSmtpConnector.java rename to src/main/java/io/kamax/mxisd/threepid/connector/email/EmailSmtpConnector.java diff --git a/src/main/groovy/io/kamax/mxisd/threepid/connector/email/IEmailConnector.java b/src/main/java/io/kamax/mxisd/threepid/connector/email/IEmailConnector.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/threepid/connector/email/IEmailConnector.java rename to src/main/java/io/kamax/mxisd/threepid/connector/email/IEmailConnector.java diff --git a/src/main/groovy/io/kamax/mxisd/threepid/notification/INotificationGenerator.java b/src/main/java/io/kamax/mxisd/threepid/notification/INotificationGenerator.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/threepid/notification/INotificationGenerator.java rename to src/main/java/io/kamax/mxisd/threepid/notification/INotificationGenerator.java diff --git a/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java b/src/main/java/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java rename to src/main/java/io/kamax/mxisd/threepid/notification/email/EmailNotificationGenerator.java diff --git a/src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationHandler.java b/src/main/java/io/kamax/mxisd/threepid/notification/email/EmailNotificationHandler.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/threepid/notification/email/EmailNotificationHandler.java rename to src/main/java/io/kamax/mxisd/threepid/notification/email/EmailNotificationHandler.java diff --git a/src/main/groovy/io/kamax/mxisd/threepid/notification/email/IEmailNotificationGenerator.java b/src/main/java/io/kamax/mxisd/threepid/notification/email/IEmailNotificationGenerator.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/threepid/notification/email/IEmailNotificationGenerator.java rename to src/main/java/io/kamax/mxisd/threepid/notification/email/IEmailNotificationGenerator.java diff --git a/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java b/src/main/java/io/kamax/mxisd/threepid/session/IThreePidSession.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java rename to src/main/java/io/kamax/mxisd/threepid/session/IThreePidSession.java diff --git a/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java b/src/main/java/io/kamax/mxisd/threepid/session/ThreePidSession.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java rename to src/main/java/io/kamax/mxisd/threepid/session/ThreePidSession.java diff --git a/src/main/groovy/io/kamax/mxisd/util/GsonParser.java b/src/main/java/io/kamax/mxisd/util/GsonParser.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/util/GsonParser.java rename to src/main/java/io/kamax/mxisd/util/GsonParser.java diff --git a/src/main/groovy/io/kamax/mxisd/util/JsonUtils.java b/src/main/java/io/kamax/mxisd/util/JsonUtils.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/util/JsonUtils.java rename to src/main/java/io/kamax/mxisd/util/JsonUtils.java diff --git a/src/main/groovy/io/kamax/mxisd/util/RestClientUtils.java b/src/main/java/io/kamax/mxisd/util/RestClientUtils.java similarity index 100% rename from src/main/groovy/io/kamax/mxisd/util/RestClientUtils.java rename to src/main/java/io/kamax/mxisd/util/RestClientUtils.java