Compare commits

...

8 Commits

25 changed files with 361 additions and 180 deletions

47
docs/MSC2140_MSC2134.md Normal file
View File

@@ -0,0 +1,47 @@
# MSC2140
## V1 vs V2
In the [MSC2140](https://github.com/matrix-org/matrix-doc/pull/2140) the v2 prefix was introduced.
Default values:
```.yaml
matrix:
v1: true # deprecated
v2: true
```
To disable change value to `false`.
NOTE: the v1 is deprecated, therefore recommend to use only v2 and disable v1 (default value can be ommited):
```.yaml
matrix:
v1: false
```
## Terms
Example:
```.yaml
policy:
policies:
term_name: # term name
version: 1.0 # version
terms:
en: # lang
name: term name en # localized name
url: https://ma1sd.host.tld/term_en.html # localized url
fe: # lang
name: term name fr # localized name
url: https://ma1sd.host.tld/term_fr.html # localized url
regexp:
- '/_matrix/identity/v2/account.*'
- '/_matrix/identity/v2/hash_lookup'
```
Where:
- `term_name` -- name of the terms.
- `regexp` -- regexp patterns for API.
## Hash lookup

View File

@@ -48,6 +48,9 @@ Create a list under the label `myOtherServers` containing two Identity servers:
## Unbind (MSC1915) ## Unbind (MSC1915)
- `session.policy.unbind.enabled`: Enable or disable unbind functionality (MSC1915). (Defaults to true). - `session.policy.unbind.enabled`: Enable or disable unbind functionality (MSC1915). (Defaults to true).
## Hash lookups, Term and others (MSC2140, MSC2134)
See the [dedicated document](MSC2140_MSC2134.md) for configuration.
*Warning*: Unbind check incoming request by two ways: *Warning*: Unbind check incoming request by two ways:
- session validation. - session validation.
- request signature via `X-Matrix` header and uses `server.publicUrl` property to construct the signing json; - request signature via `X-Matrix` header and uses `server.publicUrl` property to construct the signing json;

View File

@@ -1,47 +0,0 @@
/*
* matrix-java-sdk - Matrix Client SDK for Java
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.matrix.codec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MxSha256 {
private MessageDigest md;
public MxSha256() {
try {
md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public String hash(byte[] data) {
return MxBase64.encode(md.digest(data));
}
public String hash(String data) {
return hash(data.getBytes(StandardCharsets.UTF_8));
}
}

View File

@@ -52,7 +52,8 @@ import io.kamax.mxisd.http.undertow.handler.identity.share.SessionValidationGetH
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionValidationPostHandler; import io.kamax.mxisd.http.undertow.handler.identity.share.SessionValidationPostHandler;
import io.kamax.mxisd.http.undertow.handler.identity.share.SignEd25519Handler; import io.kamax.mxisd.http.undertow.handler.identity.share.SignEd25519Handler;
import io.kamax.mxisd.http.undertow.handler.identity.share.StoreInviteHandler; import io.kamax.mxisd.http.undertow.handler.identity.share.StoreInviteHandler;
import io.kamax.mxisd.http.undertow.handler.identity.v1.*; import io.kamax.mxisd.http.undertow.handler.identity.v1.BulkLookupHandler;
import io.kamax.mxisd.http.undertow.handler.identity.v1.SingleLookupHandler;
import io.kamax.mxisd.http.undertow.handler.identity.v2.HashDetailsHandler; import io.kamax.mxisd.http.undertow.handler.identity.v2.HashDetailsHandler;
import io.kamax.mxisd.http.undertow.handler.identity.v2.HashLookupHandler; import io.kamax.mxisd.http.undertow.handler.identity.v2.HashLookupHandler;
import io.kamax.mxisd.http.undertow.handler.invite.v1.RoomInviteHandler; import io.kamax.mxisd.http.undertow.handler.invite.v1.RoomInviteHandler;
@@ -114,13 +115,6 @@ public class HttpMxisd {
.post(LoginHandler.Path, SaneHandler.around(new LoginPostHandler(m.getAuth()))) .post(LoginHandler.Path, SaneHandler.around(new LoginPostHandler(m.getAuth())))
.post(RestAuthHandler.Path, SaneHandler.around(new RestAuthHandler(m.getAuth()))) .post(RestAuthHandler.Path, SaneHandler.around(new RestAuthHandler(m.getAuth())))
// Account endpoints
.post(AccountRegisterHandler.Path, SaneHandler.around(new AccountRegisterHandler(m.getAccMgr())))
.get(AccountGetUserInfoHandler.Path,
SaneHandler.around(AuthorizationHandler.around(m.getAccMgr(), new AccountGetUserInfoHandler(m.getAccMgr()))))
.post(AccountLogoutHandler.Path,
SaneHandler.around(AuthorizationHandler.around(m.getAccMgr(), new AccountLogoutHandler(m.getAccMgr()))))
// Directory endpoints // Directory endpoints
.post(UserDirectorySearchHandler.Path, SaneHandler.around(new UserDirectorySearchHandler(m.getDirectory()))) .post(UserDirectorySearchHandler.Path, SaneHandler.around(new UserDirectorySearchHandler(m.getDirectory())))
@@ -150,6 +144,7 @@ public class HttpMxisd {
identityEndpoints(handler); identityEndpoints(handler);
termsEndpoints(handler); termsEndpoints(handler);
hashEndpoints(handler); hashEndpoints(handler);
accountEndpoints(handler);
httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(handler).build(); httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(handler).build();
httpSrv.start(); httpSrv.start();
@@ -193,17 +188,25 @@ public class HttpMxisd {
); );
} }
private void accountEndpoints(RoutingHandler routingHandler) {
routingHandler.post(AccountRegisterHandler.Path, SaneHandler.around(new AccountRegisterHandler(m.getAccMgr())));
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new AccountGetUserInfoHandler(m.getAccMgr())),
AccountGetUserInfoHandler.Path, true);
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new AccountLogoutHandler(m.getAccMgr())),
AccountLogoutHandler.Path, true);
}
private void termsEndpoints(RoutingHandler routingHandler) { private void termsEndpoints(RoutingHandler routingHandler) {
routingHandler.get(GetTermsHandler.PATH, new GetTermsHandler(m.getConfig().getPolicy())); routingHandler.get(GetTermsHandler.PATH, new GetTermsHandler(m.getConfig().getPolicy()));
routingHandler wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.POST, sane(new AcceptTermsHandler(m.getAccMgr())),
.post(AcceptTermsHandler.PATH, AuthorizationHandler.around(m.getAccMgr(), sane(new AcceptTermsHandler(m.getAccMgr())))); AcceptTermsHandler.PATH, true);
} }
private void hashEndpoints(RoutingHandler routingHandler) { private void hashEndpoints(RoutingHandler routingHandler) {
routingHandler wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new HashDetailsHandler(m.getHashManager())),
.get(HashDetailsHandler.PATH, AuthorizationHandler.around(m.getAccMgr(), sane(new HashDetailsHandler(m.getHashManager())))); HashDetailsHandler.PATH, true);
routingHandler.post(HashLookupHandler.Path, wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.POST,
AuthorizationHandler.around(m.getAccMgr(), sane(new HashLookupHandler(m.getIdentity(), m.getHashManager())))); sane(new HashLookupHandler(m.getIdentity(), m.getHashManager())), HashLookupHandler.Path, true);
} }
private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) { private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) {
@@ -219,20 +222,32 @@ public class HttpMxisd {
routingHandler.add(method, apiHandler.getPath(IdentityServiceAPI.V1), httpHandler); routingHandler.add(method, apiHandler.getPath(IdentityServiceAPI.V1), httpHandler);
} }
if (matrixConfig.isV2()) { if (matrixConfig.isV2()) {
HttpHandler handlerWithTerms = CheckTermsHandler.around(m.getAccMgr(), httpHandler, getPolicyObjects(apiHandler)); String path = apiHandler.getPath(IdentityServiceAPI.V2);
HttpHandler wrappedHandler = useAuthorization ? AuthorizationHandler.around(m.getAccMgr(), handlerWithTerms) : handlerWithTerms; wrapWithTokenAndAuthorizationHandlers(routingHandler, method, httpHandler, path, useAuthorization);
routingHandler.add(method, apiHandler.getPath(IdentityServiceAPI.V2), wrappedHandler);
} }
} }
private void wrapWithTokenAndAuthorizationHandlers(RoutingHandler routingHandler, HttpString method, HttpHandler httpHandler,
String url, boolean useAuthorization) {
List<PolicyConfig.PolicyObject> policyObjects = getPolicyObjects(url);
HttpHandler wrappedHandler;
if (useAuthorization) {
wrappedHandler = policyObjects.isEmpty() ? httpHandler : CheckTermsHandler.around(m.getAccMgr(), httpHandler, policyObjects);
wrappedHandler = AuthorizationHandler.around(m.getAccMgr(), wrappedHandler);
} else {
wrappedHandler = httpHandler;
}
routingHandler.add(method, url, wrappedHandler);
}
@NotNull @NotNull
private List<PolicyConfig.PolicyObject> getPolicyObjects(ApiHandler apiHandler) { private List<PolicyConfig.PolicyObject> getPolicyObjects(String url) {
PolicyConfig policyConfig = m.getConfig().getPolicy(); PolicyConfig policyConfig = m.getConfig().getPolicy();
List<PolicyConfig.PolicyObject> policies = new ArrayList<>(); List<PolicyConfig.PolicyObject> policies = new ArrayList<>();
if (!policyConfig.getPolicies().isEmpty()) { if (!policyConfig.getPolicies().isEmpty()) {
for (PolicyConfig.PolicyObject policy : policyConfig.getPolicies().values()) { for (PolicyConfig.PolicyObject policy : policyConfig.getPolicies().values()) {
for (Pattern pattern : policy.getPatterns()) { for (Pattern pattern : policy.getPatterns()) {
if (pattern.matcher(apiHandler.getHandlerPath()).matches()) { if (pattern.matcher(url).matches()) {
policies.add(policy); policies.add(policy);
} }
} }

View File

@@ -125,7 +125,7 @@ public class Mxisd {
idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher, hashManager); idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher, hashManager);
pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient); pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient);
notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get()); notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get());
sessMgr = new SessionManager(cfg, store, notifMgr, resolver, httpClient, signMgr); sessMgr = new SessionManager(cfg, store, notifMgr, resolver, signMgr);
invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, resolver, notifMgr, pMgr); invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, resolver, notifMgr, pMgr);
authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient); authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient);
dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get()); dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get());

View File

@@ -16,7 +16,7 @@ import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -62,7 +62,7 @@ public class AccountManager {
String token = UUID.randomUUID().toString(); String token = UUID.randomUUID().toString();
AccountDao account = new AccountDao(openIdToken.getAccessToken(), openIdToken.getTokenType(), AccountDao account = new AccountDao(openIdToken.getAccessToken(), openIdToken.getTokenType(),
openIdToken.getMatrixServerName(), openIdToken.getExpiredIn(), openIdToken.getMatrixServerName(), openIdToken.getExpiresIn(),
Instant.now().getEpochSecond(), userId, token); Instant.now().getEpochSecond(), userId, token);
storage.insertToken(account); storage.insertToken(account);
@@ -73,13 +73,14 @@ public class AccountManager {
private String getUserId(OpenIdToken openIdToken) { private String getUserId(OpenIdToken openIdToken) {
String matrixServerName = openIdToken.getMatrixServerName(); String matrixServerName = openIdToken.getMatrixServerName();
String homeserverURL = resolver.resolve(matrixServerName).toString(); HomeserverFederationResolver.HomeserverTarget homeserverTarget = resolver.resolve(matrixServerName);
String homeserverURL = homeserverTarget.getUrl().toString();
LOGGER.info("Domain resolved: {} => {}", matrixServerName, homeserverURL); LOGGER.info("Domain resolved: {} => {}", matrixServerName, homeserverURL);
HttpGet getUserInfo = new HttpGet( HttpGet getUserInfo = new HttpGet(
homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken()); homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken());
String userId; String userId;
try (CloseableHttpClient httpClient = HttpClientBuilder.create() try (CloseableHttpClient httpClient = HttpClients.custom()
.setSSLHostnameVerifier(new MatrixHostnameVerifier(matrixServerName)).build()) { .setSSLHostnameVerifier(new MatrixHostnameVerifier(homeserverTarget.getDomain())).build()) {
try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) { try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) {
int statusCode = response.getStatusLine().getStatusCode(); int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) { if (statusCode == HttpStatus.SC_OK) {

View File

@@ -1,14 +1,20 @@
package io.kamax.mxisd.auth; package io.kamax.mxisd.auth;
import com.google.gson.annotations.SerializedName;
public class OpenIdToken { public class OpenIdToken {
@SerializedName("access_token")
private String accessToken; private String accessToken;
@SerializedName("token_type")
private String tokenType; private String tokenType;
@SerializedName("matrix_server_name")
private String matrixServerName; private String matrixServerName;
private long expiredIn; @SerializedName("expires_in")
private long expiresIn;
public String getAccessToken() { public String getAccessToken() {
return accessToken; return accessToken;
@@ -34,11 +40,11 @@ public class OpenIdToken {
this.matrixServerName = matrixServerName; this.matrixServerName = matrixServerName;
} }
public long getExpiredIn() { public long getExpiresIn() {
return expiredIn; return expiresIn;
} }
public void setExpiredIn(long expiredIn) { public void setExpiresIn(long expiresIn) {
this.expiredIn = expiredIn; this.expiresIn = expiresIn;
} }
} }

View File

@@ -11,7 +11,7 @@ public class HashingConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(HashingConfig.class); private static final Logger LOGGER = LoggerFactory.getLogger(HashingConfig.class);
private boolean enabled = false; private boolean enabled = false;
private int pepperLength = 10; private int pepperLength = 20;
private RotationPolicyEnum rotationPolicy; private RotationPolicyEnum rotationPolicy;
private HashStorageEnum hashStorageType; private HashStorageEnum hashStorageType;
private long delay = 10; private long delay = 10;
@@ -23,27 +23,28 @@ public class HashingConfig {
LOGGER.info(" Pepper length: {}", getPepperLength()); LOGGER.info(" Pepper length: {}", getPepperLength());
LOGGER.info(" Rotation policy: {}", getRotationPolicy()); LOGGER.info(" Rotation policy: {}", getRotationPolicy());
LOGGER.info(" Hash storage type: {}", getHashStorageType()); LOGGER.info(" Hash storage type: {}", getHashStorageType());
if (RotationPolicyEnum.PER_SECONDS == rotationPolicy) { if (RotationPolicyEnum.per_seconds == rotationPolicy) {
LOGGER.info(" Rotation delay: {}", delay); LOGGER.info(" Rotation delay: {}", delay);
} }
LOGGER.info(" Algorithms: {}", algorithms);
} else { } else {
LOGGER.info("Hash configuration disabled, used only `none` pepper."); LOGGER.info("Hash configuration disabled, used only `none` pepper.");
} }
} }
public enum Algorithm { public enum Algorithm {
NONE, none,
SHA256 sha256
} }
public enum RotationPolicyEnum { public enum RotationPolicyEnum {
PER_REQUESTS, per_requests,
PER_SECONDS per_seconds
} }
public enum HashStorageEnum { public enum HashStorageEnum {
IN_MEMORY, in_memory,
SQL sql
} }
public boolean isEnabled() { public boolean isEnabled() {

View File

@@ -100,10 +100,12 @@ public class PolicyConfig {
policyObjectItem.getValue().getPatterns().add(Pattern.compile(regexp)); policyObjectItem.getValue().getPatterns().add(Pattern.compile(regexp));
} }
sb.append(" terms:\n"); sb.append(" terms:\n");
for (Map.Entry<String, TermObject> termItem : policyObject.getTerms().entrySet()) { if (policyObject.getTerms() != null) {
sb.append(" - lang: ").append(termItem.getKey()).append("\n"); for (Map.Entry<String, TermObject> termItem : policyObject.getTerms().entrySet()) {
sb.append(" name: ").append(termItem.getValue().getName()).append("\n"); sb.append(" - lang: ").append(termItem.getKey()).append("\n");
sb.append(" url: ").append(termItem.getValue().getUrl()).append("\n"); sb.append(" name: ").append(termItem.getValue().getName()).append("\n");
sb.append(" url: ").append(termItem.getValue().getUrl()).append("\n");
}
} }
LOGGER.info(sb.toString()); LOGGER.info(sb.toString());
} }

View File

@@ -125,7 +125,7 @@ public abstract class SqlConfig {
} }
public static class Lookup { public static class Lookup {
private String query; private String query = "SELECT user_id AS mxid, medium, address from user_threepids";
public String getQuery() { public String getQuery() {
return query; return query;

View File

@@ -1,19 +1,22 @@
package io.kamax.mxisd.hash; package io.kamax.mxisd.hash;
import io.kamax.matrix.codec.MxSha256;
import io.kamax.mxisd.config.HashingConfig; import io.kamax.mxisd.config.HashingConfig;
import io.kamax.mxisd.hash.storage.HashStorage; import io.kamax.mxisd.hash.storage.HashStorage;
import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.provider.IThreePidProvider; import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List; import java.util.List;
public class HashEngine { public class HashEngine {
private static final Logger LOGGER = LoggerFactory.getLogger(HashEngine.class);
private final List<? extends IThreePidProvider> providers; private final List<? extends IThreePidProvider> providers;
private final HashStorage hashStorage; private final HashStorage hashStorage;
private final MxSha256 sha256 = new MxSha256();
private final HashingConfig config; private final HashingConfig config;
private String pepper; private String pepper;
@@ -24,15 +27,21 @@ public class HashEngine {
} }
public void updateHashes() { public void updateHashes() {
LOGGER.info("Start update hashes.");
synchronized (hashStorage) { synchronized (hashStorage) {
this.pepper = newPepper(); this.pepper = newPepper();
hashStorage.clear(); hashStorage.clear();
for (IThreePidProvider provider : providers) { for (IThreePidProvider provider : providers) {
for (ThreePidMapping pidMapping : provider.populateHashes()) { try {
hashStorage.add(pidMapping, hash(pidMapping)); for (ThreePidMapping pidMapping : provider.populateHashes()) {
hashStorage.add(pidMapping, hash(pidMapping));
}
} catch (Exception e) {
LOGGER.error("Unable to update hashes of the provider: " + provider.toString(), e);
} }
} }
} }
LOGGER.info("Finish update hashes.");
} }
public String getPepper() { public String getPepper() {
@@ -42,10 +51,10 @@ public class HashEngine {
} }
protected String hash(ThreePidMapping pidMapping) { protected String hash(ThreePidMapping pidMapping) {
return sha256.hash(pidMapping.getMedium() + " " + pidMapping.getValue() + " " + getPepper()); return DigestUtils.sha256Hex(pidMapping.getValue() + " " + pidMapping.getMedium() + " " + getPepper());
} }
protected String newPepper() { protected String newPepper() {
return RandomStringUtils.random(config.getPepperLength()); return RandomStringUtils.random(config.getPepperLength(), true, true);
} }
} }

View File

@@ -40,10 +40,10 @@ public class HashManager {
private void initStorage() { private void initStorage() {
if (config.isEnabled()) { if (config.isEnabled()) {
switch (config.getHashStorageType()) { switch (config.getHashStorageType()) {
case IN_MEMORY: case in_memory:
this.hashStorage = new InMemoryHashStorage(); this.hashStorage = new InMemoryHashStorage();
break; break;
case SQL: case sql:
this.hashStorage = new SqlHashStorage(storage); this.hashStorage = new SqlHashStorage(storage);
break; break;
default: default:
@@ -57,10 +57,10 @@ public class HashManager {
private void initRotationStrategy() { private void initRotationStrategy() {
if (config.isEnabled()) { if (config.isEnabled()) {
switch (config.getRotationPolicy()) { switch (config.getRotationPolicy()) {
case PER_REQUESTS: case per_requests:
this.rotationStrategy = new RotationPerRequests(); this.rotationStrategy = new RotationPerRequests();
break; break;
case PER_SECONDS: case per_seconds:
this.rotationStrategy = new TimeBasedRotation(config.getDelay()); this.rotationStrategy = new TimeBasedRotation(config.getDelay());
break; break;
default: default:

View File

@@ -12,6 +12,7 @@ public class RotationPerRequests implements HashRotationStrategy {
@Override @Override
public void register(HashEngine hashEngine) { public void register(HashEngine hashEngine) {
this.hashEngine = hashEngine; this.hashEngine = hashEngine;
trigger();
} }
@Override @Override

View File

@@ -58,7 +58,8 @@ public class AuthorizationHandler extends BasicHttpHandler {
log.error("Account not found from request from: {}", exchange.getHostAndPort()); log.error("Account not found from request from: {}", exchange.getHostAndPort());
throw new InvalidCredentialsException(); throw new InvalidCredentialsException();
} }
if (account.getExpiresIn() < System.currentTimeMillis()) { long expiredAt = (account.getCreatedAt() + account.getExpiresIn()) * 1000; // expired in milliseconds
if (expiredAt < System.currentTimeMillis()) {
log.error("Account for '{}' from: {}", account.getUserId(), exchange.getHostAndPort()); log.error("Account for '{}' from: {}", account.getUserId(), exchange.getHostAndPort());
accountManager.deleteAccount(token); accountManager.deleteAccount(token);
throw new InvalidCredentialsException(); throw new InvalidCredentialsException();

View File

@@ -23,7 +23,6 @@ package io.kamax.mxisd.http.undertow.handler;
import io.kamax.mxisd.auth.AccountManager; import io.kamax.mxisd.auth.AccountManager;
import io.kamax.mxisd.config.PolicyConfig; import io.kamax.mxisd.config.PolicyConfig;
import io.kamax.mxisd.exception.InvalidCredentialsException; import io.kamax.mxisd.exception.InvalidCredentialsException;
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
import io.undertow.server.HttpHandler; import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpServerExchange;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -54,6 +53,11 @@ public class CheckTermsHandler extends BasicHttpHandler {
@Override @Override
public void handleRequest(HttpServerExchange exchange) throws Exception { public void handleRequest(HttpServerExchange exchange) throws Exception {
if (policies == null || policies.isEmpty()) {
child.handleRequest(exchange);
return;
}
String token = findAccessToken(exchange).orElse(null); String token = findAccessToken(exchange).orElse(null);
if (token == null) { if (token == null) {
log.error("Unauthorized request from: {}", exchange.getHostAndPort()); log.error("Unauthorized request from: {}", exchange.getHostAndPort());

View File

@@ -48,7 +48,7 @@ public class AccountRegisterHandler extends BasicHttpHandler {
if (LOGGER.isInfoEnabled()) { if (LOGGER.isInfoEnabled()) {
LOGGER.info("Registration from domain: {}, expired at {}", openIdToken.getMatrixServerName(), LOGGER.info("Registration from domain: {}, expired at {}", openIdToken.getMatrixServerName(),
new Date(openIdToken.getExpiredIn())); new Date(openIdToken.getExpiresIn()));
} }
String token = accountManager.register(openIdToken); String token = accountManager.register(openIdToken);

View File

@@ -60,6 +60,7 @@ public class HashLookupHandler extends LookupHandler implements ApiHandler {
ClientHashLookupRequest input = parseJsonTo(exchange, ClientHashLookupRequest.class); ClientHashLookupRequest input = parseJsonTo(exchange, ClientHashLookupRequest.class);
HashLookupRequest lookupRequest = new HashLookupRequest(); HashLookupRequest lookupRequest = new HashLookupRequest();
setRequesterInfo(lookupRequest, exchange); setRequesterInfo(lookupRequest, exchange);
lookupRequest.setHashes(input.getAddresses());
log.info("Got bulk lookup request from {} with client {} - Is recursive? {}", log.info("Got bulk lookup request from {} with client {} - Is recursive? {}",
lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive()); lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive());
@@ -84,7 +85,7 @@ public class HashLookupHandler extends LookupHandler implements ApiHandler {
} }
private void noneAlgorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) throws Exception { private void noneAlgorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) throws Exception {
if (!hashManager.getConfig().getAlgorithms().contains(HashingConfig.Algorithm.NONE)) { if (!hashManager.getConfig().getAlgorithms().contains(HashingConfig.Algorithm.none)) {
throw new InvalidParamException(); throw new InvalidParamException();
} }
@@ -93,8 +94,8 @@ public class HashLookupHandler extends LookupHandler implements ApiHandler {
for (String address : input.getAddresses()) { for (String address : input.getAddresses()) {
String[] parts = address.split(" "); String[] parts = address.split(" ");
ThreePidMapping mapping = new ThreePidMapping(); ThreePidMapping mapping = new ThreePidMapping();
mapping.setMedium(parts[0]); mapping.setMedium(parts[1]);
mapping.setValue(parts[1]); mapping.setValue(parts[0]);
mappings.add(mapping); mappings.add(mapping);
} }
bulkLookupRequest.setMappings(mappings); bulkLookupRequest.setMappings(mappings);
@@ -110,15 +111,19 @@ public class HashLookupHandler extends LookupHandler implements ApiHandler {
} }
private void sha256Algorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) { private void sha256Algorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) {
if (!hashManager.getConfig().getAlgorithms().contains(HashingConfig.Algorithm.SHA256)) { if (!hashManager.getConfig().getAlgorithms().contains(HashingConfig.Algorithm.sha256)) {
throw new InvalidParamException(); throw new InvalidParamException();
} }
ClientHashLookupAnswer answer = new ClientHashLookupAnswer(); ClientHashLookupAnswer answer = new ClientHashLookupAnswer();
for (Pair<String, ThreePidMapping> pair : hashManager.getHashStorage().find(request.getHashes())) { if (request.getHashes() != null && !request.getHashes().isEmpty()) {
answer.getMappings().put(pair.getKey(), pair.getValue().getMxid()); for (Pair<String, ThreePidMapping> pair : hashManager.getHashStorage().find(request.getHashes())) {
answer.getMappings().put(pair.getKey(), pair.getValue().getMxid());
}
log.info("Finished bulk lookup request from {}", request.getRequester());
} else {
log.warn("Empty request");
} }
log.info("Finished bulk lookup request from {}", request.getRequester());
respondJson(exchange, answer); respondJson(exchange, answer);
} }

View File

@@ -2,7 +2,6 @@ package io.kamax.mxisd.http.undertow.handler.term.v2;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.kamax.matrix.json.GsonUtil;
import io.kamax.mxisd.auth.AccountManager; import io.kamax.mxisd.auth.AccountManager;
import io.kamax.mxisd.exception.InvalidCredentialsException; import io.kamax.mxisd.exception.InvalidCredentialsException;
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler; import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
@@ -28,7 +27,7 @@ public class AcceptTermsHandler extends BasicHttpHandler {
String token = getAccessToken(exchange); String token = getAccessToken(exchange);
JsonObject request = parseJsonObject(exchange); JsonObject request = parseJsonObject(exchange);
JsonObject accepts = GsonUtil.getObj(request, "user_accepts"); JsonElement accepts = request.get("user_accepts");
AccountDao account = accountManager.findAccount(token); AccountDao account = accountManager.findAccount(token);
if (account == null) { if (account == null) {

View File

@@ -29,7 +29,11 @@ import io.kamax.matrix.json.GsonUtil;
import io.kamax.mxisd.config.InvitationConfig; import io.kamax.mxisd.config.InvitationConfig;
import io.kamax.mxisd.config.MxisdConfig; import io.kamax.mxisd.config.MxisdConfig;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.crypto.*; import io.kamax.mxisd.crypto.GenericKeyIdentifier;
import io.kamax.mxisd.crypto.KeyIdentifier;
import io.kamax.mxisd.crypto.KeyManager;
import io.kamax.mxisd.crypto.KeyType;
import io.kamax.mxisd.crypto.SignatureManager;
import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.exception.ConfigurationException; import io.kamax.mxisd.exception.ConfigurationException;
import io.kamax.mxisd.exception.MappingAlreadyExistsException; import io.kamax.mxisd.exception.MappingAlreadyExistsException;
@@ -38,6 +42,7 @@ import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.ThreePidMapping; import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.lookup.strategy.LookupStrategy;
import io.kamax.mxisd.matrix.HomeserverFederationResolver; import io.kamax.mxisd.matrix.HomeserverFederationResolver;
import io.kamax.mxisd.matrix.HomeserverVerifier;
import io.kamax.mxisd.notification.NotificationManager; import io.kamax.mxisd.notification.NotificationManager;
import io.kamax.mxisd.profile.ProfileManager; import io.kamax.mxisd.profile.ProfileManager;
import io.kamax.mxisd.storage.IStorage; import io.kamax.mxisd.storage.IStorage;
@@ -48,23 +53,26 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.DateTimeException; import java.time.DateTimeException;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -86,7 +94,6 @@ public class InvitationManager {
private NotificationManager notifMgr; private NotificationManager notifMgr;
private ProfileManager profileMgr; private ProfileManager profileMgr;
private CloseableHttpClient client;
private Timer refreshTimer; private Timer refreshTimer;
private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>(); private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>();
@@ -129,17 +136,6 @@ public class InvitationManager {
}); });
log.info("Loaded saved invites"); log.info("Loaded saved invites");
// FIXME export such madness into matrix-java-sdk with a nice wrapper to talk to a homeserver
try {
SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build();
HostnameVerifier hostnameVerifier = new NoopHostnameVerifier();
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
client = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build();
} catch (Exception e) {
// FIXME do better...
throw new RuntimeException(e);
}
log.info("Setting up invitation mapping refresh timer"); log.info("Setting up invitation mapping refresh timer");
refreshTimer = new Timer(); refreshTimer = new Timer();
@@ -423,11 +419,11 @@ public class InvitationManager {
String address = reply.getInvite().getAddress(); String address = reply.getInvite().getAddress();
String domain = reply.getInvite().getSender().getDomain(); String domain = reply.getInvite().getSender().getDomain();
log.info("Discovering HS for domain {}", domain); log.info("Discovering HS for domain {}", domain);
String hsUrlOpt = resolver.resolve(domain).toString(); HomeserverFederationResolver.HomeserverTarget hsUrlOpt = resolver.resolve(domain);
// TODO this is needed as this will block if called during authentication cycle due to synapse implementation // TODO this is needed as this will block if called during authentication cycle due to synapse implementation
new Thread(() -> { // FIXME need to make this retry-able and within a general background working pool 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"); HttpPost req = new HttpPost(hsUrlOpt.getUrl().toString() + "/_matrix/federation/v1/3pid/onbind");
// Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr // Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr
JsonObject obj = new JsonObject(); JsonObject obj = new JsonObject();
obj.addProperty("mxid", mxid); obj.addProperty("mxid", mxid);
@@ -459,36 +455,41 @@ public class InvitationManager {
Instant resolvedAt = Instant.now(); Instant resolvedAt = Instant.now();
boolean couldPublish = false; boolean couldPublish = false;
boolean shouldArchive = true; boolean shouldArchive = true;
try { try (CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new HomeserverVerifier(hsUrlOpt.getDomain()))
log.info("Posting onBind event to {}", req.getURI()); .build()) {
CloseableHttpResponse response = client.execute(req); try {
int statusCode = response.getStatusLine().getStatusCode(); log.info("Posting onBind event to {}", req.getURI());
log.info("Answer code: {}", statusCode); CloseableHttpResponse response = httpClient.execute(req);
if (statusCode >= 300 && statusCode != 403) { int statusCode = response.getStatusLine().getStatusCode();
log.info("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8)); log.info("Answer code: {}", statusCode);
log.warn("HS returned an error."); if (statusCode >= 300 && statusCode != 403) {
log.info("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
log.warn("HS returned an error.");
shouldArchive = statusCode != 502; shouldArchive = statusCode != 502;
if (shouldArchive) {
log.info("Invite can be found in historical storage for manual re-processing");
}
} else {
couldPublish = true;
if (statusCode == 403) {
log.info("Invite is obsolete or no longer under our control");
}
}
response.close();
} catch (IOException e) {
log.warn("Unable to tell HS {} about invite being mapped", domain, e);
} finally {
if (shouldArchive) { if (shouldArchive) {
log.info("Invite can be found in historical storage for manual re-processing"); synchronized (this) {
} storage.insertHistoricalInvite(reply, mxid, resolvedAt, couldPublish);
} else { removeInvite(reply);
couldPublish = true; log.info("Moved invite {} to historical table", reply.getId());
if (statusCode == 403) { }
log.info("Invite is obsolete or no longer under our control");
} }
} }
response.close();
} catch (IOException e) { } catch (IOException e) {
log.warn("Unable to tell HS {} about invite being mapped", domain, e); log.error("Unable to create client to the " + hsUrlOpt.getUrl().toString(), e);
} finally {
if (shouldArchive) {
synchronized (this) {
storage.insertHistoricalInvite(reply, mxid, resolvedAt, couldPublish);
removeInvite(reply);
log.info("Moved invite {} to historical table", reply.getId());
}
}
} }
}).start(); }).start();
} }

View File

@@ -178,26 +178,26 @@ public class HomeserverFederationResolver {
} }
} }
public URL resolve(String domain) { public HomeserverTarget resolve(String domain) {
Optional<URL> s1 = resolveOverwrite(domain); Optional<URL> s1 = resolveOverwrite(domain);
if (s1.isPresent()) { if (s1.isPresent()) {
URL dest = s1.get(); URL dest = s1.get();
log.info("Resolution of {} via DNS overwrite to {}", domain, dest); log.info("Resolution of {} via DNS overwrite to {}", domain, dest);
return dest; return new HomeserverTarget(dest.getHost(), dest);
} }
Optional<URL> s2 = resolveLiteral(domain); Optional<URL> s2 = resolveLiteral(domain);
if (s2.isPresent()) { if (s2.isPresent()) {
URL dest = s2.get(); URL dest = s2.get();
log.info("Resolution of {} as IP literal or IP/hostname with explicit port to {}", domain, dest); log.info("Resolution of {} as IP literal or IP/hostname with explicit port to {}", domain, dest);
return dest; return new HomeserverTarget(dest.getHost(), dest);
} }
Optional<URL> s3 = resolveWellKnown(domain); Optional<URL> s3 = resolveWellKnown(domain);
if (s3.isPresent()) { if (s3.isPresent()) {
URL dest = s3.get(); URL dest = s3.get();
log.info("Resolution of {} via well-known to {}", domain, dest); log.info("Resolution of {} via well-known to {}", domain, dest);
return dest; return new HomeserverTarget(dest.getHost(), dest);
} }
// The domain needs to be resolved // The domain needs to be resolved
@@ -205,12 +205,30 @@ public class HomeserverFederationResolver {
if (s4.isPresent()) { if (s4.isPresent()) {
URL dest = s4.get(); URL dest = s4.get();
log.info("Resolution of {} via DNS SRV record to {}", domain, dest); log.info("Resolution of {} via DNS SRV record to {}", domain, dest);
return dest; return new HomeserverTarget(domain, dest);
} }
URL dest = build(domain + ":" + getDefaultPort()); URL dest = build(domain + ":" + getDefaultPort());
log.info("Resolution of {} to {}", domain, dest); log.info("Resolution of {} to {}", domain, dest);
return dest; return new HomeserverTarget(dest.getHost(), dest);
} }
public static class HomeserverTarget {
private final String domain;
private final URL url;
HomeserverTarget(String domain, URL url) {
this.domain = domain;
this.url = url;
}
public String getDomain() {
return domain;
}
public URL getUrl() {
return url;
}
}
} }

View File

@@ -0,0 +1,85 @@
package io.kamax.mxisd.matrix;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
public class HomeserverVerifier implements HostnameVerifier {
private static final Logger LOGGER = LoggerFactory.getLogger(HomeserverVerifier.class);
private static final String ALT_DNS_NAME_TYPE = "2";
private static final String ALT_IP_ADDRESS_TYPE = "7";
private final String matrixHostname;
public HomeserverVerifier(String matrixHostname) {
this.matrixHostname = matrixHostname;
}
@Override
public boolean verify(String hostname, SSLSession session) {
try {
Certificate peerCertificate = session.getPeerCertificates()[0];
if (peerCertificate instanceof X509Certificate) {
X509Certificate x509Certificate = (X509Certificate) peerCertificate;
if (x509Certificate.getSubjectAlternativeNames() == null) {
return false;
}
for (String altSubjectName : getAltSubjectNames(x509Certificate)) {
if (match(altSubjectName)) {
return true;
}
}
}
} catch (SSLPeerUnverifiedException | CertificateParsingException e) {
LOGGER.error("Unable to check remote host", e);
return false;
}
return false;
}
private List<String> getAltSubjectNames(X509Certificate x509Certificate) {
List<String> subjectNames = new ArrayList<>();
try {
for (List<?> subjectAlternativeNames : x509Certificate.getSubjectAlternativeNames()) {
if (subjectAlternativeNames == null
|| subjectAlternativeNames.size() < 2
|| subjectAlternativeNames.get(0) == null
|| subjectAlternativeNames.get(1) == null) {
continue;
}
String subjectType = subjectAlternativeNames.get(0).toString();
switch (subjectType) {
case ALT_DNS_NAME_TYPE:
case ALT_IP_ADDRESS_TYPE:
subjectNames.add(subjectAlternativeNames.get(1).toString());
break;
default:
LOGGER.trace("Unusable subject type: " + subjectType);
}
}
} catch (CertificateParsingException e) {
LOGGER.error("Unable to parse the certificate", e);
return Collections.emptyList();
}
return subjectNames;
}
private boolean match(String altSubjectName) {
if (altSubjectName.startsWith("*.")) {
return altSubjectName.toLowerCase().endsWith(matrixHostname.toLowerCase());
} else {
return matrixHostname.equalsIgnoreCase(altSubjectName);
}
}
}

View File

@@ -39,6 +39,7 @@ import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.ThreePidValidation; import io.kamax.mxisd.lookup.ThreePidValidation;
import io.kamax.mxisd.matrix.HomeserverFederationResolver; import io.kamax.mxisd.matrix.HomeserverFederationResolver;
import io.kamax.mxisd.matrix.HomeserverVerifier;
import io.kamax.mxisd.notification.NotificationManager; import io.kamax.mxisd.notification.NotificationManager;
import io.kamax.mxisd.storage.IStorage; import io.kamax.mxisd.storage.IStorage;
import io.kamax.mxisd.storage.dao.IThreePidSessionDao; import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
@@ -53,6 +54,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -73,7 +75,6 @@ public class SessionManager {
private IStorage storage; private IStorage storage;
private NotificationManager notifMgr; private NotificationManager notifMgr;
private HomeserverFederationResolver resolver; private HomeserverFederationResolver resolver;
private CloseableHttpClient client;
private SignatureManager signatureManager; private SignatureManager signatureManager;
public SessionManager( public SessionManager(
@@ -81,14 +82,12 @@ public class SessionManager {
IStorage storage, IStorage storage,
NotificationManager notifMgr, NotificationManager notifMgr,
HomeserverFederationResolver resolver, HomeserverFederationResolver resolver,
CloseableHttpClient client,
SignatureManager signatureManager SignatureManager signatureManager
) { ) {
this.cfg = cfg; this.cfg = cfg;
this.storage = storage; this.storage = storage;
this.notifMgr = notifMgr; this.notifMgr = notifMgr;
this.resolver = resolver; this.resolver = resolver;
this.client = client;
this.signatureManager = signatureManager; this.signatureManager = signatureManager;
} }
@@ -308,25 +307,34 @@ public class SessionManager {
String canonical = MatrixJson.encodeCanonical(jsonObject); String canonical = MatrixJson.encodeCanonical(jsonObject);
String originUrl = resolver.resolve(origin).toString(); HomeserverFederationResolver.HomeserverTarget homeserverTarget = resolver.resolve(origin);
validateServerKey(key, sig, canonical, originUrl); validateServerKey(key, sig, canonical, homeserverTarget);
} }
private String removeQuotes(String origin) { private String removeQuotes(String origin) {
return origin.startsWith("\"") && origin.endsWith("\"") ? origin.substring(1, origin.length() - 1) : origin; return origin.startsWith("\"") && origin.endsWith("\"") ? origin.substring(1, origin.length() - 1) : origin;
} }
private void validateServerKey(String key, String signature, String canonical, String originUrl) { private void validateServerKey(String key, String signature, String canonical,
HomeserverFederationResolver.HomeserverTarget homeserverTarget) {
String originUrl = homeserverTarget.getUrl().toString();
HttpGet request = new HttpGet(originUrl + "/_matrix/key/v2/server"); HttpGet request = new HttpGet(originUrl + "/_matrix/key/v2/server");
log.info("Get keys from the server {}", request.getURI()); log.info("Get keys from the server {}", request.getURI());
try (CloseableHttpResponse response = client.execute(request)) { try (CloseableHttpClient httpClient = HttpClients.custom()
int statusCode = response.getStatusLine().getStatusCode(); .setSSLHostnameVerifier(new HomeserverVerifier(homeserverTarget.getDomain())).build()) {
log.info("Answer code: {}", statusCode); try (CloseableHttpResponse response = httpClient.execute(request)) {
if (statusCode == 200) { int statusCode = response.getStatusLine().getStatusCode();
verifyKey(key, signature, canonical, response); log.info("Answer code: {}", statusCode);
} else { if (statusCode == 200) {
throw new RemoteHomeServerException("Unable to fetch server keys."); verifyKey(key, signature, canonical, response);
} else {
throw new RemoteHomeServerException("Unable to fetch server keys.");
}
} catch (IOException e) {
String message = "Unable to get server keys: " + originUrl;
log.error(message, e);
throw new IllegalArgumentException(message);
} }
} catch (IOException e) { } catch (IOException e) {
String message = "Unable to get server keys: " + originUrl; String message = "Unable to get server keys: " + originUrl;

View File

@@ -294,6 +294,13 @@ public class OrmLiteSqlStorage implements IStorage {
public void acceptTerm(String token, String url) { public void acceptTerm(String token, String url) {
withCatcher(() -> { withCatcher(() -> {
AccountDao account = findAccount(token).orElseThrow(InvalidCredentialsException::new); AccountDao account = findAccount(token).orElseThrow(InvalidCredentialsException::new);
List<AcceptedDao> acceptedTerms = acceptedDao.queryForEq("userId", account.getUserId());
for (AcceptedDao acceptedTerm : acceptedTerms) {
if (acceptedTerm.getUrl().equalsIgnoreCase(url)) {
// already accepted
return;
}
}
int created = acceptedDao.create(new AcceptedDao(url, account.getUserId(), System.currentTimeMillis())); int created = acceptedDao.create(new AcceptedDao(url, account.getUserId(), System.currentTimeMillis()));
if (created != 1) { if (created != 1) {
throw new RuntimeException("Unexpected row count after DB action: " + created); throw new RuntimeException("Unexpected row count after DB action: " + created);

View File

@@ -0,0 +1,15 @@
package io.kamax.mxisd.test.hash;
import static org.junit.Assert.assertEquals;
import org.apache.commons.codec.digest.DigestUtils;
import org.junit.Test;
public class HashEngineTest {
@Test
public void sha256test() {
assertEquals("a26de61ae3055f84b33ac1a179b9ad5301f9109024f4db1ae653ea525d2136f4",
DigestUtils.sha256Hex("user2@mail.homeserver.tld email I9x4vpcWjqp9X8iiOY4a"));
}
}

View File

@@ -51,13 +51,13 @@ public class HomeserverFederationResolverTest {
@Test @Test
public void hostnameWithoutPort() { public void hostnameWithoutPort() {
URL url = resolver.resolve("example.org"); URL url = resolver.resolve("example.org").getUrl();
assertEquals("https://example.org:8448", url.toString()); assertEquals("https://example.org:8448", url.toString());
} }
@Test @Test
public void hostnameWithPort() { public void hostnameWithPort() {
URL url = resolver.resolve("example.org:443"); URL url = resolver.resolve("example.org:443").getUrl();
assertEquals("https://example.org:443", url.toString()); assertEquals("https://example.org:443", url.toString());
} }