Bye bye Groovy, you won't be missed :(

This commit is contained in:
Maxime Dor
2017-09-25 02:31:31 +02:00
parent af19fed6e7
commit 33263d3cff
140 changed files with 1711 additions and 1678 deletions

View File

@@ -0,0 +1,74 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.lookup.provider;
import io.kamax.mxisd.config.RecursiveLookupBridgeConfig;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher;
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.Collections;
import java.util.List;
import java.util.Optional;
@Component
public class BridgeFetcher implements IBridgeFetcher {
private Logger log = LoggerFactory.getLogger(BridgeFetcher.class);
@Autowired
private RecursiveLookupBridgeConfig cfg;
@Autowired
private RemoteIdentityServerFetcher fetcher;
@Override
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
Optional<String> mediumUrl = Optional.ofNullable(cfg.getMappings().get(request.getType()));
if (mediumUrl.isPresent() && !StringUtils.isBlank(mediumUrl.get())) {
log.info("Using specific medium bridge lookup URL {}", mediumUrl.get());
return fetcher.find(mediumUrl.get(), request);
} else if (!StringUtils.isBlank(cfg.getServer())) {
log.info("Using generic bridge lookup URL {}", cfg.getServer());
return fetcher.find(cfg.getServer(), request);
} else {
log.info("No bridge lookup URL found/configured, skipping");
return Optional.empty();
}
}
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
log.warn("Bulk lookup on bridge lookup requested, but not supported - returning empty list");
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,181 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.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 java.util.*;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
@Component
class DnsLookupProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(DnsLookupProvider.class);
@Autowired
private MatrixConfig mxCfg;
@Autowired
private IRemoteIdentityServerFetcher fetcher;
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isLocal() {
return false;
}
@Override
public int getPriority() {
return 10;
}
private Optional<String> getDomain(String email) {
int atIndex = email.lastIndexOf("@");
if (atIndex == -1) {
return Optional.empty();
}
return Optional.of(email.substring(atIndex + 1));
}
// TODO use caching mechanism
private Optional<String> findIdentityServerForDomain(String domain) {
if (StringUtils.equals(mxCfg.getDomain(), domain)) {
log.info("We are authoritative for {}, no remote lookup", domain);
return Optional.empty();
}
return IdentityServerUtils.findIsUrlForDomain(domain);
}
@Override
public Optional<SingleLookupReply> 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("Performing DNS lookup for {}", request.getThreePid());
String domain = request.getThreePid().substring(request.getThreePid().lastIndexOf("@") + 1);
log.info("Domain name for {}: {}", request.getThreePid(), domain);
Optional<String> baseUrl = findIdentityServerForDomain(domain);
if (baseUrl.isPresent()) {
return fetcher.find(baseUrl.get(), request);
}
return Optional.empty();
}
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
Map<String, List<ThreePidMapping>> domains = new HashMap<>();
for (ThreePidMapping mapping : mappings) {
if (!StringUtils.equals("email", mapping.getMedium())) {
log.info("Skipping unsupported type {} for {}", mapping.getMedium(), mapping.getValue());
continue;
}
Optional<String> domainOpt = getDomain(mapping.getValue());
if (!domainOpt.isPresent()) {
log.warn("No domain for 3PID {}", mapping.getValue());
continue;
}
String domain = domainOpt.get();
List<ThreePidMapping> domainMappings = domains.computeIfAbsent(domain, s -> new ArrayList<>());
domainMappings.add(mapping);
}
log.info("Looking mappings across {} domains", domains.keySet().size());
ForkJoinPool pool = ForkJoinPool.commonPool();
RecursiveTask<List<ThreePidMapping>> task = new RecursiveTask<List<ThreePidMapping>>() {
@Override
protected List<ThreePidMapping> compute() {
List<ThreePidMapping> mappingsFound = new ArrayList<>();
List<DomainBulkLookupTask> tasks = new ArrayList<>();
for (String domain : domains.keySet()) {
DomainBulkLookupTask domainTask = new DomainBulkLookupTask(domain, domains.get(domain));
domainTask.fork();
tasks.add(domainTask);
}
for (DomainBulkLookupTask task : tasks) {
mappingsFound.addAll(task.join());
}
return mappingsFound;
}
};
pool.submit(task);
pool.shutdown();
List<ThreePidMapping> mappingsFound = task.join();
log.info("Found {} mappings overall", mappingsFound.size());
return mappingsFound;
}
private class DomainBulkLookupTask extends RecursiveTask<List<ThreePidMapping>> {
private String domain;
private List<ThreePidMapping> mappings;
DomainBulkLookupTask(String domain, List<ThreePidMapping> mappings) {
this.domain = domain;
this.mappings = mappings;
}
@Override
protected List<ThreePidMapping> compute() {
List<ThreePidMapping> domainMappings = new ArrayList<>();
Optional<String> baseUrl = findIdentityServerForDomain(domain);
if (!baseUrl.isPresent()) {
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);
}
return domainMappings;
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.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 java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Component
class ForwarderProvider implements IThreePidProvider {
private Logger log = LoggerFactory.getLogger(ForwarderProvider.class);
@Autowired
private ForwardConfig cfg;
@Autowired
private IRemoteIdentityServerFetcher fetcher;
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isLocal() {
return false;
}
@Override
public int getPriority() {
return 0;
}
@Override
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
for (String root : cfg.getServers()) {
Optional<SingleLookupReply> answer = fetcher.find(root, request);
if (answer.isPresent()) {
return answer;
}
}
return Optional.empty();
}
@Override
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
List<ThreePidMapping> mappingsToDo = new ArrayList<>(mappings);
List<ThreePidMapping> mappingsFoundGlobal = new ArrayList<>();
for (String root : cfg.getServers()) {
log.info("{} mappings remaining: {}", mappingsToDo.size(), mappingsToDo);
log.info("Querying {}", root);
List<ThreePidMapping> mappingsFound = fetcher.find(root, mappingsToDo);
log.info("{} returned {} mappings", root, mappingsFound.size());
mappingsFoundGlobal.addAll(mappingsFound);
mappingsToDo.removeAll(mappingsFound);
}
return mappingsFoundGlobal;
}
}

View File

@@ -0,0 +1,45 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Maxime Dor
*
* https://max.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.lookup.provider;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidMapping;
import java.util.List;
import java.util.Optional;
public interface IThreePidProvider {
boolean isEnabled();
boolean isLocal();
/**
* Higher has more priority
*/
int getPriority(); // Should not be here but let's KISS for now
Optional<SingleLookupReply> find(SingleLookupRequest request);
List<ThreePidMapping> populate(List<ThreePidMapping> mappings);
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<SingleLookupReply> 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<ThreePidMapping> find(String remote, List<ThreePidMapping> mappings) {
List<ThreePidMapping> 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<String> 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;
}
}