Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
d5680b2dfe | ||
|
5aad4fb81e | ||
|
a1f64f5159 | ||
|
a96920f533 |
@@ -10,11 +10,13 @@ ma1sd - Federated Matrix Identity Server
|
||||
- [Contribute](#contribute)
|
||||
- [Powered by ma1sd](#powered-by-ma1sd)
|
||||
- [FAQ](#faq)
|
||||
- [Migration from mxisd](#migration-from-mxisd)
|
||||
- [Contact](#contact)
|
||||
|
||||
---
|
||||
|
||||
* 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.
|
||||
|
||||
---
|
||||
|
||||
@@ -108,6 +110,10 @@ The following projects can use ma1sd under the hood for some or all their featur
|
||||
# FAQ
|
||||
See the [dedicated document](docs/faq.md)
|
||||
|
||||
# Migration from mxisd
|
||||
|
||||
See the [migration guide](docs/migration-from-mxisd.md)
|
||||
|
||||
# Contact
|
||||
Get in touch via:
|
||||
- Matrix: [#ma1sd:ru-matrix.org](https://matrix.to/#/#ma1sd:ru-matrix.org)
|
||||
|
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 {
|
||||
|
@@ -45,6 +45,14 @@ 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).
|
||||
|
||||
*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
|
||||
|
@@ -131,7 +131,7 @@ trusted_third_party_id_servers:
|
||||
It is **highly recommended** to remove `matrix.org` and `vector.im` (or any other default entry) from your configuration
|
||||
so only your own Identity server is authoritative for your HS.
|
||||
|
||||
## Validate
|
||||
## Validate (Under reconstruction)
|
||||
**NOTE:** In case your homeserver has no working federation, step 5 will not happen. If step 4 took place, consider
|
||||
your installation validated.
|
||||
|
||||
|
16
docs/migration-from-mxisd.md
Normal file
16
docs/migration-from-mxisd.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Migration from mxisd
|
||||
|
||||
Version 2.0.0 of the ma1sd uses the same format of the database schema and main configuration file as mxisd.
|
||||
|
||||
Migration from mxisd:
|
||||
- install ma1sd via deb package, docker image or zip/tar archive
|
||||
- stop mxisd
|
||||
- copy configuration file (by default /etc/mxisd/mxisd.yaml to /etc/ma1sd/ma1sd.yaml)
|
||||
- copy key store (by default /var/lib/mxisd/keys folder to /var/lib/ma1sd/keys)
|
||||
- copy storage (by default /var/lib/mxisd/store.db to /var/lib/ma1sd/store.db)
|
||||
- change paths in the new config file (ma1sd.yaml). There are options: `key.path` and `storage.provider.sqlite`
|
||||
- start ma1sd
|
||||
|
||||
Due to ma1sd uses the same ports by default as mxisd it isn't necessary to change nginx/apache configuration.
|
||||
|
||||
If you have any troubles with migration don't hesitate to ask questions in [#ma1sd:ru-matrix.org](https://matrix.to/#/#ma1sd:ru-matrix.org) room.
|
@@ -31,8 +31,8 @@ notification:
|
||||
text: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||
html: <Path to file containing the HTML part of the email. Do not set to not use one>
|
||||
unbind:
|
||||
fraudulent:
|
||||
subject: <Subject of the email notification sent for potentially fraudulent 3PID unbinds>
|
||||
notification:
|
||||
subject: <Subject of the email notification sent for 3PID unbinds>
|
||||
body:
|
||||
text: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||
html: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||
|
@@ -9,7 +9,7 @@ provide your own custom templates.
|
||||
Templates for the following events/actions are available:
|
||||
- [3PID invite](../../features/identity.md)
|
||||
- [3PID session: validation](../session/session.md)
|
||||
- [3PID session: fraudulent unbind](https://github.com/kamax-matrix/ma1sd/wiki/ma1sd-and-your-privacy#improving-your-privacy-one-commit-at-the-time)
|
||||
- [3PID session: unbind](https://github.com/kamax-matrix/ma1sd/wiki/ma1sd-and-your-privacy#improving-your-privacy-one-commit-at-the-time)
|
||||
- [Matrix ID invite](../../features/experimental/application-service.md#email-notification-about-room-invites-by-matrix-ids)
|
||||
|
||||
## Placeholders
|
||||
@@ -71,7 +71,7 @@ under the namespace `threepid.medium.<medium>.generators.template`.
|
||||
Under such namespace, the following keys are available:
|
||||
- `invite`: Path to the 3PID invite notification template
|
||||
- `session.validation`: Path to the 3PID session validation notification template
|
||||
- `session.unbind.fraudulent`: Path to the 3PID session fraudulent unbind notification template
|
||||
- `session.unbind`: Path to the 3PID session unbind notification template
|
||||
- `generic.matrixId`: Path to the Matrix ID invite notification template
|
||||
- `placeholder`: Map of key/values to set static values for some placeholders.
|
||||
|
||||
@@ -104,7 +104,7 @@ threepid:
|
||||
session:
|
||||
validation: '/path/to/validate-template.eml'
|
||||
unbind:
|
||||
fraudulent: '/path/to/unbind-fraudulent-template.eml'
|
||||
notification: '/path/to/unbind-notification-template.eml'
|
||||
generic:
|
||||
matrixId: '/path/to/mxid-invite-template.eml'
|
||||
placeholder:
|
||||
|
@@ -103,8 +103,8 @@ session:
|
||||
validation:
|
||||
enabled: true
|
||||
unbind:
|
||||
fraudulent:
|
||||
sendWarning: true
|
||||
notifications: true
|
||||
enabled: true
|
||||
|
||||
# DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION
|
||||
# CONFIGURATION EXAMPLE
|
||||
@@ -115,11 +115,7 @@ are allowed to do in terms of 3PID sessions. The policy has a global on/off swit
|
||||
|
||||
---
|
||||
|
||||
`unbind.fraudulent` controls warning notifications if an illegal/fraudulent 3PID removal is attempted on the Identity server.
|
||||
This is directly related to synapse disregard for privacy and new GDPR laws in Europe in an attempt to inform users about
|
||||
potential privacy leaks.
|
||||
|
||||
For more information, see the corresponding [synapse issue](https://github.com/matrix-org/synapse/issues/4540).
|
||||
`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,
|
||||
|
@@ -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,46 @@ 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.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.*;
|
||||
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 +98,150 @@ 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()))
|
||||
// Account endpoints
|
||||
.post(AccountRegisterHandler.Path, SaneHandler.around(new AccountRegisterHandler(m.getAccMgr())))
|
||||
.get(AccountGetUserInfoHandler.Path,
|
||||
SaneHandler.around(AuthorizationHandler.around(m.getAccMgr(), new AccountGetUserInfoHandler(m.getAccMgr()))))
|
||||
.post(AccountLogoutHandler.Path,
|
||||
SaneHandler.around(AuthorizationHandler.around(m.getAccMgr(), new AccountLogoutHandler(m.getAccMgr()))))
|
||||
|
||||
// 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())))
|
||||
// Directory endpoints
|
||||
.post(UserDirectorySearchHandler.Path, SaneHandler.around(new UserDirectorySearchHandler(m.getDirectory())))
|
||||
|
||||
// Directory endpoints
|
||||
.post(UserDirectorySearchHandler.Path, SaneHandler.around(new UserDirectorySearchHandler(m.getDirectory())))
|
||||
// Profile endpoints
|
||||
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
|
||||
.get(InternalProfileHandler.Path, SaneHandler.around(new InternalProfileHandler(m.getProfile())))
|
||||
|
||||
// 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())))
|
||||
// Registration endpoints
|
||||
.post(Register3pidRequestTokenHandler.Path,
|
||||
SaneHandler.around(new Register3pidRequestTokenHandler(m.getReg(), m.getClientDns(), m.getHttpClient())))
|
||||
|
||||
// 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())))
|
||||
// Invite endpoints
|
||||
.post(RoomInviteHandler.Path, SaneHandler.around(new RoomInviteHandler(m.getHttpClient(), m.getClientDns(), m.getInvite())))
|
||||
|
||||
// Profile endpoints
|
||||
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
|
||||
.get(InternalProfileHandler.Path, SaneHandler.around(new InternalProfileHandler(m.getProfile())))
|
||||
// Application Service endpoints
|
||||
.get(AsUserHandler.Path, asUserHandler)
|
||||
.get("/_matrix/app/v1/rooms/**", asNotFoundHandler)
|
||||
.put(AsTransactionHandler.Path, asTxnHandler)
|
||||
|
||||
// Registration endpoints
|
||||
.post(Register3pidRequestTokenHandler.Path, SaneHandler.around(new Register3pidRequestTokenHandler(m.getReg(), m.getClientDns(), m.getHttpClient())))
|
||||
.get("/users/{" + AsUserHandler.ID + "}", asUserHandler) // Legacy endpoint
|
||||
.get("/rooms/**", asNotFoundHandler) // Legacy endpoint
|
||||
.put("/transactions/{" + AsTransactionHandler.ID + "}", asTxnHandler) // Legacy endpoint
|
||||
|
||||
// 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);
|
||||
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 termsEndpoints(RoutingHandler routingHandler) {
|
||||
routingHandler.get(GetTermsHandler.PATH, new GetTermsHandler(m.getConfig().getPolicy()));
|
||||
routingHandler
|
||||
.post(AcceptTermsHandler.PATH, AuthorizationHandler.around(m.getAccMgr(), sane(new AcceptTermsHandler(m.getAccMgr()))));
|
||||
}
|
||||
|
||||
private void hashEndpoints(RoutingHandler routingHandler) {
|
||||
routingHandler
|
||||
.get(HashDetailsHandler.PATH, AuthorizationHandler.around(m.getAccMgr(), sane(new HashDetailsHandler(m.getHashManager()))));
|
||||
routingHandler.post(HashLookupHandler.Path,
|
||||
AuthorizationHandler.around(m.getAccMgr(), sane(new HashLookupHandler(m.getIdentity(), m.getHashManager()))));
|
||||
}
|
||||
|
||||
private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) {
|
||||
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()) {
|
||||
HttpHandler handlerWithTerms = CheckTermsHandler.around(m.getAccMgr(), httpHandler, getPolicyObjects(apiHandler));
|
||||
HttpHandler wrappedHandler = useAuthorization ? AuthorizationHandler.around(m.getAccMgr(), handlerWithTerms) : handlerWithTerms;
|
||||
routingHandler.add(method, apiHandler.getPath(IdentityServiceAPI.V2), wrappedHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<PolicyConfig.PolicyObject> getPolicyObjects(ApiHandler apiHandler) {
|
||||
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(apiHandler.getHandlerPath()).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.getSession(), cfg.getMatrix(), store, notifMgr);
|
||||
sessMgr = new SessionManager(cfg, store, notifMgr, resolver, httpClient, 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();
|
||||
}
|
||||
|
242
src/main/java/io/kamax/mxisd/auth/AccountManager.java
Normal file
242
src/main/java/io/kamax/mxisd/auth/AccountManager.java
Normal file
@@ -0,0 +1,242 @@
|
||||
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.HttpClientBuilder;
|
||||
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.getExpiredIn(),
|
||||
Instant.now().getEpochSecond(), userId, token);
|
||||
storage.insertToken(account);
|
||||
|
||||
LOGGER.info("User {} registered", userId);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
private String getUserId(OpenIdToken openIdToken) {
|
||||
String matrixServerName = openIdToken.getMatrixServerName();
|
||||
String homeserverURL = resolver.resolve(matrixServerName).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 = HttpClientBuilder.create()
|
||||
.setSSLHostnameVerifier(new MatrixHostnameVerifier(matrixServerName)).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);
|
||||
}
|
||||
|
44
src/main/java/io/kamax/mxisd/auth/OpenIdToken.java
Normal file
44
src/main/java/io/kamax/mxisd/auth/OpenIdToken.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package io.kamax.mxisd.auth;
|
||||
|
||||
public class OpenIdToken {
|
||||
|
||||
private String accessToken;
|
||||
|
||||
private String tokenType;
|
||||
|
||||
private String matrixServerName;
|
||||
|
||||
private long expiredIn;
|
||||
|
||||
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 getExpiredIn() {
|
||||
return expiredIn;
|
||||
}
|
||||
|
||||
public void setExpiredIn(long expiredIn) {
|
||||
this.expiredIn = expiredIn;
|
||||
}
|
||||
}
|
@@ -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());
|
||||
}
|
||||
}
|
96
src/main/java/io/kamax/mxisd/config/HashingConfig.java
Normal file
96
src/main/java/io/kamax/mxisd/config/HashingConfig.java
Normal file
@@ -0,0 +1,96 @@
|
||||
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 = 10;
|
||||
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);
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
112
src/main/java/io/kamax/mxisd/config/PolicyConfig.java
Normal file
112
src/main/java/io/kamax/mxisd/config/PolicyConfig.java
Normal file
@@ -0,0 +1,112 @@
|
||||
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");
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -46,34 +46,31 @@ public class SessionConfig {
|
||||
|
||||
public static class PolicyUnbind {
|
||||
|
||||
public static class PolicyUnbindFraudulent {
|
||||
private boolean enabled = true;
|
||||
|
||||
private boolean notifications = true;
|
||||
|
||||
private boolean sendWarning = true;
|
||||
|
||||
public boolean getSendWarning() {
|
||||
return sendWarning;
|
||||
}
|
||||
|
||||
public void setSendWarning(boolean sendWarning) {
|
||||
this.sendWarning = sendWarning;
|
||||
}
|
||||
public boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
|
||||
private PolicyUnbindFraudulent fraudulent = new PolicyUnbindFraudulent();
|
||||
|
||||
public PolicyUnbindFraudulent getFraudulent() {
|
||||
return fraudulent;
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void setFraudulent(PolicyUnbindFraudulent fraudulent) {
|
||||
this.fraudulent = fraudulent;
|
||||
|
||||
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;
|
||||
|
||||
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()) {
|
||||
|
@@ -115,24 +115,10 @@ public class EmailSendGridConfig {
|
||||
|
||||
public static class Templates {
|
||||
|
||||
public static class TemplateSessionUnbind {
|
||||
|
||||
private EmailTemplate fraudulent = new EmailTemplate();
|
||||
|
||||
public EmailTemplate getFraudulent() {
|
||||
return fraudulent;
|
||||
}
|
||||
|
||||
public void setFraudulent(EmailTemplate fraudulent) {
|
||||
this.fraudulent = fraudulent;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TemplateSession {
|
||||
|
||||
private EmailTemplate validation = new EmailTemplate();
|
||||
private TemplateSessionUnbind unbind = new TemplateSessionUnbind();
|
||||
private EmailTemplate unbind = new EmailTemplate();
|
||||
|
||||
public EmailTemplate getValidation() {
|
||||
return validation;
|
||||
@@ -142,11 +128,11 @@ public class EmailSendGridConfig {
|
||||
this.validation = validation;
|
||||
}
|
||||
|
||||
public TemplateSessionUnbind getUnbind() {
|
||||
public EmailTemplate getUnbind() {
|
||||
return unbind;
|
||||
}
|
||||
|
||||
public void setUnbind(TemplateSessionUnbind unbind) {
|
||||
public void setUnbind(EmailTemplate unbind) {
|
||||
this.unbind = unbind;
|
||||
}
|
||||
|
||||
|
@@ -31,7 +31,7 @@ public class EmailTemplateConfig extends GenericTemplateConfig {
|
||||
setInvite("classpath:/threepids/email/invite-template.eml");
|
||||
getGeneric().put("matrixId", "classpath:/threepids/email/mxid-template.eml");
|
||||
getSession().setValidation("classpath:/threepids/email/validate-template.eml");
|
||||
getSession().getUnbind().setFraudulent("classpath:/threepids/email/unbind-fraudulent.eml");
|
||||
getSession().getUnbind().setNotification("classpath:/threepids/email/unbind-notification.eml");
|
||||
}
|
||||
|
||||
public EmailTemplateConfig build() {
|
||||
@@ -40,7 +40,7 @@ public class EmailTemplateConfig extends GenericTemplateConfig {
|
||||
log.info("Session:");
|
||||
log.info(" Validation: {}", getSession().getValidation());
|
||||
log.info(" Unbind:");
|
||||
log.info(" Fraudulent: {}", getSession().getUnbind().getFraudulent());
|
||||
log.info(" Notification: {}", getSession().getUnbind().getNotification());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@@ -41,16 +41,25 @@ public class GenericTemplateConfig {
|
||||
|
||||
public static class SessionUnbind {
|
||||
|
||||
private String fraudulent;
|
||||
private String validation;
|
||||
|
||||
public String getFraudulent() {
|
||||
return fraudulent;
|
||||
private String notification;
|
||||
|
||||
public String getValidation() {
|
||||
return validation;
|
||||
}
|
||||
|
||||
public void setFraudulent(String fraudulent) {
|
||||
this.fraudulent = fraudulent;
|
||||
public void setValidation(String validation) {
|
||||
this.validation = validation;
|
||||
}
|
||||
|
||||
public String getNotification() {
|
||||
return notification;
|
||||
}
|
||||
|
||||
public void setNotification(String notification) {
|
||||
this.notification = notification;
|
||||
}
|
||||
}
|
||||
|
||||
private String validation;
|
||||
|
@@ -30,7 +30,8 @@ public class PhoneSmsTemplateConfig extends GenericTemplateConfig {
|
||||
public PhoneSmsTemplateConfig() {
|
||||
setInvite("classpath:/threepids/sms/invite-template.txt");
|
||||
getSession().setValidation("classpath:/threepids/sms/validate-template.txt");
|
||||
getSession().getUnbind().setFraudulent("classpath:/threepids/sms/unbind-fraudulent.txt");
|
||||
getSession().getUnbind().setValidation("classpath:/threepids/sms/unbind-validation.txt");
|
||||
getSession().getUnbind().setNotification("classpath:/threepids/sms/unbind-notification.txt");
|
||||
}
|
||||
|
||||
public PhoneSmsTemplateConfig build() {
|
||||
@@ -39,7 +40,8 @@ public class PhoneSmsTemplateConfig extends GenericTemplateConfig {
|
||||
log.info("Session:");
|
||||
log.info(" Validation: {}", getSession().getValidation());
|
||||
log.info(" Unbind:");
|
||||
log.info(" Fraudulent: {}", getSession().getUnbind().getFraudulent());
|
||||
log.info(" Validation: {}", getSession().getUnbind().getValidation());
|
||||
log.info(" Notification: {}", getSession().getUnbind().getNotification());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
@@ -58,5 +58,4 @@ public class CryptoFactory {
|
||||
public static SignatureManager getSignatureManager(MxisdConfig cfg, Ed25519KeyManager keyMgr) {
|
||||
return new Ed25519SignatureManager(cfg, keyMgr);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ import io.kamax.matrix.event.EventKey;
|
||||
import io.kamax.matrix.json.MatrixJson;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Objects;
|
||||
|
||||
public interface SignatureManager {
|
||||
@@ -106,4 +107,13 @@ public interface SignatureManager {
|
||||
*/
|
||||
Signature sign(byte[] data);
|
||||
|
||||
/**
|
||||
* Verify the data.
|
||||
*
|
||||
* @param publicKey public key to verify
|
||||
* @param signature signature to verify
|
||||
* @param data the data to verify
|
||||
* @return {@code true} if signature is valid, else {@code false}
|
||||
*/
|
||||
boolean verify(PublicKey publicKey, String signature, byte[] data);
|
||||
}
|
||||
|
@@ -33,7 +33,9 @@ import net.i2p.crypto.eddsa.EdDSAEngine;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.util.Base64;
|
||||
|
||||
public class Ed25519SignatureManager implements SignatureManager {
|
||||
|
||||
@@ -92,4 +94,15 @@ public class Ed25519SignatureManager implements SignatureManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(PublicKey publicKey, String signature, byte[] data) {
|
||||
try {
|
||||
EdDSAEngine signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getKeySpecs().getHashAlgorithm()));
|
||||
signEngine.initVerify(publicKey);
|
||||
signEngine.update(data);
|
||||
return signEngine.verify(Base64.getDecoder().decode(signature));
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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() {
|
||||
}
|
||||
}
|
51
src/main/java/io/kamax/mxisd/hash/HashEngine.java
Normal file
51
src/main/java/io/kamax/mxisd/hash/HashEngine.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package io.kamax.mxisd.hash;
|
||||
|
||||
import io.kamax.matrix.codec.MxSha256;
|
||||
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.lang3.RandomStringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class HashEngine {
|
||||
|
||||
private final List<? extends IThreePidProvider> providers;
|
||||
private final HashStorage hashStorage;
|
||||
private final MxSha256 sha256 = new MxSha256();
|
||||
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() {
|
||||
synchronized (hashStorage) {
|
||||
this.pepper = newPepper();
|
||||
hashStorage.clear();
|
||||
for (IThreePidProvider provider : providers) {
|
||||
for (ThreePidMapping pidMapping : provider.populateHashes()) {
|
||||
hashStorage.add(pidMapping, hash(pidMapping));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getPepper() {
|
||||
synchronized (hashStorage) {
|
||||
return pepper;
|
||||
}
|
||||
}
|
||||
|
||||
protected String hash(ThreePidMapping pidMapping) {
|
||||
return sha256.hash(pidMapping.getMedium() + " " + pidMapping.getValue() + " " + getPepper());
|
||||
}
|
||||
|
||||
protected String newPepper() {
|
||||
return RandomStringUtils.random(config.getPepperLength());
|
||||
}
|
||||
}
|
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,30 @@
|
||||
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;
|
||||
}
|
||||
|
||||
@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,69 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
if (account.getExpiresIn() < 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,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.config.PolicyConfig;
|
||||
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;
|
||||
|
||||
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 {
|
||||
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.getExpiredIn()));
|
||||
}
|
||||
|
||||
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,22 +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.exception.BadRequestException;
|
||||
import io.kamax.mxisd.exception.NotAllowedException;
|
||||
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.apache.commons.lang3.StringUtils;
|
||||
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);
|
||||
|
||||
@@ -46,20 +41,14 @@ public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
||||
@Override
|
||||
public void handleRequest(HttpServerExchange exchange) {
|
||||
String auth = exchange.getRequestHeaders().getFirst("Authorization");
|
||||
if (StringUtils.isNotEmpty(auth)) {
|
||||
// We have a auth header to process
|
||||
if (StringUtils.startsWith(auth, "X-Matrix ")) {
|
||||
log.warn("A remote host attempted to unbind without proper authorization. Request was denied");
|
||||
log.warn("See https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy for more info");
|
||||
throw new NotAllowedException("3PID can only be removed via 3PID sessions, not via Homeserver signature");
|
||||
} else {
|
||||
throw new BadRequestException("Illegal authorization type");
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject body = parseJsonObject(exchange);
|
||||
sessionMgr.unbind(body);
|
||||
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,130 @@
|
||||
/*
|
||||
* 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);
|
||||
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[0]);
|
||||
mapping.setValue(parts[1]);
|
||||
mappings.add(mapping);
|
||||
}
|
||||
bulkLookupRequest.setMappings(mappings);
|
||||
|
||||
ClientHashLookupAnswer answer = new ClientHashLookupAnswer();
|
||||
|
||||
for (ThreePidMapping mapping : strategy.find(bulkLookupRequest).get()) {
|
||||
answer.getMappings().put(mapping.getMedium() + " " + mapping.getValue(), mapping.getMxid());
|
||||
}
|
||||
log.info("Finished bulk lookup request from {}", request.getRequester());
|
||||
|
||||
respondJson(exchange, answer);
|
||||
}
|
||||
|
||||
private void sha256Algorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) {
|
||||
if (!hashManager.getConfig().getAlgorithms().contains(HashingConfig.Algorithm.SHA256)) {
|
||||
throw new InvalidParamException();
|
||||
}
|
||||
|
||||
ClientHashLookupAnswer answer = new ClientHashLookupAnswer();
|
||||
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());
|
||||
|
||||
respondJson(exchange, answer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHandlerPath() {
|
||||
return "/bulk_lookup";
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
package io.kamax.mxisd.http.undertow.handler.term.v2;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
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.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);
|
||||
JsonObject accepts = GsonUtil.getObj(request, "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);
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,9 @@
|
||||
package io.kamax.mxisd.matrix;
|
||||
|
||||
public enum IdentityServiceAPI {
|
||||
|
||||
@Deprecated
|
||||
V1,
|
||||
|
||||
V2
|
||||
}
|
@@ -37,6 +37,6 @@ public interface NotificationHandler {
|
||||
|
||||
void sendForValidation(IThreePidSession session);
|
||||
|
||||
void sendForFraudulentUnbind(ThreePid tpid);
|
||||
void sendForUnbind(ThreePid tpid);
|
||||
|
||||
}
|
||||
|
@@ -78,8 +78,8 @@ public class NotificationManager {
|
||||
ensureMedium(session.getThreePid().getMedium()).sendForValidation(session);
|
||||
}
|
||||
|
||||
public void sendForFraudulentUnbind(ThreePid tpid) throws NotImplementedException {
|
||||
ensureMedium(tpid.getMedium()).sendForFraudulentUnbind(tpid);
|
||||
public void sendForUnbind(ThreePid tpid) throws NotImplementedException {
|
||||
ensureMedium(tpid.getMedium()).sendForUnbind(tpid);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,52 +20,76 @@
|
||||
|
||||
package io.kamax.mxisd.session;
|
||||
|
||||
import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.MatrixID;
|
||||
import io.kamax.matrix.ThreePid;
|
||||
import io.kamax.matrix._MatrixID;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.config.MatrixConfig;
|
||||
import io.kamax.mxisd.config.SessionConfig;
|
||||
import io.kamax.matrix.json.MatrixJson;
|
||||
import io.kamax.mxisd.config.MxisdConfig;
|
||||
import io.kamax.mxisd.crypto.SignatureManager;
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.exception.NotAllowedException;
|
||||
import io.kamax.mxisd.exception.RemoteHomeServerException;
|
||||
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
||||
import io.kamax.mxisd.exception.SessionUnknownException;
|
||||
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.notification.NotificationManager;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||
import io.kamax.mxisd.threepid.session.ThreePidSession;
|
||||
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
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.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
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;
|
||||
import java.util.Optional;
|
||||
|
||||
import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate;
|
||||
|
||||
public class SessionManager {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
|
||||
|
||||
private SessionConfig cfg;
|
||||
private MatrixConfig mxCfg;
|
||||
private MxisdConfig cfg;
|
||||
private IStorage storage;
|
||||
private NotificationManager notifMgr;
|
||||
private HomeserverFederationResolver resolver;
|
||||
private CloseableHttpClient client;
|
||||
private SignatureManager signatureManager;
|
||||
|
||||
public SessionManager(
|
||||
SessionConfig cfg,
|
||||
MatrixConfig mxCfg,
|
||||
IStorage storage,
|
||||
NotificationManager notifMgr
|
||||
MxisdConfig cfg,
|
||||
IStorage storage,
|
||||
NotificationManager notifMgr,
|
||||
HomeserverFederationResolver resolver,
|
||||
CloseableHttpClient client,
|
||||
SignatureManager signatureManager
|
||||
) {
|
||||
this.cfg = cfg;
|
||||
this.mxCfg = mxCfg;
|
||||
this.storage = storage;
|
||||
this.notifMgr = notifMgr;
|
||||
this.resolver = resolver;
|
||||
this.client = client;
|
||||
this.signatureManager = signatureManager;
|
||||
}
|
||||
|
||||
private ThreePidSession getSession(String sid, String secret) {
|
||||
@@ -86,7 +110,7 @@ public class SessionManager {
|
||||
}
|
||||
|
||||
public String create(String server, ThreePid tpid, String secret, int attempt, String nextLink) {
|
||||
PolicyTemplate policy = cfg.getPolicy().getValidation();
|
||||
PolicyTemplate policy = cfg.getSession().getPolicy().getValidation();
|
||||
if (!policy.isEnabled()) {
|
||||
throw new NotAllowedException("Validating 3PID is disabled");
|
||||
}
|
||||
@@ -98,7 +122,8 @@ public class SessionManager {
|
||||
ThreePidSession session = new ThreePidSession(dao.get());
|
||||
log.info("We already have a session for {}: {}", tpid, session.getId());
|
||||
if (session.getAttempt() < attempt) {
|
||||
log.info("Received attempt {} is greater than stored attempt {}, sending validation communication", attempt, session.getAttempt());
|
||||
log.info("Received attempt {} is greater than stored attempt {}, sending validation communication", attempt,
|
||||
session.getAttempt());
|
||||
notifMgr.sendForValidation(session);
|
||||
log.info("Sent validation notification to {}", tpid);
|
||||
session.increaseAttempt();
|
||||
@@ -161,12 +186,13 @@ public class SessionManager {
|
||||
_MatrixID mxid = MatrixID.asAcceptable(mxidRaw);
|
||||
|
||||
// Only accept binds if the domain matches our own
|
||||
if (!StringUtils.equalsIgnoreCase(mxCfg.getDomain(), mxid.getDomain())) {
|
||||
throw new NotAllowedException("Only Matrix IDs from domain " + mxCfg.getDomain() + " can be bound");
|
||||
final String domain = cfg.getMatrix().getDomain();
|
||||
if (!StringUtils.equalsIgnoreCase(domain, mxid.getDomain())) {
|
||||
throw new NotAllowedException("Only Matrix IDs from domain " + domain + " can be bound");
|
||||
}
|
||||
|
||||
log.info("Session {}: Binding of {}:{} to Matrix ID {} is accepted",
|
||||
session.getId(), session.getThreePid().getMedium(), session.getThreePid().getAddress(), mxid.getId());
|
||||
session.getId(), session.getThreePid().getMedium(), session.getThreePid().getAddress(), mxid.getId());
|
||||
|
||||
SingleLookupRequest request = new SingleLookupRequest();
|
||||
request.setType(session.getThreePid().getMedium());
|
||||
@@ -174,7 +200,12 @@ public class SessionManager {
|
||||
return new SingleLookupReply(request, mxid);
|
||||
}
|
||||
|
||||
public void unbind(JsonObject reqData) {
|
||||
public void unbind(String auth, JsonObject reqData) {
|
||||
if (!cfg.getSession().getPolicy().getUnbind().getEnabled()) {
|
||||
log.error("Unbind disabled.");
|
||||
throw new NotAllowedException("Unbinding 3PID is disabled");
|
||||
}
|
||||
|
||||
_MatrixID mxid;
|
||||
try {
|
||||
mxid = MatrixID.asAcceptable(GsonUtil.getStringOrThrow(reqData, "mxid"));
|
||||
@@ -186,6 +217,160 @@ public class SessionManager {
|
||||
String secret = GsonUtil.getStringOrNull(reqData, "client_secret");
|
||||
ThreePid tpid = GsonUtil.get().fromJson(GsonUtil.getObj(reqData, "threepid"), ThreePid.class);
|
||||
|
||||
if (tpid == null || StringUtils.isBlank(tpid.getAddress()) || StringUtils.isBlank(tpid.getMedium())) {
|
||||
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);
|
||||
} else if (StringUtils.isNotBlank(auth)) {
|
||||
checkAuthorization(auth, reqData);
|
||||
} else {
|
||||
throw new NotAllowedException("Unable to validate request");
|
||||
}
|
||||
|
||||
log.info("Unbinding of {} {} to {} is accepted", tpid.getMedium(), tpid.getAddress(), mxid.getId());
|
||||
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) {
|
||||
if (!auth.startsWith("X-Matrix ")) {
|
||||
throw new NotAllowedException("Wrong authorization header");
|
||||
}
|
||||
|
||||
String domain = getDomain(cfg.getServer().getPublicUrl());
|
||||
|
||||
if (StringUtils.isBlank(domain)) {
|
||||
throw new NotAllowedException("Unable to verify request, missing `server.publicUrl` property");
|
||||
}
|
||||
|
||||
String[] params = auth.substring("X-Matrix ".length()).split(",");
|
||||
|
||||
String origin = null;
|
||||
String key = null;
|
||||
String sig = null;
|
||||
for (String param : params) {
|
||||
String[] paramItems = param.split("=");
|
||||
String paramKey = paramItems[0];
|
||||
String paramValue = paramItems[1];
|
||||
switch (paramKey) {
|
||||
case "origin":
|
||||
origin = removeQuotes(paramValue);
|
||||
break;
|
||||
case "key":
|
||||
key = removeQuotes(paramValue);
|
||||
break;
|
||||
case "sig":
|
||||
sig = removeQuotes(paramValue);
|
||||
break;
|
||||
default:
|
||||
log.error("Unknown parameter: {}", param);
|
||||
throw new BadRequestException("Authorization with unknown parameter");
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(origin) || StringUtils.isBlank(key) || StringUtils.isBlank(sig)) {
|
||||
log.error("Missing required parameters");
|
||||
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", domain);
|
||||
jsonObject.add("content", reqData);
|
||||
|
||||
String canonical = MatrixJson.encodeCanonical(jsonObject);
|
||||
|
||||
String originUrl = resolver.resolve(origin).toString();
|
||||
|
||||
validateServerKey(key, sig, canonical, originUrl);
|
||||
}
|
||||
|
||||
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) {
|
||||
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.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
String message = "Unable to get server keys: " + originUrl;
|
||||
log.error(message, e);
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyKey(String key, String signature, String canonical, CloseableHttpResponse response) throws IOException {
|
||||
final String content = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
log.info("Answer body: {}", content);
|
||||
final JsonObject responseObject = GsonUtil.parseObj(content);
|
||||
final long validUntilTs = GsonUtil.getLong(responseObject, "valid_until_ts");
|
||||
|
||||
final Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTimeInMillis(validUntilTs);
|
||||
if (calendar.before(Calendar.getInstance())) {
|
||||
final String msg = "Key is expired";
|
||||
log.error(msg);
|
||||
throw new RemoteHomeServerException(msg);
|
||||
}
|
||||
|
||||
final JsonObject verifyKeys = GsonUtil.getObj(responseObject, "verify_keys");
|
||||
final JsonObject keyObject = GsonUtil.getObj(verifyKeys, key);
|
||||
final String publicKey = GsonUtil.getStringOrNull(keyObject, "key");
|
||||
|
||||
if (StringUtils.isBlank(publicKey)) {
|
||||
throw new RemoteHomeServerException("Missing server key.");
|
||||
}
|
||||
|
||||
EdDSANamedCurveSpec ed25519CurveSpec = EdDSANamedCurveTable.ED_25519_CURVE_SPEC;
|
||||
EdDSAPublicKeySpec publicKeySpec = new EdDSAPublicKeySpec(Base64.getDecoder().decode(publicKey), ed25519CurveSpec);
|
||||
EdDSAPublicKey dsaPublicKey = new EdDSAPublicKey(publicKeySpec);
|
||||
|
||||
final boolean verificationResult = signatureManager.verify(dsaPublicKey, signature, canonical.getBytes(StandardCharsets.UTF_8));
|
||||
log.info("Verification result: {}", verificationResult);
|
||||
if (!verificationResult) {
|
||||
throw new RemoteHomeServerException("Unable to verify request.");
|
||||
}
|
||||
|
||||
log.info("Request was authorized.");
|
||||
}
|
||||
|
||||
private void checkSession(String sid, String secret, ThreePid tpid) {
|
||||
// We ensure the session was validated
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
|
||||
@@ -193,14 +378,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
|
||||
if (!StringUtils.equalsIgnoreCase(mxCfg.getDomain(), mxid.getDomain())) {
|
||||
throw new NotAllowedException("Only Matrix IDs from domain " + mxCfg.getDomain() + " can be unbound");
|
||||
}
|
||||
|
||||
log.info("Session {}: Unbinding of {}:{} to Matrix ID {} is accepted",
|
||||
session.getId(), session.getThreePid().getMedium(), session.getThreePid().getAddress(), mxid.getId());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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,103 @@ 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);
|
||||
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;
|
||||
}
|
||||
}
|
@@ -115,7 +115,7 @@ public class EmailSmtpConnector implements EmailConnector {
|
||||
msg.setRecipients(Message.RecipientType.TO, recipient);
|
||||
msg.saveChanges();
|
||||
|
||||
log.info("Sending invite to {} via SMTP using {}:{}", recipient, cfg.getHost(), cfg.getPort());
|
||||
log.info("Sending email to {} via SMTP using {}:{}", recipient, cfg.getHost(), cfg.getPort());
|
||||
SMTPTransport transport = (SMTPTransport) session.getTransport("smtp");
|
||||
|
||||
if (cfg.getTls() < 3) {
|
||||
@@ -134,12 +134,12 @@ public class EmailSmtpConnector implements EmailConnector {
|
||||
|
||||
try {
|
||||
transport.sendMessage(msg, InternetAddress.parse(recipient));
|
||||
log.info("Invite to {} was sent", recipient);
|
||||
log.info("Email to {} was sent", recipient);
|
||||
} finally {
|
||||
transport.close();
|
||||
}
|
||||
} catch (UnsupportedEncodingException | MessagingException e) {
|
||||
throw new RuntimeException("Unable to send e-mail invite to " + recipient, e);
|
||||
throw new RuntimeException("Unable to send e-mail to " + recipient, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -79,9 +79,9 @@ public abstract class GenericTemplateNotificationGenerator extends PlaceholderNo
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getForFraudulentUnbind(ThreePid tpid) {
|
||||
log.info("Generating notification content for fraudulent unbind");
|
||||
return populateForFraudulentUndind(tpid, getTemplateContent(cfg.getSession().getUnbind().getFraudulent()));
|
||||
public String getForNotificationUnbind(ThreePid tpid) {
|
||||
log.info("Generating notification content for unbind");
|
||||
return populateForNotificationUndind(tpid, getTemplateContent(cfg.getSession().getUnbind().getNotification()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -37,6 +37,6 @@ public interface NotificationGenerator {
|
||||
|
||||
String getForValidation(IThreePidSession session);
|
||||
|
||||
String getForFraudulentUnbind(ThreePid tpid);
|
||||
String getForNotificationUnbind(ThreePid tpid);
|
||||
|
||||
}
|
||||
|
@@ -127,7 +127,7 @@ public abstract class PlaceholderNotificationGenerator {
|
||||
.replace("%NEXT_URL%", validationLink);
|
||||
}
|
||||
|
||||
protected String populateForFraudulentUndind(ThreePid tpid, String input) {
|
||||
protected String populateForNotificationUndind(ThreePid tpid, String input) {
|
||||
return populateForCommon(tpid, input);
|
||||
}
|
||||
|
||||
|
@@ -73,8 +73,8 @@ public abstract class GenericNotificationHandler<A extends ThreePidConnector, B
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendForFraudulentUnbind(ThreePid tpid) {
|
||||
send(connector, tpid.getAddress(), generator.getForFraudulentUnbind(tpid));
|
||||
public void sendForUnbind(ThreePid tpid) {
|
||||
send(connector, tpid.getAddress(), generator.getForNotificationUnbind(tpid));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -129,10 +129,10 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendForFraudulentUnbind(ThreePid tpid) {
|
||||
EmailTemplate template = cfg.getTemplates().getSession().getUnbind().getFraudulent();
|
||||
public void sendForUnbind(ThreePid tpid) {
|
||||
EmailTemplate template = cfg.getTemplates().getSession().getUnbind();
|
||||
if (StringUtils.isAllBlank(template.getBody().getText(), template.getBody().getHtml())) {
|
||||
throw new FeatureNotAvailable("No template has been configured for fraudulent unbind notifications");
|
||||
throw new FeatureNotAvailable("No template has been configured for unbind notifications");
|
||||
}
|
||||
|
||||
Email email = getEmail();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user