Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6216113400 | ||
|
cb32441959 | ||
|
0ec4df2c06 | ||
|
86b880069b | ||
|
a97273fe77 | ||
|
f9daf4d58a | ||
|
9e4cabb69b | ||
|
0b81de3cd0 | ||
|
698a16ec17 | ||
|
619b70d860 | ||
|
494c9e3941 | ||
|
0786a6520f | ||
|
430136c391 | ||
|
eda4404335 | ||
|
c52034b18a | ||
|
8d346037b7 | ||
|
14ad4435bc | ||
|
94441d0446 | ||
|
b4776b50e2 | ||
|
2458b38b75 | ||
|
249e28a8b5 | ||
|
8ba8756871 | ||
|
ba9e2d6121 | ||
|
f042b82a50 | ||
|
59071177ad | ||
|
6450cd1f20 | ||
|
90bc244f3e | ||
|
6e52a509db | ||
|
5ca666981a | ||
|
43fe8b1aec | ||
|
a0270c7d01 | ||
|
703044d06a | ||
|
add6ed8fd9 | ||
|
baed894ff8 | ||
|
14e095a147 | ||
|
bc8795e940 | ||
|
5521c0c338 | ||
|
614b3440e2 | ||
|
d0fd9fb9b0 | ||
|
1232e9ce79 | ||
|
a47a983c10 | ||
|
fbb0d7c7ba | ||
|
f1dd309551 | ||
|
36f22e5ca6 | ||
|
a112a5e57c | ||
|
dbc764fe65 |
@@ -15,7 +15,8 @@ ma1sd - Federated Matrix Identity Server
|
||||
|
||||
---
|
||||
|
||||
* This project is a fork of the https://github.com/kamax-matrix/mxisd which has been archived and no longer supported. *
|
||||
* This project is a fork (not successor) of the https://github.com/kamax-matrix/mxisd, which has been archived and no longer maintained as a standalone product.
|
||||
Also, ma1sd is supported by the volunteer not developers of the original project.
|
||||
|
||||
---
|
||||
|
||||
|
28
build.gradle
28
build.gradle
@@ -78,7 +78,7 @@ buildscript {
|
||||
|
||||
dependencies {
|
||||
classpath 'com.github.jengelman.gradle.plugins:shadow:5.1.0'
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0'
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.27.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,12 +94,12 @@ dependencies {
|
||||
compile 'commons-io:commons-io:2.6'
|
||||
|
||||
// Config management
|
||||
compile 'org.yaml:snakeyaml:1.24'
|
||||
compile 'org.yaml:snakeyaml:1.25'
|
||||
|
||||
// Dependencies from old Matrix-java-sdk
|
||||
compile 'org.apache.commons:commons-lang3:3.9'
|
||||
compile 'com.squareup.okhttp3:okhttp:4.0.1'
|
||||
compile 'commons-codec:commons-codec:1.12'
|
||||
compile 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||
compile 'commons-codec:commons-codec:1.13'
|
||||
|
||||
// ORMLite
|
||||
compile 'com.j256.ormlite:ormlite-jdbc:5.1'
|
||||
@@ -114,10 +114,10 @@ dependencies {
|
||||
compile 'dnsjava:dnsjava:2.1.9'
|
||||
|
||||
// HTTP connections
|
||||
compile 'org.apache.httpcomponents:httpclient:4.5.9'
|
||||
compile 'org.apache.httpcomponents:httpclient:4.5.10'
|
||||
|
||||
// Phone numbers validation
|
||||
compile 'com.googlecode.libphonenumber:libphonenumber:8.10.15'
|
||||
compile 'com.googlecode.libphonenumber:libphonenumber:8.10.22'
|
||||
|
||||
// E-mail sending
|
||||
compile 'javax.mail:javax.mail-api:1.6.2'
|
||||
@@ -133,13 +133,13 @@ dependencies {
|
||||
compile 'org.xerial:sqlite-jdbc:3.28.0'
|
||||
|
||||
// PostgreSQL
|
||||
compile 'org.postgresql:postgresql:42.2.6'
|
||||
compile 'org.postgresql:postgresql:42.2.8'
|
||||
|
||||
// MariaDB/MySQL
|
||||
compile 'org.mariadb.jdbc:mariadb-java-client:2.4.2'
|
||||
compile 'org.mariadb.jdbc:mariadb-java-client:2.5.1'
|
||||
|
||||
// Twilio SDK for SMS
|
||||
compile 'com.twilio.sdk:twilio:7.40.1'
|
||||
compile 'com.twilio.sdk:twilio:7.45.0'
|
||||
|
||||
// SendGrid SDK to send emails from GCE
|
||||
compile 'com.sendgrid:sendgrid-java:2.2.2'
|
||||
@@ -148,15 +148,15 @@ dependencies {
|
||||
compile 'org.zeroturnaround:zt-exec:1.11'
|
||||
|
||||
// HTTP server
|
||||
compile 'io.undertow:undertow-core:2.0.22.Final'
|
||||
compile 'io.undertow:undertow-core:2.0.27.Final'
|
||||
|
||||
// Command parser for AS interface
|
||||
implementation 'commons-cli:commons-cli:1.4'
|
||||
|
||||
testCompile 'junit:junit:4.13-beta-3'
|
||||
testCompile 'com.github.tomakehurst:wiremock:2.24.0'
|
||||
testCompile 'com.unboundid:unboundid-ldapsdk:4.0.11'
|
||||
testCompile 'com.icegreen:greenmail:1.5.10'
|
||||
testCompile 'junit:junit:4.13-rc-1'
|
||||
testCompile 'com.github.tomakehurst:wiremock:2.25.1'
|
||||
testCompile 'com.unboundid:unboundid-ldapsdk:4.0.12'
|
||||
testCompile 'com.icegreen:greenmail:1.5.11'
|
||||
}
|
||||
|
||||
jar {
|
||||
|
47
docs/MSC2140_MSC2134.md
Normal file
47
docs/MSC2140_MSC2134.md
Normal 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
|
||||
|
@@ -45,6 +45,17 @@ Create a list under the label `myOtherServers` containing two Identity servers:
|
||||
- `server.port`: HTTP port to listen on (unencrypted)
|
||||
- `server.publicUrl`: Defaults to `https://{server.name}`
|
||||
|
||||
## Unbind (MSC1915)
|
||||
- `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:
|
||||
- session validation.
|
||||
- request signature via `X-Matrix` header and uses `server.publicUrl` property to construct the signing json;
|
||||
Commonly the `server.publicUrl` should be the same value as the `trusted_third_party_id_servers` property in the synapse config.
|
||||
|
||||
## Storage
|
||||
### SQLite
|
||||
`storage.provider.sqlite.database`: Absolute location of the SQLite database
|
||||
|
@@ -103,8 +103,8 @@ session:
|
||||
validation:
|
||||
enabled: true
|
||||
unbind:
|
||||
notification:
|
||||
enabled: true
|
||||
notifications: true
|
||||
enabled: true
|
||||
|
||||
# DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION
|
||||
# CONFIGURATION EXAMPLE
|
||||
@@ -115,7 +115,7 @@ are allowed to do in terms of 3PID sessions. The policy has a global on/off swit
|
||||
|
||||
---
|
||||
|
||||
`unbind` controls warning notifications for 3PID removal.
|
||||
`unbind` controls warning notifications for 3PID removal. Setting `notifications` for `unbind` to false will prevent unbind emails from sending.
|
||||
|
||||
### Web views
|
||||
Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submitted.
|
||||
|
5
gradle/wrapper/gradle-wrapper.properties
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,5 @@
|
||||
#Thu Jul 04 22:47:59 MSK 2019
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
6
gradlew
vendored
6
gradlew
vendored
@@ -7,7 +7,7 @@
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@@ -125,8 +125,8 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
2
gradlew.bat
vendored
2
gradlew.bat
vendored
@@ -5,7 +5,7 @@
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem http://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
@@ -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));
|
||||
}
|
||||
|
||||
}
|
@@ -20,7 +20,12 @@
|
||||
|
||||
package io.kamax.mxisd;
|
||||
|
||||
import io.kamax.mxisd.config.MatrixConfig;
|
||||
import io.kamax.mxisd.config.MxisdConfig;
|
||||
import io.kamax.mxisd.config.PolicyConfig;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.AuthorizationHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.CheckTermsHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.InternalInfoHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.OptionsHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.SaneHandler;
|
||||
@@ -31,19 +36,47 @@ import io.kamax.mxisd.http.undertow.handler.auth.RestAuthHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v1.LoginGetHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v1.LoginHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v1.LoginPostHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v2.AccountGetUserInfoHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v2.AccountLogoutHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.auth.v2.AccountRegisterHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.directory.v1.UserDirectorySearchHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.v1.*;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.EphemeralKeyIsValidHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.HelloHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.KeyGetHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.RegularKeyIsValidHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionStartHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionTpidBindHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionTpidGetValidatedHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionTpidUnbindHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.SessionValidationGetHandler;
|
||||
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.StoreInviteHandler;
|
||||
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.HashLookupHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.invite.v1.RoomInviteHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.profile.v1.InternalProfileHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.profile.v1.ProfileHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.register.v1.Register3pidRequestTokenHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.status.StatusHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.status.VersionHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.term.v2.AcceptTermsHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.term.v2.GetTermsHandler;
|
||||
import io.kamax.mxisd.matrix.IdentityServiceAPI;
|
||||
import io.undertow.Handlers;
|
||||
import io.undertow.Undertow;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.RoutingHandler;
|
||||
import io.undertow.util.HttpString;
|
||||
import io.undertow.util.Methods;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class HttpMxisd {
|
||||
|
||||
@@ -66,81 +99,164 @@ public class HttpMxisd {
|
||||
public void start() {
|
||||
m.start();
|
||||
|
||||
HttpHandler helloHandler = SaneHandler.around(new HelloHandler());
|
||||
|
||||
HttpHandler asUserHandler = SaneHandler.around(new AsUserHandler(m.getAs()));
|
||||
HttpHandler asTxnHandler = SaneHandler.around(new AsTransactionHandler(m.getAs()));
|
||||
HttpHandler asNotFoundHandler = SaneHandler.around(new AsNotFoundHandler(m.getAs()));
|
||||
|
||||
HttpHandler storeInvHandler = SaneHandler.around(new StoreInviteHandler(m.getConfig().getServer(), m.getInvite(), m.getKeyManager()));
|
||||
final RoutingHandler handler = Handlers.routing()
|
||||
.add("OPTIONS", "/**", SaneHandler.around(new OptionsHandler()))
|
||||
|
||||
httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(Handlers.routing()
|
||||
// Status endpoints
|
||||
.get(StatusHandler.Path, SaneHandler.around(new StatusHandler()))
|
||||
.get(VersionHandler.Path, SaneHandler.around(new VersionHandler()))
|
||||
|
||||
.add("OPTIONS", "/**", SaneHandler.around(new OptionsHandler()))
|
||||
// Authentication endpoints
|
||||
.get(LoginHandler.Path, SaneHandler.around(new LoginGetHandler(m.getAuth(), m.getHttpClient())))
|
||||
.post(LoginHandler.Path, SaneHandler.around(new LoginPostHandler(m.getAuth())))
|
||||
.post(RestAuthHandler.Path, SaneHandler.around(new RestAuthHandler(m.getAuth())))
|
||||
|
||||
// Status endpoints
|
||||
.get(StatusHandler.Path, SaneHandler.around(new StatusHandler()))
|
||||
.get(VersionHandler.Path, SaneHandler.around(new VersionHandler()))
|
||||
// Directory endpoints
|
||||
.post(UserDirectorySearchHandler.Path, SaneHandler.around(new UserDirectorySearchHandler(m.getDirectory())))
|
||||
|
||||
// Authentication endpoints
|
||||
.get(LoginHandler.Path, SaneHandler.around(new LoginGetHandler(m.getAuth(), m.getHttpClient())))
|
||||
.post(LoginHandler.Path, SaneHandler.around(new LoginPostHandler(m.getAuth())))
|
||||
.post(RestAuthHandler.Path, SaneHandler.around(new RestAuthHandler(m.getAuth())))
|
||||
// Profile endpoints
|
||||
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
|
||||
.get(InternalProfileHandler.Path, SaneHandler.around(new InternalProfileHandler(m.getProfile())))
|
||||
|
||||
// Directory endpoints
|
||||
.post(UserDirectorySearchHandler.Path, SaneHandler.around(new UserDirectorySearchHandler(m.getDirectory())))
|
||||
// Registration endpoints
|
||||
.post(Register3pidRequestTokenHandler.Path,
|
||||
SaneHandler.around(new Register3pidRequestTokenHandler(m.getReg(), m.getClientDns(), m.getHttpClient())))
|
||||
|
||||
// Key endpoints
|
||||
.get(KeyGetHandler.Path, SaneHandler.around(new KeyGetHandler(m.getKeyManager())))
|
||||
.get(RegularKeyIsValidHandler.Path, SaneHandler.around(new RegularKeyIsValidHandler(m.getKeyManager())))
|
||||
.get(EphemeralKeyIsValidHandler.Path, SaneHandler.around(new EphemeralKeyIsValidHandler(m.getKeyManager())))
|
||||
// Invite endpoints
|
||||
.post(RoomInviteHandler.Path, SaneHandler.around(new RoomInviteHandler(m.getHttpClient(), m.getClientDns(), m.getInvite())))
|
||||
|
||||
// Identity endpoints
|
||||
.get(HelloHandler.Path, helloHandler)
|
||||
.get(HelloHandler.Path + "/", helloHandler) // Be lax with possibly trailing slash
|
||||
.get(SingleLookupHandler.Path, SaneHandler.around(new SingleLookupHandler(m.getConfig(), m.getIdentity(), m.getSign())))
|
||||
.post(BulkLookupHandler.Path, SaneHandler.around(new BulkLookupHandler(m.getIdentity())))
|
||||
.post(StoreInviteHandler.Path, storeInvHandler)
|
||||
.post(SessionStartHandler.Path, SaneHandler.around(new SessionStartHandler(m.getSession())))
|
||||
.get(SessionValidateHandler.Path, SaneHandler.around(new SessionValidationGetHandler(m.getSession(), m.getConfig())))
|
||||
.post(SessionValidateHandler.Path, SaneHandler.around(new SessionValidationPostHandler(m.getSession())))
|
||||
.get(SessionTpidGetValidatedHandler.Path, SaneHandler.around(new SessionTpidGetValidatedHandler(m.getSession())))
|
||||
.post(SessionTpidBindHandler.Path, SaneHandler.around(new SessionTpidBindHandler(m.getSession(), m.getInvite(), m.getSign())))
|
||||
.post(SessionTpidUnbindHandler.Path, SaneHandler.around(new SessionTpidUnbindHandler(m.getSession())))
|
||||
.post(SignEd25519Handler.Path, SaneHandler.around(new SignEd25519Handler(m.getConfig(), m.getInvite(), m.getSign())))
|
||||
// Application Service endpoints
|
||||
.get(AsUserHandler.Path, asUserHandler)
|
||||
.get("/_matrix/app/v1/rooms/**", asNotFoundHandler)
|
||||
.put(AsTransactionHandler.Path, asTxnHandler)
|
||||
|
||||
// Profile endpoints
|
||||
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
|
||||
.get(InternalProfileHandler.Path, SaneHandler.around(new InternalProfileHandler(m.getProfile())))
|
||||
.get("/users/{" + AsUserHandler.ID + "}", asUserHandler) // Legacy endpoint
|
||||
.get("/rooms/**", asNotFoundHandler) // Legacy endpoint
|
||||
.put("/transactions/{" + AsTransactionHandler.ID + "}", asTxnHandler) // Legacy endpoint
|
||||
|
||||
// Registration endpoints
|
||||
.post(Register3pidRequestTokenHandler.Path, SaneHandler.around(new Register3pidRequestTokenHandler(m.getReg(), m.getClientDns(), m.getHttpClient())))
|
||||
|
||||
// Invite endpoints
|
||||
.post(RoomInviteHandler.Path, SaneHandler.around(new RoomInviteHandler(m.getHttpClient(), m.getClientDns(), m.getInvite())))
|
||||
|
||||
// Application Service endpoints
|
||||
.get(AsUserHandler.Path, asUserHandler)
|
||||
.get("/_matrix/app/v1/rooms/**", asNotFoundHandler)
|
||||
.put(AsTransactionHandler.Path, asTxnHandler)
|
||||
|
||||
.get("/users/{" + AsUserHandler.ID + "}", asUserHandler) // Legacy endpoint
|
||||
.get("/rooms/**", asNotFoundHandler) // Legacy endpoint
|
||||
.put("/transactions/{" + AsTransactionHandler.ID + "}", asTxnHandler) // Legacy endpoint
|
||||
|
||||
// Banned endpoints
|
||||
.get(InternalInfoHandler.Path, SaneHandler.around(new InternalInfoHandler()))
|
||||
|
||||
).build();
|
||||
// Banned endpoints
|
||||
.get(InternalInfoHandler.Path, SaneHandler.around(new InternalInfoHandler()));
|
||||
keyEndpoints(handler);
|
||||
identityEndpoints(handler);
|
||||
termsEndpoints(handler);
|
||||
hashEndpoints(handler);
|
||||
accountEndpoints(handler);
|
||||
httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(handler).build();
|
||||
|
||||
httpSrv.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
// Because it might have never been initialized if an exception is thrown early
|
||||
if (Objects.nonNull(httpSrv)) httpSrv.stop();
|
||||
if (Objects.nonNull(httpSrv)) {
|
||||
httpSrv.stop();
|
||||
}
|
||||
|
||||
m.stop();
|
||||
}
|
||||
|
||||
private void keyEndpoints(RoutingHandler routingHandler) {
|
||||
addEndpoints(routingHandler, Methods.GET, false,
|
||||
new KeyGetHandler(m.getKeyManager()),
|
||||
new RegularKeyIsValidHandler(m.getKeyManager()),
|
||||
new EphemeralKeyIsValidHandler(m.getKeyManager())
|
||||
);
|
||||
}
|
||||
|
||||
private void identityEndpoints(RoutingHandler routingHandler) {
|
||||
// Legacy v1
|
||||
routingHandler.get(SingleLookupHandler.Path, sane(new SingleLookupHandler(m.getConfig(), m.getIdentity(), m.getSign())));
|
||||
routingHandler.post(BulkLookupHandler.Path, sane(new BulkLookupHandler(m.getIdentity())));
|
||||
|
||||
addEndpoints(routingHandler, Methods.GET, false, new HelloHandler());
|
||||
|
||||
addEndpoints(routingHandler, Methods.GET, true,
|
||||
new SessionValidationGetHandler(m.getSession(), m.getConfig()),
|
||||
new SessionTpidGetValidatedHandler(m.getSession())
|
||||
);
|
||||
addEndpoints(routingHandler, Methods.POST, true,
|
||||
new StoreInviteHandler(m.getConfig().getServer(), m.getInvite(), m.getKeyManager()),
|
||||
new SessionStartHandler(m.getSession()),
|
||||
new SessionValidationPostHandler(m.getSession()),
|
||||
new SessionTpidBindHandler(m.getSession(), m.getInvite(), m.getSign()),
|
||||
new SessionTpidUnbindHandler(m.getSession()),
|
||||
new SignEd25519Handler(m.getConfig(), m.getInvite(), m.getSign())
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
routingHandler.get(GetTermsHandler.PATH, new GetTermsHandler(m.getConfig().getPolicy()));
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.POST, sane(new AcceptTermsHandler(m.getAccMgr())),
|
||||
AcceptTermsHandler.PATH, true);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) {
|
||||
for (ApiHandler handler : handlers) {
|
||||
attachHandler(routingHandler, method, handler, useAuthorization, sane(handler));
|
||||
}
|
||||
}
|
||||
|
||||
private void attachHandler(RoutingHandler routingHandler, HttpString method, ApiHandler apiHandler, boolean useAuthorization,
|
||||
HttpHandler httpHandler) {
|
||||
MatrixConfig matrixConfig = m.getConfig().getMatrix();
|
||||
if (matrixConfig.isV1()) {
|
||||
routingHandler.add(method, apiHandler.getPath(IdentityServiceAPI.V1), httpHandler);
|
||||
}
|
||||
if (matrixConfig.isV2()) {
|
||||
String path = apiHandler.getPath(IdentityServiceAPI.V2);
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, method, httpHandler, path, useAuthorization);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
private List<PolicyConfig.PolicyObject> getPolicyObjects(String url) {
|
||||
PolicyConfig policyConfig = m.getConfig().getPolicy();
|
||||
List<PolicyConfig.PolicyObject> policies = new ArrayList<>();
|
||||
if (!policyConfig.getPolicies().isEmpty()) {
|
||||
for (PolicyConfig.PolicyObject policy : policyConfig.getPolicies().values()) {
|
||||
for (Pattern pattern : policy.getPatterns()) {
|
||||
if (pattern.matcher(url).matches()) {
|
||||
policies.add(policy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return policies;
|
||||
}
|
||||
|
||||
private HttpHandler sane(HttpHandler httpHandler) {
|
||||
return SaneHandler.around(httpHandler);
|
||||
}
|
||||
}
|
||||
|
@@ -21,6 +21,7 @@
|
||||
package io.kamax.mxisd;
|
||||
|
||||
import io.kamax.mxisd.as.AppSvcManager;
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.auth.AuthManager;
|
||||
import io.kamax.mxisd.auth.AuthProviders;
|
||||
import io.kamax.mxisd.backend.IdentityStoreSupplier;
|
||||
@@ -34,6 +35,7 @@ import io.kamax.mxisd.directory.DirectoryManager;
|
||||
import io.kamax.mxisd.directory.DirectoryProviders;
|
||||
import io.kamax.mxisd.dns.ClientDnsOverwrite;
|
||||
import io.kamax.mxisd.dns.FederationDnsOverwrite;
|
||||
import io.kamax.mxisd.hash.HashManager;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
import io.kamax.mxisd.lookup.ThreePidProviders;
|
||||
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher;
|
||||
@@ -85,6 +87,8 @@ public class Mxisd {
|
||||
private SessionManager sessMgr;
|
||||
private NotificationManager notifMgr;
|
||||
private RegistrationManager regMgr;
|
||||
private AccountManager accMgr;
|
||||
private HashManager hashManager;
|
||||
|
||||
// HS-specific classes
|
||||
private Synapse synapse;
|
||||
@@ -115,15 +119,19 @@ 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(), store);
|
||||
|
||||
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);
|
||||
sessMgr = new SessionManager(cfg, store, notifMgr, resolver, signMgr);
|
||||
invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, resolver, notifMgr, pMgr);
|
||||
authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient);
|
||||
dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get());
|
||||
regMgr = new RegistrationManager(cfg.getRegister(), httpClient, clientDns, invMgr);
|
||||
asHander = new AppSvcManager(this);
|
||||
accMgr = new AccountManager(store, resolver, cfg.getAccountConfig(), cfg.getMatrix());
|
||||
}
|
||||
|
||||
public MxisdConfig getConfig() {
|
||||
@@ -194,6 +202,14 @@ public class Mxisd {
|
||||
return synapse;
|
||||
}
|
||||
|
||||
public AccountManager getAccMgr() {
|
||||
return accMgr;
|
||||
}
|
||||
|
||||
public HashManager getHashManager() {
|
||||
return hashManager;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
build();
|
||||
}
|
||||
|
243
src/main/java/io/kamax/mxisd/auth/AccountManager.java
Normal file
243
src/main/java/io/kamax/mxisd/auth/AccountManager.java
Normal file
@@ -0,0 +1,243 @@
|
||||
package io.kamax.mxisd.auth;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.MatrixID;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.config.AccountConfig;
|
||||
import io.kamax.mxisd.config.MatrixConfig;
|
||||
import io.kamax.mxisd.config.PolicyConfig;
|
||||
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.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
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 {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AccountManager.class);
|
||||
|
||||
private final IStorage storage;
|
||||
private final HomeserverFederationResolver resolver;
|
||||
private final AccountConfig accountConfig;
|
||||
private final MatrixConfig matrixConfig;
|
||||
|
||||
public AccountManager(IStorage storage, HomeserverFederationResolver resolver, AccountConfig accountConfig, MatrixConfig matrixConfig) {
|
||||
this.storage = storage;
|
||||
this.resolver = resolver;
|
||||
this.accountConfig = accountConfig;
|
||||
this.matrixConfig = matrixConfig;
|
||||
}
|
||||
|
||||
public String register(OpenIdToken openIdToken) {
|
||||
Objects.requireNonNull(openIdToken.getAccessToken(), "Missing required access_token");
|
||||
Objects.requireNonNull(openIdToken.getTokenType(), "Missing required token type");
|
||||
Objects.requireNonNull(openIdToken.getMatrixServerName(), "Missing required matrix domain");
|
||||
|
||||
LOGGER.info("Registration from the server: {}", openIdToken.getMatrixServerName());
|
||||
String userId = getUserId(openIdToken);
|
||||
LOGGER.info("UserId: {}", userId);
|
||||
|
||||
String token = UUID.randomUUID().toString();
|
||||
AccountDao account = new AccountDao(openIdToken.getAccessToken(), openIdToken.getTokenType(),
|
||||
openIdToken.getMatrixServerName(), openIdToken.getExpiresIn(),
|
||||
Instant.now().getEpochSecond(), userId, token);
|
||||
storage.insertToken(account);
|
||||
|
||||
LOGGER.info("User {} registered", userId);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private String getUserId(OpenIdToken openIdToken) {
|
||||
String matrixServerName = openIdToken.getMatrixServerName();
|
||||
HomeserverFederationResolver.HomeserverTarget homeserverTarget = resolver.resolve(matrixServerName);
|
||||
String homeserverURL = homeserverTarget.getUrl().toString();
|
||||
LOGGER.info("Domain resolved: {} => {}", matrixServerName, homeserverURL);
|
||||
HttpGet getUserInfo = new HttpGet(
|
||||
homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken());
|
||||
String userId;
|
||||
try (CloseableHttpClient httpClient = HttpClients.custom()
|
||||
.setSSLHostnameVerifier(new MatrixHostnameVerifier(homeserverTarget.getDomain())).build()) {
|
||||
try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
String content = EntityUtils.toString(response.getEntity());
|
||||
LOGGER.trace("Response: {}", content);
|
||||
JsonObject body = GsonUtil.parseObj(content);
|
||||
userId = GsonUtil.getStringOrThrow(body, "sub");
|
||||
} else {
|
||||
LOGGER.error("Wrong response status: {}", statusCode);
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Unable to get user info.", e);
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Unable to create a connection to host: " + homeserverURL, e);
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
checkMXID(userId);
|
||||
return userId;
|
||||
}
|
||||
|
||||
private void checkMXID(String userId) {
|
||||
MatrixID mxid;
|
||||
try {
|
||||
mxid = MatrixID.asValid(userId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOGGER.error("Wrong MXID: " + userId, e);
|
||||
throw new BadRequestException("Wrong MXID");
|
||||
}
|
||||
|
||||
if (getAccountConfig().isAllowOnlyTrustDomains()) {
|
||||
LOGGER.info("Allow registration only for trust domain.");
|
||||
if (getMatrixConfig().getDomain().equals(mxid.getDomain())) {
|
||||
LOGGER.info("Allow user {} to registration", userId);
|
||||
} else {
|
||||
LOGGER.error("Deny user {} to registration", userId);
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("Allow registration from any server.");
|
||||
}
|
||||
}
|
||||
|
||||
public String getUserId(String token) {
|
||||
return storage.findAccount(token).orElseThrow(NotFoundException::new).getUserId();
|
||||
}
|
||||
|
||||
public AccountDao findAccount(String token) {
|
||||
AccountDao accountDao = storage.findAccount(token).orElse(null);
|
||||
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
if (accountDao != null) {
|
||||
LOGGER.info("Found account for user: {}", accountDao.getUserId());
|
||||
} else {
|
||||
LOGGER.warn("Account not found.");
|
||||
}
|
||||
}
|
||||
return accountDao;
|
||||
}
|
||||
|
||||
public void logout(String token) {
|
||||
String userId = storage.findAccount(token).orElseThrow(InvalidCredentialsException::new).getUserId();
|
||||
LOGGER.info("Logout: {}", userId);
|
||||
deleteAccount(token);
|
||||
}
|
||||
|
||||
public void deleteAccount(String token) {
|
||||
storage.deleteAccepts(token);
|
||||
storage.deleteToken(token);
|
||||
}
|
||||
|
||||
public void acceptTerm(String token, String url) {
|
||||
storage.acceptTerm(token, url);
|
||||
}
|
||||
|
||||
public boolean isTermAccepted(String token, List<PolicyConfig.PolicyObject> policies) {
|
||||
return policies.isEmpty() || storage.isTermAccepted(token, policies);
|
||||
}
|
||||
|
||||
public AccountConfig getAccountConfig() {
|
||||
return accountConfig;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -140,7 +140,7 @@ public class AuthManager {
|
||||
}
|
||||
|
||||
try {
|
||||
MatrixID.asValid(mxId);
|
||||
MatrixID.asAcceptable(mxId);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("The returned User ID {} is not a valid Matrix ID. Login might fail at the Homeserver level", mxId);
|
||||
}
|
||||
|
50
src/main/java/io/kamax/mxisd/auth/OpenIdToken.java
Normal file
50
src/main/java/io/kamax/mxisd/auth/OpenIdToken.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package io.kamax.mxisd.auth;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class OpenIdToken {
|
||||
|
||||
@SerializedName("access_token")
|
||||
private String accessToken;
|
||||
|
||||
@SerializedName("token_type")
|
||||
private String tokenType;
|
||||
|
||||
@SerializedName("matrix_server_name")
|
||||
private String matrixServerName;
|
||||
|
||||
@SerializedName("expires_in")
|
||||
private long expiresIn;
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void setTokenType(String tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public String getMatrixServerName() {
|
||||
return matrixServerName;
|
||||
}
|
||||
|
||||
public void setMatrixServerName(String matrixServerName) {
|
||||
this.matrixServerName = matrixServerName;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public void setExpiresIn(long expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
}
|
@@ -164,6 +164,26 @@ public class ExecIdentityStore extends ExecStore implements IThreePidProvider {
|
||||
return input.toString();
|
||||
});
|
||||
|
||||
addBulkSuccessMapper(p);
|
||||
|
||||
p.withFailureDefault(output -> Collections.emptyList());
|
||||
|
||||
return p.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ThreePidMapping> populateHashes() {
|
||||
Processor<List<ThreePidMapping>> p = new Processor<>();
|
||||
p.withConfig(cfg.getLookup().getBulk());
|
||||
|
||||
addBulkSuccessMapper(p);
|
||||
|
||||
p.withFailureDefault(output -> Collections.emptyList());
|
||||
|
||||
return p.execute();
|
||||
}
|
||||
|
||||
private void addBulkSuccessMapper(Processor<List<ThreePidMapping>> p) {
|
||||
p.addSuccessMapper(JsonType, output -> {
|
||||
if (StringUtils.isBlank(output)) {
|
||||
return Collections.emptyList();
|
||||
@@ -188,10 +208,5 @@ public class ExecIdentityStore extends ExecStore implements IThreePidProvider {
|
||||
throw new InternalServerError("Invalid user type: " + item.getId().getType());
|
||||
}).collect(Collectors.toList());
|
||||
});
|
||||
|
||||
p.withFailureDefault(output -> Collections.emptyList());
|
||||
|
||||
return p.execute();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -48,6 +48,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MemoryIdentityStore implements AuthenticatorProvider, DirectoryProvider, IThreePidProvider, ProfileProvider {
|
||||
|
||||
@@ -171,4 +172,11 @@ public class MemoryIdentityStore implements AuthenticatorProvider, DirectoryProv
|
||||
}).orElseGet(BackendAuthResult::failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ThreePidMapping> populateHashes() {
|
||||
return cfg.getIdentities().stream()
|
||||
.map(mic -> mic.getThreepids().stream().map(mtp -> new ThreePidMapping(mtp.getMedium(), mtp.getAddress(), mic.getUsername())))
|
||||
.flatMap(s -> s).collect(
|
||||
Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@@ -36,6 +36,7 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -104,4 +105,27 @@ public abstract class SqlThreePidProvider implements IThreePidProvider {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ThreePidMapping> populateHashes() {
|
||||
if (StringUtils.isBlank(cfg.getLookup().getQuery())) {
|
||||
log.warn("Lookup query not configured, skip.");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<ThreePidMapping> result = new ArrayList<>();
|
||||
try (Connection connection = pool.get()) {
|
||||
PreparedStatement statement = connection.prepareStatement(cfg.getLookup().getQuery());
|
||||
try (ResultSet resultSet = statement.executeQuery()) {
|
||||
while (resultSet.next()) {
|
||||
String mxid = resultSet.getString("mxid");
|
||||
String medium = resultSet.getString("medium");
|
||||
String address = resultSet.getString("address");
|
||||
result.add(new ThreePidMapping(medium, address, mxid));
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
8
src/main/java/io/kamax/mxisd/config/AcceptingPolicy.java
Normal file
8
src/main/java/io/kamax/mxisd/config/AcceptingPolicy.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
public enum AcceptingPolicy {
|
||||
|
||||
ALL,
|
||||
|
||||
ANY
|
||||
}
|
24
src/main/java/io/kamax/mxisd/config/AccountConfig.java
Normal file
24
src/main/java/io/kamax/mxisd/config/AccountConfig.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AccountConfig {
|
||||
|
||||
private final static Logger log = LoggerFactory.getLogger(DirectoryConfig.class);
|
||||
|
||||
private boolean allowOnlyTrustDomains = true;
|
||||
|
||||
public boolean isAllowOnlyTrustDomains() {
|
||||
return allowOnlyTrustDomains;
|
||||
}
|
||||
|
||||
public void setAllowOnlyTrustDomains(boolean allowOnlyTrustDomains) {
|
||||
this.allowOnlyTrustDomains = allowOnlyTrustDomains;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
log.info("--- Account config ---");
|
||||
log.info("Allow registration only for trust domain: {}", isAllowOnlyTrustDomains());
|
||||
}
|
||||
}
|
97
src/main/java/io/kamax/mxisd/config/HashingConfig.java
Normal file
97
src/main/java/io/kamax/mxisd/config/HashingConfig.java
Normal file
@@ -0,0 +1,97 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class HashingConfig {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HashingConfig.class);
|
||||
|
||||
private boolean enabled = false;
|
||||
private int pepperLength = 20;
|
||||
private RotationPolicyEnum rotationPolicy;
|
||||
private HashStorageEnum hashStorageType;
|
||||
private long delay = 10;
|
||||
private List<Algorithm> algorithms = new ArrayList<>();
|
||||
|
||||
public void build() {
|
||||
if (isEnabled()) {
|
||||
LOGGER.info("--- Hash configuration ---");
|
||||
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);
|
||||
}
|
||||
LOGGER.info(" Algorithms: {}", algorithms);
|
||||
} else {
|
||||
LOGGER.info("Hash configuration disabled, used only `none` pepper.");
|
||||
}
|
||||
}
|
||||
|
||||
public enum Algorithm {
|
||||
none,
|
||||
sha256
|
||||
}
|
||||
|
||||
public enum RotationPolicyEnum {
|
||||
per_requests,
|
||||
per_seconds
|
||||
}
|
||||
|
||||
public enum HashStorageEnum {
|
||||
in_memory,
|
||||
sql
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public int getPepperLength() {
|
||||
return pepperLength;
|
||||
}
|
||||
|
||||
public void setPepperLength(int pepperLength) {
|
||||
this.pepperLength = pepperLength;
|
||||
}
|
||||
|
||||
public RotationPolicyEnum getRotationPolicy() {
|
||||
return rotationPolicy;
|
||||
}
|
||||
|
||||
public void setRotationPolicy(RotationPolicyEnum rotationPolicy) {
|
||||
this.rotationPolicy = rotationPolicy;
|
||||
}
|
||||
|
||||
public HashStorageEnum getHashStorageType() {
|
||||
return hashStorageType;
|
||||
}
|
||||
|
||||
public void setHashStorageType(HashStorageEnum hashStorageType) {
|
||||
this.hashStorageType = hashStorageType;
|
||||
}
|
||||
|
||||
public long getDelay() {
|
||||
return delay;
|
||||
}
|
||||
|
||||
public void setDelay(long delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
public List<Algorithm> getAlgorithms() {
|
||||
return algorithms;
|
||||
}
|
||||
|
||||
public void setAlgorithms(List<Algorithm> algorithms) {
|
||||
this.algorithms = algorithms;
|
||||
}
|
||||
}
|
@@ -63,6 +63,8 @@ public class MatrixConfig {
|
||||
|
||||
private String domain;
|
||||
private Identity identity = new Identity();
|
||||
private boolean v1 = true;
|
||||
private boolean v2 = true;
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
@@ -80,6 +82,22 @@ public class MatrixConfig {
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
public boolean isV1() {
|
||||
return v1;
|
||||
}
|
||||
|
||||
public void setV1(boolean v1) {
|
||||
this.v1 = v1;
|
||||
}
|
||||
|
||||
public boolean isV2() {
|
||||
return v2;
|
||||
}
|
||||
|
||||
public void setV2(boolean v2) {
|
||||
this.v2 = v2;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
log.info("--- Matrix config ---");
|
||||
|
||||
@@ -90,6 +108,11 @@ public class MatrixConfig {
|
||||
log.info("Domain: {}", getDomain());
|
||||
log.info("Identity:");
|
||||
log.info("\tServers: {}", GsonUtil.get().toJson(identity.getServers()));
|
||||
log.info("API v1: {}", v1);
|
||||
log.info("API v2: {}", v2);
|
||||
if (v1) {
|
||||
log.warn("API v1 is deprecated via MSC2140: https://github.com/matrix-org/matrix-doc/pull/2140 and will be deleted in future releases.");
|
||||
log.warn("Please upgrade your homeserver and enable only API v2.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -92,6 +92,7 @@ public class MxisdConfig {
|
||||
private AppServiceConfig appsvc = new AppServiceConfig();
|
||||
private AuthenticationConfig auth = new AuthenticationConfig();
|
||||
private DirectoryConfig directory = new DirectoryConfig();
|
||||
private AccountConfig accountConfig = new AccountConfig();
|
||||
private Dns dns = new Dns();
|
||||
private ExecConfig exec = new ExecConfig();
|
||||
private FirebaseConfig firebase = new FirebaseConfig();
|
||||
@@ -114,6 +115,8 @@ public class MxisdConfig {
|
||||
private ThreePidConfig threepid = new ThreePidConfig();
|
||||
private ViewConfig view = new ViewConfig();
|
||||
private WordpressConfig wordpress = new WordpressConfig();
|
||||
private PolicyConfig policy = new PolicyConfig();
|
||||
private HashingConfig hashing = new HashingConfig();
|
||||
|
||||
public AppServiceConfig getAppsvc() {
|
||||
return appsvc;
|
||||
@@ -131,6 +134,14 @@ public class MxisdConfig {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
public AccountConfig getAccountConfig() {
|
||||
return accountConfig;
|
||||
}
|
||||
|
||||
public void setAccountConfig(AccountConfig accountConfig) {
|
||||
this.accountConfig = accountConfig;
|
||||
}
|
||||
|
||||
public DirectoryConfig getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
@@ -315,6 +326,22 @@ public class MxisdConfig {
|
||||
this.wordpress = wordpress;
|
||||
}
|
||||
|
||||
public PolicyConfig getPolicy() {
|
||||
return policy;
|
||||
}
|
||||
|
||||
public void setPolicy(PolicyConfig policy) {
|
||||
this.policy = policy;
|
||||
}
|
||||
|
||||
public HashingConfig getHashing() {
|
||||
return hashing;
|
||||
}
|
||||
|
||||
public void setHashing(HashingConfig hashing) {
|
||||
this.hashing = hashing;
|
||||
}
|
||||
|
||||
public MxisdConfig inMemory() {
|
||||
getKey().setPath(":memory:");
|
||||
getStorage().getProvider().getSqlite().setDatabase(":memory:");
|
||||
@@ -330,6 +357,7 @@ public class MxisdConfig {
|
||||
|
||||
getAppsvc().build();
|
||||
getAuth().build();
|
||||
getAccountConfig().build();
|
||||
getDirectory().build();
|
||||
getExec().build();
|
||||
getFirebase().build();
|
||||
@@ -352,6 +380,8 @@ public class MxisdConfig {
|
||||
getThreepid().build();
|
||||
getView().build();
|
||||
getWordpress().build();
|
||||
getPolicy().build();
|
||||
getHashing().build();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
114
src/main/java/io/kamax/mxisd/config/PolicyConfig.java
Normal file
114
src/main/java/io/kamax/mxisd/config/PolicyConfig.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class PolicyConfig {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(PolicyConfig.class);
|
||||
|
||||
private Map<String, PolicyObject> policies = new HashMap<>();
|
||||
|
||||
public static class TermObject {
|
||||
|
||||
private String name;
|
||||
|
||||
private String url;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PolicyObject {
|
||||
|
||||
private String version;
|
||||
|
||||
private Map<String, TermObject> terms;
|
||||
|
||||
private List<String> regexp = new ArrayList<>();
|
||||
|
||||
private transient List<Pattern> patterns = new ArrayList<>();
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Map<String, TermObject> getTerms() {
|
||||
return terms;
|
||||
}
|
||||
|
||||
public void setTerms(Map<String, TermObject> terms) {
|
||||
this.terms = terms;
|
||||
}
|
||||
|
||||
public List<String> getRegexp() {
|
||||
return regexp;
|
||||
}
|
||||
|
||||
public void setRegexp(List<String> regexp) {
|
||||
this.regexp = regexp;
|
||||
}
|
||||
|
||||
public List<Pattern> getPatterns() {
|
||||
return patterns;
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, PolicyObject> getPolicies() {
|
||||
return policies;
|
||||
}
|
||||
|
||||
public void setPolicies(Map<String, PolicyObject> policies) {
|
||||
this.policies = policies;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
LOGGER.info("--- Policy Config ---");
|
||||
if (getPolicies().isEmpty()) {
|
||||
LOGGER.info("Empty");
|
||||
} else {
|
||||
for (Map.Entry<String, PolicyObject> policyObjectItem : getPolicies().entrySet()) {
|
||||
PolicyObject policyObject = policyObjectItem.getValue();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Policy \"").append(policyObjectItem.getKey()).append("\"\n");
|
||||
sb.append(" version: ").append(policyObject.getVersion()).append("\n");
|
||||
for (String regexp : policyObjectItem.getValue().getRegexp()) {
|
||||
sb.append(" - ").append(regexp).append("\n");
|
||||
policyObjectItem.getValue().getPatterns().add(Pattern.compile(regexp));
|
||||
}
|
||||
sb.append(" terms:\n");
|
||||
if (policyObject.getTerms() != null) {
|
||||
for (Map.Entry<String, TermObject> termItem : policyObject.getTerms().entrySet()) {
|
||||
sb.append(" - lang: ").append(termItem.getKey()).append("\n");
|
||||
sb.append(" name: ").append(termItem.getValue().getName()).append("\n");
|
||||
sb.append(" url: ").append(termItem.getValue().getUrl()).append("\n");
|
||||
}
|
||||
}
|
||||
LOGGER.info(sb.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -79,5 +79,4 @@ public class ServerConfig {
|
||||
log.info("Port: {}", getPort());
|
||||
log.info("Public URL: {}", getPublicUrl());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -47,6 +47,8 @@ public class SessionConfig {
|
||||
public static class PolicyUnbind {
|
||||
|
||||
private boolean enabled = true;
|
||||
|
||||
private boolean notifications = true;
|
||||
|
||||
public boolean getEnabled() {
|
||||
return enabled;
|
||||
@@ -55,11 +57,20 @@ public class SessionConfig {
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean shouldNotify() {
|
||||
return notifications;
|
||||
}
|
||||
|
||||
public void setNotifications(boolean notifications) {
|
||||
this.notifications = notifications;
|
||||
}
|
||||
}
|
||||
|
||||
public Policy() {
|
||||
validation.enabled = true;
|
||||
unbind.enabled = true;
|
||||
unbind.notifications = true;
|
||||
}
|
||||
|
||||
private PolicyTemplate validation = new PolicyTemplate();
|
||||
|
@@ -124,6 +124,18 @@ public abstract class SqlConfig {
|
||||
|
||||
}
|
||||
|
||||
public static class Lookup {
|
||||
private String query = "SELECT user_id AS mxid, medium, address from user_threepids";
|
||||
|
||||
public String getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
public void setQuery(String query) {
|
||||
this.query = query;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Identity {
|
||||
|
||||
private Boolean enabled;
|
||||
@@ -264,6 +276,7 @@ public abstract class SqlConfig {
|
||||
private Directory directory = new Directory();
|
||||
private Identity identity = new Identity();
|
||||
private Profile profile = new Profile();
|
||||
private Lookup lookup = new Lookup();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
@@ -321,6 +334,14 @@ public abstract class SqlConfig {
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public Lookup getLookup() {
|
||||
return lookup;
|
||||
}
|
||||
|
||||
public void setLookup(Lookup lookup) {
|
||||
this.lookup = lookup;
|
||||
}
|
||||
|
||||
protected abstract String getProviderName();
|
||||
|
||||
public void build() {
|
||||
@@ -354,6 +375,7 @@ public abstract class SqlConfig {
|
||||
log.info("Identity type: {}", getIdentity().getType());
|
||||
log.info("3PID mapping query: {}", getIdentity().getQuery());
|
||||
log.info("Identity medium queries: {}", GsonUtil.build().toJson(getIdentity().getMedium()));
|
||||
log.info("Lookup query: {}", getLookup().getQuery());
|
||||
log.info("Profile:");
|
||||
log.info(" Enabled: {}", getProfile().isEnabled());
|
||||
if (getProfile().isEnabled()) {
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.exception;
|
||||
|
||||
public class InvalidParamException extends RuntimeException {
|
||||
|
||||
public InvalidParamException() {
|
||||
}
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.exception;
|
||||
|
||||
public class InvalidPepperException extends RuntimeException {
|
||||
|
||||
public InvalidPepperException() {
|
||||
}
|
||||
}
|
60
src/main/java/io/kamax/mxisd/hash/HashEngine.java
Normal file
60
src/main/java/io/kamax/mxisd/hash/HashEngine.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package io.kamax.mxisd.hash;
|
||||
|
||||
import io.kamax.mxisd.config.HashingConfig;
|
||||
import io.kamax.mxisd.hash.storage.HashStorage;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HashEngine {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HashEngine.class);
|
||||
|
||||
private final List<? extends IThreePidProvider> providers;
|
||||
private final HashStorage hashStorage;
|
||||
private final HashingConfig config;
|
||||
private String pepper;
|
||||
|
||||
public HashEngine(List<? extends IThreePidProvider> providers, HashStorage hashStorage, HashingConfig config) {
|
||||
this.providers = providers;
|
||||
this.hashStorage = hashStorage;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public void updateHashes() {
|
||||
LOGGER.info("Start update hashes.");
|
||||
synchronized (hashStorage) {
|
||||
this.pepper = newPepper();
|
||||
hashStorage.clear();
|
||||
for (IThreePidProvider provider : providers) {
|
||||
try {
|
||||
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() {
|
||||
synchronized (hashStorage) {
|
||||
return pepper;
|
||||
}
|
||||
}
|
||||
|
||||
protected String hash(ThreePidMapping pidMapping) {
|
||||
return DigestUtils.sha256Hex(pidMapping.getValue() + " " + pidMapping.getMedium() + " " + getPepper());
|
||||
}
|
||||
|
||||
protected String newPepper() {
|
||||
return RandomStringUtils.random(config.getPepperLength(), true, true);
|
||||
}
|
||||
}
|
91
src/main/java/io/kamax/mxisd/hash/HashManager.java
Normal file
91
src/main/java/io/kamax/mxisd/hash/HashManager.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package io.kamax.mxisd.hash;
|
||||
|
||||
import io.kamax.mxisd.config.HashingConfig;
|
||||
import io.kamax.mxisd.hash.rotation.HashRotationStrategy;
|
||||
import io.kamax.mxisd.hash.rotation.NoOpRotationStrategy;
|
||||
import io.kamax.mxisd.hash.rotation.RotationPerRequests;
|
||||
import io.kamax.mxisd.hash.rotation.TimeBasedRotation;
|
||||
import io.kamax.mxisd.hash.storage.EmptyStorage;
|
||||
import io.kamax.mxisd.hash.storage.HashStorage;
|
||||
import io.kamax.mxisd.hash.storage.InMemoryHashStorage;
|
||||
import io.kamax.mxisd.hash.storage.SqlHashStorage;
|
||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class HashManager {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(HashManager.class);
|
||||
|
||||
private HashEngine hashEngine;
|
||||
private HashRotationStrategy rotationStrategy;
|
||||
private HashStorage hashStorage;
|
||||
private HashingConfig config;
|
||||
private IStorage storage;
|
||||
private AtomicBoolean configured = new AtomicBoolean(false);
|
||||
|
||||
public void init(HashingConfig config, List<? extends IThreePidProvider> providers, IStorage storage) {
|
||||
this.config = config;
|
||||
this.storage = storage;
|
||||
initStorage();
|
||||
hashEngine = new HashEngine(providers, getHashStorage(), config);
|
||||
initRotationStrategy();
|
||||
configured.set(true);
|
||||
}
|
||||
|
||||
private void initStorage() {
|
||||
if (config.isEnabled()) {
|
||||
switch (config.getHashStorageType()) {
|
||||
case in_memory:
|
||||
this.hashStorage = new InMemoryHashStorage();
|
||||
break;
|
||||
case sql:
|
||||
this.hashStorage = new SqlHashStorage(storage);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown storage type: " + config.getHashStorageType());
|
||||
}
|
||||
} else {
|
||||
this.hashStorage = new EmptyStorage();
|
||||
}
|
||||
}
|
||||
|
||||
private void initRotationStrategy() {
|
||||
if (config.isEnabled()) {
|
||||
switch (config.getRotationPolicy()) {
|
||||
case per_requests:
|
||||
this.rotationStrategy = new RotationPerRequests();
|
||||
break;
|
||||
case per_seconds:
|
||||
this.rotationStrategy = new TimeBasedRotation(config.getDelay());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown rotation type: " + config.getHashStorageType());
|
||||
}
|
||||
} else {
|
||||
this.rotationStrategy = new NoOpRotationStrategy();
|
||||
}
|
||||
|
||||
this.rotationStrategy.register(getHashEngine());
|
||||
}
|
||||
|
||||
public HashEngine getHashEngine() {
|
||||
return hashEngine;
|
||||
}
|
||||
|
||||
public HashRotationStrategy getRotationStrategy() {
|
||||
return rotationStrategy;
|
||||
}
|
||||
|
||||
public HashStorage getHashStorage() {
|
||||
return hashStorage;
|
||||
}
|
||||
|
||||
public HashingConfig getConfig() {
|
||||
return config;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package io.kamax.mxisd.hash.rotation;
|
||||
|
||||
import io.kamax.mxisd.hash.HashEngine;
|
||||
|
||||
public interface HashRotationStrategy {
|
||||
|
||||
void register(HashEngine hashEngine);
|
||||
|
||||
HashEngine getHashEngine();
|
||||
|
||||
void newRequest();
|
||||
|
||||
default void trigger() {
|
||||
getHashEngine().updateHashes();
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package io.kamax.mxisd.hash.rotation;
|
||||
|
||||
import io.kamax.mxisd.hash.HashEngine;
|
||||
|
||||
public class NoOpRotationStrategy implements HashRotationStrategy {
|
||||
|
||||
private HashEngine hashEngine;
|
||||
|
||||
@Override
|
||||
public void register(HashEngine hashEngine) {
|
||||
this.hashEngine = hashEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashEngine getHashEngine() {
|
||||
return hashEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newRequest() {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package io.kamax.mxisd.hash.rotation;
|
||||
|
||||
import io.kamax.mxisd.hash.HashEngine;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class RotationPerRequests implements HashRotationStrategy {
|
||||
|
||||
private HashEngine hashEngine;
|
||||
private final AtomicInteger counter = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public void register(HashEngine hashEngine) {
|
||||
this.hashEngine = hashEngine;
|
||||
trigger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashEngine getHashEngine() {
|
||||
return hashEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void newRequest() {
|
||||
int newValue = counter.incrementAndGet();
|
||||
if (newValue >= 10) {
|
||||
counter.set(0);
|
||||
trigger();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package io.kamax.mxisd.hash.rotation;
|
||||
|
||||
import io.kamax.mxisd.hash.HashEngine;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class TimeBasedRotation implements HashRotationStrategy {
|
||||
|
||||
private final long delay;
|
||||
private HashEngine hashEngine;
|
||||
private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
public TimeBasedRotation(long delay) {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(HashEngine hashEngine) {
|
||||
this.hashEngine = hashEngine;
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(executorService::shutdown));
|
||||
executorService.scheduleWithFixedDelay(this::trigger, 0, delay, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashEngine getHashEngine() {
|
||||
return hashEngine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void newRequest() {
|
||||
}
|
||||
}
|
25
src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java
Normal file
25
src/main/java/io/kamax/mxisd/hash/storage/EmptyStorage.java
Normal file
@@ -0,0 +1,25 @@
|
||||
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 Collection<Pair<String, ThreePidMapping>> find(Iterable<String> hashes) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(ThreePidMapping pidMapping, String hash) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
|
||||
}
|
||||
}
|
15
src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java
Normal file
15
src/main/java/io/kamax/mxisd/hash/storage/HashStorage.java
Normal file
@@ -0,0 +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;
|
||||
|
||||
public interface HashStorage {
|
||||
|
||||
Collection<Pair<String, ThreePidMapping>> find(Iterable<String> hashes);
|
||||
|
||||
void add(ThreePidMapping pidMapping, String hash);
|
||||
|
||||
void clear();
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
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;
|
||||
|
||||
public class InMemoryHashStorage implements HashStorage {
|
||||
|
||||
private final Map<String, ThreePidMapping> mapping = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public Collection<Pair<String, ThreePidMapping>> find(Iterable<String> hashes) {
|
||||
List<Pair<String, ThreePidMapping>> result = new ArrayList<>();
|
||||
for (String hash : hashes) {
|
||||
ThreePidMapping pidMapping = mapping.get(hash);
|
||||
if (pidMapping != null) {
|
||||
result.add(Pair.of(hash, pidMapping));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(ThreePidMapping pidMapping, String hash) {
|
||||
mapping.put(hash, pidMapping);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
mapping.clear();
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
package io.kamax.mxisd.hash.storage;
|
||||
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class SqlHashStorage implements HashStorage {
|
||||
|
||||
private final IStorage storage;
|
||||
|
||||
public SqlHashStorage(IStorage storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Pair<String, ThreePidMapping>> find(Iterable<String> hashes) {
|
||||
return storage.findHashes(hashes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(ThreePidMapping pidMapping, String hash) {
|
||||
storage.addHash(pidMapping.getMxid(), pidMapping.getMedium(), pidMapping.getValue(), hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
storage.clearHashes();
|
||||
}
|
||||
}
|
@@ -25,8 +25,6 @@ public class IsAPIv1 {
|
||||
public static final String Base = "/_matrix/identity/api/v1";
|
||||
|
||||
public static String getValidate(String medium, String sid, String secret, String token) {
|
||||
// FIXME use some kind of URLBuilder
|
||||
return Base + "/validate/" + medium + "/submitToken?sid=" + sid + "&client_secret=" + secret + "&token=" + token;
|
||||
return String.format("%s/validate/%s/submitToken?sid=%s&client_secret=%s&token=%s", Base, medium, sid, secret, token);
|
||||
}
|
||||
|
||||
}
|
||||
|
31
src/main/java/io/kamax/mxisd/http/IsAPIv2.java
Normal file
31
src/main/java/io/kamax/mxisd/http/IsAPIv2.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http;
|
||||
|
||||
public class IsAPIv2 {
|
||||
|
||||
public static final String Base = "/_matrix/identity/v2";
|
||||
|
||||
public static String getValidate(String medium, String sid, String secret, String token) {
|
||||
return String.format("%s/validate/%s/submitToken?sid=%s&client_secret=%s&token=%s", Base, medium, sid, secret, token);
|
||||
}
|
||||
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.io.identity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ClientHashLookupAnswer {
|
||||
|
||||
private Map<String, String> mappings = new HashMap<>();
|
||||
|
||||
public Map<String, String> getMappings() {
|
||||
return mappings;
|
||||
}
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String> 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<String> getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public void setAddresses(List<String> addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
package io.kamax.mxisd.http.undertow.handler;
|
||||
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.IsAPIv2;
|
||||
import io.kamax.mxisd.matrix.IdentityServiceAPI;
|
||||
import io.undertow.server.HttpHandler;
|
||||
|
||||
public interface ApiHandler extends HttpHandler {
|
||||
|
||||
default String getPath(IdentityServiceAPI api) {
|
||||
switch (api) {
|
||||
case V2:
|
||||
return IsAPIv2.Base + getHandlerPath();
|
||||
case V1:
|
||||
return IsAPIv1.Base + getHandlerPath();
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown api version: " + api);
|
||||
}
|
||||
}
|
||||
|
||||
String getHandlerPath();
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler;
|
||||
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AuthorizationHandler extends BasicHttpHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AuthorizationHandler.class);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
|
||||
private final HttpHandler child;
|
||||
|
||||
public static AuthorizationHandler around(AccountManager accountManager, HttpHandler child) {
|
||||
return new AuthorizationHandler(accountManager, child);
|
||||
}
|
||||
|
||||
private AuthorizationHandler(AccountManager accountManager, HttpHandler child) {
|
||||
this.accountManager = accountManager;
|
||||
this.child = child;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
String token = findAccessToken(exchange).orElse(null);
|
||||
if (token == null) {
|
||||
log.error("Unauthorized request from: {}", exchange.getHostAndPort());
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
AccountDao account = accountManager.findAccount(token);
|
||||
if (account == null) {
|
||||
log.error("Account not found from request from: {}", exchange.getHostAndPort());
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
long expiredAt = (account.getCreatedAt() + account.getExpiresIn()) * 1000; // expired in milliseconds
|
||||
if (expiredAt < System.currentTimeMillis()) {
|
||||
log.error("Account for '{}' from: {}", account.getUserId(), exchange.getHostAndPort());
|
||||
accountManager.deleteAccount(token);
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
log.trace("Access for '{}' allowed", account.getUserId());
|
||||
child.handleRequest(exchange);
|
||||
}
|
||||
}
|
@@ -28,6 +28,7 @@ import io.kamax.mxisd.exception.AccessTokenNotFoundException;
|
||||
import io.kamax.mxisd.exception.HttpMatrixException;
|
||||
import io.kamax.mxisd.exception.InternalServerError;
|
||||
import io.kamax.mxisd.proxy.Response;
|
||||
import io.kamax.mxisd.util.OptionalUtil;
|
||||
import io.kamax.mxisd.util.RestClientUtils;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
@@ -55,6 +56,24 @@ public abstract class BasicHttpHandler implements HttpHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BasicHttpHandler.class);
|
||||
|
||||
protected final static String headerName = "Authorization";
|
||||
protected final static String headerValuePrefix = "Bearer ";
|
||||
private final static String parameterName = "access_token";
|
||||
|
||||
Optional<String> findAccessTokenInHeaders(HttpServerExchange exchange) {
|
||||
return Optional.ofNullable(exchange.getRequestHeaders().getFirst(headerName))
|
||||
.filter(header -> StringUtils.startsWith(header, headerValuePrefix))
|
||||
.map(header -> header.substring(headerValuePrefix.length()));
|
||||
}
|
||||
|
||||
Optional<String> findAccessTokenInQuery(HttpServerExchange exchange) {
|
||||
return Optional.ofNullable(exchange.getQueryParameters().getOrDefault(parameterName, new LinkedList<>()).peekFirst());
|
||||
}
|
||||
|
||||
public Optional<String> findAccessToken(HttpServerExchange exchange) {
|
||||
return OptionalUtil.findFirst(() -> findAccessTokenInHeaders(exchange), () -> findAccessTokenInQuery(exchange));
|
||||
}
|
||||
|
||||
protected String getAccessToken(HttpServerExchange exchange) {
|
||||
return Optional.ofNullable(exchange.getRequestHeaders().getFirst("Authorization"))
|
||||
.flatMap(v -> {
|
||||
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler;
|
||||
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.config.PolicyConfig;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.undertow.server.HttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CheckTermsHandler extends BasicHttpHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(CheckTermsHandler.class);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
|
||||
private final HttpHandler child;
|
||||
|
||||
private final List<PolicyConfig.PolicyObject> policies;
|
||||
|
||||
public static CheckTermsHandler around(AccountManager accountManager, HttpHandler child, List<PolicyConfig.PolicyObject> policies) {
|
||||
return new CheckTermsHandler(accountManager, child, policies);
|
||||
}
|
||||
|
||||
private CheckTermsHandler(AccountManager accountManager, HttpHandler child,
|
||||
List<PolicyConfig.PolicyObject> policies) {
|
||||
this.accountManager = accountManager;
|
||||
this.child = child;
|
||||
this.policies = policies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
if (policies == null || policies.isEmpty()) {
|
||||
child.handleRequest(exchange);
|
||||
return;
|
||||
}
|
||||
|
||||
String token = findAccessToken(exchange).orElse(null);
|
||||
if (token == null) {
|
||||
log.error("Unauthorized request from: {}", exchange.getHostAndPort());
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
if (!accountManager.isTermAccepted(token, policies)) {
|
||||
log.error("Non accepting request from: {}", exchange.getHostAndPort());
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
log.trace("Access granted");
|
||||
child.handleRequest(exchange);
|
||||
}
|
||||
}
|
@@ -21,35 +21,11 @@
|
||||
package io.kamax.mxisd.http.undertow.handler;
|
||||
|
||||
import io.kamax.mxisd.exception.AccessTokenNotFoundException;
|
||||
import io.kamax.mxisd.util.OptionalUtil;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
|
||||
public abstract class HomeserverProxyHandler extends BasicHttpHandler {
|
||||
|
||||
protected final static String headerName = "Authorization";
|
||||
protected final static String headerValuePrefix = "Bearer ";
|
||||
private final static String parameterName = "access_token";
|
||||
|
||||
Optional<String> findAccessTokenInHeaders(HttpServerExchange exchange) {
|
||||
return Optional.ofNullable(exchange.getRequestHeaders().getFirst(headerName))
|
||||
.filter(header -> StringUtils.startsWith(header, headerValuePrefix))
|
||||
.map(header -> header.substring(headerValuePrefix.length()));
|
||||
}
|
||||
|
||||
Optional<String> findAccessTokenInQuery(HttpServerExchange exchange) {
|
||||
return Optional.ofNullable(exchange.getQueryParameters().getOrDefault(parameterName, new LinkedList<>()).peekFirst());
|
||||
}
|
||||
|
||||
public Optional<String> findAccessToken(HttpServerExchange exchange) {
|
||||
return OptionalUtil.findFirst(() -> findAccessTokenInHeaders(exchange), () -> findAccessTokenInQuery(exchange));
|
||||
}
|
||||
|
||||
public String getAccessToken(HttpServerExchange exchange) {
|
||||
return findAccessToken(exchange).orElseThrow(AccessTokenNotFoundException::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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());
|
||||
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.auth.v2;
|
||||
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AccountGetUserInfoHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = "/_matrix/identity/v2/account";
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AccountGetUserInfoHandler.class);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
|
||||
public AccountGetUserInfoHandler(AccountManager accountManager) {
|
||||
this.accountManager = accountManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
LOGGER.info("Get User Info.");
|
||||
String token = findAccessToken(exchange).orElseThrow(InvalidCredentialsException::new);
|
||||
|
||||
String userId = accountManager.getUserId(token);
|
||||
LOGGER.info("Account found: {}", userId);
|
||||
respond(exchange, GsonUtil.makeObj("user_id", userId));
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.auth.v2;
|
||||
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AccountLogoutHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = "/_matrix/identity/v2/account/logout";
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AccountLogoutHandler.class);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
|
||||
public AccountLogoutHandler(AccountManager accountManager) {
|
||||
this.accountManager = accountManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
LOGGER.info("Logout.");
|
||||
String token = findAccessToken(exchange).orElseThrow(InvalidCredentialsException::new);
|
||||
|
||||
accountManager.logout(token);
|
||||
|
||||
respondJson(exchange, "{}");
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.auth.v2;
|
||||
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.auth.OpenIdToken;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class AccountRegisterHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = "/_matrix/identity/v2/account/register";
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AccountRegisterHandler.class);
|
||||
|
||||
private final AccountManager accountManager;
|
||||
|
||||
public AccountRegisterHandler(AccountManager accountManager) {
|
||||
this.accountManager = accountManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
OpenIdToken openIdToken = parseJsonTo(exchange, OpenIdToken.class);
|
||||
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Registration from domain: {}, expired at {}", openIdToken.getMatrixServerName(),
|
||||
new Date(openIdToken.getExpiresIn()));
|
||||
}
|
||||
|
||||
String token = accountManager.register(openIdToken);
|
||||
respond(exchange, GsonUtil.makeObj("token", token));
|
||||
}
|
||||
}
|
@@ -18,18 +18,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.crypto.KeyManager;
|
||||
import io.kamax.mxisd.crypto.KeyType;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class EphemeralKeyIsValidHandler extends KeyIsValidHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/pubkey/ephemeral/isvalid";
|
||||
public class EphemeralKeyIsValidHandler extends KeyIsValidHandler implements ApiHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(EphemeralKeyIsValidHandler.class);
|
||||
|
||||
@@ -48,4 +46,8 @@ public class EphemeralKeyIsValidHandler extends KeyIsValidHandler {
|
||||
respondJson(exchange, mgr.isValid(KeyType.Ephemeral, pubKey) ? validKey : invalidKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/pubkey/ephemeral/isvalid";
|
||||
}
|
||||
}
|
@@ -18,19 +18,21 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
|
||||
public class HelloHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base;
|
||||
public class HelloHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
respondJson(exchange, "{}");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "";
|
||||
}
|
||||
}
|
@@ -18,23 +18,22 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.crypto.GenericKeyIdentifier;
|
||||
import io.kamax.mxisd.crypto.KeyManager;
|
||||
import io.kamax.mxisd.crypto.KeyType;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class KeyGetHandler extends BasicHttpHandler {
|
||||
public class KeyGetHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
public static final String Key = "key";
|
||||
public static final String Path = IsAPIv1.Base + "/pubkey/{" + Key + "}";
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(KeyGetHandler.class);
|
||||
|
||||
@@ -61,4 +60,8 @@ public class KeyGetHandler extends BasicHttpHandler {
|
||||
respond(exchange, obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/pubkey/{" + Key + "}";
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.http.io.identity.KeyValidityJson;
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.lookup.ALookupRequest;
|
@@ -18,18 +18,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.crypto.KeyManager;
|
||||
import io.kamax.mxisd.crypto.KeyType;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class RegularKeyIsValidHandler extends KeyIsValidHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/pubkey/isvalid";
|
||||
public class RegularKeyIsValidHandler extends KeyIsValidHandler implements ApiHandler {
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(RegularKeyIsValidHandler.class);
|
||||
|
||||
@@ -48,4 +46,8 @@ public class RegularKeyIsValidHandler extends KeyIsValidHandler {
|
||||
respondJson(exchange, mgr.isValid(KeyType.Regular, pubKey) ? validKey : invalidKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/pubkey/isvalid";
|
||||
}
|
||||
}
|
@@ -18,26 +18,25 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.matrix.ThreePidMedium;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.io.identity.RequestTokenResponse;
|
||||
import io.kamax.mxisd.http.io.identity.SessionEmailTokenRequestJson;
|
||||
import io.kamax.mxisd.http.io.identity.SessionPhoneTokenRequestJson;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SessionStartHandler extends BasicHttpHandler {
|
||||
public class SessionStartHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
public static final String Medium = "medium";
|
||||
public static final String Path = IsAPIv1.Base + "/validate/{" + Medium + "}/requestToken";
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(SessionStartHandler.class);
|
||||
|
||||
@@ -84,4 +83,8 @@ public class SessionStartHandler extends BasicHttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/validate/{" + Medium + "}/requestToken";
|
||||
}
|
||||
}
|
@@ -18,16 +18,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.crypto.SignatureManager;
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.io.identity.BindRequest;
|
||||
import io.kamax.mxisd.http.io.identity.SingeLookupReplyJson;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
@@ -42,9 +42,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.Deque;
|
||||
import java.util.Map;
|
||||
|
||||
public class SessionTpidBindHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/3pid/bind";
|
||||
public class SessionTpidBindHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionTpidBindHandler.class);
|
||||
|
||||
@@ -97,4 +95,8 @@ public class SessionTpidBindHandler extends BasicHttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/3pid/bind";
|
||||
}
|
||||
}
|
@@ -18,21 +18,19 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SessionTpidGetValidatedHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/3pid/getValidated3pid";
|
||||
public class SessionTpidGetValidatedHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionTpidGetValidatedHandler.class);
|
||||
|
||||
@@ -62,4 +60,8 @@ public class SessionTpidGetValidatedHandler extends BasicHttpHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/3pid/getValidated3pid";
|
||||
}
|
||||
}
|
@@ -18,19 +18,17 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/3pid/unbind";
|
||||
public class SessionTpidUnbindHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionTpidUnbindHandler.class);
|
||||
|
||||
@@ -48,4 +46,9 @@ public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
||||
sessionMgr.unbind(auth, body);
|
||||
writeBodyAsUtf8(exchange, "{}");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/3pid/unbind";
|
||||
}
|
||||
}
|
@@ -18,19 +18,17 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.session.SessionManager;
|
||||
import io.kamax.mxisd.session.ValidationResult;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class SessionValidateHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/validate/{medium}/submitToken";
|
||||
public abstract class SessionValidateHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private transient final Logger log = LoggerFactory.getLogger(SessionValidateHandler.class);
|
||||
|
||||
@@ -52,4 +50,8 @@ public abstract class SessionValidateHandler extends BasicHttpHandler {
|
||||
return mgr.validate(sid, secret, token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/validate/{medium}/submitToken";
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import io.kamax.mxisd.config.MxisdConfig;
|
||||
import io.kamax.mxisd.config.ServerConfig;
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.MatrixID;
|
||||
@@ -27,17 +27,15 @@ import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.matrix.json.MatrixJson;
|
||||
import io.kamax.mxisd.config.MxisdConfig;
|
||||
import io.kamax.mxisd.crypto.SignatureManager;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SignEd25519Handler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/sign-ed25519";
|
||||
public class SignEd25519Handler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SignEd25519Handler.class);
|
||||
|
||||
@@ -72,4 +70,8 @@ public class SignEd25519Handler extends BasicHttpHandler {
|
||||
respondJson(exchange, res);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/sign-ed25519";
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.share;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
@@ -28,10 +28,10 @@ import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.config.ServerConfig;
|
||||
import io.kamax.mxisd.crypto.KeyManager;
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.io.identity.StoreInviteRequest;
|
||||
import io.kamax.mxisd.http.io.identity.ThreePidInviteReplyIO;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.invitation.IThreePidInvite;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
@@ -45,9 +45,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.Deque;
|
||||
import java.util.Map;
|
||||
|
||||
public class StoreInviteHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/store-invite";
|
||||
public class StoreInviteHandler extends BasicHttpHandler implements ApiHandler {
|
||||
|
||||
private ServerConfig cfg;
|
||||
private InvitationManager invMgr;
|
||||
@@ -100,4 +98,8 @@ public class StoreInviteHandler extends BasicHttpHandler {
|
||||
respondJson(exchange, new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getServerSigningKey().getId()), cfg.getPublicUrl()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/store-invite";
|
||||
}
|
||||
}
|
@@ -23,6 +23,8 @@ package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.io.identity.ClientBulkLookupAnswer;
|
||||
import io.kamax.mxisd.http.io.identity.ClientBulkLookupRequest;
|
||||
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.ThreePidMapping;
|
||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||
@@ -33,7 +35,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BulkLookupHandler extends LookupHandler {
|
||||
public class BulkLookupHandler extends LookupHandler implements ApiHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/bulk_lookup";
|
||||
|
||||
@@ -69,4 +71,8 @@ public class BulkLookupHandler extends LookupHandler {
|
||||
respondJson(exchange, answer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/bulk_lookup";
|
||||
}
|
||||
}
|
||||
|
@@ -27,6 +27,8 @@ import io.kamax.mxisd.config.ServerConfig;
|
||||
import io.kamax.mxisd.crypto.SignatureManager;
|
||||
import io.kamax.mxisd.http.IsAPIv1;
|
||||
import io.kamax.mxisd.http.io.identity.SingeLookupReplyJson;
|
||||
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.LookupHandler;
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||
@@ -36,7 +38,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class SingleLookupHandler extends LookupHandler {
|
||||
public class SingleLookupHandler extends LookupHandler implements ApiHandler {
|
||||
|
||||
public static final String Path = IsAPIv1.Base + "/lookup";
|
||||
|
||||
@@ -77,4 +79,8 @@ public class SingleLookupHandler extends LookupHandler {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/lookup";
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,38 @@
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v2;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.config.HashingConfig;
|
||||
import io.kamax.mxisd.hash.HashManager;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
|
||||
public class HashDetailsHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String PATH = "/_matrix/identity/v2/hash_details";
|
||||
|
||||
private final HashManager hashManager;
|
||||
|
||||
public HashDetailsHandler(HashManager hashManager) {
|
||||
this.hashManager = hashManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
respond(exchange, getResponse());
|
||||
}
|
||||
|
||||
private JsonObject getResponse() {
|
||||
JsonObject response = new JsonObject();
|
||||
response.addProperty("lookup_pepper", hashManager.getHashEngine().getPepper());
|
||||
JsonArray algorithms = new JsonArray();
|
||||
HashingConfig config = hashManager.getConfig();
|
||||
if (config.isEnabled()) {
|
||||
for (HashingConfig.Algorithm algorithm : config.getAlgorithms()) {
|
||||
algorithms.add(algorithm.name().toLowerCase());
|
||||
}
|
||||
}
|
||||
response.add("algorithms", algorithms);
|
||||
return response;
|
||||
}
|
||||
}
|
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.http.undertow.handler.identity.v2;
|
||||
|
||||
import io.kamax.mxisd.config.HashingConfig;
|
||||
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;
|
||||
this.hashManager = hashManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
ClientHashLookupRequest input = parseJsonTo(exchange, ClientHashLookupRequest.class);
|
||||
HashLookupRequest lookupRequest = new HashLookupRequest();
|
||||
setRequesterInfo(lookupRequest, exchange);
|
||||
lookupRequest.setHashes(input.getAddresses());
|
||||
log.info("Got bulk lookup request from {} with client {} - Is recursive? {}",
|
||||
lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive());
|
||||
|
||||
if (!hashManager.getConfig().isEnabled()) {
|
||||
throw new InvalidParamException();
|
||||
}
|
||||
|
||||
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 {
|
||||
if (!hashManager.getConfig().getAlgorithms().contains(HashingConfig.Algorithm.none)) {
|
||||
throw new InvalidParamException();
|
||||
}
|
||||
|
||||
BulkLookupRequest bulkLookupRequest = new BulkLookupRequest();
|
||||
List<ThreePidMapping> mappings = new ArrayList<>();
|
||||
for (String address : input.getAddresses()) {
|
||||
String[] parts = address.split(" ");
|
||||
ThreePidMapping mapping = new ThreePidMapping();
|
||||
mapping.setMedium(parts[1]);
|
||||
mapping.setValue(parts[0]);
|
||||
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) {
|
||||
if (!hashManager.getConfig().getAlgorithms().contains(HashingConfig.Algorithm.sha256)) {
|
||||
throw new InvalidParamException();
|
||||
}
|
||||
|
||||
ClientHashLookupAnswer answer = new ClientHashLookupAnswer();
|
||||
if (request.getHashes() != null && !request.getHashes().isEmpty()) {
|
||||
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");
|
||||
}
|
||||
|
||||
respondJson(exchange, answer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/bulk_lookup";
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
package io.kamax.mxisd.http.undertow.handler.term.v2;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.auth.AccountManager;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class AcceptTermsHandler extends BasicHttpHandler {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AcceptTermsHandler.class);
|
||||
|
||||
public static final String PATH = "/_matrix/identity/v2/terms";
|
||||
|
||||
private final AccountManager accountManager;
|
||||
|
||||
public AcceptTermsHandler(AccountManager accountManager) {
|
||||
this.accountManager = accountManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
String token = getAccessToken(exchange);
|
||||
|
||||
JsonObject request = parseJsonObject(exchange);
|
||||
JsonElement accepts = request.get("user_accepts");
|
||||
AccountDao account = accountManager.findAccount(token);
|
||||
|
||||
if (account == null) {
|
||||
throw new InvalidCredentialsException();
|
||||
}
|
||||
|
||||
if (accepts == null || accepts.isJsonNull()) {
|
||||
respondJson(exchange, "{}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (accepts.isJsonArray()) {
|
||||
for (JsonElement acceptItem : accepts.getAsJsonArray()) {
|
||||
String termUrl = acceptItem.getAsString();
|
||||
LOGGER.info("User {} accepts the term: {}", account.getUserId(), termUrl);
|
||||
accountManager.acceptTerm(token, termUrl);
|
||||
}
|
||||
} else {
|
||||
String termUrl = accepts.getAsString();
|
||||
LOGGER.info("User {} accepts the term: {}", account.getUserId(), termUrl);
|
||||
accountManager.acceptTerm(token, termUrl);
|
||||
}
|
||||
|
||||
respondJson(exchange, "{}");
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package io.kamax.mxisd.http.undertow.handler.term.v2;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.config.PolicyConfig;
|
||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class GetTermsHandler extends BasicHttpHandler {
|
||||
|
||||
public static final String PATH = "/_matrix/identity/v2/terms";
|
||||
|
||||
private final JsonObject policyResponse;
|
||||
|
||||
public GetTermsHandler(PolicyConfig config) {
|
||||
policyResponse = new JsonObject();
|
||||
JsonObject policies = new JsonObject();
|
||||
for (Map.Entry<String, PolicyConfig.PolicyObject> policyItem : config.getPolicies().entrySet()) {
|
||||
JsonObject policy = new JsonObject();
|
||||
policy.addProperty("version", policyItem.getValue().getVersion());
|
||||
for (Map.Entry<String, PolicyConfig.TermObject> termEntry : policyItem.getValue().getTerms().entrySet()) {
|
||||
JsonObject term = new JsonObject();
|
||||
term.addProperty("name", termEntry.getValue().getName());
|
||||
term.addProperty("url", termEntry.getValue().getUrl());
|
||||
policy.add(termEntry.getKey(), term);
|
||||
}
|
||||
policies.add(policyItem.getKey(), policy);
|
||||
}
|
||||
policyResponse.add("policies", policies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||
respond(exchange, policyResponse);
|
||||
}
|
||||
}
|
@@ -29,7 +29,11 @@ import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.config.InvitationConfig;
|
||||
import io.kamax.mxisd.config.MxisdConfig;
|
||||
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.ConfigurationException;
|
||||
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.strategy.LookupStrategy;
|
||||
import io.kamax.mxisd.matrix.HomeserverFederationResolver;
|
||||
import io.kamax.mxisd.matrix.HomeserverVerifier;
|
||||
import io.kamax.mxisd.notification.NotificationManager;
|
||||
import io.kamax.mxisd.profile.ProfileManager;
|
||||
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.http.client.methods.CloseableHttpResponse;
|
||||
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.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.ssl.SSLContextBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.DateTimeException;
|
||||
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.ForkJoinPool;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -86,7 +94,6 @@ public class InvitationManager {
|
||||
private NotificationManager notifMgr;
|
||||
private ProfileManager profileMgr;
|
||||
|
||||
private CloseableHttpClient client;
|
||||
private Timer refreshTimer;
|
||||
|
||||
private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>();
|
||||
@@ -129,17 +136,6 @@ public class InvitationManager {
|
||||
});
|
||||
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");
|
||||
refreshTimer = new Timer();
|
||||
|
||||
@@ -423,11 +419,11 @@ public class InvitationManager {
|
||||
String address = reply.getInvite().getAddress();
|
||||
String domain = reply.getInvite().getSender().getDomain();
|
||||
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
|
||||
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
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("mxid", mxid);
|
||||
@@ -459,36 +455,41 @@ public class InvitationManager {
|
||||
Instant resolvedAt = Instant.now();
|
||||
boolean couldPublish = false;
|
||||
boolean shouldArchive = true;
|
||||
try {
|
||||
log.info("Posting onBind event to {}", req.getURI());
|
||||
CloseableHttpResponse response = client.execute(req);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
log.info("Answer code: {}", statusCode);
|
||||
if (statusCode >= 300 && statusCode != 403) {
|
||||
log.info("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
|
||||
log.warn("HS returned an error.");
|
||||
try (CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new HomeserverVerifier(hsUrlOpt.getDomain()))
|
||||
.build()) {
|
||||
try {
|
||||
log.info("Posting onBind event to {}", req.getURI());
|
||||
CloseableHttpResponse response = httpClient.execute(req);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
log.info("Answer code: {}", statusCode);
|
||||
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) {
|
||||
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");
|
||||
synchronized (this) {
|
||||
storage.insertHistoricalInvite(reply, mxid, resolvedAt, couldPublish);
|
||||
removeInvite(reply);
|
||||
log.info("Moved invite {} to historical table", reply.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
response.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Unable to tell HS {} about invite being mapped", domain, e);
|
||||
} finally {
|
||||
if (shouldArchive) {
|
||||
synchronized (this) {
|
||||
storage.insertHistoricalInvite(reply, mxid, resolvedAt, couldPublish);
|
||||
removeInvite(reply);
|
||||
log.info("Moved invite {} to historical table", reply.getId());
|
||||
}
|
||||
}
|
||||
log.error("Unable to create client to the " + hsUrlOpt.getUrl().toString(), e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
16
src/main/java/io/kamax/mxisd/lookup/HashLookupRequest.java
Normal file
16
src/main/java/io/kamax/mxisd/lookup/HashLookupRequest.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package io.kamax.mxisd.lookup;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HashLookupRequest extends ALookupRequest {
|
||||
|
||||
private List<String> hashes;
|
||||
|
||||
public List<String> getHashes() {
|
||||
return hashes;
|
||||
}
|
||||
|
||||
public void setHashes(List<String> hashes) {
|
||||
this.hashes = hashes;
|
||||
}
|
||||
}
|
@@ -24,6 +24,7 @@ import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -40,4 +41,7 @@ public interface IThreePidProvider {
|
||||
|
||||
List<ThreePidMapping> populate(List<ThreePidMapping> mappings);
|
||||
|
||||
default Iterable<ThreePidMapping> populateHashes() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
@@ -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<List<ThreePidMapping>> find(BulkLookupRequest requests);
|
||||
|
||||
CompletableFuture<List<ThreePidMapping>> find(HashLookupRequest request);
|
||||
}
|
||||
|
@@ -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<CIDRUtils> allowedCidr = new ArrayList<>();
|
||||
|
||||
public RecursivePriorityLookupStrategy(MxisdConfig.Lookup cfg, List<? extends IThreePidProvider> providers, IBridgeFetcher bridge) {
|
||||
private HashManager hashManager;
|
||||
|
||||
public RecursivePriorityLookupStrategy(MxisdConfig.Lookup cfg, List<? extends IThreePidProvider> 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<SingleLookupReply> 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<SingleLookupReply> 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<List<ThreePidMapping>> find(HashLookupRequest request) {
|
||||
HashStorage hashStorage = hashManager.getHashStorage();
|
||||
CompletableFuture<List<ThreePidMapping>> result = new CompletableFuture<>();
|
||||
result.complete(hashStorage.find(request.getHashes()).stream().map(Pair::getValue).collect(Collectors.toList()));
|
||||
hashManager.getRotationStrategy().newRequest();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@@ -178,26 +178,26 @@ public class HomeserverFederationResolver {
|
||||
}
|
||||
}
|
||||
|
||||
public URL resolve(String domain) {
|
||||
public HomeserverTarget resolve(String domain) {
|
||||
Optional<URL> s1 = resolveOverwrite(domain);
|
||||
if (s1.isPresent()) {
|
||||
URL dest = s1.get();
|
||||
log.info("Resolution of {} via DNS overwrite to {}", domain, dest);
|
||||
return dest;
|
||||
return new HomeserverTarget(dest.getHost(), dest);
|
||||
}
|
||||
|
||||
Optional<URL> s2 = resolveLiteral(domain);
|
||||
if (s2.isPresent()) {
|
||||
URL dest = s2.get();
|
||||
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);
|
||||
if (s3.isPresent()) {
|
||||
URL dest = s3.get();
|
||||
log.info("Resolution of {} via well-known to {}", domain, dest);
|
||||
return dest;
|
||||
return new HomeserverTarget(dest.getHost(), dest);
|
||||
}
|
||||
// The domain needs to be resolved
|
||||
|
||||
@@ -205,12 +205,30 @@ public class HomeserverFederationResolver {
|
||||
if (s4.isPresent()) {
|
||||
URL dest = s4.get();
|
||||
log.info("Resolution of {} via DNS SRV record to {}", domain, dest);
|
||||
return dest;
|
||||
return new HomeserverTarget(domain, dest);
|
||||
}
|
||||
|
||||
URL dest = build(domain + ":" + getDefaultPort());
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
85
src/main/java/io/kamax/mxisd/matrix/HomeserverVerifier.java
Normal file
85
src/main/java/io/kamax/mxisd/matrix/HomeserverVerifier.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
package io.kamax.mxisd.matrix;
|
||||
|
||||
public enum IdentityServiceAPI {
|
||||
|
||||
@Deprecated
|
||||
V1,
|
||||
|
||||
V2
|
||||
}
|
@@ -39,6 +39,7 @@ import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||
import io.kamax.mxisd.matrix.HomeserverFederationResolver;
|
||||
import io.kamax.mxisd.matrix.HomeserverVerifier;
|
||||
import io.kamax.mxisd.notification.NotificationManager;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||
@@ -53,10 +54,14 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.Calendar;
|
||||
@@ -70,7 +75,6 @@ public class SessionManager {
|
||||
private IStorage storage;
|
||||
private NotificationManager notifMgr;
|
||||
private HomeserverFederationResolver resolver;
|
||||
private CloseableHttpClient client;
|
||||
private SignatureManager signatureManager;
|
||||
|
||||
public SessionManager(
|
||||
@@ -78,14 +82,12 @@ public class SessionManager {
|
||||
IStorage storage,
|
||||
NotificationManager notifMgr,
|
||||
HomeserverFederationResolver resolver,
|
||||
CloseableHttpClient client,
|
||||
SignatureManager signatureManager
|
||||
) {
|
||||
this.cfg = cfg;
|
||||
this.storage = storage;
|
||||
this.notifMgr = notifMgr;
|
||||
this.resolver = resolver;
|
||||
this.client = client;
|
||||
this.signatureManager = signatureManager;
|
||||
}
|
||||
|
||||
@@ -218,8 +220,15 @@ public class SessionManager {
|
||||
throw new BadRequestException("Missing required 3PID");
|
||||
}
|
||||
|
||||
// We only allow unbind for the domain we manage, mirroring bind
|
||||
final CharSequence domain = cfg.getMatrix().getDomain();
|
||||
if (!StringUtils.equalsIgnoreCase(domain, mxid.getDomain())) {
|
||||
throw new NotAllowedException("Only Matrix IDs from domain " + domain + " can be unbound");
|
||||
}
|
||||
|
||||
log.info("Request was authorized.");
|
||||
if (StringUtils.isNotBlank(sid) && StringUtils.isNotBlank(secret)) {
|
||||
checkSession(sid, secret, tpid, mxid);
|
||||
checkSession(sid, secret, tpid);
|
||||
} else if (StringUtils.isNotBlank(auth)) {
|
||||
checkAuthorization(auth, reqData);
|
||||
} else {
|
||||
@@ -227,7 +236,21 @@ public class SessionManager {
|
||||
}
|
||||
|
||||
log.info("Unbinding of {} {} to {} is accepted", tpid.getMedium(), tpid.getAddress(), mxid.getId());
|
||||
notifMgr.sendForUnbind(tpid);
|
||||
if (cfg.getSession().getPolicy().getUnbind().shouldNotify()) {
|
||||
notifMgr.sendForUnbind(tpid);
|
||||
}
|
||||
}
|
||||
|
||||
private String getDomain(String publicUrl) {
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(publicUrl);
|
||||
} catch (MalformedURLException e) {
|
||||
log.error("Malformed public url, use as is");
|
||||
return publicUrl;
|
||||
}
|
||||
int port = url.getPort();
|
||||
return url.getHost() + (port != -1 ? ":" + url.getPort() : "");
|
||||
}
|
||||
|
||||
private void checkAuthorization(String auth, JsonObject reqData) {
|
||||
@@ -235,7 +258,9 @@ public class SessionManager {
|
||||
throw new NotAllowedException("Wrong authorization header");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(cfg.getServer().getPublicUrl())) {
|
||||
String domain = getDomain(cfg.getServer().getPublicUrl());
|
||||
|
||||
if (StringUtils.isBlank(domain)) {
|
||||
throw new NotAllowedException("Unable to verify request, missing `server.publicUrl` property");
|
||||
}
|
||||
|
||||
@@ -269,34 +294,47 @@ public class SessionManager {
|
||||
throw new BadRequestException("Missing required header parameters");
|
||||
}
|
||||
|
||||
if (!cfg.getMatrix().getDomain().equalsIgnoreCase(origin)) {
|
||||
throw new NotAllowedException("Only Matrix IDs from domain " + origin + " can be unbound");
|
||||
}
|
||||
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.addProperty("method", "POST");
|
||||
jsonObject.addProperty("uri", "/_matrix/identity/api/v1/3pid/unbind");
|
||||
jsonObject.addProperty("origin", origin);
|
||||
jsonObject.addProperty("destination_is", cfg.getServer().getPublicUrl());
|
||||
jsonObject.addProperty("destination_is", domain);
|
||||
jsonObject.add("content", reqData);
|
||||
|
||||
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) {
|
||||
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");
|
||||
log.info("Get keys from the server {}", request.getURI());
|
||||
try (CloseableHttpResponse response = client.execute(request)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
log.info("Answer code: {}", statusCode);
|
||||
if (statusCode == 200) {
|
||||
verifyKey(key, signature, canonical, response);
|
||||
} else {
|
||||
throw new RemoteHomeServerException("Unable to fetch server keys.");
|
||||
try (CloseableHttpClient httpClient = HttpClients.custom()
|
||||
.setSSLHostnameVerifier(new HomeserverVerifier(homeserverTarget.getDomain())).build()) {
|
||||
try (CloseableHttpResponse response = httpClient.execute(request)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
log.info("Answer code: {}", statusCode);
|
||||
if (statusCode == 200) {
|
||||
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) {
|
||||
String message = "Unable to get server keys: " + originUrl;
|
||||
@@ -340,7 +378,7 @@ public class SessionManager {
|
||||
log.info("Request was authorized.");
|
||||
}
|
||||
|
||||
private void checkSession(String sid, String secret, ThreePid tpid, _MatrixID mxid) {
|
||||
private void checkSession(String sid, String secret, ThreePid tpid) {
|
||||
// We ensure the session was validated
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
|
||||
@@ -348,13 +386,5 @@ public class SessionManager {
|
||||
if (!session.getThreePid().equals(tpid)) {
|
||||
throw new BadRequestException("3PID to unbind does not match the one from the validated session");
|
||||
}
|
||||
|
||||
// We only allow unbind for the domain we manage, mirroring bind
|
||||
final CharSequence domain = cfg.getMatrix().getDomain();
|
||||
if (!StringUtils.equalsIgnoreCase(domain, mxid.getDomain())) {
|
||||
throw new NotAllowedException("Only Matrix IDs from domain " + domain + " can be unbound");
|
||||
}
|
||||
|
||||
log.info("Request was authorized.");
|
||||
}
|
||||
}
|
||||
|
@@ -21,13 +21,18 @@
|
||||
package io.kamax.mxisd.storage;
|
||||
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.mxisd.config.PolicyConfig;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
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.ThreePidInviteIO;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface IStorage {
|
||||
@@ -52,4 +57,21 @@ public interface IStorage {
|
||||
|
||||
Optional<ASTransactionDao> getTransactionResult(String localpart, String txnId);
|
||||
|
||||
void insertToken(AccountDao accountDao);
|
||||
|
||||
Optional<AccountDao> findAccount(String token);
|
||||
|
||||
void deleteToken(String token);
|
||||
|
||||
void acceptTerm(String token, String url);
|
||||
|
||||
void deleteAccepts(String token);
|
||||
|
||||
boolean isTermAccepted(String token, List<PolicyConfig.PolicyObject> policies);
|
||||
|
||||
void clearHashes();
|
||||
|
||||
void addHash(String mxid, String medium, String address, String hash);
|
||||
|
||||
Collection<Pair<String, ThreePidMapping>> findHashes(Iterable<String> hashes);
|
||||
}
|
||||
|
@@ -24,25 +24,38 @@ import com.j256.ormlite.dao.CloseableWrappedIterable;
|
||||
import com.j256.ormlite.dao.Dao;
|
||||
import com.j256.ormlite.dao.DaoManager;
|
||||
import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
||||
import com.j256.ormlite.stmt.QueryBuilder;
|
||||
import com.j256.ormlite.support.ConnectionSource;
|
||||
import com.j256.ormlite.table.TableUtils;
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.mxisd.config.MxisdConfig;
|
||||
import io.kamax.mxisd.config.PolicyConfig;
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
import io.kamax.mxisd.exception.InternalServerError;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
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.HashDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.HistoricalThreePidInviteIO;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AcceptedDao;
|
||||
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 java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class OrmLiteSqlStorage implements IStorage {
|
||||
|
||||
@@ -64,6 +77,9 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
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<HashDao, String> hashDao;
|
||||
|
||||
public OrmLiteSqlStorage(MxisdConfig cfg) {
|
||||
this(cfg.getStorage().getBackend(), cfg.getStorage().getProvider().getSqlite().getDatabase());
|
||||
@@ -84,6 +100,9 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
expInvDao = createDaoAndTable(connPool, HistoricalThreePidInviteIO.class);
|
||||
sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class);
|
||||
asTxnDao = createDaoAndTable(connPool, ASTransactionDao.class);
|
||||
accountDao = createDaoAndTable(connPool, AccountDao.class);
|
||||
acceptedDao = createDaoAndTable(connPool, AcceptedDao.class);
|
||||
hashDao = createDaoAndTable(connPool, HashDao.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,7 +194,7 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
List<ThreePidSessionDao> daoList = sessionDao.queryForMatchingArgs(new ThreePidSessionDao(tpid, secret));
|
||||
if (daoList.size() > 1) {
|
||||
throw new InternalServerError("Lookup for 3PID Session " +
|
||||
tpid + " returned more than one result");
|
||||
tpid + " returned more than one result");
|
||||
}
|
||||
|
||||
if (daoList.isEmpty()) {
|
||||
@@ -226,7 +245,7 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
|
||||
if (daoList.size() > 1) {
|
||||
throw new InternalServerError("Lookup for Transaction " +
|
||||
txnId + " for localpart " + localpart + " returned more than one result");
|
||||
txnId + " for localpart " + localpart + " returned more than one result");
|
||||
}
|
||||
|
||||
if (daoList.isEmpty()) {
|
||||
@@ -237,4 +256,110 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertToken(AccountDao account) {
|
||||
withCatcher(() -> {
|
||||
int created = accountDao.create(account);
|
||||
if (created != 1) {
|
||||
throw new RuntimeException("Unexpected row count after DB action: " + created);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AccountDao> findAccount(String token) {
|
||||
return withCatcher(() -> {
|
||||
List<AccountDao> accounts = accountDao.queryForEq("token", token);
|
||||
if (accounts.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
if (accounts.size() != 1) {
|
||||
throw new RuntimeException("Unexpected rows for access token: " + accounts.size());
|
||||
}
|
||||
return Optional.of(accounts.get(0));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteToken(String token) {
|
||||
withCatcher(() -> {
|
||||
int updated = accountDao.deleteById(token);
|
||||
if (updated != 1) {
|
||||
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void acceptTerm(String token, String url) {
|
||||
withCatcher(() -> {
|
||||
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()));
|
||||
if (created != 1) {
|
||||
throw new RuntimeException("Unexpected row count after DB action: " + created);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAccepts(String token) {
|
||||
withCatcher(() -> {
|
||||
AccountDao account = findAccount(token).orElseThrow(InvalidCredentialsException::new);
|
||||
acceptedDao.delete(acceptedDao.queryForEq("userId", account.getUserId()));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTermAccepted(String token, List<PolicyConfig.PolicyObject> policies) {
|
||||
return withCatcher(() -> {
|
||||
AccountDao account = findAccount(token).orElseThrow(InvalidCredentialsException::new);
|
||||
List<AcceptedDao> acceptedTerms = acceptedDao.queryForEq("userId", account.getUserId());
|
||||
for (AcceptedDao acceptedTerm : acceptedTerms) {
|
||||
for (PolicyConfig.PolicyObject policy : policies) {
|
||||
for (PolicyConfig.TermObject termObject : policy.getTerms().values()) {
|
||||
if (termObject.getUrl().equalsIgnoreCase(acceptedTerm.getUrl())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearHashes() {
|
||||
withCatcher(() -> {
|
||||
List<HashDao> allHashes = hashDao.queryForAll();
|
||||
int deleted = hashDao.delete(allHashes);
|
||||
if (deleted != allHashes.size()) {
|
||||
throw new RuntimeException("Not all hashes deleted: " + deleted);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHash(String mxid, String medium, String address, String hash) {
|
||||
withCatcher(() -> {
|
||||
hashDao.create(new HashDao(mxid, medium, address, hash));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Pair<String, ThreePidMapping>> findHashes(Iterable<String> hashes) {
|
||||
return withCatcher(() -> {
|
||||
QueryBuilder<HashDao, String> builder = hashDao.queryBuilder();
|
||||
builder.where().in("hash", hashes);
|
||||
return hashDao.query(builder.prepare()).stream()
|
||||
.map(dao -> Pair.of(dao.getHash(), new ThreePidMapping(dao.getMedium(), dao.getAddress(), dao.getMxid()))).collect(
|
||||
Collectors.toList());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.storage.ormlite.dao;
|
||||
|
||||
import com.j256.ormlite.field.DatabaseField;
|
||||
import com.j256.ormlite.table.DatabaseTable;
|
||||
|
||||
@DatabaseTable(tableName = "accepted")
|
||||
public class AcceptedDao {
|
||||
|
||||
@DatabaseField(canBeNull = false, id = true)
|
||||
private String url;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String userId;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private long acceptedAt;
|
||||
|
||||
public AcceptedDao() {
|
||||
// Needed for ORMLite
|
||||
}
|
||||
|
||||
public AcceptedDao(String url, String userId, long acceptedAt) {
|
||||
this.url = url;
|
||||
this.userId = userId;
|
||||
this.acceptedAt = acceptedAt;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public long getAcceptedAt() {
|
||||
return acceptedAt;
|
||||
}
|
||||
|
||||
public void setAcceptedAt(long acceptedAt) {
|
||||
this.acceptedAt = acceptedAt;
|
||||
}
|
||||
}
|
119
src/main/java/io/kamax/mxisd/storage/ormlite/dao/AccountDao.java
Normal file
119
src/main/java/io/kamax/mxisd/storage/ormlite/dao/AccountDao.java
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.storage.ormlite.dao;
|
||||
|
||||
import com.j256.ormlite.field.DatabaseField;
|
||||
import com.j256.ormlite.table.DatabaseTable;
|
||||
|
||||
@DatabaseTable(tableName = "account")
|
||||
public class AccountDao {
|
||||
|
||||
@DatabaseField(canBeNull = false, id = true)
|
||||
private String token;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String accessToken;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String tokenType;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String matrixServerName;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private long expiresIn;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private long createdAt;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String userId;
|
||||
|
||||
public AccountDao() {
|
||||
// Needed for ORMLite
|
||||
}
|
||||
|
||||
public AccountDao(String accessToken, String tokenType, String matrixServerName, long expiresIn, long createdAt, String userId, String token) {
|
||||
this.accessToken = accessToken;
|
||||
this.tokenType = tokenType;
|
||||
this.matrixServerName = matrixServerName;
|
||||
this.expiresIn = expiresIn;
|
||||
this.createdAt = createdAt;
|
||||
this.userId = userId;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public void setAccessToken(String accessToken) {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
public String getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void setTokenType(String tokenType) {
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
|
||||
public String getMatrixServerName() {
|
||||
return matrixServerName;
|
||||
}
|
||||
|
||||
public void setMatrixServerName(String matrixServerName) {
|
||||
this.matrixServerName = matrixServerName;
|
||||
}
|
||||
|
||||
public long getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public void setExpiresIn(long expiresIn) {
|
||||
this.expiresIn = expiresIn;
|
||||
}
|
||||
|
||||
public long getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(long createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
package io.kamax.mxisd.storage.ormlite.dao;
|
||||
|
||||
import com.j256.ormlite.field.DatabaseField;
|
||||
import com.j256.ormlite.table.DatabaseTable;
|
||||
|
||||
@DatabaseTable(tableName = "hashes")
|
||||
public class HashDao {
|
||||
|
||||
@DatabaseField(canBeNull = false, id = true)
|
||||
private String mxid;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String medium;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String address;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String hash;
|
||||
|
||||
public HashDao() {
|
||||
}
|
||||
|
||||
public HashDao(String mxid, String medium, String address, String hash) {
|
||||
this.mxid = mxid;
|
||||
this.medium = medium;
|
||||
this.address = address;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
public String getMxid() {
|
||||
return mxid;
|
||||
}
|
||||
|
||||
public void setMxid(String mxid) {
|
||||
this.mxid = mxid;
|
||||
}
|
||||
|
||||
public String getMedium() {
|
||||
return medium;
|
||||
}
|
||||
|
||||
public void setMedium(String medium) {
|
||||
this.medium = medium;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public void setHash(String hash) {
|
||||
this.hash = hash;
|
||||
}
|
||||
}
|
15
src/test/java/io/kamax/mxisd/test/hash/HashEngineTest.java
Normal file
15
src/test/java/io/kamax/mxisd/test/hash/HashEngineTest.java
Normal 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"));
|
||||
}
|
||||
}
|
@@ -51,13 +51,13 @@ public class HomeserverFederationResolverTest {
|
||||
|
||||
@Test
|
||||
public void hostnameWithoutPort() {
|
||||
URL url = resolver.resolve("example.org");
|
||||
URL url = resolver.resolve("example.org").getUrl();
|
||||
assertEquals("https://example.org:8448", url.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
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());
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user