diff --git a/src/main/java/io/kamax/mxisd/HttpMxisd.java b/src/main/java/io/kamax/mxisd/HttpMxisd.java index 6197815..8f861a3 100644 --- a/src/main/java/io/kamax/mxisd/HttpMxisd.java +++ b/src/main/java/io/kamax/mxisd/HttpMxisd.java @@ -199,7 +199,10 @@ public class HttpMxisd { } private void hashEndpoints(RoutingHandler routingHandler) { - routingHandler.get(HashDetailsHandler.PATH, AuthorizationHandler.around(m.getAccMgr(), sane(new HashDetailsHandler(m.getHashManager())))); + routingHandler + .get(HashDetailsHandler.PATH, AuthorizationHandler.around(m.getAccMgr(), sane(new HashDetailsHandler(m.getHashManager())))); + routingHandler.post(HashLookupHandler.Path, + AuthorizationHandler.around(m.getAccMgr(), sane(new HashLookupHandler(m.getIdentity(), m.getHashManager())))); } private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) { diff --git a/src/main/java/io/kamax/mxisd/Mxisd.java b/src/main/java/io/kamax/mxisd/Mxisd.java index fa09208..9ada30a 100644 --- a/src/main/java/io/kamax/mxisd/Mxisd.java +++ b/src/main/java/io/kamax/mxisd/Mxisd.java @@ -119,7 +119,10 @@ public class Mxisd { ServiceLoader.load(IdentityStoreSupplier.class).iterator().forEachRemaining(p -> p.accept(this)); ServiceLoader.load(NotificationHandlerSupplier.class).iterator().forEachRemaining(p -> p.accept(this)); - idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher); + hashManager = new HashManager(); + hashManager.init(cfg.getHashing(), ThreePidProviders.get()); + + idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher, hashManager); pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient); notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get()); sessMgr = new SessionManager(cfg, store, notifMgr, resolver, httpClient, signMgr); @@ -129,9 +132,6 @@ public class Mxisd { regMgr = new RegistrationManager(cfg.getRegister(), httpClient, clientDns, invMgr); asHander = new AppSvcManager(this); accMgr = new AccountManager(store, resolver, getHttpClient(), cfg.getAccountConfig(), cfg.getMatrix()); - - hashManager = new HashManager(); - hashManager.init(cfg.getHashing(), ThreePidProviders.get()); } public MxisdConfig getConfig() { diff --git a/src/main/java/io/kamax/mxisd/exception/InvalidParamException.java b/src/main/java/io/kamax/mxisd/exception/InvalidParamException.java new file mode 100644 index 0000000..571329f --- /dev/null +++ b/src/main/java/io/kamax/mxisd/exception/InvalidParamException.java @@ -0,0 +1,27 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2017 Kamax Sarl + * + * https://www.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.exception; + +public class InvalidParamException extends RuntimeException { + + public InvalidParamException() { + } +} diff --git a/src/main/java/io/kamax/mxisd/exception/InvalidPepperException.java b/src/main/java/io/kamax/mxisd/exception/InvalidPepperException.java new file mode 100644 index 0000000..59bc1ff --- /dev/null +++ b/src/main/java/io/kamax/mxisd/exception/InvalidPepperException.java @@ -0,0 +1,27 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2017 Kamax Sarl + * + * https://www.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.exception; + +public class InvalidPepperException extends RuntimeException { + + public InvalidPepperException() { + } +} diff --git a/src/main/java/io/kamax/mxisd/hash/HashManager.java b/src/main/java/io/kamax/mxisd/hash/HashManager.java index 4365bf3..df89904 100644 --- a/src/main/java/io/kamax/mxisd/hash/HashManager.java +++ b/src/main/java/io/kamax/mxisd/hash/HashManager.java @@ -11,6 +11,7 @@ import io.kamax.mxisd.lookup.provider.IThreePidProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; diff --git a/src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java b/src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java index 3eeaf8b..0ff6c68 100644 --- a/src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java +++ b/src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java @@ -1,13 +1,15 @@ package io.kamax.mxisd.hash.storage; import io.kamax.mxisd.lookup.ThreePidMapping; +import org.apache.commons.lang3.tuple.Pair; +import java.util.Collection; import java.util.Collections; public class EmptyStorage implements HashStorage { @Override - public Iterable find(Iterable hashes) { + public Collection> find(Iterable hashes) { return Collections.emptyList(); } diff --git a/src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java b/src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java index 6101ae3..cb40519 100644 --- a/src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java +++ b/src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java @@ -1,10 +1,13 @@ package io.kamax.mxisd.hash.storage; import io.kamax.mxisd.lookup.ThreePidMapping; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.Collection; public interface HashStorage { - Iterable find(Iterable hashes); + Collection> find(Iterable hashes); void add(ThreePidMapping pidMapping, String hash); diff --git a/src/main/java/io/kamax/mxisd/hash/storage/InMemoryHashStorage.java b/src/main/java/io/kamax/mxisd/hash/storage/InMemoryHashStorage.java index 2cb714c..903708d 100644 --- a/src/main/java/io/kamax/mxisd/hash/storage/InMemoryHashStorage.java +++ b/src/main/java/io/kamax/mxisd/hash/storage/InMemoryHashStorage.java @@ -1,8 +1,10 @@ package io.kamax.mxisd.hash.storage; import io.kamax.mxisd.lookup.ThreePidMapping; +import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -12,12 +14,12 @@ public class InMemoryHashStorage implements HashStorage { private final Map mapping = new ConcurrentHashMap<>(); @Override - public Iterable find(Iterable hashes) { - List result = new ArrayList<>(); + public Collection> find(Iterable hashes) { + List> result = new ArrayList<>(); for (String hash : hashes) { ThreePidMapping pidMapping = mapping.get(hash); if (pidMapping != null) { - result.add(pidMapping); + result.add(Pair.of(hash, pidMapping)); } } return result; diff --git a/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupAnswer.java b/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupAnswer.java new file mode 100644 index 0000000..3473bb3 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupAnswer.java @@ -0,0 +1,33 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2017 Kamax Sarl + * + * https://www.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.http.io.identity; + +import java.util.HashMap; +import java.util.Map; + +public class ClientHashLookupAnswer { + + private Map mappings = new HashMap<>(); + + public Map getMappings() { + return mappings; + } +} diff --git a/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupRequest.java b/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupRequest.java new file mode 100644 index 0000000..1c8b171 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/http/io/identity/ClientHashLookupRequest.java @@ -0,0 +1,55 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2017 Kamax Sarl + * + * https://www.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.http.io.identity; + +import java.util.ArrayList; +import java.util.List; + +public class ClientHashLookupRequest { + + private String algorithm; + private String pepper; + private List addresses = new ArrayList<>(); + + public String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + public String getPepper() { + return pepper; + } + + public void setPepper(String pepper) { + this.pepper = pepper; + } + + public List getAddresses() { + return addresses; + } + + public void setAddresses(List addresses) { + this.addresses = addresses; + } +} diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/SaneHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/SaneHandler.java index 37dd79d..eecdc40 100644 --- a/src/main/java/io/kamax/mxisd/http/undertow/handler/SaneHandler.java +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/SaneHandler.java @@ -87,6 +87,10 @@ public class SaneHandler extends BasicHttpHandler { respond(exchange, HttpStatus.SC_NOT_FOUND, "M_NOT_FOUND", e.getMessage()); } catch (NotImplementedException e) { respond(exchange, HttpStatus.SC_NOT_IMPLEMENTED, "M_NOT_IMPLEMENTED", e.getMessage()); + } catch (InvalidPepperException e) { + respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_INVALID_PEPPER", e.getMessage()); + } catch (InvalidParamException e) { + respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_INVALID_PARAM", e.getMessage()); } catch (FeatureNotAvailable e) { if (StringUtils.isNotBlank(e.getInternalReason())) { log.error("Feature not available: {}", e.getInternalReason()); diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/HashLookupHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/HashLookupHandler.java new file mode 100644 index 0000000..049ebf4 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/HashLookupHandler.java @@ -0,0 +1,116 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2018 Kamax Sarl + * + * https://www.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.http.undertow.handler.identity.v1; + +import io.kamax.mxisd.exception.InvalidParamException; +import io.kamax.mxisd.exception.InvalidPepperException; +import io.kamax.mxisd.hash.HashManager; +import io.kamax.mxisd.http.IsAPIv2; +import io.kamax.mxisd.http.io.identity.ClientHashLookupAnswer; +import io.kamax.mxisd.http.io.identity.ClientHashLookupRequest; +import io.kamax.mxisd.http.undertow.handler.ApiHandler; +import io.kamax.mxisd.http.undertow.handler.identity.share.LookupHandler; +import io.kamax.mxisd.lookup.BulkLookupRequest; +import io.kamax.mxisd.lookup.HashLookupRequest; +import io.kamax.mxisd.lookup.ThreePidMapping; +import io.kamax.mxisd.lookup.strategy.LookupStrategy; +import io.undertow.server.HttpServerExchange; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class HashLookupHandler extends LookupHandler implements ApiHandler { + + public static final String Path = IsAPIv2.Base + "/lookup"; + + private static final Logger log = LoggerFactory.getLogger(HashLookupHandler.class); + + private LookupStrategy strategy; + private HashManager hashManager; + + public HashLookupHandler(LookupStrategy strategy, HashManager hashManager) { + this.strategy = strategy; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + ClientHashLookupRequest input = parseJsonTo(exchange, ClientHashLookupRequest.class); + HashLookupRequest lookupRequest = new HashLookupRequest(); + setRequesterInfo(lookupRequest, exchange); + log.info("Got bulk lookup request from {} with client {} - Is recursive? {}", + lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive()); + + if (!hashManager.getHashEngine().getPepper().equals(input.getPepper())) { + throw new InvalidPepperException(); + } + + switch (input.getAlgorithm()) { + case "none": + noneAlgorithm(exchange, lookupRequest, input); + break; + case "sha256": + sha256Algorithm(exchange, lookupRequest, input); + break; + default: + throw new InvalidParamException(); + } + } + + private void noneAlgorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) throws Exception { + BulkLookupRequest bulkLookupRequest = new BulkLookupRequest(); + List mappings = new ArrayList<>(); + for (String address : input.getAddresses()) { + String[] parts = address.split(" "); + ThreePidMapping mapping = new ThreePidMapping(); + mapping.setMedium(parts[0]); + mapping.setValue(parts[1]); + mappings.add(mapping); + } + bulkLookupRequest.setMappings(mappings); + + ClientHashLookupAnswer answer = new ClientHashLookupAnswer(); + + for (ThreePidMapping mapping : strategy.find(bulkLookupRequest).get()) { + answer.getMappings().put(mapping.getMedium() + " " + mapping.getValue(), mapping.getMxid()); + } + log.info("Finished bulk lookup request from {}", request.getRequester()); + + respondJson(exchange, answer); + } + + private void sha256Algorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) { + ClientHashLookupAnswer answer = new ClientHashLookupAnswer(); + for (Pair pair : hashManager.getHashStorage().find(request.getHashes())) { + answer.getMappings().put(pair.getKey(), pair.getValue().getMxid()); + } + log.info("Finished bulk lookup request from {}", request.getRequester()); + + respondJson(exchange, answer); + } + + @Override + public String getHandlerPath() { + return "/bulk_lookup"; + } +} diff --git a/src/main/java/io/kamax/mxisd/lookup/HashLookupRequest.java b/src/main/java/io/kamax/mxisd/lookup/HashLookupRequest.java new file mode 100644 index 0000000..5792ff9 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/lookup/HashLookupRequest.java @@ -0,0 +1,16 @@ +package io.kamax.mxisd.lookup; + +import java.util.List; + +public class HashLookupRequest extends ALookupRequest { + + private List hashes; + + public List getHashes() { + return hashes; + } + + public void setHashes(List hashes) { + this.hashes = hashes; + } +} diff --git a/src/main/java/io/kamax/mxisd/lookup/strategy/LookupStrategy.java b/src/main/java/io/kamax/mxisd/lookup/strategy/LookupStrategy.java index 3007249..5e31f08 100644 --- a/src/main/java/io/kamax/mxisd/lookup/strategy/LookupStrategy.java +++ b/src/main/java/io/kamax/mxisd/lookup/strategy/LookupStrategy.java @@ -21,6 +21,7 @@ package io.kamax.mxisd.lookup.strategy; import io.kamax.mxisd.lookup.BulkLookupRequest; +import io.kamax.mxisd.lookup.HashLookupRequest; import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.ThreePidMapping; @@ -46,4 +47,5 @@ public interface LookupStrategy { CompletableFuture> find(BulkLookupRequest requests); + CompletableFuture> find(HashLookupRequest request); } diff --git a/src/main/java/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.java b/src/main/java/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.java index 3589eb7..39fa89d 100644 --- a/src/main/java/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.java +++ b/src/main/java/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.java @@ -25,10 +25,13 @@ import io.kamax.matrix.json.GsonUtil; import io.kamax.matrix.json.MatrixJson; import io.kamax.mxisd.config.MxisdConfig; import io.kamax.mxisd.exception.ConfigurationException; +import io.kamax.mxisd.hash.HashManager; +import io.kamax.mxisd.hash.storage.HashStorage; import io.kamax.mxisd.lookup.*; import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher; import io.kamax.mxisd.lookup.provider.IThreePidProvider; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,10 +53,14 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy { private List allowedCidr = new ArrayList<>(); - public RecursivePriorityLookupStrategy(MxisdConfig.Lookup cfg, List providers, IBridgeFetcher bridge) { + private HashManager hashManager; + + public RecursivePriorityLookupStrategy(MxisdConfig.Lookup cfg, List providers, IBridgeFetcher bridge, + HashManager hashManager) { this.cfg = cfg; this.bridge = bridge; this.providers = new ArrayList<>(providers); + this.hashManager = hashManager; try { log.info("Found {} providers", providers.size()); @@ -65,6 +72,8 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy { log.info("{} is allowed for recursion", cidr); allowedCidr.add(new CIDRUtils(cidr)); } + + log.info("Hash lookups enabled: {}", hashManager.getConfig().isEnabled()); } catch (UnknownHostException e) { throw new ConfigurationException("lookup.recursive.allowedCidrs", "Allowed CIDRs"); } @@ -154,20 +163,20 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy { Optional lookupDataOpt = provider.find(request); if (lookupDataOpt.isPresent()) { log.info("Found 3PID mapping: {medium: '{}', address: '{}', mxid: '{}'}", - request.getType(), request.getThreePid(), lookupDataOpt.get().getMxid().getId()); + request.getType(), request.getThreePid(), lookupDataOpt.get().getMxid().getId()); return lookupDataOpt; } } if ( - cfg.getRecursive().getBridge() != null && - cfg.getRecursive().getBridge().getEnabled() && - (!cfg.getRecursive().getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester())) + cfg.getRecursive().getBridge() != null && + cfg.getRecursive().getBridge().getEnabled() && + (!cfg.getRecursive().getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester())) ) { log.info("Using bridge failover for lookup"); Optional lookupDataOpt = bridge.find(request); log.info("Found 3PID mapping: {medium: '{}', address: '{}', mxid: '{}'}", - request.getThreePid(), request.getId(), lookupDataOpt.get().getMxid().getId()); + request.getThreePid(), request.getId(), lookupDataOpt.get().getMxid().getId()); return lookupDataOpt; } @@ -230,4 +239,12 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy { return bulkLookupInProgress.remove(payloadId); } + @Override + public CompletableFuture> find(HashLookupRequest request) { + HashStorage hashStorage = hashManager.getHashStorage(); + CompletableFuture> result = new CompletableFuture<>(); + result.complete(hashStorage.find(request.getHashes()).stream().map(Pair::getValue).collect(Collectors.toList())); + hashManager.getRotationStrategy().newRequest(); + return result; + } }