Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0786a6520f | ||
|
430136c391 | ||
|
eda4404335 | ||
|
c52034b18a | ||
|
8d346037b7 | ||
|
14ad4435bc | ||
|
8ba8756871 | ||
|
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)
|
- [Contribute](#contribute)
|
||||||
- [Powered by ma1sd](#powered-by-ma1sd)
|
- [Powered by ma1sd](#powered-by-ma1sd)
|
||||||
- [FAQ](#faq)
|
- [FAQ](#faq)
|
||||||
|
- [Migration from mxisd](#migration-from-mxisd)
|
||||||
- [Contact](#contact)
|
- [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
|
# FAQ
|
||||||
See the [dedicated document](docs/faq.md)
|
See the [dedicated document](docs/faq.md)
|
||||||
|
|
||||||
|
# Migration from mxisd
|
||||||
|
|
||||||
|
See the [migration guide](docs/migration-from-mxisd.md)
|
||||||
|
|
||||||
# Contact
|
# Contact
|
||||||
Get in touch via:
|
Get in touch via:
|
||||||
- Matrix: [#ma1sd:ru-matrix.org](https://matrix.to/#/#ma1sd:ru-matrix.org)
|
- Matrix: [#ma1sd:ru-matrix.org](https://matrix.to/#/#ma1sd:ru-matrix.org)
|
||||||
|
28
build.gradle
28
build.gradle
@@ -78,7 +78,7 @@ buildscript {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.github.jengelman.gradle.plugins:shadow:5.1.0'
|
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'
|
compile 'commons-io:commons-io:2.6'
|
||||||
|
|
||||||
// Config management
|
// Config management
|
||||||
compile 'org.yaml:snakeyaml:1.24'
|
compile 'org.yaml:snakeyaml:1.25'
|
||||||
|
|
||||||
// Dependencies from old Matrix-java-sdk
|
// Dependencies from old Matrix-java-sdk
|
||||||
compile 'org.apache.commons:commons-lang3:3.9'
|
compile 'org.apache.commons:commons-lang3:3.9'
|
||||||
compile 'com.squareup.okhttp3:okhttp:4.0.1'
|
compile 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||||
compile 'commons-codec:commons-codec:1.12'
|
compile 'commons-codec:commons-codec:1.13'
|
||||||
|
|
||||||
// ORMLite
|
// ORMLite
|
||||||
compile 'com.j256.ormlite:ormlite-jdbc:5.1'
|
compile 'com.j256.ormlite:ormlite-jdbc:5.1'
|
||||||
@@ -114,10 +114,10 @@ dependencies {
|
|||||||
compile 'dnsjava:dnsjava:2.1.9'
|
compile 'dnsjava:dnsjava:2.1.9'
|
||||||
|
|
||||||
// HTTP connections
|
// HTTP connections
|
||||||
compile 'org.apache.httpcomponents:httpclient:4.5.9'
|
compile 'org.apache.httpcomponents:httpclient:4.5.10'
|
||||||
|
|
||||||
// Phone numbers validation
|
// Phone numbers validation
|
||||||
compile 'com.googlecode.libphonenumber:libphonenumber:8.10.15'
|
compile 'com.googlecode.libphonenumber:libphonenumber:8.10.22'
|
||||||
|
|
||||||
// E-mail sending
|
// E-mail sending
|
||||||
compile 'javax.mail:javax.mail-api:1.6.2'
|
compile 'javax.mail:javax.mail-api:1.6.2'
|
||||||
@@ -133,13 +133,13 @@ dependencies {
|
|||||||
compile 'org.xerial:sqlite-jdbc:3.28.0'
|
compile 'org.xerial:sqlite-jdbc:3.28.0'
|
||||||
|
|
||||||
// PostgreSQL
|
// PostgreSQL
|
||||||
compile 'org.postgresql:postgresql:42.2.6'
|
compile 'org.postgresql:postgresql:42.2.8'
|
||||||
|
|
||||||
// MariaDB/MySQL
|
// 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
|
// 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
|
// SendGrid SDK to send emails from GCE
|
||||||
compile 'com.sendgrid:sendgrid-java:2.2.2'
|
compile 'com.sendgrid:sendgrid-java:2.2.2'
|
||||||
@@ -148,15 +148,15 @@ dependencies {
|
|||||||
compile 'org.zeroturnaround:zt-exec:1.11'
|
compile 'org.zeroturnaround:zt-exec:1.11'
|
||||||
|
|
||||||
// HTTP server
|
// 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
|
// Command parser for AS interface
|
||||||
implementation 'commons-cli:commons-cli:1.4'
|
implementation 'commons-cli:commons-cli:1.4'
|
||||||
|
|
||||||
testCompile 'junit:junit:4.13-beta-3'
|
testCompile 'junit:junit:4.13-rc-1'
|
||||||
testCompile 'com.github.tomakehurst:wiremock:2.24.0'
|
testCompile 'com.github.tomakehurst:wiremock:2.25.1'
|
||||||
testCompile 'com.unboundid:unboundid-ldapsdk:4.0.11'
|
testCompile 'com.unboundid:unboundid-ldapsdk:4.0.12'
|
||||||
testCompile 'com.icegreen:greenmail:1.5.10'
|
testCompile 'com.icegreen:greenmail:1.5.11'
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
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.port`: HTTP port to listen on (unencrypted)
|
||||||
- `server.publicUrl`: Defaults to `https://{server.name}`
|
- `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
|
## Storage
|
||||||
### SQLite
|
### SQLite
|
||||||
`storage.provider.sqlite.database`: Absolute location of the SQLite database
|
`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
|
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.
|
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
|
**NOTE:** In case your homeserver has no working federation, step 5 will not happen. If step 4 took place, consider
|
||||||
your installation validated.
|
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>
|
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>
|
html: <Path to file containing the HTML part of the email. Do not set to not use one>
|
||||||
unbind:
|
unbind:
|
||||||
fraudulent:
|
notification:
|
||||||
subject: <Subject of the email notification sent for potentially fraudulent 3PID unbinds>
|
subject: <Subject of the email notification sent for 3PID unbinds>
|
||||||
body:
|
body:
|
||||||
text: <Path to file containing the raw text part of the email. Do not set to not use one>
|
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>
|
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:
|
Templates for the following events/actions are available:
|
||||||
- [3PID invite](../../features/identity.md)
|
- [3PID invite](../../features/identity.md)
|
||||||
- [3PID session: validation](../session/session.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)
|
- [Matrix ID invite](../../features/experimental/application-service.md#email-notification-about-room-invites-by-matrix-ids)
|
||||||
|
|
||||||
## Placeholders
|
## Placeholders
|
||||||
@@ -71,7 +71,7 @@ under the namespace `threepid.medium.<medium>.generators.template`.
|
|||||||
Under such namespace, the following keys are available:
|
Under such namespace, the following keys are available:
|
||||||
- `invite`: Path to the 3PID invite notification template
|
- `invite`: Path to the 3PID invite notification template
|
||||||
- `session.validation`: Path to the 3PID session validation 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
|
- `generic.matrixId`: Path to the Matrix ID invite notification template
|
||||||
- `placeholder`: Map of key/values to set static values for some placeholders.
|
- `placeholder`: Map of key/values to set static values for some placeholders.
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ threepid:
|
|||||||
session:
|
session:
|
||||||
validation: '/path/to/validate-template.eml'
|
validation: '/path/to/validate-template.eml'
|
||||||
unbind:
|
unbind:
|
||||||
fraudulent: '/path/to/unbind-fraudulent-template.eml'
|
notification: '/path/to/unbind-notification-template.eml'
|
||||||
generic:
|
generic:
|
||||||
matrixId: '/path/to/mxid-invite-template.eml'
|
matrixId: '/path/to/mxid-invite-template.eml'
|
||||||
placeholder:
|
placeholder:
|
||||||
|
@@ -103,8 +103,8 @@ session:
|
|||||||
validation:
|
validation:
|
||||||
enabled: true
|
enabled: true
|
||||||
unbind:
|
unbind:
|
||||||
fraudulent:
|
notification:
|
||||||
sendWarning: true
|
enabled: true
|
||||||
|
|
||||||
# DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION
|
# DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION
|
||||||
# CONFIGURATION EXAMPLE
|
# 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.
|
`unbind` controls warning notifications for 3PID removal.
|
||||||
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).
|
|
||||||
|
|
||||||
### Web views
|
### Web views
|
||||||
Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submitted.
|
Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submitted.
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
|||||||
#Thu Jul 04 22:47:59 MSK 2019
|
#Thu Jul 04 22:47:59 MSK 2019
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@@ -20,7 +20,12 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd;
|
package io.kamax.mxisd;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
import io.kamax.mxisd.config.MxisdConfig;
|
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.InternalInfoHandler;
|
||||||
import io.kamax.mxisd.http.undertow.handler.OptionsHandler;
|
import io.kamax.mxisd.http.undertow.handler.OptionsHandler;
|
||||||
import io.kamax.mxisd.http.undertow.handler.SaneHandler;
|
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.LoginGetHandler;
|
||||||
import io.kamax.mxisd.http.undertow.handler.auth.v1.LoginHandler;
|
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.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.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.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.invite.v1.RoomInviteHandler;
|
||||||
import io.kamax.mxisd.http.undertow.handler.profile.v1.InternalProfileHandler;
|
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.profile.v1.ProfileHandler;
|
||||||
import io.kamax.mxisd.http.undertow.handler.register.v1.Register3pidRequestTokenHandler;
|
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.StatusHandler;
|
||||||
import io.kamax.mxisd.http.undertow.handler.status.VersionHandler;
|
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.Handlers;
|
||||||
import io.undertow.Undertow;
|
import io.undertow.Undertow;
|
||||||
import io.undertow.server.HttpHandler;
|
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.Objects;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class HttpMxisd {
|
public class HttpMxisd {
|
||||||
|
|
||||||
@@ -66,16 +98,11 @@ public class HttpMxisd {
|
|||||||
public void start() {
|
public void start() {
|
||||||
m.start();
|
m.start();
|
||||||
|
|
||||||
HttpHandler helloHandler = SaneHandler.around(new HelloHandler());
|
|
||||||
|
|
||||||
HttpHandler asUserHandler = SaneHandler.around(new AsUserHandler(m.getAs()));
|
HttpHandler asUserHandler = SaneHandler.around(new AsUserHandler(m.getAs()));
|
||||||
HttpHandler asTxnHandler = SaneHandler.around(new AsTransactionHandler(m.getAs()));
|
HttpHandler asTxnHandler = SaneHandler.around(new AsTransactionHandler(m.getAs()));
|
||||||
HttpHandler asNotFoundHandler = SaneHandler.around(new AsNotFoundHandler(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()
|
||||||
|
|
||||||
httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(Handlers.routing()
|
|
||||||
|
|
||||||
.add("OPTIONS", "/**", SaneHandler.around(new OptionsHandler()))
|
.add("OPTIONS", "/**", SaneHandler.around(new OptionsHandler()))
|
||||||
|
|
||||||
// Status endpoints
|
// Status endpoints
|
||||||
@@ -87,34 +114,23 @@ public class HttpMxisd {
|
|||||||
.post(LoginHandler.Path, SaneHandler.around(new LoginPostHandler(m.getAuth())))
|
.post(LoginHandler.Path, SaneHandler.around(new LoginPostHandler(m.getAuth())))
|
||||||
.post(RestAuthHandler.Path, SaneHandler.around(new RestAuthHandler(m.getAuth())))
|
.post(RestAuthHandler.Path, SaneHandler.around(new RestAuthHandler(m.getAuth())))
|
||||||
|
|
||||||
|
// Account endpoints
|
||||||
|
.post(AccountRegisterHandler.Path, SaneHandler.around(new AccountRegisterHandler(m.getAccMgr())))
|
||||||
|
.get(AccountGetUserInfoHandler.Path,
|
||||||
|
SaneHandler.around(AuthorizationHandler.around(m.getAccMgr(), new AccountGetUserInfoHandler(m.getAccMgr()))))
|
||||||
|
.post(AccountLogoutHandler.Path,
|
||||||
|
SaneHandler.around(AuthorizationHandler.around(m.getAccMgr(), new AccountLogoutHandler(m.getAccMgr()))))
|
||||||
|
|
||||||
// Directory endpoints
|
// Directory endpoints
|
||||||
.post(UserDirectorySearchHandler.Path, SaneHandler.around(new UserDirectorySearchHandler(m.getDirectory())))
|
.post(UserDirectorySearchHandler.Path, SaneHandler.around(new UserDirectorySearchHandler(m.getDirectory())))
|
||||||
|
|
||||||
// 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())))
|
|
||||||
|
|
||||||
// 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())))
|
|
||||||
|
|
||||||
// Profile endpoints
|
// Profile endpoints
|
||||||
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
|
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
|
||||||
.get(InternalProfileHandler.Path, SaneHandler.around(new InternalProfileHandler(m.getProfile())))
|
.get(InternalProfileHandler.Path, SaneHandler.around(new InternalProfileHandler(m.getProfile())))
|
||||||
|
|
||||||
// Registration endpoints
|
// Registration endpoints
|
||||||
.post(Register3pidRequestTokenHandler.Path, SaneHandler.around(new Register3pidRequestTokenHandler(m.getReg(), m.getClientDns(), m.getHttpClient())))
|
.post(Register3pidRequestTokenHandler.Path,
|
||||||
|
SaneHandler.around(new Register3pidRequestTokenHandler(m.getReg(), m.getClientDns(), m.getHttpClient())))
|
||||||
|
|
||||||
// Invite endpoints
|
// Invite endpoints
|
||||||
.post(RoomInviteHandler.Path, SaneHandler.around(new RoomInviteHandler(m.getHttpClient(), m.getClientDns(), m.getInvite())))
|
.post(RoomInviteHandler.Path, SaneHandler.around(new RoomInviteHandler(m.getHttpClient(), m.getClientDns(), m.getInvite())))
|
||||||
@@ -129,18 +145,103 @@ public class HttpMxisd {
|
|||||||
.put("/transactions/{" + AsTransactionHandler.ID + "}", asTxnHandler) // Legacy endpoint
|
.put("/transactions/{" + AsTransactionHandler.ID + "}", asTxnHandler) // Legacy endpoint
|
||||||
|
|
||||||
// Banned endpoints
|
// Banned endpoints
|
||||||
.get(InternalInfoHandler.Path, SaneHandler.around(new InternalInfoHandler()))
|
.get(InternalInfoHandler.Path, SaneHandler.around(new InternalInfoHandler()));
|
||||||
|
keyEndpoints(handler);
|
||||||
).build();
|
identityEndpoints(handler);
|
||||||
|
termsEndpoints(handler);
|
||||||
|
hashEndpoints(handler);
|
||||||
|
httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(handler).build();
|
||||||
|
|
||||||
httpSrv.start();
|
httpSrv.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
// Because it might have never been initialized if an exception is thrown early
|
// 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();
|
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;
|
package io.kamax.mxisd;
|
||||||
|
|
||||||
import io.kamax.mxisd.as.AppSvcManager;
|
import io.kamax.mxisd.as.AppSvcManager;
|
||||||
|
import io.kamax.mxisd.auth.AccountManager;
|
||||||
import io.kamax.mxisd.auth.AuthManager;
|
import io.kamax.mxisd.auth.AuthManager;
|
||||||
import io.kamax.mxisd.auth.AuthProviders;
|
import io.kamax.mxisd.auth.AuthProviders;
|
||||||
import io.kamax.mxisd.backend.IdentityStoreSupplier;
|
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.directory.DirectoryProviders;
|
||||||
import io.kamax.mxisd.dns.ClientDnsOverwrite;
|
import io.kamax.mxisd.dns.ClientDnsOverwrite;
|
||||||
import io.kamax.mxisd.dns.FederationDnsOverwrite;
|
import io.kamax.mxisd.dns.FederationDnsOverwrite;
|
||||||
|
import io.kamax.mxisd.hash.HashManager;
|
||||||
import io.kamax.mxisd.invitation.InvitationManager;
|
import io.kamax.mxisd.invitation.InvitationManager;
|
||||||
import io.kamax.mxisd.lookup.ThreePidProviders;
|
import io.kamax.mxisd.lookup.ThreePidProviders;
|
||||||
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher;
|
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher;
|
||||||
@@ -85,6 +87,8 @@ public class Mxisd {
|
|||||||
private SessionManager sessMgr;
|
private SessionManager sessMgr;
|
||||||
private NotificationManager notifMgr;
|
private NotificationManager notifMgr;
|
||||||
private RegistrationManager regMgr;
|
private RegistrationManager regMgr;
|
||||||
|
private AccountManager accMgr;
|
||||||
|
private HashManager hashManager;
|
||||||
|
|
||||||
// HS-specific classes
|
// HS-specific classes
|
||||||
private Synapse synapse;
|
private Synapse synapse;
|
||||||
@@ -115,15 +119,19 @@ public class Mxisd {
|
|||||||
ServiceLoader.load(IdentityStoreSupplier.class).iterator().forEachRemaining(p -> p.accept(this));
|
ServiceLoader.load(IdentityStoreSupplier.class).iterator().forEachRemaining(p -> p.accept(this));
|
||||||
ServiceLoader.load(NotificationHandlerSupplier.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);
|
pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient);
|
||||||
notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get());
|
notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get());
|
||||||
sessMgr = new SessionManager(cfg.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);
|
invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, resolver, notifMgr, pMgr);
|
||||||
authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient);
|
authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient);
|
||||||
dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get());
|
dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get());
|
||||||
regMgr = new RegistrationManager(cfg.getRegister(), httpClient, clientDns, invMgr);
|
regMgr = new RegistrationManager(cfg.getRegister(), httpClient, clientDns, invMgr);
|
||||||
asHander = new AppSvcManager(this);
|
asHander = new AppSvcManager(this);
|
||||||
|
accMgr = new AccountManager(store, resolver, getHttpClient(), cfg.getAccountConfig(), cfg.getMatrix());
|
||||||
}
|
}
|
||||||
|
|
||||||
public MxisdConfig getConfig() {
|
public MxisdConfig getConfig() {
|
||||||
@@ -194,6 +202,14 @@ public class Mxisd {
|
|||||||
return synapse;
|
return synapse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AccountManager getAccMgr() {
|
||||||
|
return accMgr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashManager getHashManager() {
|
||||||
|
return hashManager;
|
||||||
|
}
|
||||||
|
|
||||||
public void start() {
|
public void start() {
|
||||||
build();
|
build();
|
||||||
}
|
}
|
||||||
|
160
src/main/java/io/kamax/mxisd/auth/AccountManager.java
Normal file
160
src/main/java/io/kamax/mxisd/auth/AccountManager.java
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
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.http.undertow.handler.auth.v1.LoginGetHandler;
|
||||||
|
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.util.EntityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class AccountManager {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(AccountManager.class);
|
||||||
|
|
||||||
|
private final IStorage storage;
|
||||||
|
private final HomeserverFederationResolver resolver;
|
||||||
|
private final CloseableHttpClient httpClient;
|
||||||
|
private final AccountConfig accountConfig;
|
||||||
|
private final MatrixConfig matrixConfig;
|
||||||
|
|
||||||
|
public AccountManager(IStorage storage, HomeserverFederationResolver resolver,
|
||||||
|
CloseableHttpClient httpClient, AccountConfig accountConfig, MatrixConfig matrixConfig) {
|
||||||
|
this.storage = storage;
|
||||||
|
this.resolver = resolver;
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
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 homeserverURL = resolver.resolve(openIdToken.getMatrixServerName()).toString();
|
||||||
|
LOGGER.info("Domain resolved: {} => {}", openIdToken.getMatrixServerName(), homeserverURL);
|
||||||
|
HttpGet getUserInfo = new HttpGet(
|
||||||
|
homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken());
|
||||||
|
String userId;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
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();
|
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 -> {
|
p.addSuccessMapper(JsonType, output -> {
|
||||||
if (StringUtils.isBlank(output)) {
|
if (StringUtils.isBlank(output)) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
@@ -188,10 +208,5 @@ public class ExecIdentityStore extends ExecStore implements IThreePidProvider {
|
|||||||
throw new InternalServerError("Invalid user type: " + item.getId().getType());
|
throw new InternalServerError("Invalid user type: " + item.getId().getType());
|
||||||
}).collect(Collectors.toList());
|
}).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.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class MemoryIdentityStore implements AuthenticatorProvider, DirectoryProvider, IThreePidProvider, ProfileProvider {
|
public class MemoryIdentityStore implements AuthenticatorProvider, DirectoryProvider, IThreePidProvider, ProfileProvider {
|
||||||
|
|
||||||
@@ -171,4 +172,11 @@ public class MemoryIdentityStore implements AuthenticatorProvider, DirectoryProv
|
|||||||
}).orElseGet(BackendAuthResult::failure);
|
}).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.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -104,4 +105,27 @@ public abstract class SqlThreePidProvider implements IThreePidProvider {
|
|||||||
return new ArrayList<>();
|
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 String domain;
|
||||||
private Identity identity = new Identity();
|
private Identity identity = new Identity();
|
||||||
|
private boolean v1 = true;
|
||||||
|
private boolean v2 = true;
|
||||||
|
|
||||||
public String getDomain() {
|
public String getDomain() {
|
||||||
return domain;
|
return domain;
|
||||||
@@ -80,6 +82,22 @@ public class MatrixConfig {
|
|||||||
this.identity = identity;
|
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() {
|
public void build() {
|
||||||
log.info("--- Matrix config ---");
|
log.info("--- Matrix config ---");
|
||||||
|
|
||||||
@@ -90,6 +108,11 @@ public class MatrixConfig {
|
|||||||
log.info("Domain: {}", getDomain());
|
log.info("Domain: {}", getDomain());
|
||||||
log.info("Identity:");
|
log.info("Identity:");
|
||||||
log.info("\tServers: {}", GsonUtil.get().toJson(identity.getServers()));
|
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 AppServiceConfig appsvc = new AppServiceConfig();
|
||||||
private AuthenticationConfig auth = new AuthenticationConfig();
|
private AuthenticationConfig auth = new AuthenticationConfig();
|
||||||
private DirectoryConfig directory = new DirectoryConfig();
|
private DirectoryConfig directory = new DirectoryConfig();
|
||||||
|
private AccountConfig accountConfig = new AccountConfig();
|
||||||
private Dns dns = new Dns();
|
private Dns dns = new Dns();
|
||||||
private ExecConfig exec = new ExecConfig();
|
private ExecConfig exec = new ExecConfig();
|
||||||
private FirebaseConfig firebase = new FirebaseConfig();
|
private FirebaseConfig firebase = new FirebaseConfig();
|
||||||
@@ -114,6 +115,8 @@ public class MxisdConfig {
|
|||||||
private ThreePidConfig threepid = new ThreePidConfig();
|
private ThreePidConfig threepid = new ThreePidConfig();
|
||||||
private ViewConfig view = new ViewConfig();
|
private ViewConfig view = new ViewConfig();
|
||||||
private WordpressConfig wordpress = new WordpressConfig();
|
private WordpressConfig wordpress = new WordpressConfig();
|
||||||
|
private PolicyConfig policy = new PolicyConfig();
|
||||||
|
private HashingConfig hashing = new HashingConfig();
|
||||||
|
|
||||||
public AppServiceConfig getAppsvc() {
|
public AppServiceConfig getAppsvc() {
|
||||||
return appsvc;
|
return appsvc;
|
||||||
@@ -131,6 +134,14 @@ public class MxisdConfig {
|
|||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AccountConfig getAccountConfig() {
|
||||||
|
return accountConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAccountConfig(AccountConfig accountConfig) {
|
||||||
|
this.accountConfig = accountConfig;
|
||||||
|
}
|
||||||
|
|
||||||
public DirectoryConfig getDirectory() {
|
public DirectoryConfig getDirectory() {
|
||||||
return directory;
|
return directory;
|
||||||
}
|
}
|
||||||
@@ -315,6 +326,22 @@ public class MxisdConfig {
|
|||||||
this.wordpress = wordpress;
|
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() {
|
public MxisdConfig inMemory() {
|
||||||
getKey().setPath(":memory:");
|
getKey().setPath(":memory:");
|
||||||
getStorage().getProvider().getSqlite().setDatabase(":memory:");
|
getStorage().getProvider().getSqlite().setDatabase(":memory:");
|
||||||
@@ -330,6 +357,7 @@ public class MxisdConfig {
|
|||||||
|
|
||||||
getAppsvc().build();
|
getAppsvc().build();
|
||||||
getAuth().build();
|
getAuth().build();
|
||||||
|
getAccountConfig().build();
|
||||||
getDirectory().build();
|
getDirectory().build();
|
||||||
getExec().build();
|
getExec().build();
|
||||||
getFirebase().build();
|
getFirebase().build();
|
||||||
@@ -352,6 +380,8 @@ public class MxisdConfig {
|
|||||||
getThreepid().build();
|
getThreepid().build();
|
||||||
getView().build();
|
getView().build();
|
||||||
getWordpress().build();
|
getWordpress().build();
|
||||||
|
getPolicy().build();
|
||||||
|
getHashing().build();
|
||||||
|
|
||||||
return this;
|
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("Port: {}", getPort());
|
||||||
log.info("Public URL: {}", getPublicUrl());
|
log.info("Public URL: {}", getPublicUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -46,34 +46,20 @@ public class SessionConfig {
|
|||||||
|
|
||||||
public static class PolicyUnbind {
|
public static class PolicyUnbind {
|
||||||
|
|
||||||
public static class PolicyUnbindFraudulent {
|
private boolean enabled = true;
|
||||||
|
|
||||||
private boolean sendWarning = true;
|
public boolean getEnabled() {
|
||||||
|
return enabled;
|
||||||
public boolean getSendWarning() {
|
|
||||||
return sendWarning;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSendWarning(boolean sendWarning) {
|
public void setEnabled(boolean enabled) {
|
||||||
this.sendWarning = sendWarning;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private PolicyUnbindFraudulent fraudulent = new PolicyUnbindFraudulent();
|
|
||||||
|
|
||||||
public PolicyUnbindFraudulent getFraudulent() {
|
|
||||||
return fraudulent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFraudulent(PolicyUnbindFraudulent fraudulent) {
|
|
||||||
this.fraudulent = fraudulent;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Policy() {
|
public Policy() {
|
||||||
validation.enabled = true;
|
validation.enabled = true;
|
||||||
|
unbind.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PolicyTemplate validation = new PolicyTemplate();
|
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 {
|
public static class Identity {
|
||||||
|
|
||||||
private Boolean enabled;
|
private Boolean enabled;
|
||||||
@@ -264,6 +276,7 @@ public abstract class SqlConfig {
|
|||||||
private Directory directory = new Directory();
|
private Directory directory = new Directory();
|
||||||
private Identity identity = new Identity();
|
private Identity identity = new Identity();
|
||||||
private Profile profile = new Profile();
|
private Profile profile = new Profile();
|
||||||
|
private Lookup lookup = new Lookup();
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
@@ -321,6 +334,14 @@ public abstract class SqlConfig {
|
|||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Lookup getLookup() {
|
||||||
|
return lookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLookup(Lookup lookup) {
|
||||||
|
this.lookup = lookup;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract String getProviderName();
|
protected abstract String getProviderName();
|
||||||
|
|
||||||
public void build() {
|
public void build() {
|
||||||
@@ -354,6 +375,7 @@ public abstract class SqlConfig {
|
|||||||
log.info("Identity type: {}", getIdentity().getType());
|
log.info("Identity type: {}", getIdentity().getType());
|
||||||
log.info("3PID mapping query: {}", getIdentity().getQuery());
|
log.info("3PID mapping query: {}", getIdentity().getQuery());
|
||||||
log.info("Identity medium queries: {}", GsonUtil.build().toJson(getIdentity().getMedium()));
|
log.info("Identity medium queries: {}", GsonUtil.build().toJson(getIdentity().getMedium()));
|
||||||
|
log.info("Lookup query: {}", getLookup().getQuery());
|
||||||
log.info("Profile:");
|
log.info("Profile:");
|
||||||
log.info(" Enabled: {}", getProfile().isEnabled());
|
log.info(" Enabled: {}", getProfile().isEnabled());
|
||||||
if (getProfile().isEnabled()) {
|
if (getProfile().isEnabled()) {
|
||||||
|
@@ -115,24 +115,10 @@ public class EmailSendGridConfig {
|
|||||||
|
|
||||||
public static class Templates {
|
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 {
|
public static class TemplateSession {
|
||||||
|
|
||||||
private EmailTemplate validation = new EmailTemplate();
|
private EmailTemplate validation = new EmailTemplate();
|
||||||
private TemplateSessionUnbind unbind = new TemplateSessionUnbind();
|
private EmailTemplate unbind = new EmailTemplate();
|
||||||
|
|
||||||
public EmailTemplate getValidation() {
|
public EmailTemplate getValidation() {
|
||||||
return validation;
|
return validation;
|
||||||
@@ -142,11 +128,11 @@ public class EmailSendGridConfig {
|
|||||||
this.validation = validation;
|
this.validation = validation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TemplateSessionUnbind getUnbind() {
|
public EmailTemplate getUnbind() {
|
||||||
return unbind;
|
return unbind;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUnbind(TemplateSessionUnbind unbind) {
|
public void setUnbind(EmailTemplate unbind) {
|
||||||
this.unbind = unbind;
|
this.unbind = unbind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ public class EmailTemplateConfig extends GenericTemplateConfig {
|
|||||||
setInvite("classpath:/threepids/email/invite-template.eml");
|
setInvite("classpath:/threepids/email/invite-template.eml");
|
||||||
getGeneric().put("matrixId", "classpath:/threepids/email/mxid-template.eml");
|
getGeneric().put("matrixId", "classpath:/threepids/email/mxid-template.eml");
|
||||||
getSession().setValidation("classpath:/threepids/email/validate-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() {
|
public EmailTemplateConfig build() {
|
||||||
@@ -40,7 +40,7 @@ public class EmailTemplateConfig extends GenericTemplateConfig {
|
|||||||
log.info("Session:");
|
log.info("Session:");
|
||||||
log.info(" Validation: {}", getSession().getValidation());
|
log.info(" Validation: {}", getSession().getValidation());
|
||||||
log.info(" Unbind:");
|
log.info(" Unbind:");
|
||||||
log.info(" Fraudulent: {}", getSession().getUnbind().getFraudulent());
|
log.info(" Notification: {}", getSession().getUnbind().getNotification());
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@@ -41,16 +41,25 @@ public class GenericTemplateConfig {
|
|||||||
|
|
||||||
public static class SessionUnbind {
|
public static class SessionUnbind {
|
||||||
|
|
||||||
private String fraudulent;
|
private String validation;
|
||||||
|
|
||||||
public String getFraudulent() {
|
private String notification;
|
||||||
return fraudulent;
|
|
||||||
|
public String getValidation() {
|
||||||
|
return validation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFraudulent(String fraudulent) {
|
public void setValidation(String validation) {
|
||||||
this.fraudulent = fraudulent;
|
this.validation = validation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNotification() {
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotification(String notification) {
|
||||||
|
this.notification = notification;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String validation;
|
private String validation;
|
||||||
|
@@ -30,7 +30,8 @@ public class PhoneSmsTemplateConfig extends GenericTemplateConfig {
|
|||||||
public PhoneSmsTemplateConfig() {
|
public PhoneSmsTemplateConfig() {
|
||||||
setInvite("classpath:/threepids/sms/invite-template.txt");
|
setInvite("classpath:/threepids/sms/invite-template.txt");
|
||||||
getSession().setValidation("classpath:/threepids/sms/validate-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() {
|
public PhoneSmsTemplateConfig build() {
|
||||||
@@ -39,7 +40,8 @@ public class PhoneSmsTemplateConfig extends GenericTemplateConfig {
|
|||||||
log.info("Session:");
|
log.info("Session:");
|
||||||
log.info(" Validation: {}", getSession().getValidation());
|
log.info(" Validation: {}", getSession().getValidation());
|
||||||
log.info(" Unbind:");
|
log.info(" Unbind:");
|
||||||
log.info(" Fraudulent: {}", getSession().getUnbind().getFraudulent());
|
log.info(" Validation: {}", getSession().getUnbind().getValidation());
|
||||||
|
log.info(" Notification: {}", getSession().getUnbind().getNotification());
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@@ -58,5 +58,4 @@ public class CryptoFactory {
|
|||||||
public static SignatureManager getSignatureManager(MxisdConfig cfg, Ed25519KeyManager keyMgr) {
|
public static SignatureManager getSignatureManager(MxisdConfig cfg, Ed25519KeyManager keyMgr) {
|
||||||
return new Ed25519SignatureManager(cfg, keyMgr);
|
return new Ed25519SignatureManager(cfg, keyMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -26,6 +26,7 @@ import io.kamax.matrix.event.EventKey;
|
|||||||
import io.kamax.matrix.json.MatrixJson;
|
import io.kamax.matrix.json.MatrixJson;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public interface SignatureManager {
|
public interface SignatureManager {
|
||||||
@@ -106,4 +107,13 @@ public interface SignatureManager {
|
|||||||
*/
|
*/
|
||||||
Signature sign(byte[] data);
|
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.InvalidKeyException;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PublicKey;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
public class Ed25519SignatureManager implements SignatureManager {
|
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 final String Base = "/_matrix/identity/api/v1";
|
||||||
|
|
||||||
public static String getValidate(String medium, String sid, String secret, String token) {
|
public static String getValidate(String medium, String sid, String secret, String token) {
|
||||||
// FIXME use some kind of URLBuilder
|
return String.format("%s/validate/%s/submitToken?sid=%s&client_secret=%s&token=%s", Base, medium, sid, secret, token);
|
||||||
return Base + "/validate/" + medium + "/submitToken?sid=" + sid + "&client_secret=" + secret + "&token=" + 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.HttpMatrixException;
|
||||||
import io.kamax.mxisd.exception.InternalServerError;
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
import io.kamax.mxisd.proxy.Response;
|
import io.kamax.mxisd.proxy.Response;
|
||||||
|
import io.kamax.mxisd.util.OptionalUtil;
|
||||||
import io.kamax.mxisd.util.RestClientUtils;
|
import io.kamax.mxisd.util.RestClientUtils;
|
||||||
import io.undertow.server.HttpHandler;
|
import io.undertow.server.HttpHandler;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
@@ -55,6 +56,24 @@ public abstract class BasicHttpHandler implements HttpHandler {
|
|||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(BasicHttpHandler.class);
|
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) {
|
protected String getAccessToken(HttpServerExchange exchange) {
|
||||||
return Optional.ofNullable(exchange.getRequestHeaders().getFirst("Authorization"))
|
return Optional.ofNullable(exchange.getRequestHeaders().getFirst("Authorization"))
|
||||||
.flatMap(v -> {
|
.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;
|
package io.kamax.mxisd.http.undertow.handler;
|
||||||
|
|
||||||
import io.kamax.mxisd.exception.AccessTokenNotFoundException;
|
import io.kamax.mxisd.exception.AccessTokenNotFoundException;
|
||||||
import io.kamax.mxisd.util.OptionalUtil;
|
|
||||||
import io.undertow.server.HttpServerExchange;
|
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 {
|
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) {
|
public String getAccessToken(HttpServerExchange exchange) {
|
||||||
return findAccessToken(exchange).orElseThrow(AccessTokenNotFoundException::new);
|
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());
|
respond(exchange, HttpStatus.SC_NOT_FOUND, "M_NOT_FOUND", e.getMessage());
|
||||||
} catch (NotImplementedException e) {
|
} catch (NotImplementedException e) {
|
||||||
respond(exchange, HttpStatus.SC_NOT_IMPLEMENTED, "M_NOT_IMPLEMENTED", e.getMessage());
|
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) {
|
} catch (FeatureNotAvailable e) {
|
||||||
if (StringUtils.isNotBlank(e.getInternalReason())) {
|
if (StringUtils.isNotBlank(e.getInternalReason())) {
|
||||||
log.error("Feature not available: {}", 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/>.
|
* 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.KeyManager;
|
||||||
import io.kamax.mxisd.crypto.KeyType;
|
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 io.undertow.server.HttpServerExchange;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class EphemeralKeyIsValidHandler extends KeyIsValidHandler {
|
public class EphemeralKeyIsValidHandler extends KeyIsValidHandler implements ApiHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/pubkey/ephemeral/isvalid";
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(EphemeralKeyIsValidHandler.class);
|
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);
|
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/>.
|
* 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.BasicHttpHandler;
|
||||||
|
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
|
|
||||||
public class HelloHandler extends BasicHttpHandler {
|
public class HelloHandler extends BasicHttpHandler implements ApiHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleRequest(HttpServerExchange exchange) {
|
public void handleRequest(HttpServerExchange exchange) {
|
||||||
respondJson(exchange, "{}");
|
respondJson(exchange, "{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHandlerPath() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
@@ -18,23 +18,22 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.JsonObject;
|
||||||
import io.kamax.mxisd.crypto.GenericKeyIdentifier;
|
import io.kamax.mxisd.crypto.GenericKeyIdentifier;
|
||||||
import io.kamax.mxisd.crypto.KeyManager;
|
import io.kamax.mxisd.crypto.KeyManager;
|
||||||
import io.kamax.mxisd.crypto.KeyType;
|
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.BasicHttpHandler;
|
||||||
|
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 Key = "key";
|
||||||
public static final String Path = IsAPIv1.Base + "/pubkey/{" + Key + "}";
|
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(KeyGetHandler.class);
|
private transient final Logger log = LoggerFactory.getLogger(KeyGetHandler.class);
|
||||||
|
|
||||||
@@ -61,4 +60,8 @@ public class KeyGetHandler extends BasicHttpHandler {
|
|||||||
respond(exchange, obj);
|
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/>.
|
* 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.matrix.json.GsonUtil;
|
||||||
import io.kamax.mxisd.http.io.identity.KeyValidityJson;
|
import io.kamax.mxisd.http.io.identity.KeyValidityJson;
|
@@ -18,7 +18,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.http.undertow.handler.BasicHttpHandler;
|
||||||
import io.kamax.mxisd.lookup.ALookupRequest;
|
import io.kamax.mxisd.lookup.ALookupRequest;
|
@@ -18,18 +18,16 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.KeyManager;
|
||||||
import io.kamax.mxisd.crypto.KeyType;
|
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 io.undertow.server.HttpServerExchange;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class RegularKeyIsValidHandler extends KeyIsValidHandler {
|
public class RegularKeyIsValidHandler extends KeyIsValidHandler implements ApiHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/pubkey/isvalid";
|
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(RegularKeyIsValidHandler.class);
|
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);
|
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/>.
|
* 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.JsonObject;
|
||||||
import io.kamax.matrix.ThreePid;
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.matrix.ThreePidMedium;
|
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.RequestTokenResponse;
|
||||||
import io.kamax.mxisd.http.io.identity.SessionEmailTokenRequestJson;
|
import io.kamax.mxisd.http.io.identity.SessionEmailTokenRequestJson;
|
||||||
import io.kamax.mxisd.http.io.identity.SessionPhoneTokenRequestJson;
|
import io.kamax.mxisd.http.io.identity.SessionPhoneTokenRequestJson;
|
||||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
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.SessionManager;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 Medium = "medium";
|
||||||
public static final String Path = IsAPIv1.Base + "/validate/{" + Medium + "}/requestToken";
|
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(SessionStartHandler.class);
|
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/>.
|
* 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.JsonObject;
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
import io.kamax.mxisd.crypto.SignatureManager;
|
import io.kamax.mxisd.crypto.SignatureManager;
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
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.BindRequest;
|
||||||
import io.kamax.mxisd.http.io.identity.SingeLookupReplyJson;
|
import io.kamax.mxisd.http.io.identity.SingeLookupReplyJson;
|
||||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
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.invitation.InvitationManager;
|
||||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
import io.kamax.mxisd.session.SessionManager;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
@@ -42,9 +42,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class SessionTpidBindHandler extends BasicHttpHandler {
|
public class SessionTpidBindHandler extends BasicHttpHandler implements ApiHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/3pid/bind";
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SessionTpidBindHandler.class);
|
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/>.
|
* 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.JsonObject;
|
||||||
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
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.BasicHttpHandler;
|
||||||
|
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||||
import io.kamax.mxisd.session.SessionManager;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class SessionTpidGetValidatedHandler extends BasicHttpHandler {
|
public class SessionTpidGetValidatedHandler extends BasicHttpHandler implements ApiHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/3pid/getValidated3pid";
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SessionTpidGetValidatedHandler.class);
|
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/>.
|
* 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.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.BasicHttpHandler;
|
||||||
|
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||||
import io.kamax.mxisd.session.SessionManager;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
public class SessionTpidUnbindHandler extends BasicHttpHandler implements ApiHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/3pid/unbind";
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SessionTpidUnbindHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(SessionTpidUnbindHandler.class);
|
||||||
|
|
||||||
@@ -46,20 +41,14 @@ public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
|||||||
@Override
|
@Override
|
||||||
public void handleRequest(HttpServerExchange exchange) {
|
public void handleRequest(HttpServerExchange exchange) {
|
||||||
String auth = exchange.getRequestHeaders().getFirst("Authorization");
|
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);
|
JsonObject body = parseJsonObject(exchange);
|
||||||
sessionMgr.unbind(body);
|
sessionMgr.unbind(auth, body);
|
||||||
writeBodyAsUtf8(exchange, "{}");
|
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/>.
|
* 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.BasicHttpHandler;
|
||||||
|
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||||
import io.kamax.mxisd.session.SessionManager;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
import io.kamax.mxisd.session.ValidationResult;
|
import io.kamax.mxisd.session.ValidationResult;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public abstract class SessionValidateHandler extends BasicHttpHandler {
|
public abstract class SessionValidateHandler extends BasicHttpHandler implements ApiHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/validate/{medium}/submitToken";
|
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(SessionValidateHandler.class);
|
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);
|
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/>.
|
* 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.MxisdConfig;
|
||||||
import io.kamax.mxisd.config.ServerConfig;
|
import io.kamax.mxisd.config.ServerConfig;
|
@@ -18,7 +18,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.JsonObject;
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
@@ -18,7 +18,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.JsonObject;
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
@@ -27,17 +27,15 @@ import io.kamax.matrix.json.GsonUtil;
|
|||||||
import io.kamax.matrix.json.MatrixJson;
|
import io.kamax.matrix.json.MatrixJson;
|
||||||
import io.kamax.mxisd.config.MxisdConfig;
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
import io.kamax.mxisd.crypto.SignatureManager;
|
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.BasicHttpHandler;
|
||||||
|
import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
import io.kamax.mxisd.invitation.InvitationManager;
|
import io.kamax.mxisd.invitation.InvitationManager;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class SignEd25519Handler extends BasicHttpHandler {
|
public class SignEd25519Handler extends BasicHttpHandler implements ApiHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/sign-ed25519";
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SignEd25519Handler.class);
|
private static final Logger log = LoggerFactory.getLogger(SignEd25519Handler.class);
|
||||||
|
|
||||||
@@ -72,4 +70,8 @@ public class SignEd25519Handler extends BasicHttpHandler {
|
|||||||
respondJson(exchange, res);
|
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/>.
|
* 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.JsonObject;
|
||||||
import com.google.gson.reflect.TypeToken;
|
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.config.ServerConfig;
|
||||||
import io.kamax.mxisd.crypto.KeyManager;
|
import io.kamax.mxisd.crypto.KeyManager;
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
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.StoreInviteRequest;
|
||||||
import io.kamax.mxisd.http.io.identity.ThreePidInviteReplyIO;
|
import io.kamax.mxisd.http.io.identity.ThreePidInviteReplyIO;
|
||||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
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.IThreePidInvite;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
import io.kamax.mxisd.invitation.InvitationManager;
|
import io.kamax.mxisd.invitation.InvitationManager;
|
||||||
@@ -45,9 +45,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class StoreInviteHandler extends BasicHttpHandler {
|
public class StoreInviteHandler extends BasicHttpHandler implements ApiHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/store-invite";
|
|
||||||
|
|
||||||
private ServerConfig cfg;
|
private ServerConfig cfg;
|
||||||
private InvitationManager invMgr;
|
private InvitationManager invMgr;
|
||||||
@@ -100,4 +98,8 @@ public class StoreInviteHandler extends BasicHttpHandler {
|
|||||||
respondJson(exchange, new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getServerSigningKey().getId()), cfg.getPublicUrl()));
|
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.IsAPIv1;
|
||||||
import io.kamax.mxisd.http.io.identity.ClientBulkLookupAnswer;
|
import io.kamax.mxisd.http.io.identity.ClientBulkLookupAnswer;
|
||||||
import io.kamax.mxisd.http.io.identity.ClientBulkLookupRequest;
|
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.BulkLookupRequest;
|
||||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||||
@@ -33,7 +35,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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";
|
public static final String Path = IsAPIv1.Base + "/bulk_lookup";
|
||||||
|
|
||||||
@@ -69,4 +71,8 @@ public class BulkLookupHandler extends LookupHandler {
|
|||||||
respondJson(exchange, answer);
|
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.crypto.SignatureManager;
|
||||||
import io.kamax.mxisd.http.IsAPIv1;
|
import io.kamax.mxisd.http.IsAPIv1;
|
||||||
import io.kamax.mxisd.http.io.identity.SingeLookupReplyJson;
|
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.SingleLookupReply;
|
||||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||||
@@ -36,7 +38,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class SingleLookupHandler extends LookupHandler {
|
public class SingleLookupHandler extends LookupHandler implements ApiHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/lookup";
|
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.SingleLookupRequest;
|
||||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -40,4 +41,7 @@ public interface IThreePidProvider {
|
|||||||
|
|
||||||
List<ThreePidMapping> populate(List<ThreePidMapping> mappings);
|
List<ThreePidMapping> populate(List<ThreePidMapping> mappings);
|
||||||
|
|
||||||
|
default Iterable<ThreePidMapping> populateHashes() {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
package io.kamax.mxisd.lookup.strategy;
|
package io.kamax.mxisd.lookup.strategy;
|
||||||
|
|
||||||
import io.kamax.mxisd.lookup.BulkLookupRequest;
|
import io.kamax.mxisd.lookup.BulkLookupRequest;
|
||||||
|
import io.kamax.mxisd.lookup.HashLookupRequest;
|
||||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
@@ -46,4 +47,5 @@ public interface LookupStrategy {
|
|||||||
|
|
||||||
CompletableFuture<List<ThreePidMapping>> find(BulkLookupRequest requests);
|
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.matrix.json.MatrixJson;
|
||||||
import io.kamax.mxisd.config.MxisdConfig;
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
import io.kamax.mxisd.exception.ConfigurationException;
|
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.*;
|
||||||
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher;
|
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher;
|
||||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||||
import org.apache.commons.codec.digest.DigestUtils;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -50,10 +53,14 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
|
|||||||
|
|
||||||
private List<CIDRUtils> allowedCidr = new ArrayList<>();
|
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.cfg = cfg;
|
||||||
this.bridge = bridge;
|
this.bridge = bridge;
|
||||||
this.providers = new ArrayList<>(providers);
|
this.providers = new ArrayList<>(providers);
|
||||||
|
this.hashManager = hashManager;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.info("Found {} providers", providers.size());
|
log.info("Found {} providers", providers.size());
|
||||||
@@ -65,6 +72,8 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
|
|||||||
log.info("{} is allowed for recursion", cidr);
|
log.info("{} is allowed for recursion", cidr);
|
||||||
allowedCidr.add(new CIDRUtils(cidr));
|
allowedCidr.add(new CIDRUtils(cidr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("Hash lookups enabled: {}", hashManager.getConfig().isEnabled());
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
throw new ConfigurationException("lookup.recursive.allowedCidrs", "Allowed CIDRs");
|
throw new ConfigurationException("lookup.recursive.allowedCidrs", "Allowed CIDRs");
|
||||||
}
|
}
|
||||||
@@ -230,4 +239,12 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
|
|||||||
return bulkLookupInProgress.remove(payloadId);
|
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 sendForValidation(IThreePidSession session);
|
||||||
|
|
||||||
void sendForFraudulentUnbind(ThreePid tpid);
|
void sendForUnbind(ThreePid tpid);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -78,8 +78,8 @@ public class NotificationManager {
|
|||||||
ensureMedium(session.getThreePid().getMedium()).sendForValidation(session);
|
ensureMedium(session.getThreePid().getMedium()).sendForValidation(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendForFraudulentUnbind(ThreePid tpid) throws NotImplementedException {
|
public void sendForUnbind(ThreePid tpid) throws NotImplementedException {
|
||||||
ensureMedium(tpid.getMedium()).sendForFraudulentUnbind(tpid);
|
ensureMedium(tpid.getMedium()).sendForUnbind(tpid);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -20,52 +20,75 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.session;
|
package io.kamax.mxisd.session;
|
||||||
|
|
||||||
|
import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
import io.kamax.matrix.ThreePid;
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.matrix._MatrixID;
|
import io.kamax.matrix._MatrixID;
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
import io.kamax.matrix.json.MatrixJson;
|
||||||
import io.kamax.mxisd.config.SessionConfig;
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
|
import io.kamax.mxisd.crypto.SignatureManager;
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
import io.kamax.mxisd.exception.BadRequestException;
|
||||||
import io.kamax.mxisd.exception.NotAllowedException;
|
import io.kamax.mxisd.exception.NotAllowedException;
|
||||||
|
import io.kamax.mxisd.exception.RemoteHomeServerException;
|
||||||
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
||||||
import io.kamax.mxisd.exception.SessionUnknownException;
|
import io.kamax.mxisd.exception.SessionUnknownException;
|
||||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||||
|
import io.kamax.mxisd.matrix.HomeserverFederationResolver;
|
||||||
import io.kamax.mxisd.notification.NotificationManager;
|
import io.kamax.mxisd.notification.NotificationManager;
|
||||||
import io.kamax.mxisd.storage.IStorage;
|
import io.kamax.mxisd.storage.IStorage;
|
||||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||||
import io.kamax.mxisd.threepid.session.ThreePidSession;
|
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.RandomStringUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Calendar;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate;
|
|
||||||
|
|
||||||
public class SessionManager {
|
public class SessionManager {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
|
private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
|
||||||
|
|
||||||
private SessionConfig cfg;
|
private MxisdConfig cfg;
|
||||||
private MatrixConfig mxCfg;
|
|
||||||
private IStorage storage;
|
private IStorage storage;
|
||||||
private NotificationManager notifMgr;
|
private NotificationManager notifMgr;
|
||||||
|
private HomeserverFederationResolver resolver;
|
||||||
|
private CloseableHttpClient client;
|
||||||
|
private SignatureManager signatureManager;
|
||||||
|
|
||||||
public SessionManager(
|
public SessionManager(
|
||||||
SessionConfig cfg,
|
MxisdConfig cfg,
|
||||||
MatrixConfig mxCfg,
|
|
||||||
IStorage storage,
|
IStorage storage,
|
||||||
NotificationManager notifMgr
|
NotificationManager notifMgr,
|
||||||
|
HomeserverFederationResolver resolver,
|
||||||
|
CloseableHttpClient client,
|
||||||
|
SignatureManager signatureManager
|
||||||
) {
|
) {
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
this.mxCfg = mxCfg;
|
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.notifMgr = notifMgr;
|
this.notifMgr = notifMgr;
|
||||||
|
this.resolver = resolver;
|
||||||
|
this.client = client;
|
||||||
|
this.signatureManager = signatureManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ThreePidSession getSession(String sid, String secret) {
|
private ThreePidSession getSession(String sid, String secret) {
|
||||||
@@ -86,7 +109,7 @@ public class SessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String create(String server, ThreePid tpid, String secret, int attempt, String nextLink) {
|
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()) {
|
if (!policy.isEnabled()) {
|
||||||
throw new NotAllowedException("Validating 3PID is disabled");
|
throw new NotAllowedException("Validating 3PID is disabled");
|
||||||
}
|
}
|
||||||
@@ -98,7 +121,8 @@ public class SessionManager {
|
|||||||
ThreePidSession session = new ThreePidSession(dao.get());
|
ThreePidSession session = new ThreePidSession(dao.get());
|
||||||
log.info("We already have a session for {}: {}", tpid, session.getId());
|
log.info("We already have a session for {}: {}", tpid, session.getId());
|
||||||
if (session.getAttempt() < attempt) {
|
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);
|
notifMgr.sendForValidation(session);
|
||||||
log.info("Sent validation notification to {}", tpid);
|
log.info("Sent validation notification to {}", tpid);
|
||||||
session.increaseAttempt();
|
session.increaseAttempt();
|
||||||
@@ -161,8 +185,9 @@ public class SessionManager {
|
|||||||
_MatrixID mxid = MatrixID.asAcceptable(mxidRaw);
|
_MatrixID mxid = MatrixID.asAcceptable(mxidRaw);
|
||||||
|
|
||||||
// Only accept binds if the domain matches our own
|
// Only accept binds if the domain matches our own
|
||||||
if (!StringUtils.equalsIgnoreCase(mxCfg.getDomain(), mxid.getDomain())) {
|
final String domain = cfg.getMatrix().getDomain();
|
||||||
throw new NotAllowedException("Only Matrix IDs from domain " + mxCfg.getDomain() + " can be bound");
|
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",
|
log.info("Session {}: Binding of {}:{} to Matrix ID {} is accepted",
|
||||||
@@ -174,7 +199,12 @@ public class SessionManager {
|
|||||||
return new SingleLookupReply(request, mxid);
|
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;
|
_MatrixID mxid;
|
||||||
try {
|
try {
|
||||||
mxid = MatrixID.asAcceptable(GsonUtil.getStringOrThrow(reqData, "mxid"));
|
mxid = MatrixID.asAcceptable(GsonUtil.getStringOrThrow(reqData, "mxid"));
|
||||||
@@ -186,6 +216,158 @@ public class SessionManager {
|
|||||||
String secret = GsonUtil.getStringOrNull(reqData, "client_secret");
|
String secret = GsonUtil.getStringOrNull(reqData, "client_secret");
|
||||||
ThreePid tpid = GsonUtil.get().fromJson(GsonUtil.getObj(reqData, "threepid"), ThreePid.class);
|
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());
|
||||||
|
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
|
// We ensure the session was validated
|
||||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||||
|
|
||||||
@@ -193,14 +375,5 @@ public class SessionManager {
|
|||||||
if (!session.getThreePid().equals(tpid)) {
|
if (!session.getThreePid().equals(tpid)) {
|
||||||
throw new BadRequestException("3PID to unbind does not match the one from the validated session");
|
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;
|
package io.kamax.mxisd.storage;
|
||||||
|
|
||||||
import io.kamax.matrix.ThreePid;
|
import io.kamax.matrix.ThreePid;
|
||||||
|
import io.kamax.mxisd.config.PolicyConfig;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||||
import io.kamax.mxisd.storage.ormlite.dao.ASTransactionDao;
|
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 io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface IStorage {
|
public interface IStorage {
|
||||||
@@ -52,4 +57,21 @@ public interface IStorage {
|
|||||||
|
|
||||||
Optional<ASTransactionDao> getTransactionResult(String localpart, String txnId);
|
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.Dao;
|
||||||
import com.j256.ormlite.dao.DaoManager;
|
import com.j256.ormlite.dao.DaoManager;
|
||||||
import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
||||||
|
import com.j256.ormlite.stmt.QueryBuilder;
|
||||||
import com.j256.ormlite.support.ConnectionSource;
|
import com.j256.ormlite.support.ConnectionSource;
|
||||||
import com.j256.ormlite.table.TableUtils;
|
import com.j256.ormlite.table.TableUtils;
|
||||||
import io.kamax.matrix.ThreePid;
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.mxisd.config.MxisdConfig;
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
|
import io.kamax.mxisd.config.PolicyConfig;
|
||||||
import io.kamax.mxisd.exception.ConfigurationException;
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
import io.kamax.mxisd.exception.InternalServerError;
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
|
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
|
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||||
import io.kamax.mxisd.storage.IStorage;
|
import io.kamax.mxisd.storage.IStorage;
|
||||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||||
import io.kamax.mxisd.storage.ormlite.dao.ASTransactionDao;
|
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.HistoricalThreePidInviteIO;
|
||||||
|
import io.kamax.mxisd.storage.ormlite.dao.AcceptedDao;
|
||||||
import io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO;
|
import io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO;
|
||||||
import io.kamax.mxisd.storage.ormlite.dao.ThreePidSessionDao;
|
import io.kamax.mxisd.storage.ormlite.dao.ThreePidSessionDao;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.time.Instant;
|
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 {
|
public class OrmLiteSqlStorage implements IStorage {
|
||||||
|
|
||||||
@@ -64,6 +77,9 @@ public class OrmLiteSqlStorage implements IStorage {
|
|||||||
private Dao<HistoricalThreePidInviteIO, String> expInvDao;
|
private Dao<HistoricalThreePidInviteIO, String> expInvDao;
|
||||||
private Dao<ThreePidSessionDao, String> sessionDao;
|
private Dao<ThreePidSessionDao, String> sessionDao;
|
||||||
private Dao<ASTransactionDao, String> asTxnDao;
|
private Dao<ASTransactionDao, String> asTxnDao;
|
||||||
|
private Dao<AccountDao, String> accountDao;
|
||||||
|
private Dao<AcceptedDao, String> acceptedDao;
|
||||||
|
private Dao<HashDao, String> hashDao;
|
||||||
|
|
||||||
public OrmLiteSqlStorage(MxisdConfig cfg) {
|
public OrmLiteSqlStorage(MxisdConfig cfg) {
|
||||||
this(cfg.getStorage().getBackend(), cfg.getStorage().getProvider().getSqlite().getDatabase());
|
this(cfg.getStorage().getBackend(), cfg.getStorage().getProvider().getSqlite().getDatabase());
|
||||||
@@ -84,6 +100,9 @@ public class OrmLiteSqlStorage implements IStorage {
|
|||||||
expInvDao = createDaoAndTable(connPool, HistoricalThreePidInviteIO.class);
|
expInvDao = createDaoAndTable(connPool, HistoricalThreePidInviteIO.class);
|
||||||
sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class);
|
sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class);
|
||||||
asTxnDao = createDaoAndTable(connPool, ASTransactionDao.class);
|
asTxnDao = createDaoAndTable(connPool, ASTransactionDao.class);
|
||||||
|
accountDao = createDaoAndTable(connPool, AccountDao.class);
|
||||||
|
acceptedDao = createDaoAndTable(connPool, AcceptedDao.class);
|
||||||
|
hashDao = createDaoAndTable(connPool, HashDao.class);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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.setRecipients(Message.RecipientType.TO, recipient);
|
||||||
msg.saveChanges();
|
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");
|
SMTPTransport transport = (SMTPTransport) session.getTransport("smtp");
|
||||||
|
|
||||||
if (cfg.getTls() < 3) {
|
if (cfg.getTls() < 3) {
|
||||||
@@ -134,12 +134,12 @@ public class EmailSmtpConnector implements EmailConnector {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
transport.sendMessage(msg, InternetAddress.parse(recipient));
|
transport.sendMessage(msg, InternetAddress.parse(recipient));
|
||||||
log.info("Invite to {} was sent", recipient);
|
log.info("Email to {} was sent", recipient);
|
||||||
} finally {
|
} finally {
|
||||||
transport.close();
|
transport.close();
|
||||||
}
|
}
|
||||||
} catch (UnsupportedEncodingException | MessagingException e) {
|
} 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
|
@Override
|
||||||
public String getForFraudulentUnbind(ThreePid tpid) {
|
public String getForNotificationUnbind(ThreePid tpid) {
|
||||||
log.info("Generating notification content for fraudulent unbind");
|
log.info("Generating notification content for unbind");
|
||||||
return populateForFraudulentUndind(tpid, getTemplateContent(cfg.getSession().getUnbind().getFraudulent()));
|
return populateForNotificationUndind(tpid, getTemplateContent(cfg.getSession().getUnbind().getNotification()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -37,6 +37,6 @@ public interface NotificationGenerator {
|
|||||||
|
|
||||||
String getForValidation(IThreePidSession session);
|
String getForValidation(IThreePidSession session);
|
||||||
|
|
||||||
String getForFraudulentUnbind(ThreePid tpid);
|
String getForNotificationUnbind(ThreePid tpid);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -127,7 +127,7 @@ public abstract class PlaceholderNotificationGenerator {
|
|||||||
.replace("%NEXT_URL%", validationLink);
|
.replace("%NEXT_URL%", validationLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String populateForFraudulentUndind(ThreePid tpid, String input) {
|
protected String populateForNotificationUndind(ThreePid tpid, String input) {
|
||||||
return populateForCommon(tpid, input);
|
return populateForCommon(tpid, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -73,8 +73,8 @@ public abstract class GenericNotificationHandler<A extends ThreePidConnector, B
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendForFraudulentUnbind(ThreePid tpid) {
|
public void sendForUnbind(ThreePid tpid) {
|
||||||
send(connector, tpid.getAddress(), generator.getForFraudulentUnbind(tpid));
|
send(connector, tpid.getAddress(), generator.getForNotificationUnbind(tpid));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -129,10 +129,10 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendForFraudulentUnbind(ThreePid tpid) {
|
public void sendForUnbind(ThreePid tpid) {
|
||||||
EmailTemplate template = cfg.getTemplates().getSession().getUnbind().getFraudulent();
|
EmailTemplate template = cfg.getTemplates().getSession().getUnbind();
|
||||||
if (StringUtils.isAllBlank(template.getBody().getText(), template.getBody().getHtml())) {
|
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();
|
Email email = getEmail();
|
||||||
|
@@ -1,135 +0,0 @@
|
|||||||
Subject: IMPORTANT - %DOMAIN% Matrix Identity Server - Unauthorized 3PID unbind blocked
|
|
||||||
MIME-Version: 1.0
|
|
||||||
Content-Type: multipart/alternative;
|
|
||||||
boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
|
|
||||||
|
|
||||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
|
||||||
Content-Type: text/plain; charset=UTF-8
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
Hi,
|
|
||||||
|
|
||||||
**THIS IS IMPORTANT, PLEASE READ CAREFULLY**.
|
|
||||||
If you are the system administrator of the Matrix installation, read the second section.
|
|
||||||
|
|
||||||
This is a notification email that a possibly unauthorized entity has attempted to alter your
|
|
||||||
3PIDs (email, phone numbers, etc.) settings. The request was denied and no change has been made.
|
|
||||||
|
|
||||||
This is so you are aware of a possible failure in case you just tried to remove a 3PID from your account.
|
|
||||||
|
|
||||||
If you do not understand this email, please forward it to your System administrator.
|
|
||||||
|
|
||||||
-----------
|
|
||||||
|
|
||||||
As the system administrator:
|
|
||||||
|
|
||||||
If you are using synapse as a Homeserver, this is a known issue related to MSC1194 [1] and abuse of separation of concerns.
|
|
||||||
As a privacy-centric product and to protect your privacy, the request was actively blocked. We have written a more detailed
|
|
||||||
explanation on our Privacy wiki page [2] (Direct link [3]) so you can fully grasp the impact for you and your users.
|
|
||||||
|
|
||||||
We have open an issue [4] on the synapse repos to reflect the related privacy concerns and GDPR violation(s) and would
|
|
||||||
appreciate if you could comment on it or simply adds a thumbs up so the concerns are finally dealt with by the synapse dev team.
|
|
||||||
|
|
||||||
If you are using another Homeserver or this came following no action from your own users, then you have been the target
|
|
||||||
of an unbind attack from a rogue entity which was blocked. You may want to check your logs to see the exact source of
|
|
||||||
the attack and take relevant actions following your policy.
|
|
||||||
|
|
||||||
If you would like to disable these notifications, please see the 3PID sessions configuration documentation [5].
|
|
||||||
|
|
||||||
Thanks,
|
|
||||||
|
|
||||||
%DOMAIN_PRETTY% Admins
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[1] https://github.com/matrix-org/matrix-doc/issues/1194
|
|
||||||
[2] https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy
|
|
||||||
[3] https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy#msc1194-synapse-and-impacts-on-your-privacy
|
|
||||||
[4] https://github.com/matrix-org/synapse/issues/4540
|
|
||||||
[5] https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/session/session.md#configuration
|
|
||||||
|
|
||||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
|
||||||
Content-Type: multipart/related;
|
|
||||||
boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
|
|
||||||
type="text/html"
|
|
||||||
|
|
||||||
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR
|
|
||||||
Content-Type: text/html; charset=UTF-8
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<style type="text/css">
|
|
||||||
body {
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre, code {
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
#page {
|
|
||||||
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
|
|
||||||
font-color: #454545;
|
|
||||||
font-size: 12pt;
|
|
||||||
width: 100%%;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#inner {
|
|
||||||
width: 640px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<table id="page">
|
|
||||||
<tr>
|
|
||||||
<td> </td>
|
|
||||||
<td id="inner">
|
|
||||||
<p>Hi,</p>
|
|
||||||
|
|
||||||
<p><b>THIS IS IMPORTANT, PLEASE READ CAREFULLY</b>.<br/>
|
|
||||||
If you are the system administrator of the Matrix installation, read the second section.</p>
|
|
||||||
|
|
||||||
<p>This is a notification email that a possibly unauthorized entity has attempted to alter your
|
|
||||||
3PIDs (email, phone numbers, etc.) settings. The request was denied and no change has been made.</p>
|
|
||||||
|
|
||||||
<p>This is so you are aware of a possible failure in case you just tried to remove a 3PID from your account.</p>
|
|
||||||
|
|
||||||
<p>If you do not understand this email, please forward it to your System administrator.</p>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<p>As the system administrator:</p>
|
|
||||||
|
|
||||||
<p>If you are using synapse as a Homeserver, this is a known issue related to <a href="https://github.com/matrix-org/matrix-doc/issues/1194">MSC1194</a>
|
|
||||||
and abuse of separation of concerns. As a privacy-centric product and to protect your privacy, the request was actively
|
|
||||||
blocked. We have written a more detailed explanation on our <a href="https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy">Privacy wiki page</a>
|
|
||||||
(<a href="https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy#msc1194-synapse-and-impacts-on-your-privacy">Direct link to section</a>)
|
|
||||||
so you can fully grasp the impact for you and your users.</p>
|
|
||||||
|
|
||||||
<p>We have open an issue on the synapse repos to reflect the related privacy concerns and GDPR violation(s) and would
|
|
||||||
appreciate if you could comment on it or simply adds a thumbs up so the concerns are finally dealt with by the synapse dev team.<br/>
|
|
||||||
Issue: <a href="https://github.com/matrix-org/synapse/issues/4540">https://github.com/matrix-org/synapse/issues/4540</a></p>
|
|
||||||
|
|
||||||
<p>If you are using another Homeserver or this came following no action from your own users, then you have been the target
|
|
||||||
of an unbind attack from a rogue entity which was blocked. You may want to check your logs to see the exact source of
|
|
||||||
the attack and take relevant actions following your policy.</p>
|
|
||||||
|
|
||||||
<p>If you would like to disable these notifications, please see the
|
|
||||||
<a href="https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/session/session.md#configuration">3PID sessions configuration documentation.</a></p>
|
|
||||||
|
|
||||||
<p>Thanks,</p>
|
|
||||||
|
|
||||||
<p>%DOMAIN_PRETTY% Admins</p>
|
|
||||||
</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR--
|
|
||||||
|
|
||||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--
|
|
77
src/main/resources/threepids/email/unbind-notification.eml
Normal file
77
src/main/resources/threepids/email/unbind-notification.eml
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
Subject: Unbind 3PID
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
|
||||||
|
|
||||||
|
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Disposition: inline
|
||||||
|
|
||||||
|
Hello there!
|
||||||
|
|
||||||
|
You or a server on your behalf unbinded your email.
|
||||||
|
|
||||||
|
If you didn't make this request, please contact the system administrator.
|
||||||
|
|
||||||
|
%DOMAIN_PRETTY% Admins
|
||||||
|
|
||||||
|
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
||||||
|
Content-Type: multipart/related;
|
||||||
|
boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
|
||||||
|
type="text/html"
|
||||||
|
|
||||||
|
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR
|
||||||
|
Content-Type: text/html; charset=UTF-8
|
||||||
|
Content-Disposition: inline
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, code {
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page {
|
||||||
|
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
|
||||||
|
font-color: #454545;
|
||||||
|
font-size: 12pt;
|
||||||
|
width: 100%%;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inner {
|
||||||
|
width: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notif_link a, .footer a {
|
||||||
|
color: #76CFA6 ! important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table id="page">
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td id="inner">
|
||||||
|
<p>Hello there!</p>
|
||||||
|
|
||||||
|
<p>You or a server on your behalf unbinded your email.</p>
|
||||||
|
|
||||||
|
<p>If you didn't make this request, please contact the system administrator.</p>
|
||||||
|
|
||||||
|
<p>%DOMAIN_PRETTY% Admins</p>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR--
|
||||||
|
|
||||||
|
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--
|
@@ -1 +0,0 @@
|
|||||||
INFORMATIONAL ONLY - Someone attempted to change your Matrix 3PIDs, with a potential data leak. Please contact your system administrator.
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user