Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
eb1326c56a | ||
|
10cdb4360e | ||
|
17ebc2a421 | ||
|
cbb9fced8d | ||
|
7509174611 | ||
|
51d9225dda |
@@ -7,7 +7,7 @@ Default values:
|
||||
```.yaml
|
||||
matrix:
|
||||
v1: true # deprecated
|
||||
v2: true
|
||||
v2: false
|
||||
```
|
||||
|
||||
To disable change value to `false`.
|
||||
@@ -17,9 +17,16 @@ NOTE: the v1 is deprecated, therefore recommend to use only v2 and disable v1 (d
|
||||
matrix:
|
||||
v1: false
|
||||
```
|
||||
NOTE: Riot Web version 1.5.5 and below checks the v1 for backward compatibility.
|
||||
|
||||
NOTE: v2 disabled by default in order to preserve backward compatibility.
|
||||
|
||||
## Terms
|
||||
|
||||
###### Requires: No.
|
||||
|
||||
Administrator can omit terms configuration. In this case the terms checking will be disabled.
|
||||
|
||||
Example:
|
||||
```.yaml
|
||||
policy:
|
||||
@@ -35,13 +42,99 @@ policy:
|
||||
url: https://ma1sd.host.tld/term_fr.html # localized url
|
||||
regexp:
|
||||
- '/_matrix/identity/v2/account.*'
|
||||
- '/_matrix/identity/v2/hash_lookup'
|
||||
- '/_matrix/identity/v2/hash_details'
|
||||
- '/_matrix/identity/v2/lookup'
|
||||
```
|
||||
Where:
|
||||
|
||||
- `term_name` -- name of the terms.
|
||||
- `regexp` -- regexp patterns for API.
|
||||
- `version` -- the terms version.
|
||||
- `lang` -- the term language.
|
||||
- `name` -- the name of the term.
|
||||
- `url` -- the url of the term. Might be any url (i.e. from another host) for a html page.
|
||||
- `regexp` -- regexp patterns for API which should be available only after accepting the terms.
|
||||
|
||||
API will be checks for accepted terms only with authorization.
|
||||
There are the next API:
|
||||
- [`GET /_matrix/identity/v2/account`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-account) - Gets information about what user owns the access token used in the request.
|
||||
- [`POST /_matrix/identity/v2/account/logout`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-account-logout) - Logs out the access token, preventing it from being used to authenticate future requests to the server.
|
||||
- [`GET /_matrix/identity/v2/hash_details`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-hash-details) - Gets parameters for hashing identifiers from the server. This can include any of the algorithms defined in this specification.
|
||||
- [`POST /_matrix/identity/v2/lookup`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-lookup) - Looks up the set of Matrix User IDs which have bound the 3PIDs given, if bindings are available. Note that the format of the addresses is defined later in this specification.
|
||||
- [`POST /_matrix/identity/v2/validate/email/requestToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-email-requesttoken) - Create a session for validating an email address.
|
||||
- [`POST /_matrix/identity/v2/validate/email/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-email-submittoken) - Validate ownership of an email address.
|
||||
- [`GET /_matrix/identity/v2/validate/email/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-validate-email-submittoken) - Validate ownership of an email address.
|
||||
- [`POST /_matrix/identity/v2/validate/msisdn/requestToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-msisdn-requesttoken) - Create a session for validating a phone number.
|
||||
- [`POST /_matrix/identity/v2/validate/msisdn/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-validate-msisdn-submittoken) - Validate ownership of a phone number.
|
||||
- [`GET /_matrix/identity/v2/validate/msisdn/submitToken`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-validate-msisdn-submittoken) - Validate ownership of a phone number.
|
||||
- [`GET /_matrix/identity/v2/3pid/getValidated3pid`](https://matrix.org/docs/spec/identity_service/r0.3.0#get-matrix-identity-v2-3pid-getvalidated3pid) - Determines if a given 3pid has been validated by a user.
|
||||
- [`POST /_matrix/identity/v2/3pid/bind`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-3pid-bind) - Publish an association between a session and a Matrix user ID.
|
||||
- [`POST /_matrix/identity/v2/3pid/unbind`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-3pid-unbind) - Remove an association between a session and a Matrix user ID.
|
||||
- [`POST /_matrix/identity/v2/store-invite`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-store-invite) - Store pending invitations to a user's 3pid.
|
||||
- [`POST /_matrix/identity/v2/sign-ed25519`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-sign-ed25519) - Sign invitation details.
|
||||
|
||||
There is only one exception: [`POST /_matrix/identity/v2/terms`](https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-terms) which uses for accepting the terms and requires the authorization.
|
||||
|
||||
## [Hash lookup](https://github.com/matrix-org/matrix-doc/blob/hs/hash-identity/proposals/2134-identity-hash-lookup.md)
|
||||
|
||||
Hashes and the pepper updates together according to the `rotationPolicy`.
|
||||
|
||||
###### Requires: No.
|
||||
|
||||
In case the `none` algorithms ma1sd will be lookup using the v1 bulk API.
|
||||
|
||||
```.yaml
|
||||
hashing:
|
||||
enabled: true # enable or disable the hash lookup MSC2140 (default is false)
|
||||
pepperLength: 20 # length of the pepper value (default is 20)
|
||||
rotationPolicy: per_requests # or `per_seconds` how often the hashes will be updating
|
||||
hashStorageType: sql # or `in_memory` where the hashes will be stored
|
||||
algorithms:
|
||||
- none # the same as v1 bulk lookup
|
||||
- sha256 # hash the 3PID and pepper.
|
||||
delay: 2m # how often hashes will be updated if rotation policy = per_seconds (default is 10s)
|
||||
requests: 10 # how many lookup requests will be performed before updating hashes if rotation policy = per_requests (default is 10)
|
||||
```
|
||||
|
||||
When enabled and client requests the `none` algorithms then hash lookups works as v1 bulk lookup.
|
||||
|
||||
Delay specified in the format: `2d 4h 12m 34s` - this means 2 days 4 hours 12 minutes and 34 seconds. Zero units may be omitted. For example:
|
||||
|
||||
- 12s - 12 seconds
|
||||
- 3m - 3 minutes
|
||||
- 5m 6s - 5 minutes and 6 seconds
|
||||
- 6h 3s - 6 hours and 3 seconds
|
||||
|
||||
|
||||
## Hash lookup
|
||||
Sha256 algorithm supports only sql, memory and exec 3PID providers.
|
||||
For sql provider (i.e. for the `synapseSql`):
|
||||
```.yaml
|
||||
synapseSql:
|
||||
lookup:
|
||||
query: 'select user_id as mxid, medium, address from user_threepids' # query for retrive 3PIDs for hashes.
|
||||
```
|
||||
|
||||
For general sql provider:
|
||||
```.yaml
|
||||
sql:
|
||||
lookup:
|
||||
query: 'select user as mxid, field1 as medium, field2 as address from some_table' # query for retrive 3PIDs for hashes.
|
||||
```
|
||||
|
||||
Each query should return the `mxid`, `medium` and `address` fields.
|
||||
|
||||
|
||||
For memory providers:
|
||||
```.yaml
|
||||
memory:
|
||||
hashEnabled: true # enable the hash lookup (defaults is false)
|
||||
```
|
||||
|
||||
For exec providers:
|
||||
```.yaml
|
||||
exec:
|
||||
identity:
|
||||
hashEnabled: true # enable the hash lookup (defaults is false)
|
||||
```
|
||||
|
||||
NOTE: Federation requests work only with `none` algorithms.
|
||||
|
||||
|
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,6 @@
|
||||
#Thu Dec 05 22:39:36 MSK 2019
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-all.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
@@ -21,6 +21,8 @@
|
||||
#
|
||||
matrix:
|
||||
domain: ''
|
||||
v1: true # deprecated
|
||||
v2: false # MSC2140 API v2. Disabled by default in order to preserve backward compatibility.
|
||||
|
||||
|
||||
################
|
||||
@@ -109,3 +111,40 @@ threepid:
|
||||
|
||||
# Password for the account
|
||||
password: "ThePassword"
|
||||
|
||||
|
||||
#### MSC2134 (hash lookup)
|
||||
|
||||
#hashing:
|
||||
# enabled: false # enable or disable the hash lookup MSC2140 (default is false)
|
||||
# pepperLength: 20 # length of the pepper value (default is 20)
|
||||
# rotationPolicy: per_requests # or `per_seconds` how often the hashes will be updating
|
||||
# hashStorageType: sql # or `in_memory` where the hashes will be stored
|
||||
# algorithms:
|
||||
# - none # the same as v1 bulk lookup
|
||||
# - sha256 # hash the 3PID and pepper.
|
||||
# delay: 2m # how often hashes will be updated if rotation policy = per_seconds (default is 10s)
|
||||
# requests: 10 # how many lookup requests will be performed before updating hashes if rotation policy = per_requests (default is 10)
|
||||
|
||||
### hash lookup for synapseSql provider.
|
||||
# synapseSql:
|
||||
# lookup:
|
||||
# query: 'select user_id as mxid, medium, address from user_threepids' # query for retrive 3PIDs for hashes.
|
||||
|
||||
#### MSC2140 (Terms)
|
||||
#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_details'
|
||||
# - '/_matrix/identity/v2/lookup'
|
||||
#
|
||||
|
@@ -189,24 +189,32 @@ 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);
|
||||
MatrixConfig matrixConfig = m.getConfig().getMatrix();
|
||||
if (matrixConfig.isV2()) {
|
||||
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) {
|
||||
routingHandler.get(GetTermsHandler.PATH, new GetTermsHandler(m.getConfig().getPolicy()));
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.POST, sane(new AcceptTermsHandler(m.getAccMgr())),
|
||||
AcceptTermsHandler.PATH, true);
|
||||
MatrixConfig matrixConfig = m.getConfig().getMatrix();
|
||||
if (matrixConfig.isV2()) {
|
||||
routingHandler.get(GetTermsHandler.PATH, new GetTermsHandler(m.getConfig().getPolicy()));
|
||||
routingHandler.post(AcceptTermsHandler.PATH, sane(new AcceptTermsHandler(m.getAccMgr())));
|
||||
}
|
||||
}
|
||||
|
||||
private void hashEndpoints(RoutingHandler routingHandler) {
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new HashDetailsHandler(m.getHashManager())),
|
||||
HashDetailsHandler.PATH, true);
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.POST,
|
||||
sane(new HashLookupHandler(m.getIdentity(), m.getHashManager())), HashLookupHandler.Path, true);
|
||||
MatrixConfig matrixConfig = m.getConfig().getMatrix();
|
||||
if (matrixConfig.isV2()) {
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new HashDetailsHandler(m.getHashManager())),
|
||||
HashDetailsHandler.PATH, true);
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.POST,
|
||||
sane(new HashLookupHandler(m.getIdentity(), m.getHashManager())), HashLookupHandler.Path, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) {
|
||||
|
@@ -10,6 +10,7 @@ import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.exception.NotFoundException;
|
||||
import io.kamax.mxisd.matrix.HomeserverFederationResolver;
|
||||
import io.kamax.mxisd.matrix.HomeserverVerifier;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import org.apache.http.HttpStatus;
|
||||
@@ -22,18 +23,10 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
public class AccountManager {
|
||||
|
||||
@@ -80,7 +73,7 @@ public class AccountManager {
|
||||
homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken());
|
||||
String userId;
|
||||
try (CloseableHttpClient httpClient = HttpClients.custom()
|
||||
.setSSLHostnameVerifier(new MatrixHostnameVerifier(homeserverTarget.getDomain())).build()) {
|
||||
.setSSLHostnameVerifier(new HomeserverVerifier(homeserverTarget.getDomain())).build()) {
|
||||
try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
@@ -170,74 +163,4 @@ public class AccountManager {
|
||||
public MatrixConfig getMatrixConfig() {
|
||||
return matrixConfig;
|
||||
}
|
||||
|
||||
public static class MatrixHostnameVerifier implements HostnameVerifier {
|
||||
|
||||
private static final String ALT_DNS_NAME_TYPE = "2";
|
||||
private static final String ALT_IP_ADDRESS_TYPE = "7";
|
||||
|
||||
private final String matrixHostname;
|
||||
|
||||
public MatrixHostnameVerifier(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -173,6 +173,10 @@ public class ExecIdentityStore extends ExecStore implements IThreePidProvider {
|
||||
|
||||
@Override
|
||||
public Iterable<ThreePidMapping> populateHashes() {
|
||||
if (!cfg.isHashLookup()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Processor<List<ThreePidMapping>> p = new Processor<>();
|
||||
p.withConfig(cfg.getLookup().getBulk());
|
||||
|
||||
|
@@ -174,6 +174,10 @@ public class MemoryIdentityStore implements AuthenticatorProvider, DirectoryProv
|
||||
|
||||
@Override
|
||||
public Iterable<ThreePidMapping> populateHashes() {
|
||||
if (!cfg.isHashEnabled()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return cfg.getIdentities().stream()
|
||||
.map(mic -> mic.getThreepids().stream().map(mtp -> new ThreePidMapping(mtp.getMedium(), mtp.getAddress(), mic.getUsername())))
|
||||
.flatMap(s -> s).collect(
|
||||
|
@@ -0,0 +1,30 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
public class DurationDeserializer {
|
||||
|
||||
public long deserialize(String argument) {
|
||||
long duration = 0L;
|
||||
for (String part : argument.split(" ")) {
|
||||
String unit = part.substring(part.length() - 1);
|
||||
long value = Long.parseLong(part.substring(0, part.length() - 1));
|
||||
switch (unit) {
|
||||
case "s":
|
||||
duration += value;
|
||||
break;
|
||||
case "m":
|
||||
duration += value * 60;
|
||||
break;
|
||||
case "h":
|
||||
duration += value * 60 * 60;
|
||||
break;
|
||||
case "d":
|
||||
duration += value * 60 * 60 * 24;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Unknown duration unit: %s", unit));
|
||||
}
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
}
|
@@ -309,6 +309,7 @@ public class ExecConfig {
|
||||
private Boolean enabled;
|
||||
private int priority;
|
||||
private Lookup lookup = new Lookup();
|
||||
private boolean hashLookup = false;
|
||||
|
||||
public Boolean isEnabled() {
|
||||
return enabled;
|
||||
@@ -334,6 +335,13 @@ public class ExecConfig {
|
||||
this.lookup = lookup;
|
||||
}
|
||||
|
||||
public boolean isHashLookup() {
|
||||
return hashLookup;
|
||||
}
|
||||
|
||||
public void setHashLookup(boolean hashLookup) {
|
||||
this.hashLookup = hashLookup;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Profile {
|
||||
|
@@ -14,7 +14,9 @@ public class HashingConfig {
|
||||
private int pepperLength = 20;
|
||||
private RotationPolicyEnum rotationPolicy;
|
||||
private HashStorageEnum hashStorageType;
|
||||
private long delay = 10;
|
||||
private String delay = "10s";
|
||||
private transient long delayInSeconds = 10;
|
||||
private int requests = 10;
|
||||
private List<Algorithm> algorithms = new ArrayList<>();
|
||||
|
||||
public void build() {
|
||||
@@ -23,10 +25,15 @@ public class HashingConfig {
|
||||
LOGGER.info(" Pepper length: {}", getPepperLength());
|
||||
LOGGER.info(" Rotation policy: {}", getRotationPolicy());
|
||||
LOGGER.info(" Hash storage type: {}", getHashStorageType());
|
||||
if (RotationPolicyEnum.per_seconds == rotationPolicy) {
|
||||
LOGGER.info(" Rotation delay: {}", delay);
|
||||
if (RotationPolicyEnum.per_seconds == getRotationPolicy()) {
|
||||
setDelayInSeconds(new DurationDeserializer().deserialize(getDelay()));
|
||||
LOGGER.info(" Rotation delay: {}", getDelay());
|
||||
LOGGER.info(" Rotation delay in seconds: {}", getDelayInSeconds());
|
||||
}
|
||||
LOGGER.info(" Algorithms: {}", algorithms);
|
||||
if (RotationPolicyEnum.per_requests == getRotationPolicy()) {
|
||||
LOGGER.info(" Rotation after requests: {}", getRequests());
|
||||
}
|
||||
LOGGER.info(" Algorithms: {}", getAlgorithms());
|
||||
} else {
|
||||
LOGGER.info("Hash configuration disabled, used only `none` pepper.");
|
||||
}
|
||||
@@ -79,14 +86,30 @@ public class HashingConfig {
|
||||
this.hashStorageType = hashStorageType;
|
||||
}
|
||||
|
||||
public long getDelay() {
|
||||
public String getDelay() {
|
||||
return delay;
|
||||
}
|
||||
|
||||
public void setDelay(long delay) {
|
||||
public void setDelay(String delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
public long getDelayInSeconds() {
|
||||
return delayInSeconds;
|
||||
}
|
||||
|
||||
public void setDelayInSeconds(long delayInSeconds) {
|
||||
this.delayInSeconds = delayInSeconds;
|
||||
}
|
||||
|
||||
public int getRequests() {
|
||||
return requests;
|
||||
}
|
||||
|
||||
public void setRequests(int requests) {
|
||||
this.requests = requests;
|
||||
}
|
||||
|
||||
public List<Algorithm> getAlgorithms() {
|
||||
return algorithms;
|
||||
}
|
||||
|
@@ -64,7 +64,7 @@ public class MatrixConfig {
|
||||
private String domain;
|
||||
private Identity identity = new Identity();
|
||||
private boolean v1 = true;
|
||||
private boolean v2 = true;
|
||||
private boolean v2 = false;
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
|
@@ -27,6 +27,7 @@ public class MemoryStoreConfig {
|
||||
|
||||
private boolean enabled;
|
||||
private List<MemoryIdentityConfig> identities = new ArrayList<>();
|
||||
private boolean hashEnabled = false;
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
@@ -44,6 +45,14 @@ public class MemoryStoreConfig {
|
||||
this.identities = identities;
|
||||
}
|
||||
|
||||
public boolean isHashEnabled() {
|
||||
return hashEnabled;
|
||||
}
|
||||
|
||||
public void setHashEnabled(boolean hashEnabled) {
|
||||
this.hashEnabled = hashEnabled;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
// no-op
|
||||
}
|
||||
|
@@ -9,6 +9,7 @@ import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
public class HashEngine {
|
||||
@@ -18,6 +19,7 @@ public class HashEngine {
|
||||
private final List<? extends IThreePidProvider> providers;
|
||||
private final HashStorage hashStorage;
|
||||
private final HashingConfig config;
|
||||
private final Base64.Encoder base64 = Base64.getUrlEncoder().withoutPadding();
|
||||
private String pepper;
|
||||
|
||||
public HashEngine(List<? extends IThreePidProvider> providers, HashStorage hashStorage, HashingConfig config) {
|
||||
@@ -51,7 +53,7 @@ public class HashEngine {
|
||||
}
|
||||
|
||||
protected String hash(ThreePidMapping pidMapping) {
|
||||
return DigestUtils.sha256Hex(pidMapping.getValue() + " " + pidMapping.getMedium() + " " + getPepper());
|
||||
return base64.encodeToString(DigestUtils.sha256(pidMapping.getValue() + " " + pidMapping.getMedium() + " " + getPepper()));
|
||||
}
|
||||
|
||||
protected String newPepper() {
|
||||
|
@@ -58,10 +58,10 @@ public class HashManager {
|
||||
if (config.isEnabled()) {
|
||||
switch (config.getRotationPolicy()) {
|
||||
case per_requests:
|
||||
this.rotationStrategy = new RotationPerRequests();
|
||||
this.rotationStrategy = new RotationPerRequests(config.getRequests());
|
||||
break;
|
||||
case per_seconds:
|
||||
this.rotationStrategy = new TimeBasedRotation(config.getDelay());
|
||||
this.rotationStrategy = new TimeBasedRotation(config.getDelayInSeconds());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown rotation type: " + config.getHashStorageType());
|
||||
|
@@ -8,6 +8,11 @@ public class RotationPerRequests implements HashRotationStrategy {
|
||||
|
||||
private HashEngine hashEngine;
|
||||
private final AtomicInteger counter = new AtomicInteger(0);
|
||||
private final int barrier;
|
||||
|
||||
public RotationPerRequests(int barrier) {
|
||||
this.barrier = barrier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(HashEngine hashEngine) {
|
||||
@@ -23,7 +28,7 @@ public class RotationPerRequests implements HashRotationStrategy {
|
||||
@Override
|
||||
public synchronized void newRequest() {
|
||||
int newValue = counter.incrementAndGet();
|
||||
if (newValue >= 10) {
|
||||
if (newValue >= barrier) {
|
||||
counter.set(0);
|
||||
trigger();
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ public class SqlHashStorage implements HashStorage {
|
||||
|
||||
public SqlHashStorage(IStorage storage) {
|
||||
this.storage = storage;
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(storage::clearHashes));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -48,7 +48,7 @@ public class AccountRegisterHandler extends BasicHttpHandler {
|
||||
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Registration from domain: {}, expired at {}", openIdToken.getMatrixServerName(),
|
||||
new Date(openIdToken.getExpiresIn()));
|
||||
new Date(System.currentTimeMillis() + openIdToken.getExpiresIn()));
|
||||
}
|
||||
|
||||
String token = accountManager.register(openIdToken);
|
||||
|
@@ -31,6 +31,8 @@ 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.SingleLookupReply;
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
@@ -40,6 +42,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class HashLookupHandler extends LookupHandler implements ApiHandler {
|
||||
|
||||
@@ -89,6 +92,18 @@ public class HashLookupHandler extends LookupHandler implements ApiHandler {
|
||||
throw new InvalidParamException();
|
||||
}
|
||||
|
||||
ClientHashLookupAnswer answer = null;
|
||||
if (input.getAddresses() != null && input.getAddresses().size() > 0) {
|
||||
if (input.getAddresses().size() == 1) {
|
||||
answer = noneSingleLookup(request, input);
|
||||
} else {
|
||||
answer = noneBulkLookup(request, input);
|
||||
}
|
||||
}
|
||||
respondJson(exchange, answer != null ? answer : new ClientHashLookupAnswer());
|
||||
}
|
||||
|
||||
private ClientHashLookupAnswer noneBulkLookup(HashLookupRequest request, ClientHashLookupRequest input) throws Exception {
|
||||
BulkLookupRequest bulkLookupRequest = new BulkLookupRequest();
|
||||
List<ThreePidMapping> mappings = new ArrayList<>();
|
||||
for (String address : input.getAddresses()) {
|
||||
@@ -107,7 +122,26 @@ public class HashLookupHandler extends LookupHandler implements ApiHandler {
|
||||
}
|
||||
log.info("Finished bulk lookup request from {}", request.getRequester());
|
||||
|
||||
respondJson(exchange, answer);
|
||||
return answer;
|
||||
}
|
||||
|
||||
private ClientHashLookupAnswer noneSingleLookup(HashLookupRequest request, ClientHashLookupRequest input) {
|
||||
SingleLookupRequest singleLookupRequest = new SingleLookupRequest();
|
||||
String address = input.getAddresses().get(0);
|
||||
String[] parts = address.split(" ");
|
||||
singleLookupRequest.setThreePid(parts[0]);
|
||||
singleLookupRequest.setType(parts[1]);
|
||||
|
||||
ClientHashLookupAnswer answer = new ClientHashLookupAnswer();
|
||||
|
||||
Optional<SingleLookupReply> singleLookupReply = strategy.find(singleLookupRequest);
|
||||
if (singleLookupReply.isPresent()) {
|
||||
SingleLookupReply reply = singleLookupReply.get();
|
||||
answer.getMappings().put(address, reply.getMxid().toString());
|
||||
}
|
||||
log.info("Finished single lookup request from {}", request.getRequester());
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
private void sha256Algorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) {
|
||||
|
@@ -77,7 +77,8 @@ public class HomeserverVerifier implements HostnameVerifier {
|
||||
|
||||
private boolean match(String altSubjectName) {
|
||||
if (altSubjectName.startsWith("*.")) {
|
||||
return altSubjectName.toLowerCase().endsWith(matrixHostname.toLowerCase());
|
||||
String subjectNameWithoutMask = altSubjectName.substring(1); // remove wildcard
|
||||
return matrixHostname.toLowerCase().endsWith(subjectNameWithoutMask.toLowerCase());
|
||||
} else {
|
||||
return matrixHostname.equalsIgnoreCase(altSubjectName);
|
||||
}
|
||||
|
@@ -39,6 +39,7 @@ import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.ASTransactionDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.ChangelogDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.HashDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.HistoricalThreePidInviteIO;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AcceptedDao;
|
||||
@@ -46,12 +47,15 @@ import io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.ThreePidSessionDao;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -59,6 +63,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
public class OrmLiteSqlStorage implements IStorage {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(OrmLiteSqlStorage.class);
|
||||
|
||||
@FunctionalInterface
|
||||
private interface Getter<T> {
|
||||
|
||||
@@ -73,13 +79,18 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
|
||||
}
|
||||
|
||||
public static class Migrations {
|
||||
public static final String FIX_ACCEPTED_DAO = "2019_12_09__2254__fix_accepted_dao";
|
||||
}
|
||||
|
||||
private Dao<ThreePidInviteIO, String> invDao;
|
||||
private Dao<HistoricalThreePidInviteIO, String> expInvDao;
|
||||
private Dao<ThreePidSessionDao, String> sessionDao;
|
||||
private Dao<ASTransactionDao, String> asTxnDao;
|
||||
private Dao<AccountDao, String> accountDao;
|
||||
private Dao<AcceptedDao, String> acceptedDao;
|
||||
private Dao<AcceptedDao, Long> acceptedDao;
|
||||
private Dao<HashDao, String> hashDao;
|
||||
private Dao<ChangelogDao, String> changelogDao;
|
||||
|
||||
public OrmLiteSqlStorage(MxisdConfig cfg) {
|
||||
this(cfg.getStorage().getBackend(), cfg.getStorage().getProvider().getSqlite().getDatabase());
|
||||
@@ -96,6 +107,7 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
|
||||
withCatcher(() -> {
|
||||
ConnectionSource connPool = new JdbcConnectionSource("jdbc:" + backend + ":" + path);
|
||||
changelogDao = createDaoAndTable(connPool, ChangelogDao.class);
|
||||
invDao = createDaoAndTable(connPool, ThreePidInviteIO.class);
|
||||
expInvDao = createDaoAndTable(connPool, HistoricalThreePidInviteIO.class);
|
||||
sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class);
|
||||
@@ -103,10 +115,26 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
accountDao = createDaoAndTable(connPool, AccountDao.class);
|
||||
acceptedDao = createDaoAndTable(connPool, AcceptedDao.class);
|
||||
hashDao = createDaoAndTable(connPool, HashDao.class);
|
||||
runMigration(connPool);
|
||||
});
|
||||
}
|
||||
|
||||
private void runMigration(ConnectionSource connPol) throws SQLException {
|
||||
ChangelogDao fixAcceptedDao = changelogDao.queryForId(Migrations.FIX_ACCEPTED_DAO);
|
||||
if (fixAcceptedDao == null) {
|
||||
fixAcceptedDao(connPol);
|
||||
changelogDao.create(new ChangelogDao(Migrations.FIX_ACCEPTED_DAO, new Date(), "Recreate the accepted table."));
|
||||
}
|
||||
}
|
||||
|
||||
private void fixAcceptedDao(ConnectionSource connPool) throws SQLException {
|
||||
LOGGER.info("Migration: {}", Migrations.FIX_ACCEPTED_DAO);
|
||||
TableUtils.dropTable(acceptedDao, true);
|
||||
TableUtils.createTableIfNotExists(connPool, AcceptedDao.class);
|
||||
}
|
||||
|
||||
private <V, K> Dao<V, K> createDaoAndTable(ConnectionSource connPool, Class<V> c) throws SQLException {
|
||||
LOGGER.info("Create the dao: {}", c.getSimpleName());
|
||||
Dao<V, K> dao = DaoManager.createDao(connPool, c);
|
||||
TableUtils.createTableIfNotExists(connPool, c);
|
||||
return dao;
|
||||
|
@@ -26,7 +26,10 @@ import com.j256.ormlite.table.DatabaseTable;
|
||||
@DatabaseTable(tableName = "accepted")
|
||||
public class AcceptedDao {
|
||||
|
||||
@DatabaseField(canBeNull = false, id = true)
|
||||
@DatabaseField(generatedId = true)
|
||||
private Long id;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String url;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
@@ -45,6 +48,14 @@ public class AcceptedDao {
|
||||
this.acceptedAt = acceptedAt;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
@@ -0,0 +1,52 @@
|
||||
package io.kamax.mxisd.storage.ormlite.dao;
|
||||
|
||||
import com.j256.ormlite.field.DatabaseField;
|
||||
import com.j256.ormlite.table.DatabaseTable;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@DatabaseTable(tableName = "changelog")
|
||||
public class ChangelogDao {
|
||||
|
||||
@DatabaseField(id = true)
|
||||
private String id;
|
||||
|
||||
@DatabaseField
|
||||
private Date createdAt;
|
||||
|
||||
@DatabaseField
|
||||
private String comment;
|
||||
|
||||
public ChangelogDao() {
|
||||
}
|
||||
|
||||
public ChangelogDao(String id, Date createdAt, String comment) {
|
||||
this.id = id;
|
||||
this.createdAt = createdAt;
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Date getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(Date createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
package io.kamax.mxisd.test.config;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import io.kamax.mxisd.config.DurationDeserializer;
|
||||
import org.junit.Test;
|
||||
|
||||
public class DurationDeserializerTest {
|
||||
|
||||
@Test
|
||||
public void durationLoadTest() {
|
||||
DurationDeserializer deserializer = new DurationDeserializer();
|
||||
|
||||
assertEquals(4, deserializer.deserialize("4s"));
|
||||
assertEquals((60 * 60) + 4, deserializer.deserialize("1h 4s"));
|
||||
assertEquals((2 * 60) + 4, deserializer.deserialize("2m 4s"));
|
||||
assertEquals((2 * 60 * 60) + (7 * 60) + 4, deserializer.deserialize("2h 7m 4s"));
|
||||
assertEquals((60 * 60 * 24) + (2 * 60 * 60) + (7 * 60) + 4, deserializer.deserialize("1d 2h 7m 4s"));
|
||||
}
|
||||
}
|
@@ -5,11 +5,14 @@ import static org.junit.Assert.assertEquals;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
public class HashEngineTest {
|
||||
|
||||
@Test
|
||||
public void sha256test() {
|
||||
assertEquals("a26de61ae3055f84b33ac1a179b9ad5301f9109024f4db1ae653ea525d2136f4",
|
||||
DigestUtils.sha256Hex("user2@mail.homeserver.tld email I9x4vpcWjqp9X8iiOY4a"));
|
||||
Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
|
||||
assertEquals("rujYzy1w0JxulN_rVlErGUmkdXT5znL0sjSF_IWreko",
|
||||
encoder.encodeToString(DigestUtils.sha256("user@mail.homeserver.tld email I9x4vpcWjqp9X8iiOY4a")));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user