Compare commits
11 Commits
v1.3.0-rc.
...
v1.4.0-alp
Author | SHA1 | Date | |
---|---|---|---|
|
acd8c7d7c5 | ||
|
249cc0ea92 | ||
|
99697d7c75 | ||
|
e133e120d7 | ||
|
e39d6bfa10 | ||
|
217bc423ed | ||
|
8f0654c34e | ||
|
8afdb3ed83 | ||
|
bd4ccbc5e5 | ||
|
6d1c6ed109 | ||
|
1619f5311c |
@@ -74,6 +74,9 @@ Also, check [our FAQ entry](docs/faq.md#what-kind-of-setup-is-mxisd-really-desig
|
|||||||
See the [dedicated document](docs/getting-started.md)
|
See the [dedicated document](docs/getting-started.md)
|
||||||
|
|
||||||
# Support
|
# Support
|
||||||
|
## Troubleshooting
|
||||||
|
A basic troubleshooting guide is available [here](docs/troubleshooting.md).
|
||||||
|
|
||||||
## Community
|
## Community
|
||||||
Over Matrix: [#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io) ([Preview](https://view.matrix.org/room/!NPRUEisLjcaMtHIzDr:kamax.io/))
|
Over Matrix: [#mxisd:kamax.io](https://matrix.to/#/#mxisd:kamax.io) ([Preview](https://view.matrix.org/room/!NPRUEisLjcaMtHIzDr:kamax.io/))
|
||||||
|
|
||||||
|
@@ -101,7 +101,7 @@ dependencies {
|
|||||||
compile 'com.j256.ormlite:ormlite-jdbc:5.0'
|
compile 'com.j256.ormlite:ormlite-jdbc:5.0'
|
||||||
|
|
||||||
// ed25519 handling
|
// ed25519 handling
|
||||||
compile 'net.i2p.crypto:eddsa:0.1.0'
|
compile 'net.i2p.crypto:eddsa:0.3.0'
|
||||||
|
|
||||||
// LDAP connector
|
// LDAP connector
|
||||||
compile 'org.apache.directory.api:api-all:1.0.0'
|
compile 'org.apache.directory.api:api-all:1.0.0'
|
||||||
@@ -190,13 +190,13 @@ task debBuild(dependsOn: shadowJar) {
|
|||||||
ant.replaceregexp( // FIXME adapt to new config format
|
ant.replaceregexp( // FIXME adapt to new config format
|
||||||
file: "${debBuildConfPath}/${debConfFileName}",
|
file: "${debBuildConfPath}/${debConfFileName}",
|
||||||
match: "key:\\R path:(.*)",
|
match: "key:\\R path:(.*)",
|
||||||
replace: "key:\n path: '${debDataPath}/signing.key'"
|
replace: "key:\n path: '${debDataPath}/keys'"
|
||||||
)
|
)
|
||||||
|
|
||||||
ant.replaceregexp( // FIXME adapt to new config format
|
ant.replaceregexp( // FIXME adapt to new config format
|
||||||
file: "${debBuildConfPath}/${debConfFileName}",
|
file: "${debBuildConfPath}/${debConfFileName}",
|
||||||
match: "storage:\\R provider:\\R sqlite:\\R database:(.*)",
|
match: "storage:\\R provider:\\R sqlite:\\R database:(.*)",
|
||||||
replace: "storage:\n provider:\n sqlite:\n database: '${debDataPath}/mxisd.db'"
|
replace: "storage:\n provider:\n sqlite:\n database: '${debDataPath}/store.db'"
|
||||||
)
|
)
|
||||||
|
|
||||||
copy {
|
copy {
|
||||||
|
@@ -26,7 +26,7 @@ synapseSql:
|
|||||||
connection: '<DB CONNECTION URL>'
|
connection: '<DB CONNECTION URL>'
|
||||||
```
|
```
|
||||||
|
|
||||||
The `synapseSql` section is used to retrieve display names which are not directly accessible in this mode.
|
The `synapseSql` section is optional. It is used to retrieve display names which are not directly accessible in this mode.
|
||||||
For details about `type` and `connection`, see the [relevant documentation](../../stores/synapse.md).
|
For details about `type` and `connection`, see the [relevant documentation](../../stores/synapse.md).
|
||||||
If you do not configure it, some placeholders will not be available in the notification, like the Room name.
|
If you do not configure it, some placeholders will not be available in the notification, like the Room name.
|
||||||
|
|
||||||
|
@@ -46,15 +46,6 @@ lookup:
|
|||||||
invite:
|
invite:
|
||||||
resolution:
|
resolution:
|
||||||
recursive: false
|
recursive: false
|
||||||
session:
|
|
||||||
policy:
|
|
||||||
validation:
|
|
||||||
forLocal:
|
|
||||||
toRemote:
|
|
||||||
enabled: false
|
|
||||||
forRemote:
|
|
||||||
toRemote:
|
|
||||||
enabled: false
|
|
||||||
```
|
```
|
||||||
|
|
||||||
There is currently no way to selectively disable federation towards specific servers, but this feature is planned.
|
There is currently no way to selectively disable federation towards specific servers, but this feature is planned.
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
# Identity
|
# Identity
|
||||||
**WARNING**: This document is incomplete and can be misleading.
|
|
||||||
|
|
||||||
Implementation of the [Identity Service API r0.1.0](https://matrix.org/docs/spec/identity_service/r0.1.0.html).
|
Implementation of the [Identity Service API r0.1.0](https://matrix.org/docs/spec/identity_service/r0.1.0.html).
|
||||||
|
|
||||||
## Lookups
|
## Lookups
|
||||||
|
@@ -54,17 +54,10 @@ See the [Latest release](https://github.com/kamax-matrix/mxisd/releases/latest)
|
|||||||
|
|
||||||
> **NOTE**: Details about configuration syntax and format are described [here](configure.md)
|
> **NOTE**: Details about configuration syntax and format are described [here](configure.md)
|
||||||
|
|
||||||
Create/edit a minimal configuration (see installer doc for the location):
|
If you haven't created a configuration file yet, copy `mxisd.example.yaml` to where the configuration file is stored given
|
||||||
```yaml
|
your installation method and edit to your needs.
|
||||||
matrix:
|
|
||||||
domain: 'example.org'
|
The following items must be at least configured:
|
||||||
key:
|
|
||||||
path: '/path/to/signing.key.file'
|
|
||||||
storage:
|
|
||||||
provider:
|
|
||||||
sqlite:
|
|
||||||
database: '/path/to/mxisd.db'
|
|
||||||
```
|
|
||||||
- `matrix.domain` should be set to your Homeserver domain (`server_name` in synapse configuration)
|
- `matrix.domain` should be set to your Homeserver domain (`server_name` in synapse configuration)
|
||||||
- `key.path` will store the signing keys, which must be kept safe! If the file does not exist, keys will be generated for you.
|
- `key.path` will store the signing keys, which must be kept safe! If the file does not exist, keys will be generated for you.
|
||||||
- `storage.provider.sqlite.database` is the location of the SQLite Database file which will hold state (invites, etc.)
|
- `storage.provider.sqlite.database` is the location of the SQLite Database file which will hold state (invites, etc.)
|
||||||
@@ -88,7 +81,7 @@ Typical configuration would look like:
|
|||||||
<VirtualHost *:443>
|
<VirtualHost *:443>
|
||||||
ServerName matrix.example.org
|
ServerName matrix.example.org
|
||||||
|
|
||||||
...
|
# ...
|
||||||
|
|
||||||
ProxyPreserveHost on
|
ProxyPreserveHost on
|
||||||
ProxyPass /_matrix/identity http://localhost:8090/_matrix/identity
|
ProxyPass /_matrix/identity http://localhost:8090/_matrix/identity
|
||||||
@@ -112,7 +105,7 @@ server {
|
|||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
server_name matrix.example.org;
|
server_name matrix.example.org;
|
||||||
|
|
||||||
...
|
# ...
|
||||||
|
|
||||||
location /_matrix/identity {
|
location /_matrix/identity {
|
||||||
proxy_pass http://localhost:8090/_matrix/identity;
|
proxy_pass http://localhost:8090/_matrix/identity;
|
||||||
@@ -151,7 +144,8 @@ by the relevant hostname which you configured in your reverse proxy.
|
|||||||
**NOTE:** You might not see a suggestion for the e-mail address, which is normal. Still proceed with the invite.
|
**NOTE:** You might not see a suggestion for the e-mail address, which is normal. Still proceed with the invite.
|
||||||
|
|
||||||
If it worked, it means you are up and running and can enjoy mxisd in its basic mode! Congratulations!
|
If it worked, it means you are up and running and can enjoy mxisd in its basic mode! Congratulations!
|
||||||
If it did not work, [get in touch](../README.md#support) and we'll do our best to get you started.
|
If it did not work, read the basic [troubleshooting guide](troubleshooting.md), [get in touch](../README.md#support) and
|
||||||
|
we'll do our best to get you started.
|
||||||
|
|
||||||
## Next steps
|
## Next steps
|
||||||
Once your mxisd server is up and running, there are several ways you can enhance and integrate further with your
|
Once your mxisd server is up and running, there are several ways you can enhance and integrate further with your
|
||||||
|
@@ -7,7 +7,7 @@ Follow the [build instructions](../build.md) then:
|
|||||||
# Create a dedicated user
|
# Create a dedicated user
|
||||||
useradd -r mxisd
|
useradd -r mxisd
|
||||||
|
|
||||||
# Create config directory and set ownership
|
# Create config directory
|
||||||
mkdir -p /etc/mxisd
|
mkdir -p /etc/mxisd
|
||||||
|
|
||||||
# Create data directory and set ownership
|
# Create data directory and set ownership
|
||||||
@@ -26,7 +26,7 @@ ln -s /usr/lib/mxisd/mxisd /usr/bin/mxisd
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Prepare config file
|
### Prepare config file
|
||||||
Copy the sample config file `./mxisd.example.yaml` to `/etc/mxisd/mxisd.yaml`, edit to your needs
|
Copy the configuration file you've created following the build instructions to `/etc/mxisd/mxisd.yaml`
|
||||||
|
|
||||||
### Prepare Systemd
|
### Prepare Systemd
|
||||||
1. Copy `src/systemd/mxisd.service` to `/etc/systemd/system/` and edit if needed
|
1. Copy `src/systemd/mxisd.service` to `/etc/systemd/system/` and edit if needed
|
||||||
|
@@ -212,21 +212,29 @@ The command will use the default values for:
|
|||||||
#### Advanced
|
#### Advanced
|
||||||
Given the fictional `placeholder` feature:
|
Given the fictional `placeholder` feature:
|
||||||
```yaml
|
```yaml
|
||||||
exec.enabled: true
|
exec:
|
||||||
exec.token.mxid: '{matrixId}'
|
enabled: true
|
||||||
|
token:
|
||||||
exec.placeholder.token.localpart: '{username}'
|
mxid: '{matrixId}'
|
||||||
exec.placeholder.command: '/path/to/executable'
|
auth:
|
||||||
exec.placeholder.args:
|
token:
|
||||||
- '-u'
|
localpart: '{username}'
|
||||||
- '{username}'
|
command: '/path/to/executable'
|
||||||
exec.placeholder.env:
|
args:
|
||||||
MATRIX_DOMAIN: '{domain}'
|
- '-u'
|
||||||
MATRIX_USER_ID: '{matrixId}'
|
- '{username}'
|
||||||
|
env:
|
||||||
exec.placeholder.output.type: 'json'
|
MATRIX_DOMAIN: '{domain}'
|
||||||
exec.placeholder.exit.success: [0, 128]
|
MATRIX_USER_ID: '{matrixId}'
|
||||||
exec.placeholder.exit.failure: [1, 129]
|
output:
|
||||||
|
type: 'json'
|
||||||
|
exit:
|
||||||
|
success:
|
||||||
|
- 0
|
||||||
|
- 128
|
||||||
|
failure:
|
||||||
|
- 1
|
||||||
|
- 129
|
||||||
```
|
```
|
||||||
With:
|
With:
|
||||||
- The Identity store enabled for all features
|
- The Identity store enabled for all features
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
# Email notifications - SMTP connector
|
# Email notifications - SMTP connector
|
||||||
Enabled by default.
|
|
||||||
|
|
||||||
Connector ID: `smtp`
|
Connector ID: `smtp`
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
# SMS notifications - Twilio connector
|
# SMS notifications - Twilio connector
|
||||||
Enabled by default.
|
|
||||||
|
|
||||||
Connector ID: `twilio`
|
Connector ID: `twilio`
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
@@ -51,7 +51,7 @@ This template is used when someone is invited into a room using an email address
|
|||||||
| `%ROOM_NAME%` | The Name of the room in which the invite took place. If not available/set, empty |
|
| `%ROOM_NAME%` | The Name of the room in which the invite took place. If not available/set, empty |
|
||||||
| `%ROOM_NAME_OR_ID%` | The Name of the room in which the invite took place. If not available/set, its Matrix ID |
|
| `%ROOM_NAME_OR_ID%` | The Name of the room in which the invite took place. If not available/set, its Matrix ID |
|
||||||
|
|
||||||
### Local validation of 3PID Session
|
### Validation of 3PID Session
|
||||||
This template is used when to user which added their 3PID address to their profile/settings and the session policy
|
This template is used when to user which added their 3PID address to their profile/settings and the session policy
|
||||||
allows at least local sessions.
|
allows at least local sessions.
|
||||||
|
|
||||||
@@ -59,17 +59,5 @@ allows at least local sessions.
|
|||||||
| Placeholder | Purpose |
|
| Placeholder | Purpose |
|
||||||
|----------------------|--------------------------------------------------------------------------------------|
|
|----------------------|--------------------------------------------------------------------------------------|
|
||||||
| `%VALIDATION_LINK%` | URL, including token, to validate the 3PID session. |
|
| `%VALIDATION_LINK%` | URL, including token, to validate the 3PID session. |
|
||||||
| `%VALIDATION_TOKEN%` | The token needed to validate the local session, in case the user cannot use the link |
|
| `%VALIDATION_TOKEN%` | The token needed to validate the session, in case the user cannot use the link. |
|
||||||
|
| `%NEXT_URL%` | URL to redirect to after the sessions has been validated. |
|
||||||
### Remote validation of 3PID Session
|
|
||||||
This template is used when to user which added their 3PID address to their profile/settings and the session policy only
|
|
||||||
allows remote sessions.
|
|
||||||
|
|
||||||
**NOTE:** 3PID session always require local validation of a token, even if a remote session is enforced.
|
|
||||||
One cannot bind a Matrix ID to the session until both local and remote sessions have been validated.
|
|
||||||
|
|
||||||
#### Placeholders
|
|
||||||
| Placeholder | Purpose |
|
|
||||||
|----------------------|--------------------------------------------------------|
|
|
||||||
| `%VALIDATION_TOKEN%` | The token needed to validate the session |
|
|
||||||
| `%NEXT_URL%` | URL to continue with remote validation of the session. |
|
|
||||||
|
53
docs/troubleshooting.md
Normal file
53
docs/troubleshooting.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
- [Purpose](#purpose)
|
||||||
|
- [Logs](#logs)
|
||||||
|
- [Locations](#locations)
|
||||||
|
- [Reading Them](#reading-them)
|
||||||
|
- [Common issues](#common-issues)
|
||||||
|
- [Submit an issue](#submit-an-issue)
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
This document describes basic troubleshooting steps for mxisd.
|
||||||
|
|
||||||
|
## Logs
|
||||||
|
### Locations
|
||||||
|
mxisd logs to `STDOUT` (Standard Output) and `STDERR` (Standard Error) only, which gets redirected
|
||||||
|
to log file(s) depending on your system.
|
||||||
|
|
||||||
|
If you use the [Debian package](install/debian.md), this goes to `syslog`.
|
||||||
|
If you use the [Docker image](install/docker.md), this goes to the container logs.
|
||||||
|
|
||||||
|
For any other platform, please refer to your package maintainer.
|
||||||
|
|
||||||
|
### Reading them
|
||||||
|
Before reporting an issue, it is important to produce clean and complete logs so they can be understood.
|
||||||
|
|
||||||
|
It is usually useless to try to troubleshoot an issue based on a single log line. Any action or API request
|
||||||
|
in mxisd would trigger more than one log lines, and those would be considered necessary context to
|
||||||
|
understand what happened.
|
||||||
|
|
||||||
|
You may also find things called *stacktraces*. Those are important to pin-point bugs and the likes and should
|
||||||
|
always be included in any report. They also tend to be very specific about the issue at hand.
|
||||||
|
|
||||||
|
Example of a stacktrace:
|
||||||
|
```
|
||||||
|
Exception in thread "main" java.lang.NullPointerException
|
||||||
|
at com.example.myproject.Book.getTitle(Book.java:16)
|
||||||
|
at com.example.myproject.Author.getBookTitles(Author.java:25)
|
||||||
|
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common issues
|
||||||
|
#### Internal Server Error
|
||||||
|
`Contact your administrator with reference Transaction #123456789`
|
||||||
|
|
||||||
|
This is a generic message produced in case of an unknown error. The transaction reference allows to easily find
|
||||||
|
the location in the logs to look for an error.
|
||||||
|
|
||||||
|
**IMPORTANT:** That line alone does not tell you anything about the error. You'll need the log lines before and after,
|
||||||
|
usually including a stacktrace, to know what happened. Please take the time to read the surround output to get
|
||||||
|
context about the issue at hand.
|
||||||
|
|
||||||
|
## Submit an issue
|
||||||
|
In case the logs do not allow you to understand the issue at hand, please submit clean and complete logs
|
||||||
|
as explained [here](#reading-them) in a new issue on the repository, or [get in touch](../README.md#contact).
|
@@ -1,6 +1,11 @@
|
|||||||
# Sample configuration file explaining the minimum required keys to be set to run mxisd
|
# Sample configuration file explaining the minimum required keys to be set to run mxisd
|
||||||
#
|
#
|
||||||
# For a complete list of options, see https://github.com/kamax-matrix/mxisd/docs/README.md
|
# For a complete list of options, see https://github.com/kamax-matrix/mxisd/docs/README.md
|
||||||
|
#
|
||||||
|
# Please follow the Getting Started guide if this is your first time using/configuring mxisd
|
||||||
|
#
|
||||||
|
# -- https://github.com/kamax-matrix/mxisd/blob/master/docs/getting-started.md#getting-started
|
||||||
|
#
|
||||||
|
|
||||||
#######################
|
#######################
|
||||||
# Matrix config items #
|
# Matrix config items #
|
||||||
@@ -16,26 +21,27 @@ matrix:
|
|||||||
################
|
################
|
||||||
# Signing keys #
|
# Signing keys #
|
||||||
################
|
################
|
||||||
# Absolute path for the Identity Server signing key.
|
# Absolute path for the Identity Server signing keys database.
|
||||||
# This is **NOT** your homeserver key.
|
# /!\ THIS MUST **NOT** BE YOUR HOMESERVER KEYS FILE /!\
|
||||||
# The signing key is auto-generated during execution time if not present.
|
# If this path does not exist, it will be auto-generated.
|
||||||
#
|
#
|
||||||
# During testing, /var/tmp/mxisd.key is a possible value
|
# During testing, /var/tmp/mxisd/keys is a possible value
|
||||||
# For production, recommended location shall be one of the following:
|
# For production, recommended location shall be one of the following:
|
||||||
# - /var/opt/mxisd/sign.key
|
# - /var/lib/mxisd/keys
|
||||||
# - /var/local/mxisd/sign.key
|
# - /var/opt/mxisd/keys
|
||||||
# - /var/lib/mxisd/sign.key
|
# - /var/local/mxisd/keys
|
||||||
#
|
#
|
||||||
key:
|
key:
|
||||||
path: ''
|
path: ''
|
||||||
|
|
||||||
|
|
||||||
# Path to the SQLite DB file for mxisd internal storage
|
# Path to the SQLite DB file for mxisd internal storage
|
||||||
|
# /!\ THIS MUST **NOT** BE YOUR HOMESERVER DATABASE /!\
|
||||||
#
|
#
|
||||||
# Examples:
|
# Examples:
|
||||||
# - /var/opt/mxisd/mxisd.db
|
# - /var/opt/mxisd/store.db
|
||||||
# - /var/local/mxisd/mxisd.db
|
# - /var/local/mxisd/store.db
|
||||||
# - /var/lib/mxisd/mxisd.db
|
# - /var/lib/mxisd/store.db
|
||||||
#
|
#
|
||||||
storage:
|
storage:
|
||||||
provider:
|
provider:
|
||||||
@@ -43,48 +49,31 @@ storage:
|
|||||||
database: '/path/to/mxisd.db'
|
database: '/path/to/mxisd.db'
|
||||||
|
|
||||||
|
|
||||||
####################
|
###################
|
||||||
# Fallback servers #
|
# Identity Stores #
|
||||||
####################
|
###################
|
||||||
|
# If you are using synapse standalone and do not have an Identity store,
|
||||||
|
# see https://github.com/kamax-matrix/mxisd/blob/master/docs/stores/synapse.md#synapse-identity-store
|
||||||
#
|
#
|
||||||
# Root/Central servers to be used as final fallback when performing lookups.
|
|
||||||
# By default, for privacy reasons, matrix.org servers are not enabled.
|
|
||||||
# See the following issue: https://github.com/kamax-matrix/mxisd/issues/76
|
|
||||||
#
|
|
||||||
# If you would like to use them and trade away your privacy for convenience, uncomment the following option:
|
|
||||||
#
|
|
||||||
#forward:
|
|
||||||
# servers: ['matrix-org']
|
|
||||||
|
|
||||||
|
|
||||||
################
|
|
||||||
# LDAP Backend #
|
|
||||||
################
|
|
||||||
# If you would like to integrate with your AD/Samba/LDAP server,
|
# If you would like to integrate with your AD/Samba/LDAP server,
|
||||||
# see https://github.com/kamax-matrix/mxisd/blob/master/docs/stores/ldap.md
|
# see https://github.com/kamax-matrix/mxisd/blob/master/docs/stores/ldap.md
|
||||||
|
#
|
||||||
|
# For any other Identity store, or to simply discover them,
|
||||||
###############
|
# see https://github.com/kamax-matrix/mxisd/blob/master/docs/stores/README.md
|
||||||
# SQL Backend #
|
|
||||||
###############
|
|
||||||
# If you would like to integrate with a MySQL/MariaDB/PostgreQL/SQLite DB,
|
|
||||||
# see https://github.com/kamax-matrix/mxisd/blob/master/docs/stores/sql.md
|
|
||||||
|
|
||||||
|
|
||||||
################
|
|
||||||
# REST Backend #
|
|
||||||
################
|
|
||||||
# If you would like to integrate with an existing web service/webapp,
|
|
||||||
# see https://github.com/kamax-matrix/mxisd/blob/master/docs/stores/rest.md
|
|
||||||
|
|
||||||
|
|
||||||
#################################################
|
#################################################
|
||||||
# Notifications for invites/addition to profile #
|
# Notifications for invites/addition to profile #
|
||||||
#################################################
|
#################################################
|
||||||
# If you would like to change the content,
|
# This is mandatory to deal with anything e-mail related.
|
||||||
|
#
|
||||||
|
# For an introduction to sessions, invites and 3PIDs in general,
|
||||||
|
# see https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/session/session.md#3pid-sessions
|
||||||
|
#
|
||||||
|
# If you would like to change the content of the notifications,
|
||||||
# see https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/notification/template-generator.md
|
# see https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/notification/template-generator.md
|
||||||
#
|
#
|
||||||
#### E-mail invite sender
|
#### E-mail connector
|
||||||
threepid:
|
threepid:
|
||||||
medium:
|
medium:
|
||||||
email:
|
email:
|
||||||
@@ -100,12 +89,13 @@ threepid:
|
|||||||
# SMTP port
|
# SMTP port
|
||||||
port: 587
|
port: 587
|
||||||
|
|
||||||
# TLS mode for the connection.
|
# STARTLS mode for the connection.
|
||||||
|
# SSL/TLS is currently not supported. See https://github.com/kamax-matrix/mxisd/issues/125
|
||||||
#
|
#
|
||||||
# Possible values:
|
# Possible values:
|
||||||
# 0 Disable TLS entirely
|
# 0 Disable any kind of TLS entirely
|
||||||
# 1 Enable TLS if supported by server (default)
|
# 1 Enable STARTLS if supported by server (default)
|
||||||
# 2 Force TLS and fail if not available
|
# 2 Force STARTLS and fail if not available
|
||||||
#
|
#
|
||||||
tls: 1
|
tls: 1
|
||||||
|
|
||||||
|
@@ -1,27 +1,26 @@
|
|||||||
/*
|
/*
|
||||||
* The MIT License
|
* The MIT License
|
||||||
*
|
*
|
||||||
* Copyright (c) 2013 Edin Dazdarevic (edin.dazdarevic@gmail.com)
|
* Copyright (c) 2013 Edin Dazdarevic (edin.dazdarevic@gmail.com)
|
||||||
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
* in the Software without restriction, including without limitation the rights
|
* in the Software without restriction, including without limitation the rights
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
* furnished to do so, subject to the following conditions:
|
* furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
* The above copyright notice and this permission notice shall be included in
|
||||||
* all copies or substantial portions of the Software.
|
* all copies or substantial portions of the Software.
|
||||||
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*
|
*/
|
||||||
* */
|
|
||||||
|
|
||||||
package edazdarevic.commons.net;
|
package edazdarevic.commons.net;
|
||||||
|
|
||||||
|
@@ -82,7 +82,7 @@ public class HttpMxisd {
|
|||||||
// Identity endpoints
|
// Identity endpoints
|
||||||
.get(HelloHandler.Path, helloHandler)
|
.get(HelloHandler.Path, helloHandler)
|
||||||
.get(HelloHandler.Path + "/", helloHandler) // Be lax with possibly trailing slash
|
.get(HelloHandler.Path + "/", helloHandler) // Be lax with possibly trailing slash
|
||||||
.get(SingleLookupHandler.Path, SaneHandler.around(new SingleLookupHandler(m.getIdentity(), m.getSign())))
|
.get(SingleLookupHandler.Path, SaneHandler.around(new SingleLookupHandler(m.getConfig(), m.getIdentity(), m.getSign())))
|
||||||
.post(BulkLookupHandler.Path, SaneHandler.around(new BulkLookupHandler(m.getIdentity())))
|
.post(BulkLookupHandler.Path, SaneHandler.around(new BulkLookupHandler(m.getIdentity())))
|
||||||
.post(StoreInviteHandler.Path, storeInvHandler)
|
.post(StoreInviteHandler.Path, storeInvHandler)
|
||||||
.post(SessionStartHandler.Path, SaneHandler.around(new SessionStartHandler(m.getSession())))
|
.post(SessionStartHandler.Path, SaneHandler.around(new SessionStartHandler(m.getSession())))
|
||||||
|
@@ -20,8 +20,6 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd;
|
package io.kamax.mxisd;
|
||||||
|
|
||||||
import io.kamax.matrix.crypto.KeyManager;
|
|
||||||
import io.kamax.matrix.crypto.SignatureManager;
|
|
||||||
import io.kamax.mxisd.as.AppSvcManager;
|
import io.kamax.mxisd.as.AppSvcManager;
|
||||||
import io.kamax.mxisd.auth.AuthManager;
|
import io.kamax.mxisd.auth.AuthManager;
|
||||||
import io.kamax.mxisd.auth.AuthProviders;
|
import io.kamax.mxisd.auth.AuthProviders;
|
||||||
@@ -48,6 +46,9 @@ import io.kamax.mxisd.profile.ProfileManager;
|
|||||||
import io.kamax.mxisd.profile.ProfileProviders;
|
import io.kamax.mxisd.profile.ProfileProviders;
|
||||||
import io.kamax.mxisd.session.SessionManager;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
import io.kamax.mxisd.storage.IStorage;
|
import io.kamax.mxisd.storage.IStorage;
|
||||||
|
import io.kamax.mxisd.storage.crypto.Ed25519KeyManager;
|
||||||
|
import io.kamax.mxisd.storage.crypto.KeyManager;
|
||||||
|
import io.kamax.mxisd.storage.crypto.SignatureManager;
|
||||||
import io.kamax.mxisd.storage.ormlite.OrmLiteSqlStorage;
|
import io.kamax.mxisd.storage.ormlite.OrmLiteSqlStorage;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.impl.client.HttpClients;
|
import org.apache.http.impl.client.HttpClients;
|
||||||
@@ -63,7 +64,7 @@ public class Mxisd {
|
|||||||
|
|
||||||
private IStorage store;
|
private IStorage store;
|
||||||
|
|
||||||
private KeyManager keyMgr;
|
private Ed25519KeyManager keyMgr;
|
||||||
private SignatureManager signMgr;
|
private SignatureManager signMgr;
|
||||||
|
|
||||||
// Features
|
// Features
|
||||||
@@ -92,7 +93,7 @@ public class Mxisd {
|
|||||||
|
|
||||||
store = new OrmLiteSqlStorage(cfg);
|
store = new OrmLiteSqlStorage(cfg);
|
||||||
keyMgr = CryptoFactory.getKeyManager(cfg.getKey());
|
keyMgr = CryptoFactory.getKeyManager(cfg.getKey());
|
||||||
signMgr = CryptoFactory.getSignatureManager(keyMgr, cfg.getServer());
|
signMgr = CryptoFactory.getSignatureManager(keyMgr);
|
||||||
ClientDnsOverwrite clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite());
|
ClientDnsOverwrite clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite());
|
||||||
FederationDnsOverwrite fedDns = new FederationDnsOverwrite(cfg.getDns().getOverwrite());
|
FederationDnsOverwrite fedDns = new FederationDnsOverwrite(cfg.getDns().getOverwrite());
|
||||||
Synapse synapse = new Synapse(cfg.getSynapseSql());
|
Synapse synapse = new Synapse(cfg.getSynapseSql());
|
||||||
@@ -105,7 +106,7 @@ public class Mxisd {
|
|||||||
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, idStrategy, httpClient);
|
sessMgr = new SessionManager(cfg.getSession(), cfg.getMatrix(), store, notifMgr, idStrategy, httpClient);
|
||||||
invMgr = new InvitationManager(cfg.getInvite(), store, idStrategy, signMgr, fedDns, notifMgr);
|
invMgr = new InvitationManager(cfg, store, idStrategy, signMgr, fedDns, notifMgr);
|
||||||
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());
|
||||||
asHander = new AppSvcManager(cfg, store, pMgr, notifMgr, synapse);
|
asHander = new AppSvcManager(cfg, store, pMgr, notifMgr, synapse);
|
||||||
|
@@ -22,40 +22,40 @@ package io.kamax.mxisd;
|
|||||||
|
|
||||||
import io.kamax.mxisd.config.MxisdConfig;
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
import io.kamax.mxisd.config.YamlConfigLoader;
|
import io.kamax.mxisd.config.YamlConfigLoader;
|
||||||
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
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;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class MxisdStandaloneExec {
|
public class MxisdStandaloneExec {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger("");
|
private static final Logger log = LoggerFactory.getLogger("App");
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
|
||||||
log.info("------------- mxisd starting -------------");
|
|
||||||
MxisdConfig cfg = null;
|
|
||||||
|
|
||||||
Iterator<String> argsIt = Arrays.asList(args).iterator();
|
|
||||||
while (argsIt.hasNext()) {
|
|
||||||
String arg = argsIt.next();
|
|
||||||
if (StringUtils.equals("-c", arg)) {
|
|
||||||
String cfgFile = argsIt.next();
|
|
||||||
cfg = YamlConfigLoader.loadFromFile(cfgFile);
|
|
||||||
} else {
|
|
||||||
log.info("Invalid argument: {}", arg);
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Objects.isNull(cfg)) {
|
|
||||||
cfg = YamlConfigLoader.tryLoadFromFile("mxisd.yaml").orElseGet(MxisdConfig::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
try {
|
try {
|
||||||
|
log.info("------------- mxisd starting -------------");
|
||||||
|
MxisdConfig cfg = null;
|
||||||
|
|
||||||
|
Iterator<String> argsIt = Arrays.asList(args).iterator();
|
||||||
|
while (argsIt.hasNext()) {
|
||||||
|
String arg = argsIt.next();
|
||||||
|
if (StringUtils.equals("-c", arg)) {
|
||||||
|
String cfgFile = argsIt.next();
|
||||||
|
cfg = YamlConfigLoader.loadFromFile(cfgFile);
|
||||||
|
} else {
|
||||||
|
log.info("Invalid argument: {}", arg);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Objects.isNull(cfg)) {
|
||||||
|
cfg = YamlConfigLoader.tryLoadFromFile("mxisd.yaml").orElseGet(MxisdConfig::new);
|
||||||
|
}
|
||||||
|
|
||||||
HttpMxisd mxisd = new HttpMxisd(cfg);
|
HttpMxisd mxisd = new HttpMxisd(cfg);
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
mxisd.stop();
|
mxisd.stop();
|
||||||
@@ -64,6 +64,10 @@ public class MxisdStandaloneExec {
|
|||||||
mxisd.start();
|
mxisd.start();
|
||||||
|
|
||||||
log.info("------------- mxisd started -------------");
|
log.info("------------- mxisd started -------------");
|
||||||
|
} catch (ConfigurationException e) {
|
||||||
|
log.error(e.getDetailedMessage());
|
||||||
|
log.error(e.getMessage());
|
||||||
|
System.exit(2);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
|
@@ -44,6 +44,7 @@ public class ExecAuthStore extends ExecStore implements AuthenticatorProvider {
|
|||||||
private ExecConfig.Auth cfg;
|
private ExecConfig.Auth cfg;
|
||||||
|
|
||||||
public ExecAuthStore(ExecConfig cfg) {
|
public ExecAuthStore(ExecConfig cfg) {
|
||||||
|
super(cfg);
|
||||||
this.cfg = Objects.requireNonNull(cfg.getAuth());
|
this.cfg = Objects.requireNonNull(cfg.getAuth());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -36,11 +36,12 @@ public class ExecDirectoryStore extends ExecStore implements DirectoryProvider {
|
|||||||
private MatrixConfig mxCfg;
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
public ExecDirectoryStore(MxisdConfig cfg) {
|
public ExecDirectoryStore(MxisdConfig cfg) {
|
||||||
this(cfg.getExec().getDirectory(), cfg.getMatrix());
|
this(cfg.getExec(), cfg.getMatrix());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExecDirectoryStore(ExecConfig.Directory cfg, MatrixConfig mxCfg) {
|
public ExecDirectoryStore(ExecConfig cfg, MatrixConfig mxCfg) {
|
||||||
this.cfg = cfg;
|
super(cfg);
|
||||||
|
this.cfg = cfg.getDirectory();
|
||||||
this.mxCfg = mxCfg;
|
this.mxCfg = mxCfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -55,11 +55,8 @@ public class ExecIdentityStore extends ExecStore implements IThreePidProvider {
|
|||||||
private final MatrixConfig mxCfg;
|
private final MatrixConfig mxCfg;
|
||||||
|
|
||||||
public ExecIdentityStore(ExecConfig cfg, MatrixConfig mxCfg) {
|
public ExecIdentityStore(ExecConfig cfg, MatrixConfig mxCfg) {
|
||||||
this(cfg.getIdentity(), mxCfg);
|
super(cfg);
|
||||||
}
|
this.cfg = cfg.getIdentity();
|
||||||
|
|
||||||
public ExecIdentityStore(ExecConfig.Identity cfg, MatrixConfig mxCfg) {
|
|
||||||
this.cfg = cfg;
|
|
||||||
this.mxCfg = mxCfg;
|
this.mxCfg = mxCfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -38,11 +38,8 @@ public class ExecProfileStore extends ExecStore implements ProfileProvider {
|
|||||||
private ExecConfig.Profile cfg;
|
private ExecConfig.Profile cfg;
|
||||||
|
|
||||||
public ExecProfileStore(ExecConfig cfg) {
|
public ExecProfileStore(ExecConfig cfg) {
|
||||||
this(cfg.getProfile());
|
super(cfg);
|
||||||
}
|
this.cfg = cfg.getProfile();
|
||||||
|
|
||||||
public ExecProfileStore(ExecConfig.Profile cfg) {
|
|
||||||
this.cfg = cfg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<JsonProfileResult> getFull(_MatrixID userId, ExecConfig.Process cfg) {
|
private Optional<JsonProfileResult> getFull(_MatrixID userId, ExecConfig.Process cfg) {
|
||||||
|
@@ -43,14 +43,19 @@ public class ExecStore {
|
|||||||
public static final String JsonType = "json";
|
public static final String JsonType = "json";
|
||||||
public static final String PlainType = "plain";
|
public static final String PlainType = "plain";
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ExecStore.class);
|
||||||
|
|
||||||
protected static String toJson(Object o) {
|
protected static String toJson(Object o) {
|
||||||
return GsonUtil.get().toJson(o);
|
return GsonUtil.get().toJson(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(ExecStore.class);
|
private final ExecConfig cfg;
|
||||||
|
|
||||||
private Supplier<ProcessExecutor> executorSupplier = () -> new ProcessExecutor().readOutput(true);
|
private Supplier<ProcessExecutor> executorSupplier = () -> new ProcessExecutor().readOutput(true);
|
||||||
|
|
||||||
|
public ExecStore(ExecConfig cfg) {
|
||||||
|
this.cfg = cfg;
|
||||||
|
}
|
||||||
|
|
||||||
public void setExecutorSupplier(Supplier<ProcessExecutor> supplier) {
|
public void setExecutorSupplier(Supplier<ProcessExecutor> supplier) {
|
||||||
executorSupplier = supplier;
|
executorSupplier = supplier;
|
||||||
}
|
}
|
||||||
@@ -64,7 +69,7 @@ public class ExecStore {
|
|||||||
private Function<String, String> inputUnknownTypeMapper;
|
private Function<String, String> inputUnknownTypeMapper;
|
||||||
private Map<String, Supplier<String>> inputTypeSuppliers;
|
private Map<String, Supplier<String>> inputTypeSuppliers;
|
||||||
|
|
||||||
private Map<String, Function<ExecConfig.TokenOverride, String>> inputTypeTemplates;
|
private Map<String, Function<ExecConfig.Token, String>> inputTypeTemplates;
|
||||||
private Supplier<String> inputTypeNoTemplateHandler;
|
private Supplier<String> inputTypeNoTemplateHandler;
|
||||||
private Map<String, Supplier<String>> tokenMappers;
|
private Map<String, Supplier<String>> tokenMappers;
|
||||||
private Function<String, String> tokenHandler;
|
private Function<String, String> tokenHandler;
|
||||||
@@ -156,11 +161,11 @@ public class ExecStore {
|
|||||||
inputTypeSuppliers.put(type, handler);
|
inputTypeSuppliers.put(type, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addInputTemplate(String type, Function<ExecConfig.TokenOverride, String> template) {
|
protected void addInputTemplate(String type, Function<ExecConfig.Token, String> template) {
|
||||||
inputTypeTemplates.put(type, template);
|
inputTypeTemplates.put(type, template);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addJsonInputTemplate(Function<ExecConfig.TokenOverride, Object> template) {
|
public void addJsonInputTemplate(Function<ExecConfig.Token, Object> template) {
|
||||||
inputTypeTemplates.put(JsonType, token -> GsonUtil.get().toJson(template.apply(token)));
|
inputTypeTemplates.put(JsonType, token -> GsonUtil.get().toJson(template.apply(token)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,4 +37,5 @@ public class LookupSingleRequestJson {
|
|||||||
public String getAddress() {
|
public String getAddress() {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -32,7 +32,7 @@ import io.kamax.mxisd.profile.JsonProfileRequest;
|
|||||||
import io.kamax.mxisd.profile.JsonProfileResult;
|
import io.kamax.mxisd.profile.JsonProfileResult;
|
||||||
import io.kamax.mxisd.profile.ProfileProvider;
|
import io.kamax.mxisd.profile.ProfileProvider;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.client.utils.URIBuilder;
|
import org.apache.http.client.utils.URIBuilder;
|
||||||
@@ -49,7 +49,7 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
public class RestProfileProvider extends RestProvider implements ProfileProvider {
|
public class RestProfileProvider extends RestProvider implements ProfileProvider {
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(RestProfileProvider.class);
|
private static final Logger log = LoggerFactory.getLogger(RestProfileProvider.class);
|
||||||
|
|
||||||
public RestProfileProvider(RestBackendConfig cfg) {
|
public RestProfileProvider(RestBackendConfig cfg) {
|
||||||
super(cfg);
|
super(cfg);
|
||||||
@@ -60,64 +60,71 @@ public class RestProfileProvider extends RestProvider implements ProfileProvider
|
|||||||
Function<RestBackendConfig.ProfileEndpoints, Optional<String>> endpoint,
|
Function<RestBackendConfig.ProfileEndpoints, Optional<String>> endpoint,
|
||||||
Function<JsonProfileResult, Optional<T>> value
|
Function<JsonProfileResult, Optional<T>> value
|
||||||
) {
|
) {
|
||||||
return cfg.getEndpoints().getProfile()
|
Optional<String> url = endpoint.apply(cfg.getEndpoints().getProfile());
|
||||||
// We get the endpoint
|
if (!url.isPresent()) {
|
||||||
.flatMap(endpoint)
|
return Optional.empty();
|
||||||
// We only continue if there is a value
|
}
|
||||||
.filter(StringUtils::isNotBlank)
|
|
||||||
// We use the endpoint
|
|
||||||
.flatMap(url -> {
|
|
||||||
try {
|
|
||||||
URIBuilder builder = new URIBuilder(url);
|
|
||||||
HttpPost req = new HttpPost(builder.build());
|
|
||||||
req.setEntity(new StringEntity(GsonUtil.get().toJson(new JsonProfileRequest(userId)), ContentType.APPLICATION_JSON));
|
|
||||||
try (CloseableHttpResponse res = client.execute(req)) {
|
|
||||||
int sc = res.getStatusLine().getStatusCode();
|
|
||||||
if (sc == 404) {
|
|
||||||
log.info("Got 404 - No result found");
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sc != 200) {
|
try {
|
||||||
throw new InternalServerError("Unexpected backed status code: " + sc);
|
URIBuilder builder = new URIBuilder(url.get());
|
||||||
}
|
HttpPost req = new HttpPost(builder.build());
|
||||||
|
req.setEntity(new StringEntity(GsonUtil.get().toJson(new JsonProfileRequest(userId)), ContentType.APPLICATION_JSON));
|
||||||
|
try (CloseableHttpResponse res = client.execute(req)) {
|
||||||
|
int sc = res.getStatusLine().getStatusCode();
|
||||||
|
if (sc == 404) {
|
||||||
|
log.info("Got 404 - No result found");
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
String body = IOUtils.toString(res.getEntity().getContent(), StandardCharsets.UTF_8);
|
if (sc != 200) {
|
||||||
if (StringUtils.isBlank(body)) {
|
throw new InternalServerError("Unexpected backed status code: " + sc);
|
||||||
log.warn("Backend response body is empty/blank, expected JSON object with profile key");
|
}
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<JsonObject> pJson = GsonUtil.findObj(GsonUtil.parseObj(body), "profile");
|
String body = IOUtils.toString(res.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
if (!pJson.isPresent()) {
|
if (StringUtils.isBlank(body)) {
|
||||||
log.warn("Backend response body is invalid, expected JSON object with profile key");
|
log.warn("Backend response body is empty/blank, expected JSON object with profile key");
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonProfileResult profile = gson.fromJson(pJson.get(), JsonProfileResult.class);
|
Optional<JsonObject> pJson = GsonUtil.findObj(GsonUtil.parseObj(body), "profile");
|
||||||
return value.apply(profile);
|
if (!pJson.isPresent()) {
|
||||||
}
|
log.warn("Backend response body is invalid, expected JSON object with profile key");
|
||||||
} catch (JsonSyntaxException | InvalidJsonException e) {
|
return Optional.empty();
|
||||||
log.error("Unable to parse backend response as JSON", e);
|
}
|
||||||
throw new InternalServerError(e);
|
|
||||||
} catch (URISyntaxException e) {
|
JsonProfileResult profile = gson.fromJson(pJson.get(), JsonProfileResult.class);
|
||||||
log.error("Unable to build a valid request URL", e);
|
return value.apply(profile);
|
||||||
throw new InternalServerError(e);
|
}
|
||||||
} catch (IOException e) {
|
} catch (JsonSyntaxException | InvalidJsonException e) {
|
||||||
log.error("I/O Error during backend request", e);
|
log.error("Unable to parse backend response as JSON", e);
|
||||||
throw new InternalServerError();
|
throw new InternalServerError(e);
|
||||||
}
|
} catch (URISyntaxException e) {
|
||||||
});
|
log.error("Unable to build a valid request URL", e);
|
||||||
|
throw new InternalServerError(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("I/O Error during backend request", e);
|
||||||
|
throw new InternalServerError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<String> getDisplayName(_MatrixID userId) {
|
public Optional<String> getDisplayName(_MatrixID userId) {
|
||||||
return doRequest(userId, p -> Optional.ofNullable(p.getDisplayName()), profile -> Optional.ofNullable(profile.getDisplayName()));
|
return doRequest(userId, p -> {
|
||||||
|
if (StringUtils.isBlank(p.getDisplayName())) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(p.getDisplayName());
|
||||||
|
}, profile -> Optional.ofNullable(profile.getDisplayName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<_ThreePid> getThreepids(_MatrixID userId) {
|
public List<_ThreePid> getThreepids(_MatrixID userId) {
|
||||||
return doRequest(userId, p -> Optional.ofNullable(p.getThreepids()), profile -> {
|
return doRequest(userId, p -> {
|
||||||
|
if (StringUtils.isBlank(p.getThreepids())) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(p.getThreepids());
|
||||||
|
}, profile -> {
|
||||||
List<_ThreePid> t = new ArrayList<>();
|
List<_ThreePid> t = new ArrayList<>();
|
||||||
if (Objects.nonNull(profile.getThreepids())) {
|
if (Objects.nonNull(profile.getThreepids())) {
|
||||||
t.addAll(profile.getThreepids());
|
t.addAll(profile.getThreepids());
|
||||||
@@ -128,7 +135,12 @@ public class RestProfileProvider extends RestProvider implements ProfileProvider
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getRoles(_MatrixID userId) {
|
public List<String> getRoles(_MatrixID userId) {
|
||||||
return doRequest(userId, p -> Optional.ofNullable(p.getRoles()), profile -> {
|
return doRequest(userId, p -> {
|
||||||
|
if (StringUtils.isBlank(p.getRoles())) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.ofNullable(p.getRoles());
|
||||||
|
}, profile -> {
|
||||||
List<String> t = new ArrayList<>();
|
List<String> t = new ArrayList<>();
|
||||||
if (Objects.nonNull(profile.getRoles())) {
|
if (Objects.nonNull(profile.getRoles())) {
|
||||||
t.addAll(profile.getRoles());
|
t.addAll(profile.getRoles());
|
||||||
|
@@ -36,9 +36,8 @@ public class DirectoryConfig {
|
|||||||
return homeserver;
|
return homeserver;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Exclude setHomeserver(boolean homeserver) {
|
public void setHomeserver(boolean homeserver) {
|
||||||
this.homeserver = homeserver;
|
this.homeserver = homeserver;
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getThreepid() {
|
public boolean getThreepid() {
|
||||||
|
@@ -20,13 +20,11 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.config;
|
package io.kamax.mxisd.config;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class ExecConfig {
|
public class ExecConfig {
|
||||||
|
|
||||||
public class IO {
|
public static class IO {
|
||||||
|
|
||||||
private String type;
|
private String type;
|
||||||
private String template;
|
private String template;
|
||||||
@@ -49,7 +47,7 @@ public class ExecConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Exit {
|
public static class Exit {
|
||||||
|
|
||||||
private List<Integer> success = Collections.singletonList(0);
|
private List<Integer> success = Collections.singletonList(0);
|
||||||
private List<Integer> failure = Collections.singletonList(1);
|
private List<Integer> failure = Collections.singletonList(1);
|
||||||
@@ -72,84 +70,7 @@ public class ExecConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TokenOverride {
|
public static class Token {
|
||||||
|
|
||||||
private String localpart;
|
|
||||||
private String domain;
|
|
||||||
private String mxid;
|
|
||||||
private String password;
|
|
||||||
private String medium;
|
|
||||||
private String address;
|
|
||||||
private String type;
|
|
||||||
private String query;
|
|
||||||
|
|
||||||
public String getLocalpart() {
|
|
||||||
return StringUtils.defaultIfEmpty(localpart, getToken().getLocalpart());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLocalpart(String localpart) {
|
|
||||||
this.localpart = localpart;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDomain() {
|
|
||||||
return StringUtils.defaultIfEmpty(domain, getToken().getDomain());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDomain(String domain) {
|
|
||||||
this.domain = domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMxid() {
|
|
||||||
return StringUtils.defaultIfEmpty(mxid, getToken().getMxid());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMxid(String mxid) {
|
|
||||||
this.mxid = mxid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPassword() {
|
|
||||||
return StringUtils.defaultIfEmpty(password, getToken().getPassword());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPassword(String password) {
|
|
||||||
this.password = password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMedium() {
|
|
||||||
return StringUtils.defaultIfEmpty(medium, getToken().getMedium());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMedium(String medium) {
|
|
||||||
this.medium = medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return StringUtils.defaultIfEmpty(address, getToken().getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAddress(String address) {
|
|
||||||
this.address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getType() {
|
|
||||||
return StringUtils.defaultIfEmpty(type, getToken().getType());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setType(String type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getQuery() {
|
|
||||||
return StringUtils.defaultIfEmpty(query, getToken().getQuery());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setQuery(String query) {
|
|
||||||
this.query = query;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Token {
|
|
||||||
|
|
||||||
private String localpart = "{localpart}";
|
private String localpart = "{localpart}";
|
||||||
private String domain = "{domain}";
|
private String domain = "{domain}";
|
||||||
@@ -226,9 +147,9 @@ public class ExecConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Process {
|
public static class Process {
|
||||||
|
|
||||||
private TokenOverride token = new TokenOverride();
|
private Token token = new Token();
|
||||||
private String command;
|
private String command;
|
||||||
|
|
||||||
private List<String> args = new ArrayList<>();
|
private List<String> args = new ArrayList<>();
|
||||||
@@ -238,11 +159,11 @@ public class ExecConfig {
|
|||||||
private Exit exit = new Exit();
|
private Exit exit = new Exit();
|
||||||
private IO output = new IO();
|
private IO output = new IO();
|
||||||
|
|
||||||
public TokenOverride getToken() {
|
public Token getToken() {
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setToken(TokenOverride token) {
|
public void setToken(Token token) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +221,7 @@ public class ExecConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Auth extends Process {
|
public static class Auth extends Process {
|
||||||
|
|
||||||
private Boolean enabled;
|
private Boolean enabled;
|
||||||
|
|
||||||
@@ -314,9 +235,9 @@ public class ExecConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Directory {
|
public static class Directory {
|
||||||
|
|
||||||
public class Search {
|
public static class Search {
|
||||||
|
|
||||||
private Process byName = new Process();
|
private Process byName = new Process();
|
||||||
private Process byThreepid = new Process();
|
private Process byThreepid = new Process();
|
||||||
@@ -360,7 +281,7 @@ public class ExecConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Lookup {
|
public static class Lookup {
|
||||||
|
|
||||||
private Process single = new Process();
|
private Process single = new Process();
|
||||||
private Process bulk = new Process();
|
private Process bulk = new Process();
|
||||||
@@ -383,7 +304,7 @@ public class ExecConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Identity {
|
public static class Identity {
|
||||||
|
|
||||||
private Boolean enabled;
|
private Boolean enabled;
|
||||||
private int priority;
|
private int priority;
|
||||||
@@ -415,7 +336,7 @@ public class ExecConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Profile {
|
public static class Profile {
|
||||||
|
|
||||||
private Boolean enabled;
|
private Boolean enabled;
|
||||||
private Process displayName = new Process();
|
private Process displayName = new Process();
|
||||||
|
@@ -21,12 +21,16 @@
|
|||||||
package io.kamax.mxisd.config;
|
package io.kamax.mxisd.config;
|
||||||
|
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
import org.yaml.snakeyaml.constructor.Constructor;
|
import org.yaml.snakeyaml.constructor.Constructor;
|
||||||
|
import org.yaml.snakeyaml.introspector.BeanAccess;
|
||||||
|
import org.yaml.snakeyaml.parser.ParserException;
|
||||||
import org.yaml.snakeyaml.representer.Representer;
|
import org.yaml.snakeyaml.representer.Representer;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -37,17 +41,26 @@ public class YamlConfigLoader {
|
|||||||
private static final Logger log = LoggerFactory.getLogger(YamlConfigLoader.class);
|
private static final Logger log = LoggerFactory.getLogger(YamlConfigLoader.class);
|
||||||
|
|
||||||
public static MxisdConfig loadFromFile(String path) throws IOException {
|
public static MxisdConfig loadFromFile(String path) throws IOException {
|
||||||
log.debug("Reading config from {}", path);
|
File f = new File(path).getAbsoluteFile();
|
||||||
|
log.info("Reading config from {}", f.toString());
|
||||||
Representer rep = new Representer();
|
Representer rep = new Representer();
|
||||||
|
rep.getPropertyUtils().setBeanAccess(BeanAccess.FIELD);
|
||||||
rep.getPropertyUtils().setAllowReadOnlyProperties(true);
|
rep.getPropertyUtils().setAllowReadOnlyProperties(true);
|
||||||
rep.getPropertyUtils().setSkipMissingProperties(true);
|
rep.getPropertyUtils().setSkipMissingProperties(true);
|
||||||
Yaml yaml = new Yaml(new Constructor(MxisdConfig.class), rep);
|
Yaml yaml = new Yaml(new Constructor(MxisdConfig.class), rep);
|
||||||
try (FileInputStream is = new FileInputStream(path)) {
|
try (FileInputStream is = new FileInputStream(f)) {
|
||||||
Object o = yaml.load(is);
|
MxisdConfig raw = yaml.load(is);
|
||||||
log.debug("Read config in memory from {}", path);
|
log.debug("Read config in memory from {}", path);
|
||||||
MxisdConfig cfg = GsonUtil.get().fromJson(GsonUtil.get().toJson(o), MxisdConfig.class);
|
|
||||||
|
// SnakeYaml set objects to null when there is no value set in the config, even a full sub-tree.
|
||||||
|
// This is problematic for default config values and objects, to avoid NPEs.
|
||||||
|
// Therefore, we'll use Gson to re-parse the data in a way that avoids us checking the whole config for nulls.
|
||||||
|
MxisdConfig cfg = GsonUtil.get().fromJson(GsonUtil.get().toJson(raw), MxisdConfig.class);
|
||||||
|
|
||||||
log.info("Loaded config from {}", path);
|
log.info("Loaded config from {}", path);
|
||||||
return cfg;
|
return cfg;
|
||||||
|
} catch (ParserException t) {
|
||||||
|
throw new ConfigurationException(t.getMessage(), "Could not parse YAML config file - Please check indentation and that the configuration options exist");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,7 +28,6 @@ import org.slf4j.LoggerFactory;
|
|||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class RestBackendConfig {
|
public class RestBackendConfig {
|
||||||
|
|
||||||
@@ -118,8 +117,8 @@ public class RestBackendConfig {
|
|||||||
this.identity = identity;
|
this.identity = identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ProfileEndpoints> getProfile() {
|
public ProfileEndpoints getProfile() {
|
||||||
return Optional.ofNullable(profile);
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProfile(ProfileEndpoints profile) {
|
public void setProfile(ProfileEndpoints profile) {
|
||||||
@@ -128,7 +127,7 @@ public class RestBackendConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(RestBackendConfig.class);
|
private static final Logger log = LoggerFactory.getLogger(RestBackendConfig.class);
|
||||||
|
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private String host;
|
private String host;
|
||||||
@@ -197,6 +196,11 @@ public class RestBackendConfig {
|
|||||||
log.info("Directory endpoint: {}", endpoints.getDirectory());
|
log.info("Directory endpoint: {}", endpoints.getDirectory());
|
||||||
log.info("Identity Single endpoint: {}", endpoints.identity.getSingle());
|
log.info("Identity Single endpoint: {}", endpoints.identity.getSingle());
|
||||||
log.info("Identity Bulk endpoint: {}", endpoints.identity.getBulk());
|
log.info("Identity Bulk endpoint: {}", endpoints.identity.getBulk());
|
||||||
|
|
||||||
|
log.info("Profile endpoints:");
|
||||||
|
log.info(" - Display name: {}", getEndpoints().getProfile().getDisplayName());
|
||||||
|
log.info(" - 3PIDs: {}", getEndpoints().getProfile().getThreepids());
|
||||||
|
log.info(" - Roles: {}", getEndpoints().getProfile().getRoles());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
package io.kamax.mxisd.config.sql;
|
package io.kamax.mxisd.config.sql;
|
||||||
|
|
||||||
import io.kamax.mxisd.util.GsonUtil;
|
import io.kamax.mxisd.util.GsonUtil;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -314,7 +315,8 @@ public abstract class SqlConfig {
|
|||||||
log.info("Enabled: {}", isEnabled());
|
log.info("Enabled: {}", isEnabled());
|
||||||
if (isEnabled()) {
|
if (isEnabled()) {
|
||||||
log.info("Type: {}", getType());
|
log.info("Type: {}", getType());
|
||||||
log.info("Connection: {}", getConnection());
|
log.info("Has connection info? {}", !StringUtils.isEmpty(getConnection()));
|
||||||
|
log.debug("Connection: {}", getConnection());
|
||||||
log.info("Auth enabled: {}", getAuth().isEnabled());
|
log.info("Auth enabled: {}", getAuth().isEnabled());
|
||||||
log.info("Directory queries: {}", GsonUtil.build().toJson(getDirectory().getQuery()));
|
log.info("Directory queries: {}", GsonUtil.build().toJson(getDirectory().getQuery()));
|
||||||
log.info("Identity type: {}", getIdentity().getType());
|
log.info("Identity type: {}", getIdentity().getType());
|
||||||
|
@@ -20,9 +20,8 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.crypto;
|
package io.kamax.mxisd.crypto;
|
||||||
|
|
||||||
import io.kamax.matrix.crypto.*;
|
|
||||||
import io.kamax.mxisd.config.KeyConfig;
|
import io.kamax.mxisd.config.KeyConfig;
|
||||||
import io.kamax.mxisd.config.ServerConfig;
|
import io.kamax.mxisd.storage.crypto.*;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -31,10 +30,10 @@ import java.io.IOException;
|
|||||||
|
|
||||||
public class CryptoFactory {
|
public class CryptoFactory {
|
||||||
|
|
||||||
public static KeyManager getKeyManager(KeyConfig keyCfg) {
|
public static Ed25519KeyManager getKeyManager(KeyConfig keyCfg) {
|
||||||
_KeyStore store;
|
KeyStore store;
|
||||||
if (StringUtils.equals(":memory:", keyCfg.getPath())) {
|
if (StringUtils.equals(":memory:", keyCfg.getPath())) {
|
||||||
store = new KeyMemoryStore();
|
store = new MemoryKeyStore();
|
||||||
} else {
|
} else {
|
||||||
File keyStore = new File(keyCfg.getPath());
|
File keyStore = new File(keyCfg.getPath());
|
||||||
if (!keyStore.exists()) {
|
if (!keyStore.exists()) {
|
||||||
@@ -45,14 +44,14 @@ public class CryptoFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
store = new KeyFileStore(keyCfg.getPath());
|
store = new FileKeyStore(keyCfg.getPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
return new KeyManager(store);
|
return new Ed25519KeyManager(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SignatureManager getSignatureManager(KeyManager keyMgr, ServerConfig cfg) {
|
public static SignatureManager getSignatureManager(Ed25519KeyManager keyMgr) {
|
||||||
return new SignatureManager(keyMgr, cfg.getName());
|
return new Ed25519SignatureManager(keyMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -20,11 +20,8 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.exception;
|
package io.kamax.mxisd.exception;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class ConfigurationException extends RuntimeException {
|
public class ConfigurationException extends RuntimeException {
|
||||||
|
|
||||||
private String key;
|
|
||||||
private String detailedMsg;
|
private String detailedMsg;
|
||||||
|
|
||||||
public ConfigurationException(String key) {
|
public ConfigurationException(String key) {
|
||||||
@@ -40,8 +37,8 @@ public class ConfigurationException extends RuntimeException {
|
|||||||
this.detailedMsg = detailedMsg;
|
this.detailedMsg = detailedMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<String> getDetailedMessage() {
|
public String getDetailedMessage() {
|
||||||
return Optional.ofNullable(detailedMsg);
|
return detailedMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -25,8 +25,14 @@ import org.apache.http.HttpStatus;
|
|||||||
|
|
||||||
public class NotAllowedException extends HttpMatrixException {
|
public class NotAllowedException extends HttpMatrixException {
|
||||||
|
|
||||||
|
public static final String ErrCode = "M_FORBIDDEN";
|
||||||
|
|
||||||
|
public NotAllowedException(int code, String s) {
|
||||||
|
super(code, ErrCode, s);
|
||||||
|
}
|
||||||
|
|
||||||
public NotAllowedException(String s) {
|
public NotAllowedException(String s) {
|
||||||
super(HttpStatus.SC_FORBIDDEN, "M_FORBIDDEN", s);
|
super(HttpStatus.SC_FORBIDDEN, ErrCode, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -101,6 +101,10 @@ public abstract class BasicHttpHandler implements HttpHandler {
|
|||||||
return GsonUtil.parseObj(getBodyUtf8(exchange));
|
return GsonUtil.parseObj(getBodyUtf8(exchange));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void putHeader(HttpServerExchange ex, String name, String value) {
|
||||||
|
ex.getResponseHeaders().put(HttpString.tryFromString(name), value);
|
||||||
|
}
|
||||||
|
|
||||||
protected void respond(HttpServerExchange ex, int statusCode, JsonElement bodyJson) {
|
protected void respond(HttpServerExchange ex, int statusCode, JsonElement bodyJson) {
|
||||||
respondJson(ex, statusCode, GsonUtil.get().toJson(bodyJson));
|
respondJson(ex, statusCode, GsonUtil.get().toJson(bodyJson));
|
||||||
}
|
}
|
||||||
|
@@ -27,8 +27,7 @@ import io.kamax.matrix.json.InvalidJsonException;
|
|||||||
import io.kamax.mxisd.exception.*;
|
import io.kamax.mxisd.exception.*;
|
||||||
import io.undertow.server.HttpHandler;
|
import io.undertow.server.HttpHandler;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import io.undertow.util.HttpString;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
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;
|
||||||
@@ -37,15 +36,22 @@ import java.time.Instant;
|
|||||||
|
|
||||||
public class SaneHandler extends BasicHttpHandler {
|
public class SaneHandler extends BasicHttpHandler {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SaneHandler.class);
|
||||||
|
|
||||||
|
private static final String CorsOriginName = "Access-Control-Allow-Origin";
|
||||||
|
private static final String CorsOriginValue = "*";
|
||||||
|
private static final String CorsMethodsName = "Access-Control-Allow-Methods";
|
||||||
|
private static final String CorsMethodsValue = "GET, POST, PUT, DELETE, OPTIONS";
|
||||||
|
private static final String CorsHeadersName = "Access-Control-Allow-Headers";
|
||||||
|
private static final String CorsHeadersValue = "Origin, X-Requested-With, Content-Type, Accept, Authorization";
|
||||||
|
|
||||||
public static SaneHandler around(HttpHandler h) {
|
public static SaneHandler around(HttpHandler h) {
|
||||||
return new SaneHandler(h);
|
return new SaneHandler(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(SaneHandler.class);
|
private final HttpHandler child;
|
||||||
|
|
||||||
private HttpHandler child;
|
private SaneHandler(HttpHandler child) {
|
||||||
|
|
||||||
public SaneHandler(HttpHandler child) {
|
|
||||||
this.child = child;
|
this.child = child;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,9 +64,9 @@ public class SaneHandler extends BasicHttpHandler {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
// CORS headers as per spec
|
// CORS headers as per spec
|
||||||
exchange.getResponseHeaders().put(HttpString.tryFromString("Access-Control-Allow-Origin"), "*");
|
putHeader(exchange, CorsOriginName, CorsOriginValue);
|
||||||
exchange.getResponseHeaders().put(HttpString.tryFromString("Access-Control-Allow-Methods"), "GET, POST, PUT, DELETE, OPTIONS");
|
putHeader(exchange, CorsMethodsName, CorsMethodsValue);
|
||||||
exchange.getResponseHeaders().put(HttpString.tryFromString("Access-Control-Allow-Headers"), "Origin, X-Requested-With, Content-Type, Accept, Authorization");
|
putHeader(exchange, CorsHeadersName, CorsHeadersValue);
|
||||||
|
|
||||||
child.handleRequest(exchange);
|
child.handleRequest(exchange);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
@@ -89,9 +95,9 @@ public class SaneHandler extends BasicHttpHandler {
|
|||||||
handleException(exchange, e);
|
handleException(exchange, e);
|
||||||
} catch (InternalServerError e) {
|
} catch (InternalServerError e) {
|
||||||
if (StringUtils.isNotBlank(e.getInternalReason())) {
|
if (StringUtils.isNotBlank(e.getInternalReason())) {
|
||||||
log.error("Reference #{} - {}", e.getReference(), e.getInternalReason());
|
log.error("Transaction #{} - {}", e.getReference(), e.getInternalReason());
|
||||||
} else {
|
} else {
|
||||||
log.error("Reference #{}", e);
|
log.error("Transaction #{}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleException(exchange, e);
|
handleException(exchange, e);
|
||||||
@@ -105,14 +111,11 @@ public class SaneHandler extends BasicHttpHandler {
|
|||||||
respond(exchange, e.getStatus(), buildErrorBody(exchange, e.getErrorCode(), e.getError()));
|
respond(exchange, e.getStatus(), buildErrorBody(exchange, e.getErrorCode(), e.getError()));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
log.error("Unknown error when handling {}", exchange.getRequestURL(), e);
|
log.error("Unknown error when handling {}", exchange.getRequestURL(), e);
|
||||||
respond(exchange, HttpStatus.SC_INTERNAL_SERVER_ERROR, buildErrorBody(exchange,
|
String message = e.getMessage();
|
||||||
"M_UNKNOWN",
|
if (StringUtils.isBlank(message)) {
|
||||||
StringUtils.defaultIfBlank(
|
message = "An internal server error occurred. Contact your administrator with reference Transaction #" + Instant.now().toEpochMilli();
|
||||||
e.getMessage(),
|
}
|
||||||
"An internal server error occurred. If this error persists, please contact support with reference #" +
|
respond(exchange, HttpStatus.SC_INTERNAL_SERVER_ERROR, buildErrorBody(exchange, "M_UNKNOWN", message));
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
)
|
|
||||||
));
|
|
||||||
} finally {
|
} finally {
|
||||||
exchange.endExchange();
|
exchange.endExchange();
|
||||||
}
|
}
|
||||||
|
@@ -21,10 +21,11 @@
|
|||||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import io.kamax.matrix.crypto.KeyManager;
|
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
|
||||||
import io.kamax.mxisd.http.IsAPIv1;
|
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.storage.crypto.GenericKeyIdentifier;
|
||||||
|
import io.kamax.mxisd.storage.crypto.KeyManager;
|
||||||
|
import io.kamax.mxisd.storage.crypto.KeyType;
|
||||||
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;
|
||||||
@@ -46,16 +47,12 @@ public class KeyGetHandler extends BasicHttpHandler {
|
|||||||
public void handleRequest(HttpServerExchange exchange) {
|
public void handleRequest(HttpServerExchange exchange) {
|
||||||
String key = getQueryParameter(exchange, Key);
|
String key = getQueryParameter(exchange, Key);
|
||||||
String[] v = key.split(":", 2);
|
String[] v = key.split(":", 2);
|
||||||
String keyType = v[0];
|
String keyAlgo = v[0];
|
||||||
int keyId = Integer.parseInt(v[1]);
|
String keyId = v[1];
|
||||||
|
|
||||||
if (!"ed25519".contentEquals(keyType)) {
|
log.info("Key {}:{} was requested", keyAlgo, keyId);
|
||||||
throw new BadRequestException("Invalid algorithm: " + keyType);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Key {}:{} was requested", keyType, keyId);
|
|
||||||
JsonObject obj = new JsonObject();
|
JsonObject obj = new JsonObject();
|
||||||
obj.addProperty("public_key", mgr.getPublicKeyBase64(keyId));
|
obj.addProperty("public_key", mgr.getPublicKeyBase64(new GenericKeyIdentifier(KeyType.Regular, keyAlgo, keyId)));
|
||||||
respond(exchange, obj);
|
respond(exchange, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,10 +20,10 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||||
|
|
||||||
import io.kamax.matrix.crypto.KeyManager;
|
|
||||||
import io.kamax.mxisd.http.IsAPIv1;
|
import io.kamax.mxisd.http.IsAPIv1;
|
||||||
|
import io.kamax.mxisd.storage.crypto.KeyManager;
|
||||||
|
import io.kamax.mxisd.storage.crypto.KeyType;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -44,9 +44,7 @@ public class RegularKeyIsValidHandler extends KeyIsValidHandler {
|
|||||||
String pubKey = getQueryParameter(exchange, "public_key");
|
String pubKey = getQueryParameter(exchange, "public_key");
|
||||||
log.info("Validating public key {}", pubKey);
|
log.info("Validating public key {}", pubKey);
|
||||||
|
|
||||||
// TODO do in manager
|
respondJson(exchange, mgr.isValid(KeyType.Regular, pubKey) ? validKey : invalidKey);
|
||||||
boolean valid = StringUtils.equals(pubKey, mgr.getPublicKeyBase64(mgr.getCurrentIndex()));
|
|
||||||
respondJson(exchange, valid ? validKey : invalidKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -21,15 +21,17 @@
|
|||||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import io.kamax.matrix.crypto.SignatureManager;
|
|
||||||
import io.kamax.matrix.event.EventKey;
|
import io.kamax.matrix.event.EventKey;
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
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.ServerConfig;
|
||||||
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.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;
|
||||||
|
import io.kamax.mxisd.storage.crypto.SignatureManager;
|
||||||
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;
|
||||||
@@ -42,10 +44,12 @@ public class SingleLookupHandler extends LookupHandler {
|
|||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(SingleLookupHandler.class);
|
private transient final Logger log = LoggerFactory.getLogger(SingleLookupHandler.class);
|
||||||
|
|
||||||
|
private ServerConfig cfg;
|
||||||
private LookupStrategy strategy;
|
private LookupStrategy strategy;
|
||||||
private SignatureManager signMgr;
|
private SignatureManager signMgr;
|
||||||
|
|
||||||
public SingleLookupHandler(LookupStrategy strategy, SignatureManager signMgr) {
|
public SingleLookupHandler(MxisdConfig cfg, LookupStrategy strategy, SignatureManager signMgr) {
|
||||||
|
this.cfg = cfg.getServer();
|
||||||
this.strategy = strategy;
|
this.strategy = strategy;
|
||||||
this.signMgr = signMgr;
|
this.signMgr = signMgr;
|
||||||
}
|
}
|
||||||
@@ -72,7 +76,7 @@ public class SingleLookupHandler extends LookupHandler {
|
|||||||
|
|
||||||
// FIXME signing should be done in the business model, not in the controller
|
// FIXME signing should be done in the business model, not in the controller
|
||||||
JsonObject obj = GsonUtil.makeObj(new SingeLookupReplyJson(lookup));
|
JsonObject obj = GsonUtil.makeObj(new SingeLookupReplyJson(lookup));
|
||||||
obj.add(EventKey.Signatures.get(), signMgr.signMessageGson(MatrixJson.encodeCanonical(obj)));
|
obj.add(EventKey.Signatures.get(), signMgr.signMessageGson(cfg.getName(), MatrixJson.encodeCanonical(obj)));
|
||||||
|
|
||||||
respondJson(exchange, obj);
|
respondJson(exchange, obj);
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,6 @@ import com.google.gson.JsonObject;
|
|||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
import io.kamax.matrix._MatrixID;
|
import io.kamax.matrix._MatrixID;
|
||||||
import io.kamax.matrix.crypto.KeyManager;
|
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
import io.kamax.mxisd.config.ServerConfig;
|
import io.kamax.mxisd.config.ServerConfig;
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
import io.kamax.mxisd.exception.BadRequestException;
|
||||||
@@ -36,6 +35,7 @@ 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;
|
||||||
import io.kamax.mxisd.invitation.ThreePidInvite;
|
import io.kamax.mxisd.invitation.ThreePidInvite;
|
||||||
|
import io.kamax.mxisd.storage.crypto.KeyManager;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import io.undertow.util.QueryParameterUtils;
|
import io.undertow.util.QueryParameterUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -96,7 +96,8 @@ public class StoreInviteHandler extends BasicHttpHandler {
|
|||||||
IThreePidInvite invite = new ThreePidInvite(sender, inv.getMedium(), inv.getAddress(), inv.getRoomId(), parameters);
|
IThreePidInvite invite = new ThreePidInvite(sender, inv.getMedium(), inv.getAddress(), inv.getRoomId(), parameters);
|
||||||
IThreePidInviteReply reply = invMgr.storeInvite(invite);
|
IThreePidInviteReply reply = invMgr.storeInvite(invite);
|
||||||
|
|
||||||
respondJson(exchange, new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), cfg.getPublicUrl()));
|
// FIXME the key info must be set by the invitation manager in the reply object!
|
||||||
|
respondJson(exchange, new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getServerSigningKey().getId()), cfg.getPublicUrl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -23,9 +23,10 @@ package io.kamax.mxisd.invitation;
|
|||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
import io.kamax.matrix.crypto.SignatureManager;
|
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
import io.kamax.mxisd.config.InvitationConfig;
|
import io.kamax.mxisd.config.InvitationConfig;
|
||||||
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
|
import io.kamax.mxisd.config.ServerConfig;
|
||||||
import io.kamax.mxisd.dns.FederationDnsOverwrite;
|
import io.kamax.mxisd.dns.FederationDnsOverwrite;
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
import io.kamax.mxisd.exception.BadRequestException;
|
||||||
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
|
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
|
||||||
@@ -34,6 +35,7 @@ import io.kamax.mxisd.lookup.ThreePidMapping;
|
|||||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||||
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.crypto.SignatureManager;
|
||||||
import io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO;
|
import io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang.RandomStringUtils;
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
@@ -67,6 +69,7 @@ public class InvitationManager {
|
|||||||
private transient final Logger log = LoggerFactory.getLogger(InvitationManager.class);
|
private transient final Logger log = LoggerFactory.getLogger(InvitationManager.class);
|
||||||
|
|
||||||
private InvitationConfig cfg;
|
private InvitationConfig cfg;
|
||||||
|
private ServerConfig srvCfg;
|
||||||
private IStorage storage;
|
private IStorage storage;
|
||||||
private LookupStrategy lookupMgr;
|
private LookupStrategy lookupMgr;
|
||||||
private SignatureManager signMgr;
|
private SignatureManager signMgr;
|
||||||
@@ -79,14 +82,15 @@ public class InvitationManager {
|
|||||||
private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>();
|
private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public InvitationManager(
|
public InvitationManager(
|
||||||
InvitationConfig cfg,
|
MxisdConfig mxisdCfg,
|
||||||
IStorage storage,
|
IStorage storage,
|
||||||
LookupStrategy lookupMgr,
|
LookupStrategy lookupMgr,
|
||||||
SignatureManager signMgr,
|
SignatureManager signMgr,
|
||||||
FederationDnsOverwrite dns,
|
FederationDnsOverwrite dns,
|
||||||
NotificationManager notifMgr
|
NotificationManager notifMgr
|
||||||
) {
|
) {
|
||||||
this.cfg = cfg;
|
this.cfg = mxisdCfg.getInvite();
|
||||||
|
this.srvCfg = mxisdCfg.getServer();
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.lookupMgr = lookupMgr;
|
this.lookupMgr = lookupMgr;
|
||||||
this.signMgr = signMgr;
|
this.signMgr = signMgr;
|
||||||
@@ -280,7 +284,7 @@ public class InvitationManager {
|
|||||||
JsonObject obj = new JsonObject();
|
JsonObject obj = new JsonObject();
|
||||||
obj.addProperty("mxid", mxid);
|
obj.addProperty("mxid", mxid);
|
||||||
obj.addProperty("token", reply.getToken());
|
obj.addProperty("token", reply.getToken());
|
||||||
obj.add("signatures", signMgr.signMessageGson(obj.toString()));
|
obj.add("signatures", signMgr.signMessageGson(srvCfg.getName(), obj.toString()));
|
||||||
|
|
||||||
JsonObject objUp = new JsonObject();
|
JsonObject objUp = new JsonObject();
|
||||||
objUp.addProperty("mxid", mxid);
|
objUp.addProperty("mxid", mxid);
|
||||||
@@ -298,7 +302,7 @@ public class InvitationManager {
|
|||||||
content.addProperty("address", address);
|
content.addProperty("address", address);
|
||||||
content.addProperty("mxid", mxid);
|
content.addProperty("mxid", mxid);
|
||||||
|
|
||||||
content.add("signatures", signMgr.signMessageGson(content.toString()));
|
content.add("signatures", signMgr.signMessageGson(srvCfg.getName(), content.toString()));
|
||||||
|
|
||||||
StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8);
|
StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8);
|
||||||
entity.setContentType("application/json");
|
entity.setContentType("application/json");
|
||||||
|
@@ -178,7 +178,6 @@ public class SessionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void unbind(JsonObject reqData) {
|
public void unbind(JsonObject reqData) {
|
||||||
// TODO also check for HS header to know which domain attempting the unbind
|
|
||||||
if (reqData.entrySet().size() == 2 && reqData.has("mxid") && reqData.has("threepid")) {
|
if (reqData.entrySet().size() == 2 && reqData.has("mxid") && reqData.has("threepid")) {
|
||||||
/* This is a HS request to remove a 3PID and is considered:
|
/* This is a HS request to remove a 3PID and is considered:
|
||||||
* - An attack on user privacy
|
* - An attack on user privacy
|
||||||
@@ -218,11 +217,13 @@ public class SessionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new NotAllowedException("You have attempted to alter 3PID bindings, which can only be done by the 3PID owner directly. " +
|
||||||
|
"We have informed the 3PID owner of your fraudulent attempt.");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Denying request");
|
log.info("Denying unbind request as the endpoint is not defined in the spec.");
|
||||||
throw new NotAllowedException("You have attempted to alter 3PID bindings, which can only be done by the 3PID owner directly. " +
|
throw new NotAllowedException(499, "This endpoint does not exist in the spec and therefore is not supported.");
|
||||||
"We have informed the 3PID owner of your fraudulent attempt.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
public class Ed2219RegularKeyIdentifier extends RegularKeyIdentifier {
|
||||||
|
|
||||||
|
public Ed2219RegularKeyIdentifier(String serial) {
|
||||||
|
super(KeyAlgorithm.Ed25519, serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
53
src/main/java/io/kamax/mxisd/storage/crypto/Ed25519Key.java
Normal file
53
src/main/java/io/kamax/mxisd/storage/crypto/Ed25519Key.java
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
public class Ed25519Key implements Key {
|
||||||
|
|
||||||
|
private KeyIdentifier id;
|
||||||
|
private String privKey;
|
||||||
|
|
||||||
|
public Ed25519Key(KeyIdentifier id, String privKey) {
|
||||||
|
if (!KeyAlgorithm.Ed25519.equals(id.getAlgorithm())) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.id = new GenericKeyIdentifier(id);
|
||||||
|
this.privKey = privKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyIdentifier getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrivateKeyBase64() {
|
||||||
|
return privKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
import io.kamax.matrix.codec.MxBase64;
|
||||||
|
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
|
||||||
|
import net.i2p.crypto.eddsa.EdDSAPublicKey;
|
||||||
|
import net.i2p.crypto.eddsa.KeyPairGenerator;
|
||||||
|
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
|
||||||
|
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
|
||||||
|
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
|
||||||
|
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Ed25519KeyManager implements KeyManager {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(Ed25519KeyManager.class);
|
||||||
|
|
||||||
|
private final EdDSAParameterSpec keySpecs;
|
||||||
|
private final KeyStore store;
|
||||||
|
|
||||||
|
public Ed25519KeyManager(KeyStore store) {
|
||||||
|
this.keySpecs = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
|
||||||
|
this.store = store;
|
||||||
|
|
||||||
|
if (!store.getCurrentKey().isPresent()) {
|
||||||
|
List<KeyIdentifier> keys = store.list(KeyType.Regular);
|
||||||
|
if (keys.isEmpty()) {
|
||||||
|
keys.add(generateKey(KeyType.Regular));
|
||||||
|
}
|
||||||
|
|
||||||
|
store.setCurrentKey(keys.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String generateId() {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
|
||||||
|
buffer.putLong(Instant.now().toEpochMilli() - 1546297200000L); // TS since 2019-01-01T00:00:00Z to keep IDs short
|
||||||
|
return Base64.encodeBase64URLSafeString(buffer.array()) + RandomStringUtils.randomAlphanumeric(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getPrivateKeyBase64(EdDSAPrivateKey key) {
|
||||||
|
return MxBase64.encode(key.getSeed());
|
||||||
|
}
|
||||||
|
|
||||||
|
public EdDSAParameterSpec getKeySpecs() {
|
||||||
|
return keySpecs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyIdentifier generateKey(KeyType type) {
|
||||||
|
KeyIdentifier id;
|
||||||
|
do {
|
||||||
|
id = new GenericKeyIdentifier(type, KeyAlgorithm.Ed25519, generateId());
|
||||||
|
} while (store.has(id));
|
||||||
|
|
||||||
|
KeyPair pair = (new KeyPairGenerator()).generateKeyPair();
|
||||||
|
String keyEncoded = getPrivateKeyBase64((EdDSAPrivateKey) pair.getPrivate());
|
||||||
|
|
||||||
|
Key key = new GenericKey(id, true, keyEncoded);
|
||||||
|
store.add(key);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<KeyIdentifier> getKeys(KeyType type) {
|
||||||
|
return store.list(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key getServerSigningKey() {
|
||||||
|
return store.get(store.getCurrentKey().orElseThrow(IllegalStateException::new));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key getKey(KeyIdentifier id) {
|
||||||
|
return store.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EdDSAPrivateKeySpec getPrivateKeySpecs(KeyIdentifier id) {
|
||||||
|
return new EdDSAPrivateKeySpec(java.util.Base64.getDecoder().decode(getKey(id).getPrivateKeyBase64()), keySpecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EdDSAPrivateKey getPrivateKey(KeyIdentifier id) {
|
||||||
|
return new EdDSAPrivateKey(getPrivateKeySpecs(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public EdDSAPublicKey getPublicKey(KeyIdentifier id) {
|
||||||
|
EdDSAPrivateKeySpec privKeySpec = getPrivateKeySpecs(id);
|
||||||
|
EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKeySpec.getA(), keySpecs);
|
||||||
|
return new EdDSAPublicKey(pubKeySpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void disableKey(KeyIdentifier id) {
|
||||||
|
Key key = store.get(id);
|
||||||
|
key = new GenericKey(id, false, key.getPrivateKeyBase64());
|
||||||
|
store.update(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPublicKeyBase64(KeyIdentifier id) {
|
||||||
|
return MxBase64.encode(getPublicKey(id).getAbyte());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(KeyType type, String publicKeyBase64) {
|
||||||
|
// TODO caching?
|
||||||
|
return getKeys(type).stream().anyMatch(id -> StringUtils.equals(getPublicKeyBase64(id), publicKeyBase64));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kamax.matrix.codec.MxBase64;
|
||||||
|
import io.kamax.matrix.json.MatrixJson;
|
||||||
|
import net.i2p.crypto.eddsa.EdDSAEngine;
|
||||||
|
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SignatureException;
|
||||||
|
|
||||||
|
public class Ed25519SignatureManager implements SignatureManager {
|
||||||
|
|
||||||
|
private final Ed25519KeyManager keyMgr;
|
||||||
|
|
||||||
|
public Ed25519SignatureManager(Ed25519KeyManager keyMgr) {
|
||||||
|
this.keyMgr = keyMgr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObject signMessageGson(String domain, String message) {
|
||||||
|
Signature sign = sign(message);
|
||||||
|
|
||||||
|
JsonObject keySignature = new JsonObject();
|
||||||
|
// FIXME should create a signing key object what would give this ed and index values
|
||||||
|
keySignature.addProperty(sign.getKey().getAlgorithm() + ":" + sign.getKey().getSerial(), sign.getSignature());
|
||||||
|
JsonObject signature = new JsonObject();
|
||||||
|
signature.add(domain, keySignature);
|
||||||
|
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Signature sign(JsonObject obj) {
|
||||||
|
|
||||||
|
return sign(MatrixJson.encodeCanonical(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Signature sign(byte[] data) {
|
||||||
|
try {
|
||||||
|
KeyIdentifier signingKeyId = keyMgr.getServerSigningKey().getId();
|
||||||
|
EdDSAEngine signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getKeySpecs().getHashAlgorithm()));
|
||||||
|
signEngine.initSign(keyMgr.getPrivateKey(signingKeyId));
|
||||||
|
byte[] signRaw = signEngine.signOneShot(data);
|
||||||
|
String sign = MxBase64.encode(signRaw);
|
||||||
|
|
||||||
|
return new Signature() {
|
||||||
|
@Override
|
||||||
|
public KeyIdentifier getKey() {
|
||||||
|
return signingKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSignature() {
|
||||||
|
return sign;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.exception.ObjectNotFoundException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class FileKeyStore implements KeyStore {
|
||||||
|
|
||||||
|
public FileKeyStore(String path) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean has(KeyIdentifier id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<KeyIdentifier> list() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<KeyIdentifier> list(KeyType type) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key get(KeyIdentifier id) throws ObjectNotFoundException {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(Key key) throws IllegalStateException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Key key) throws ObjectNotFoundException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(KeyIdentifier id) throws ObjectNotFoundException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentKey(KeyIdentifier id) throws IllegalArgumentException {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<KeyIdentifier> getCurrentKey() {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
51
src/main/java/io/kamax/mxisd/storage/crypto/GenericKey.java
Normal file
51
src/main/java/io/kamax/mxisd/storage/crypto/GenericKey.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
public class GenericKey implements Key {
|
||||||
|
|
||||||
|
private final KeyIdentifier id;
|
||||||
|
private final boolean isValid;
|
||||||
|
private final String privKey;
|
||||||
|
|
||||||
|
public GenericKey(KeyIdentifier id, boolean isValid, String privKey) {
|
||||||
|
this.id = new GenericKeyIdentifier(id);
|
||||||
|
this.isValid = isValid;
|
||||||
|
this.privKey = privKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyIdentifier getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPrivateKeyBase64() {
|
||||||
|
return privKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
public class GenericKeyIdentifier implements KeyIdentifier {
|
||||||
|
|
||||||
|
private final KeyType type;
|
||||||
|
private final String algo;
|
||||||
|
private final String serial;
|
||||||
|
|
||||||
|
public GenericKeyIdentifier(KeyIdentifier id) {
|
||||||
|
this(id.getType(), id.getAlgorithm(), id.getSerial());
|
||||||
|
}
|
||||||
|
|
||||||
|
public GenericKeyIdentifier(KeyType type, String algo, String serial) {
|
||||||
|
this.type = type;
|
||||||
|
this.algo = algo;
|
||||||
|
this.serial = serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeyType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return algo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSerial() {
|
||||||
|
return serial;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
44
src/main/java/io/kamax/mxisd/storage/crypto/Key.java
Normal file
44
src/main/java/io/kamax/mxisd/storage/crypto/Key.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A signing key
|
||||||
|
*/
|
||||||
|
public interface Key {
|
||||||
|
|
||||||
|
KeyIdentifier getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the key is currently valid
|
||||||
|
*
|
||||||
|
* @return true if the key is valid, false if not
|
||||||
|
*/
|
||||||
|
boolean isValid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the private key
|
||||||
|
*
|
||||||
|
* @return the private key encoded as Base64
|
||||||
|
*/
|
||||||
|
String getPrivateKeyBase64();
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
public interface KeyAlgorithm {
|
||||||
|
|
||||||
|
String Ed25519 = "ed25519";
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifying data for a given Key.
|
||||||
|
*/
|
||||||
|
public interface KeyIdentifier {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of key.
|
||||||
|
*
|
||||||
|
* @return The type of the key
|
||||||
|
*/
|
||||||
|
KeyType getType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Algorithm of the key. Typically <code>ed25519</code>.
|
||||||
|
*
|
||||||
|
* @return The algorithm of the key
|
||||||
|
*/
|
||||||
|
String getAlgorithm();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serial of the key, unique for the algorithm.
|
||||||
|
* It is typically made of random alphanumerical characters.
|
||||||
|
*
|
||||||
|
* @return The serial of the key
|
||||||
|
*/
|
||||||
|
String getSerial();
|
||||||
|
|
||||||
|
}
|
41
src/main/java/io/kamax/mxisd/storage/crypto/KeyManager.java
Normal file
41
src/main/java/io/kamax/mxisd/storage/crypto/KeyManager.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface KeyManager {
|
||||||
|
|
||||||
|
KeyIdentifier generateKey(KeyType type);
|
||||||
|
|
||||||
|
List<KeyIdentifier> getKeys(KeyType type);
|
||||||
|
|
||||||
|
Key getServerSigningKey();
|
||||||
|
|
||||||
|
Key getKey(KeyIdentifier id);
|
||||||
|
|
||||||
|
void disableKey(KeyIdentifier id);
|
||||||
|
|
||||||
|
String getPublicKeyBase64(KeyIdentifier id);
|
||||||
|
|
||||||
|
boolean isValid(KeyType type, String publicKeyBase64);
|
||||||
|
|
||||||
|
}
|
98
src/main/java/io/kamax/mxisd/storage/crypto/KeyStore.java
Normal file
98
src/main/java/io/kamax/mxisd/storage/crypto/KeyStore.java
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.exception.ObjectNotFoundException;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store to persist signing keys and the identifier for the current long-term signing key
|
||||||
|
*/
|
||||||
|
public interface KeyStore {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a given key is currently stored
|
||||||
|
*
|
||||||
|
* @param id The Identifier elements for the key
|
||||||
|
* @return true if the key is stored, false if not
|
||||||
|
*/
|
||||||
|
boolean has(KeyIdentifier id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all keys within the store
|
||||||
|
*
|
||||||
|
* @return The list of key identifiers
|
||||||
|
*/
|
||||||
|
List<KeyIdentifier> list();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all keys of a given type within the store
|
||||||
|
*
|
||||||
|
* @param type The type to filter on
|
||||||
|
* @return The list of keys identifiers matching the given type
|
||||||
|
*/
|
||||||
|
List<KeyIdentifier> list(KeyType type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the key that relates to the given identifier
|
||||||
|
*
|
||||||
|
* @param id The identifier of the key to get
|
||||||
|
* @return The key
|
||||||
|
* @throws ObjectNotFoundException If no key is found for that identifier
|
||||||
|
*/
|
||||||
|
Key get(KeyIdentifier id) throws ObjectNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a key to the store
|
||||||
|
*
|
||||||
|
* @param key The key to store
|
||||||
|
* @throws IllegalStateException If a key already exist for the given identifier data
|
||||||
|
*/
|
||||||
|
void add(Key key) throws IllegalStateException;
|
||||||
|
|
||||||
|
void update(Key key) throws ObjectNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a key from the store
|
||||||
|
*
|
||||||
|
* @param id The key identifier of the key to delete
|
||||||
|
* @throws ObjectNotFoundException If no key is found for that identifier
|
||||||
|
*/
|
||||||
|
void delete(KeyIdentifier id) throws ObjectNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the information of which key is the current signing key
|
||||||
|
*
|
||||||
|
* @param id The key identifier
|
||||||
|
* @throws ObjectNotFoundException If the key is not known to the store
|
||||||
|
*/
|
||||||
|
void setCurrentKey(KeyIdentifier id) throws ObjectNotFoundException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the previously stored information of which key is the current signing key, if any
|
||||||
|
*
|
||||||
|
* @return The optional key identifier that was previously stored
|
||||||
|
*/
|
||||||
|
Optional<KeyIdentifier> getCurrentKey();
|
||||||
|
|
||||||
|
}
|
39
src/main/java/io/kamax/mxisd/storage/crypto/KeyType.java
Normal file
39
src/main/java/io/kamax/mxisd/storage/crypto/KeyType.java
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of keys used by an Identity server.
|
||||||
|
* See https://matrix.org/docs/spec/identity_service/r0.1.0.html#key-management
|
||||||
|
*/
|
||||||
|
public enum KeyType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ephemeral keys are related to 3PID invites and are only valid while the invite is pending.
|
||||||
|
*/
|
||||||
|
Ephemeral,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular keys are used by the Identity Server itself to sign requests/responses
|
||||||
|
*/
|
||||||
|
Regular
|
||||||
|
|
||||||
|
}
|
109
src/main/java/io/kamax/mxisd/storage/crypto/MemoryKeyStore.java
Normal file
109
src/main/java/io/kamax/mxisd/storage/crypto/MemoryKeyStore.java
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.exception.ObjectNotFoundException;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class MemoryKeyStore implements KeyStore {
|
||||||
|
|
||||||
|
private Map<KeyType, Map<String, Map<String, String>>> keys = new ConcurrentHashMap<>();
|
||||||
|
private KeyIdentifier current;
|
||||||
|
|
||||||
|
private Map<String, String> getMap(KeyType type, String algo) {
|
||||||
|
return keys.computeIfAbsent(type, k -> new ConcurrentHashMap<>()).computeIfAbsent(algo, k -> new ConcurrentHashMap<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean has(KeyIdentifier id) {
|
||||||
|
return getMap(id.getType(), id.getAlgorithm()).containsKey(id.getSerial());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<KeyIdentifier> list() {
|
||||||
|
List<KeyIdentifier> keyIds = new ArrayList<>();
|
||||||
|
keys.forEach((key, value) -> value.forEach((key1, value1) -> value1.forEach((key2, value2) -> keyIds.add(new GenericKeyIdentifier(key, key1, key2)))));
|
||||||
|
return keyIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<KeyIdentifier> list(KeyType type) {
|
||||||
|
List<KeyIdentifier> keyIds = new ArrayList<>();
|
||||||
|
keys.computeIfAbsent(type, t -> new ConcurrentHashMap<>()).forEach((key, value) -> value.forEach((key1, value1) -> keyIds.add(new GenericKeyIdentifier(type, key, key1))));
|
||||||
|
return keyIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Key get(KeyIdentifier id) throws ObjectNotFoundException {
|
||||||
|
String data = getMap(id.getType(), id.getAlgorithm()).get(id.getSerial());
|
||||||
|
if (Objects.isNull(data)) {
|
||||||
|
throw new ObjectNotFoundException("Key", id.getType() + ":" + id.getAlgorithm() + ":" + id.getSerial());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GenericKey(new GenericKeyIdentifier(id), StringUtils.isEmpty(data), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void set(Key key) {
|
||||||
|
String data = key.isValid() ? key.getPrivateKeyBase64() : "";
|
||||||
|
getMap(key.getId().getType(), key.getId().getAlgorithm()).put(key.getId().getSerial(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(Key key) throws IllegalStateException {
|
||||||
|
if (has(key.getId())) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Key key) throws ObjectNotFoundException {
|
||||||
|
if (!has(key.getId())) {
|
||||||
|
throw new ObjectNotFoundException("Key", key.getId().getType() + ":" + key.getId().getAlgorithm() + ":" + key.getId().getSerial());
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(KeyIdentifier id) throws ObjectNotFoundException {
|
||||||
|
keys.computeIfAbsent(id.getType(), k -> new ConcurrentHashMap<>()).computeIfAbsent(id.getAlgorithm(), k -> new ConcurrentHashMap<>()).remove(id.getSerial());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentKey(KeyIdentifier id) throws ObjectNotFoundException {
|
||||||
|
if (!has(id)) {
|
||||||
|
throw new ObjectNotFoundException("Key", id.getType() + ":" + id.getAlgorithm() + ":" + id.getSerial());
|
||||||
|
}
|
||||||
|
|
||||||
|
current = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<KeyIdentifier> getCurrentKey() {
|
||||||
|
return Optional.ofNullable(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
public class RegularKeyIdentifier extends GenericKeyIdentifier {
|
||||||
|
|
||||||
|
public RegularKeyIdentifier(String algo, String serial) {
|
||||||
|
super(KeyType.Regular, algo, serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
src/main/java/io/kamax/mxisd/storage/crypto/Signature.java
Normal file
29
src/main/java/io/kamax/mxisd/storage/crypto/Signature.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
public interface Signature {
|
||||||
|
|
||||||
|
KeyIdentifier getKey();
|
||||||
|
|
||||||
|
String getSignature();
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.crypto;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public interface SignatureManager {
|
||||||
|
|
||||||
|
JsonObject signMessageGson(String domain, String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign the canonical form of a JSON object
|
||||||
|
*
|
||||||
|
* @param obj The JSON object to canonicalize and sign
|
||||||
|
* @return The signature
|
||||||
|
*/
|
||||||
|
Signature sign(JsonObject obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign the message, using UTF-8 as decoding character set
|
||||||
|
*
|
||||||
|
* @param message The UTF-8 encoded message
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
default Signature sign(String message) {
|
||||||
|
return sign(message.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign the data
|
||||||
|
*
|
||||||
|
* @param data The data to sign
|
||||||
|
* @return The signature
|
||||||
|
*/
|
||||||
|
Signature sign(byte[] data);
|
||||||
|
|
||||||
|
}
|
@@ -1,80 +0,0 @@
|
|||||||
package io.kamax.mxisd.test;
|
|
||||||
|
|
||||||
import com.icegreen.greenmail.util.GreenMail;
|
|
||||||
import com.icegreen.greenmail.util.ServerSetupTest;
|
|
||||||
import io.kamax.matrix.MatrixID;
|
|
||||||
import io.kamax.matrix.ThreePidMedium;
|
|
||||||
import io.kamax.matrix._MatrixID;
|
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
|
||||||
import io.kamax.mxisd.Mxisd;
|
|
||||||
import io.kamax.mxisd.as.MatrixIdInvite;
|
|
||||||
import io.kamax.mxisd.config.MxisdConfig;
|
|
||||||
import io.kamax.mxisd.config.threepid.connector.EmailSmtpConfig;
|
|
||||||
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
|
||||||
import io.kamax.mxisd.threepid.connector.email.EmailSmtpConnector;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import javax.mail.Message;
|
|
||||||
import javax.mail.MessagingException;
|
|
||||||
import javax.mail.internet.MimeMessage;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import static junit.framework.TestCase.assertEquals;
|
|
||||||
|
|
||||||
public class MxisdEmailNotifTest {
|
|
||||||
|
|
||||||
private final String domain = "localhost";
|
|
||||||
private Mxisd m;
|
|
||||||
private GreenMail gm;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void before() {
|
|
||||||
EmailSmtpConfig smtpCfg = new EmailSmtpConfig();
|
|
||||||
smtpCfg.setPort(3025);
|
|
||||||
smtpCfg.setLogin("mxisd");
|
|
||||||
smtpCfg.setPassword("mxisd");
|
|
||||||
|
|
||||||
EmailConfig eCfg = new EmailConfig();
|
|
||||||
eCfg.setConnector(EmailSmtpConnector.ID);
|
|
||||||
eCfg.getIdentity().setFrom("mxisd@" + domain);
|
|
||||||
eCfg.getIdentity().setName("Mxisd Server (Unit Test)");
|
|
||||||
eCfg.getConnectors().put(EmailSmtpConnector.ID, GsonUtil.makeObj(smtpCfg));
|
|
||||||
|
|
||||||
MxisdConfig cfg = new MxisdConfig();
|
|
||||||
cfg.getMatrix().setDomain(domain);
|
|
||||||
cfg.getKey().setPath(":memory:");
|
|
||||||
cfg.getStorage().getProvider().getSqlite().setDatabase(":memory:");
|
|
||||||
cfg.getThreepid().getMedium().put(ThreePidMedium.Email.getId(), GsonUtil.makeObj(eCfg));
|
|
||||||
|
|
||||||
m = new Mxisd(cfg);
|
|
||||||
m.start();
|
|
||||||
|
|
||||||
gm = new GreenMail(ServerSetupTest.SMTP_IMAP);
|
|
||||||
gm.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void after() {
|
|
||||||
gm.stop();
|
|
||||||
m.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void forMatrixIdInvite() throws MessagingException {
|
|
||||||
gm.setUser("mxisd", "mxisd");
|
|
||||||
|
|
||||||
_MatrixID sender = MatrixID.asAcceptable("mxisd", domain);
|
|
||||||
_MatrixID recipient = MatrixID.asAcceptable("john", domain);
|
|
||||||
MatrixIdInvite idInvite = new MatrixIdInvite("!rid:" + domain, sender, recipient, ThreePidMedium.Email.getId(), "john@" + domain, Collections.emptyMap());
|
|
||||||
m.getNotif().sendForInvite(idInvite);
|
|
||||||
|
|
||||||
assertEquals(1, gm.getReceivedMessages().length);
|
|
||||||
MimeMessage msg = gm.getReceivedMessages()[0];
|
|
||||||
assertEquals(1, msg.getFrom().length);
|
|
||||||
assertEquals("\"Mxisd Server (Unit Test)\" <mxisd@localhost>", msg.getFrom()[0].toString());
|
|
||||||
assertEquals(1, msg.getRecipients(Message.RecipientType.TO).length);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -56,32 +56,32 @@ public class ExecDirectoryStoreTest extends ExecStoreTest {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecConfig.Directory getCfg() {
|
private ExecConfig getCfg() {
|
||||||
ExecConfig.Directory cfg = new ExecConfig().build().getDirectory();
|
ExecConfig cfg = new ExecConfig().build();
|
||||||
assertFalse(cfg.isEnabled());
|
assertFalse(cfg.isEnabled());
|
||||||
cfg.setEnabled(true);
|
cfg.setEnabled(true);
|
||||||
assertTrue(cfg.isEnabled());
|
assertTrue(cfg.isEnabled());
|
||||||
cfg.getSearch().getByName().getOutput().setType(ExecStore.JsonType);
|
cfg.getDirectory().getSearch().getByName().getOutput().setType(ExecStore.JsonType);
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecDirectoryStore getStore(ExecConfig.Directory cfg) {
|
private ExecDirectoryStore getStore(ExecConfig cfg) {
|
||||||
ExecDirectoryStore store = new ExecDirectoryStore(cfg, getMatrixCfg());
|
ExecDirectoryStore store = new ExecDirectoryStore(cfg, getMatrixCfg());
|
||||||
store.setExecutorSupplier(this::build);
|
store.setExecutorSupplier(this::build);
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecDirectoryStore getStore(String command) {
|
private ExecDirectoryStore getStore(String command) {
|
||||||
ExecConfig.Directory cfg = getCfg();
|
ExecConfig cfg = getCfg();
|
||||||
cfg.getSearch().getByName().setCommand(command);
|
cfg.getDirectory().getSearch().getByName().setCommand(command);
|
||||||
cfg.getSearch().getByThreepid().setCommand(command);
|
cfg.getDirectory().getSearch().getByThreepid().setCommand(command);
|
||||||
return getStore(cfg);
|
return getStore(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void byNameNoCommandDefined() {
|
public void byNameNoCommandDefined() {
|
||||||
ExecConfig.Directory cfg = getCfg();
|
ExecConfig cfg = getCfg();
|
||||||
assertTrue(StringUtils.isEmpty(cfg.getSearch().getByName().getCommand()));
|
assertTrue(StringUtils.isEmpty(cfg.getDirectory().getSearch().getByName().getCommand()));
|
||||||
ExecDirectoryStore store = getStore(cfg);
|
ExecDirectoryStore store = getStore(cfg);
|
||||||
|
|
||||||
UserDirectorySearchResult result = store.searchByDisplayName("user");
|
UserDirectorySearchResult result = store.searchByDisplayName("user");
|
||||||
|
@@ -62,17 +62,17 @@ public class ExecIdentityStoreTest extends ExecStoreTest {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecConfig.Identity getCfg() {
|
private ExecConfig getCfg() {
|
||||||
ExecConfig.Identity cfg = new ExecConfig().build().getIdentity();
|
ExecConfig cfg = new ExecConfig().build();
|
||||||
assertFalse(cfg.isEnabled());
|
assertFalse(cfg.isEnabled());
|
||||||
cfg.setEnabled(true);
|
cfg.setEnabled(true);
|
||||||
assertTrue(cfg.isEnabled());
|
assertTrue(cfg.isEnabled());
|
||||||
cfg.getLookup().getSingle().getOutput().setType(ExecStore.JsonType);
|
cfg.getIdentity().getLookup().getSingle().getOutput().setType(ExecStore.JsonType);
|
||||||
cfg.getLookup().getBulk().getOutput().setType(ExecStore.JsonType);
|
cfg.getIdentity().getLookup().getBulk().getOutput().setType(ExecStore.JsonType);
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecIdentityStore getStore(ExecConfig.Identity cfg) {
|
private ExecIdentityStore getStore(ExecConfig cfg) {
|
||||||
ExecIdentityStore store = new ExecIdentityStore(cfg, getMatrixCfg());
|
ExecIdentityStore store = new ExecIdentityStore(cfg, getMatrixCfg());
|
||||||
store.setExecutorSupplier(this::build);
|
store.setExecutorSupplier(this::build);
|
||||||
assertTrue(store.isLocal());
|
assertTrue(store.isLocal());
|
||||||
@@ -80,9 +80,9 @@ public class ExecIdentityStoreTest extends ExecStoreTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ExecIdentityStore getStore(String command) {
|
private ExecIdentityStore getStore(String command) {
|
||||||
ExecConfig.Identity cfg = getCfg();
|
ExecConfig cfg = getCfg();
|
||||||
cfg.getLookup().getSingle().setCommand(command);
|
cfg.getIdentity().getLookup().getSingle().setCommand(command);
|
||||||
cfg.getLookup().getBulk().setCommand(command);
|
cfg.getIdentity().getLookup().getBulk().setCommand(command);
|
||||||
return getStore(cfg);
|
return getStore(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -70,28 +70,28 @@ public class ExecProfileStoreTest extends ExecStoreTest {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecConfig.Profile getCfg() {
|
private ExecConfig getCfg() {
|
||||||
ExecConfig.Profile cfg = new ExecConfig().build().getProfile();
|
ExecConfig cfg = new ExecConfig().build();
|
||||||
assertFalse(cfg.isEnabled());
|
assertFalse(cfg.isEnabled());
|
||||||
cfg.setEnabled(true);
|
cfg.setEnabled(true);
|
||||||
assertTrue(cfg.isEnabled());
|
assertTrue(cfg.isEnabled());
|
||||||
cfg.getDisplayName().getOutput().setType(ExecStore.JsonType);
|
cfg.getProfile().getDisplayName().getOutput().setType(ExecStore.JsonType);
|
||||||
cfg.getThreePid().getOutput().setType(ExecStore.JsonType);
|
cfg.getProfile().getThreePid().getOutput().setType(ExecStore.JsonType);
|
||||||
cfg.getRole().getOutput().setType(ExecStore.JsonType);
|
cfg.getProfile().getRole().getOutput().setType(ExecStore.JsonType);
|
||||||
return cfg;
|
return cfg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecProfileStore getStore(ExecConfig.Profile cfg) {
|
private ExecProfileStore getStore(ExecConfig cfg) {
|
||||||
ExecProfileStore store = new ExecProfileStore(cfg);
|
ExecProfileStore store = new ExecProfileStore(cfg);
|
||||||
store.setExecutorSupplier(this::build);
|
store.setExecutorSupplier(this::build);
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecProfileStore getStore(String command) {
|
private ExecProfileStore getStore(String command) {
|
||||||
ExecConfig.Profile cfg = getCfg();
|
ExecConfig cfg = getCfg();
|
||||||
cfg.getDisplayName().setCommand(command);
|
cfg.getProfile().getDisplayName().setCommand(command);
|
||||||
cfg.getThreePid().setCommand(command);
|
cfg.getProfile().getThreePid().setCommand(command);
|
||||||
cfg.getRole().setCommand(command);
|
cfg.getProfile().getRole().setCommand(command);
|
||||||
return getStore(cfg);
|
return getStore(cfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,6 +23,7 @@ package io.kamax.mxisd.test.backend.rest;
|
|||||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||||
import io.kamax.matrix.MatrixID;
|
import io.kamax.matrix.MatrixID;
|
||||||
import io.kamax.matrix._MatrixID;
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.matrix._ThreePid;
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
import io.kamax.mxisd.backend.rest.RestProfileProvider;
|
import io.kamax.mxisd.backend.rest.RestProfileProvider;
|
||||||
import io.kamax.mxisd.config.rest.RestBackendConfig;
|
import io.kamax.mxisd.config.rest.RestBackendConfig;
|
||||||
@@ -34,6 +35,7 @@ import org.junit.Before;
|
|||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||||
@@ -42,27 +44,40 @@ import static org.junit.Assert.*;
|
|||||||
|
|
||||||
public class RestProfileProviderTest {
|
public class RestProfileProviderTest {
|
||||||
|
|
||||||
|
private static final int MockHttpPort = 65000;
|
||||||
|
private static final String MockHttpHost = "localhost";
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public WireMockRule wireMockRule = new WireMockRule(65000);
|
public WireMockRule wireMockRule = new WireMockRule(MockHttpPort);
|
||||||
|
|
||||||
private final String displayNameEndpoint = "/displayName";
|
private final String displayNameEndpoint = "/displayName";
|
||||||
|
|
||||||
private final _MatrixID userId = MatrixID.from("john", "matrix.localhost").valid();
|
private final _MatrixID userId = MatrixID.from("john", "matrix." + MockHttpHost).valid();
|
||||||
|
|
||||||
private RestProfileProvider p;
|
private RestProfileProvider p;
|
||||||
|
|
||||||
@Before
|
private RestBackendConfig getCfg(RestBackendConfig.Endpoints endpoints) {
|
||||||
public void before() {
|
|
||||||
ProfileEndpoints endpoints = new ProfileEndpoints();
|
|
||||||
endpoints.setDisplayName(displayNameEndpoint);
|
|
||||||
|
|
||||||
RestBackendConfig cfg = new RestBackendConfig();
|
RestBackendConfig cfg = new RestBackendConfig();
|
||||||
cfg.setEnabled(true);
|
cfg.setEnabled(true);
|
||||||
cfg.setHost("http://localhost:65000");
|
cfg.setHost("http://" + MockHttpHost + ":" + MockHttpPort);
|
||||||
cfg.getEndpoints().setProfile(endpoints);
|
cfg.setEndpoints(endpoints);
|
||||||
cfg.build();
|
cfg.build();
|
||||||
|
|
||||||
p = new RestProfileProvider(cfg);
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RestProfileProvider get(RestBackendConfig cfg) {
|
||||||
|
return new RestProfileProvider(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
ProfileEndpoints pEndpoints = new ProfileEndpoints();
|
||||||
|
pEndpoints.setDisplayName(displayNameEndpoint);
|
||||||
|
RestBackendConfig.Endpoints endpoints = new RestBackendConfig.Endpoints();
|
||||||
|
endpoints.setProfile(pEndpoints);
|
||||||
|
|
||||||
|
p = get(getCfg(endpoints));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -144,4 +159,26 @@ public class RestProfileProviderTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void forEmptyEndpoints() {
|
||||||
|
ProfileEndpoints pEndpoints = new ProfileEndpoints();
|
||||||
|
pEndpoints.setDisplayName("");
|
||||||
|
pEndpoints.setThreepids("");
|
||||||
|
pEndpoints.setRoles("");
|
||||||
|
|
||||||
|
RestBackendConfig.Endpoints endpoints = new RestBackendConfig.Endpoints();
|
||||||
|
endpoints.setProfile(pEndpoints);
|
||||||
|
|
||||||
|
RestProfileProvider p = get(getCfg(endpoints));
|
||||||
|
|
||||||
|
Optional<String> dn = p.getDisplayName(userId);
|
||||||
|
assertFalse(dn.isPresent());
|
||||||
|
|
||||||
|
List<String> roles = p.getRoles(userId);
|
||||||
|
assertTrue(roles.isEmpty());
|
||||||
|
|
||||||
|
List<_ThreePid> tpids = p.getThreepids(userId);
|
||||||
|
assertTrue(tpids.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 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.test.notification;
|
||||||
|
|
||||||
|
import com.icegreen.greenmail.util.GreenMail;
|
||||||
|
import com.icegreen.greenmail.util.ServerSetupTest;
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
import io.kamax.mxisd.Mxisd;
|
||||||
|
import io.kamax.mxisd.as.MatrixIdInvite;
|
||||||
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
|
import io.kamax.mxisd.config.threepid.connector.EmailSmtpConfig;
|
||||||
|
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
||||||
|
import io.kamax.mxisd.threepid.connector.email.EmailSmtpConnector;
|
||||||
|
import io.kamax.mxisd.threepid.session.ThreePidSession;
|
||||||
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import javax.mail.Message;
|
||||||
|
import javax.mail.MessagingException;
|
||||||
|
import javax.mail.internet.MimeBodyPart;
|
||||||
|
import javax.mail.internet.MimeMessage;
|
||||||
|
import javax.mail.internet.MimeMultipart;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static junit.framework.TestCase.assertEquals;
|
||||||
|
import static junit.framework.TestCase.assertTrue;
|
||||||
|
|
||||||
|
public class EmailNotificationTest {
|
||||||
|
|
||||||
|
private final String domain = "localhost";
|
||||||
|
private final String user = "mxisd";
|
||||||
|
private final String notifiee = "john";
|
||||||
|
private final String sender = user + "@" + domain;
|
||||||
|
private final String senderEmail = "\"Mxisd Server (Unit Test)\" <" + sender + ">";
|
||||||
|
private final String target = notifiee + "@" + domain;
|
||||||
|
|
||||||
|
private Mxisd m;
|
||||||
|
private GreenMail gm;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
EmailSmtpConfig smtpCfg = new EmailSmtpConfig();
|
||||||
|
smtpCfg.setPort(3025);
|
||||||
|
smtpCfg.setLogin(user);
|
||||||
|
smtpCfg.setPassword(user);
|
||||||
|
|
||||||
|
EmailConfig eCfg = new EmailConfig();
|
||||||
|
eCfg.setConnector(EmailSmtpConnector.ID);
|
||||||
|
eCfg.getIdentity().setFrom(sender);
|
||||||
|
eCfg.getIdentity().setName("Mxisd Server (Unit Test)");
|
||||||
|
eCfg.getConnectors().put(EmailSmtpConnector.ID, GsonUtil.makeObj(smtpCfg));
|
||||||
|
|
||||||
|
MxisdConfig cfg = new MxisdConfig();
|
||||||
|
cfg.getMatrix().setDomain(domain);
|
||||||
|
cfg.getKey().setPath(":memory:");
|
||||||
|
cfg.getStorage().getProvider().getSqlite().setDatabase(":memory:");
|
||||||
|
cfg.getThreepid().getMedium().put(ThreePidMedium.Email.getId(), GsonUtil.makeObj(eCfg));
|
||||||
|
|
||||||
|
m = new Mxisd(cfg);
|
||||||
|
m.start();
|
||||||
|
|
||||||
|
gm = new GreenMail(ServerSetupTest.SMTP_IMAP);
|
||||||
|
gm.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void after() {
|
||||||
|
gm.stop();
|
||||||
|
m.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void forMatrixIdInvite() throws MessagingException {
|
||||||
|
gm.setUser("mxisd", "mxisd");
|
||||||
|
|
||||||
|
_MatrixID sender = MatrixID.asAcceptable(user, domain);
|
||||||
|
_MatrixID recipient = MatrixID.asAcceptable(notifiee, domain);
|
||||||
|
MatrixIdInvite idInvite = new MatrixIdInvite(
|
||||||
|
"!rid:" + domain,
|
||||||
|
sender,
|
||||||
|
recipient,
|
||||||
|
ThreePidMedium.Email.getId(),
|
||||||
|
target,
|
||||||
|
Collections.emptyMap()
|
||||||
|
);
|
||||||
|
|
||||||
|
m.getNotif().sendForInvite(idInvite);
|
||||||
|
|
||||||
|
assertEquals(1, gm.getReceivedMessages().length);
|
||||||
|
MimeMessage msg = gm.getReceivedMessages()[0];
|
||||||
|
assertEquals(1, msg.getFrom().length);
|
||||||
|
assertEquals(senderEmail, msg.getFrom()[0].toString());
|
||||||
|
assertEquals(1, msg.getRecipients(Message.RecipientType.TO).length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void forValidation() throws MessagingException, IOException {
|
||||||
|
gm.setUser(user, user);
|
||||||
|
|
||||||
|
String token = RandomStringUtils.randomAlphanumeric(128);
|
||||||
|
ThreePidSession session = new ThreePidSession(
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
new ThreePid(ThreePidMedium.Email.getId(), target),
|
||||||
|
"",
|
||||||
|
1,
|
||||||
|
"",
|
||||||
|
token
|
||||||
|
);
|
||||||
|
|
||||||
|
m.getNotif().sendForValidation(session);
|
||||||
|
|
||||||
|
assertEquals(1, gm.getReceivedMessages().length);
|
||||||
|
MimeMessage msg = gm.getReceivedMessages()[0];
|
||||||
|
assertEquals(1, msg.getFrom().length);
|
||||||
|
assertEquals(senderEmail, msg.getFrom()[0].toString());
|
||||||
|
assertEquals(1, msg.getRecipients(Message.RecipientType.TO).length);
|
||||||
|
|
||||||
|
// We just check on the text/plain one. HTML is multipart and it's difficult so we skip
|
||||||
|
MimeMultipart content = (MimeMultipart) msg.getContent();
|
||||||
|
MimeBodyPart mbp = (MimeBodyPart) content.getBodyPart(0);
|
||||||
|
String mbpContent = mbp.getContent().toString();
|
||||||
|
assertTrue(mbpContent.contains(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.test.storage.crypto;
|
||||||
|
|
||||||
|
public class KeyTest {
|
||||||
|
|
||||||
|
// As per https://matrix.org/docs/spec/appendices.html#signing-key
|
||||||
|
public static final String Private = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1";
|
||||||
|
|
||||||
|
// The corresponding public key, not being documented in the spec
|
||||||
|
public static final String Public = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
|
*
|
||||||
|
* 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.test.storage.crypto;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
import io.kamax.matrix.json.MatrixJson;
|
||||||
|
import io.kamax.mxisd.storage.crypto.*;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.core.Is.is;
|
||||||
|
import static org.hamcrest.core.IsEqual.equalTo;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
public class SignatureManagerTest {
|
||||||
|
|
||||||
|
private static SignatureManager signMgr;
|
||||||
|
|
||||||
|
private static SignatureManager build(String keySeed) {
|
||||||
|
Ed25519Key key = new Ed25519Key(new Ed2219RegularKeyIdentifier("0"), keySeed);
|
||||||
|
KeyStore store = new MemoryKeyStore();
|
||||||
|
store.add(key);
|
||||||
|
|
||||||
|
return new Ed25519SignatureManager(new Ed25519KeyManager(store));
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() {
|
||||||
|
signMgr = build(KeyTest.Private);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testSign(String value, String sign) {
|
||||||
|
assertThat(signMgr.sign(value).getSignature(), is(equalTo(sign)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// As per https://matrix.org/docs/spec/appendices.html#json-signing
|
||||||
|
@Test
|
||||||
|
public void onEmptyObject() {
|
||||||
|
String value = "{}";
|
||||||
|
String sign = "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ";
|
||||||
|
|
||||||
|
testSign(value, sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
// As per https://matrix.org/docs/spec/appendices.html#json-signing
|
||||||
|
@Test
|
||||||
|
public void onSimpleObject() {
|
||||||
|
JsonObject data = new JsonObject();
|
||||||
|
data.addProperty("one", 1);
|
||||||
|
data.addProperty("two", "Two");
|
||||||
|
|
||||||
|
String value = GsonUtil.get().toJson(data);
|
||||||
|
String sign = "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw";
|
||||||
|
|
||||||
|
testSign(value, sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onFederationHeader() {
|
||||||
|
SignatureManager mgr = build("1QblgjFeL3IxoY4DKOR7p5mL5sQTC0ChmeMJlqb4d5M");
|
||||||
|
|
||||||
|
JsonObject o = new JsonObject();
|
||||||
|
o.addProperty("method", "GET");
|
||||||
|
o.addProperty("uri", "/_matrix/federation/v1/query/directory?room_alias=%23a%3Amxhsd.local.kamax.io%3A8447");
|
||||||
|
o.addProperty("origin", "synapse.local.kamax.io");
|
||||||
|
o.addProperty("destination", "mxhsd.local.kamax.io:8447");
|
||||||
|
|
||||||
|
String signExpected = "SEMGSOJEsoalrBfHqPO2QrSlbLaUYLHLk4e3q4IJ2JbgvCynT1onp7QF1U4Sl3G3NzybrgdnVvpqcaEgV0WPCw";
|
||||||
|
Signature signProduced = mgr.sign(o);
|
||||||
|
assertThat(signProduced.getSignature(), is(equalTo(signExpected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onIdentityLookup() {
|
||||||
|
String value = MatrixJson.encodeCanonical("{\n" + " \"address\": \"mxisd-federation-test@kamax.io\",\n"
|
||||||
|
+ " \"medium\": \"email\",\n" + " \"mxid\": \"@mxisd-lookup-test:kamax.io\",\n"
|
||||||
|
+ " \"not_after\": 253402300799000,\n" + " \"not_before\": 0,\n" + " \"ts\": 1523482030147\n" + "}");
|
||||||
|
|
||||||
|
String sign = "ObKA4PNQh2g6c7Yo2QcTcuDgIwhknG7ZfqmNYzbhrbLBOqZomU22xX9raufN2Y3ke1FXsDqsGs7WBDodmzZJCg";
|
||||||
|
testSign(value, sign);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user