Compare commits

...

18 Commits

Author SHA1 Message Date
Max Dor
acd8c7d7c5 Skeleton for full support of all key types 2019-02-17 02:08:50 +01:00
Max Dor
249cc0ea92 Improve troubleshooting doc/flows
- Use better wording for unknown server error
- Add basic troubleshooting doc
2019-02-17 02:06:13 +01:00
Max Dor
99697d7c75 Various doc fixes and improvements 2019-02-14 00:39:33 +01:00
Max Dor
e133e120d7 Fix Exec store breakage following change to new config format 2019-02-13 21:08:56 +01:00
Max Dor
e39d6bfa10 Better handling of YAML->Java object config processing 2019-02-13 21:08:35 +01:00
Max Dor
217bc423ed Fix edge case of error when parsing valid config for directory 2019-02-13 20:19:26 +01:00
Max Dor
8f0654c34e Fix oversight in potentially printing credentials to log 2019-02-13 12:40:01 +01:00
Max Dor
8afdb3ed83 Improve feedback in case of parsing error in config file 2019-02-11 03:18:50 +01:00
Max Dor
bd4ccbc5e5 Fix some edge cases configuration parsing
- Optional in getter but not in setter seems problematic
- Document config parsing better
- Properly handle empty values in REST Profile so no HTTP call is made
- Possibly related to #113
2019-02-11 02:56:02 +01:00
Max Dor
6d1c6ed109 Last cosmetic changes for v1.3.0 2019-02-10 20:41:40 +01:00
Max Dor
1619f5311c Add email verification notification test (/requestToken) 2019-02-09 15:18:06 +01:00
Max Dor
6fa36ea092 Add missing header 2019-02-07 01:39:10 +01:00
Max Dor
471e06536b Improve logging 2019-02-07 01:35:43 +01:00
Max Dor
3a6b75996c Use a proper HTTP client when discovering federated IS to avoid 4xx's 2019-02-06 23:23:40 +01:00
Max Dor
566e4f3137 Correctly handle 3PID notification revamping (forgotten code) 2019-02-06 22:27:42 +01:00
Max Dor
a4c18dee5d Handle possibly trailing slashes for older versions of mxisd 2019-02-06 19:55:22 +01:00
Max Dor
8d6850d346 Link to targeted setups in main README 2019-02-06 04:03:33 +01:00
Max Dor
67bc18af7d Improve docs 2019-02-06 03:53:42 +01:00
89 changed files with 1946 additions and 713 deletions

View File

@@ -14,13 +14,14 @@ mxisd - Federated Matrix Identity Server
# Overview # Overview
mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures with [enhanced features](#features). mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures with [enhanced features](#features).
As an enhanced Identity service, it implements the [Matrix Identity service API](https://kamax.io/matrix/api/identity_service/unstable.html) As an enhanced Identity service, it implements the [Identity service API](https://matrix.org/docs/spec/identity_service/r0.1.0.html)
and several [extra features](#features) that greatly enhance user experience within Matrix. and several [extra features](#features) that greatly enhance user experience within Matrix.
It is the one stop shop for anything regarding Authentication, Directory and Identity management in Matrix built in a It is the one stop shop for anything regarding Authentication, Directory and Identity management in Matrix built in a
single coherent product. single coherent product.
mxisd is specifically designed to connect to an existing on-premise Identity store (AD/Samba/LDAP, SQL Database, mxisd is specifically designed to connect to an existing on-premise Identity store (AD/Samba/LDAP, SQL Database,
Web services/app, etc.) and ease the integration of a Matrix infrastructure within an existing one. Web services/app, etc.) and ease the integration of a Matrix infrastructure within an existing one.
Check [our FAQ entry](docs/faq.md#what-kind-of-setup-is-mxisd-really-designed-for) to know if mxisd is a good fit for you.
The core principle of mxisd is to map between Matrix IDs and 3PIDs (Third-Party IDentifiers) for the Homeserver and its The core principle of mxisd is to map between Matrix IDs and 3PIDs (Third-Party IDentifiers) for the Homeserver and its
users. 3PIDs can be anything that uniquely and globally identify a user, like: users. 3PIDs can be anything that uniquely and globally identify a user, like:
@@ -33,15 +34,15 @@ users. 3PIDs can be anything that uniquely and globally identify a user, like:
If you are unfamiliar with the Identity vocabulary and concepts in Matrix, **please read this [introduction](docs/concepts.md)**. If you are unfamiliar with the Identity vocabulary and concepts in Matrix, **please read this [introduction](docs/concepts.md)**.
# Features # Features
[Identity](docs/features/identity.md): As a [regular Matrix Identity service](https://kamax.io/matrix/api/identity_service/unstable.html#general-principles): [Identity](docs/features/identity.md): As a [regular Matrix Identity service](https://matrix.org/docs/spec/identity_service/r0.1.0.html#general-principles):
- Search for people by 3PID using its own Identity stores - Search for people by 3PID using its own Identity stores
([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#association-lookup)) ([Spec](https://matrix.org/docs/spec/identity_service/r0.1.0.html#association-lookup))
- Invite people to rooms by 3PID using its own Identity stores, with notifications to the invitee (Email, SMS, etc.) - Invite people to rooms by 3PID using its own Identity stores, with notifications to the invitee (Email, SMS, etc.)
([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#post-matrix-identity-api-v1-store-invite)) ([Spec](https://matrix.org/docs/spec/identity_service/r0.1.0.html#post-matrix-identity-api-v1-store-invite))
- Allow users to add 3PIDs to their settings/profile - Allow users to add 3PIDs to their settings/profile
([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#establishing-associations)) ([Spec](https://matrix.org/docs/spec/identity_service/r0.1.0.html#establishing-associations))
- Register accounts on your Homeserver with 3PIDs - Register accounts on your Homeserver with 3PIDs
([Spec](https://kamax.io/matrix/api/identity_service/unstable.html#establishing-associations)) ([Spec](https://matrix.org/docs/spec/identity_service/r0.1.0.html#establishing-associations))
As an enhanced Identity service: As an enhanced Identity service:
- [Federation](docs/features/federation.md): Use a recursive lookup mechanism when searching and inviting people by 3PID, - [Federation](docs/features/federation.md): Use a recursive lookup mechanism when searching and inviting people by 3PID,
@@ -67,10 +68,15 @@ As an enhanced Identity service:
- Users can directly find each other using whatever attribute is relevant within your Identity store - Users can directly find each other using whatever attribute is relevant within your Identity store
- Federate your Identity server so you can discover others and/or others can discover you - Federate your Identity server so you can discover others and/or others can discover you
Also, check [our FAQ entry](docs/faq.md#what-kind-of-setup-is-mxisd-really-designed-for) to know if mxisd is a good fit for you.
# Getting started # Getting started
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/))

View File

@@ -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 {

View File

@@ -16,6 +16,18 @@ of the Matrix protocol is required for some advanced features.
If all fails, come over to [the project room](https://matrix.to/#/#mxisd:kamax.io) and we'll do our best to get you If all fails, come over to [the project room](https://matrix.to/#/#mxisd:kamax.io) and we'll do our best to get you
started and answer questions you might have. started and answer questions you might have.
### What kind of setup is mxisd really designed for?
mxisd is primarily designed for setups that:
- [Care for their privacy](https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy)
- Have their own [domains](https://en.wikipedia.org/wiki/Domain_name)
- Use those domains for their email addresses and all other services
- Already have an [Identity store](stores/README.md), typically [LDAP-based](stores/ldap.md).
If you meet all the conditions, then you are the prime use case we designed mxisd for.
If you meet some of the conditions, but not all, mxisd will still be a good fit for you but you won't fully enjoy all its
features.
### Do I need to use mxisd if I run a Homeserver? ### Do I need to use mxisd if I run a Homeserver?
No, but it is strongly recommended, even if you don't use any Identity store or integration. No, but it is strongly recommended, even if you don't use any Identity store or integration.
@@ -23,9 +35,6 @@ In its default configuration, mxisd uses other federated public servers when per
It can also [be configured](features/identity.md#lookups) to use the central matrix.org servers, giving you access to at It can also [be configured](features/identity.md#lookups) to use the central matrix.org servers, giving you access to at
least the same information as if you were not running it. least the same information as if you were not running it.
It will also give your users a choice to make their 3PIDs available publicly, ensuring they are made aware of the
privacy consequences, which is not the case with the central Matrix.org servers.
So mxisd is like your gatekeeper and guardian angel. It does not change what you already know, just adds some nice So mxisd is like your gatekeeper and guardian angel. It does not change what you already know, just adds some nice
simple features on top of it. simple features on top of it.
@@ -47,13 +56,14 @@ Accounts cannot currently migrate/move from one server to another.
See a [brief explanation document](concepts.md) about Matrix and mxisd concepts and vocabulary. See a [brief explanation document](concepts.md) about Matrix and mxisd concepts and vocabulary.
### I already use the synapse LDAP3 auth provider. Why should I care about mxisd? ### I already use the synapse LDAP3 auth provider. Why should I care about mxisd?
The [synapse LDAP3 auth provider](https://github.com/matrix-org/matrix-synapse-ldap3) is not longer maintained and The [synapse LDAP3 auth provider](https://github.com/matrix-org/matrix-synapse-ldap3) is not longer maintained despite
only handles on specific flow: validate credentials at login. saying so and only handles on specific flow: validate credentials at login.
It does not: It does not:
- Auto-provision user profiles - Auto-provision user profiles
- Integrate with Identity management - Integrate with Identity management
- Integrate with Directory searches - Integrate with Directory searches
- Integrate with Profile data
mxisd is a replacement and enhancement of it, offering coherent results in all areas, which the LDAP3 auth provider mxisd is a replacement and enhancement of it, offering coherent results in all areas, which the LDAP3 auth provider
does not. does not.
@@ -74,7 +84,7 @@ No.
In its default configuration, mxisd does not talk to the central Identity server matrix.org to avoid leaking your private In its default configuration, mxisd does not talk to the central Identity server matrix.org to avoid leaking your private
data and those of people you might know. data and those of people you might know.
mxisd [can be configured](features/identity.md#lookups) to talk to the central Identity servers if you wish. [You can configure it](features/identity.md#lookups) to talk to the central Identity servers if you wish.
### So mxisd is just a big hack! I don't want to use non-official features! ### So mxisd is just a big hack! I don't want to use non-official features!
mxisd primary concerns are your privacy and to always be compatible with the Matrix ecosystem and the Identity service API. mxisd primary concerns are your privacy and to always be compatible with the Matrix ecosystem and the Identity service API.

View File

@@ -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.

View File

@@ -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.

View File

@@ -1,7 +1,5 @@
# 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 [Unofficial Matrix Identity Service API](https://kamax.io/matrix/api/identity_service/unstable.html).
## Lookups ## Lookups
If you would like to use the central matrix.org Identity server to ensure maximum discovery at the cost of potentially If you would like to use the central matrix.org Identity server to ensure maximum discovery at the cost of potentially

View File

@@ -6,8 +6,7 @@
5. [Validate](#validate) 5. [Validate](#validate)
6. [Next steps](#next-steps) 6. [Next steps](#next-steps)
Following these quick start instructions, you will have a basic setup that can perform recursive/federated lookups and Following these quick start instructions, you will have a basic setup that can perform recursive/federated lookups.
talk to the central Matrix.org Identity server.
This will be a good ground work for further integration with features and your existing Identity stores. This will be a good ground work for further integration with features and your existing Identity stores.
--- ---
@@ -24,13 +23,17 @@ You will need:
- Working Homeserver, ideally with working federation - Working Homeserver, ideally with working federation
- Reverse proxy with regular TLS/SSL certificate (Let's encrypt) for your mxisd domain - Reverse proxy with regular TLS/SSL certificate (Let's encrypt) for your mxisd domain
As synapse requires an HTTPS connection when talking to an Identity service, **a reverse proxy is required** as mxisd does If you use synapse:
not support HTTPS listener at this time. - It requires an HTTPS connection when talking to an Identity service, **a reverse proxy is required** as mxisd does
not support HTTPS listener at this time.
- HTTPS is hardcoded when talking to the Identity server. If your Identity server URL in your client is `https://matrix.example.org/`,
then you need to ensure `https://matrix.example.org/_matrix/identity/api/v1/...` will reach mxisd if called from the synapse host.
In doubt, test with `curl` or similar.
For maximum integration, it is best to have your Homeserver and mxisd reachable via the same hostname. For maximum integration, it is best to have your Homeserver and mxisd reachable via the same public hostname.
Be aware of a [NAT/Reverse proxy gotcha](https://github.com/kamax-matrix/mxisd/wiki/Gotchas#nating) if you use the same Be aware of a [NAT/Reverse proxy gotcha](https://github.com/kamax-matrix/mxisd/wiki/Gotchas#nating) if you use the same
hostname. host.
The following Quick Start guide assumes you will host the Homeserver and mxisd under the same hostname. The following Quick Start guide assumes you will host the Homeserver and mxisd under the same hostname.
If you would like a high-level view of the infrastructure and how each feature is integrated, see the If you would like a high-level view of the infrastructure and how each feature is integrated, see the
@@ -51,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.)
@@ -83,9 +79,9 @@ ProxyPass /_matrix/identity http://0.0.0.0:8090/_matrix/identity
Typical configuration would look like: Typical configuration would look like:
```apache ```apache
<VirtualHost *:443> <VirtualHost *:443>
ServerName 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
@@ -107,9 +103,9 @@ Typical configuration would look like:
```nginx ```nginx
server { server {
listen 443 ssl; listen 443 ssl;
server_name 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;
@@ -130,17 +126,17 @@ Add your mxisd domain into the `homeserver.yaml` at `trusted_third_party_id_serv
In a typical configuration, you would end up with something similar to: In a typical configuration, you would end up with something similar to:
```yaml ```yaml
trusted_third_party_id_servers: trusted_third_party_id_servers:
- example.org - matrix.example.org
``` ```
It is recommended to remove `matrix.org` and `vector.im` (or any other default entry) from your configuration so only It is **highly recommended** to remove `matrix.org` and `vector.im` (or any other default entry) from your configuration
your own Identity server is authoritative for your HS. so only your own Identity server is authoritative for your HS.
## Validate ## Validate
**NOTE:** In case your homeserver has no working federation, step 5 will not happen. If step 4 took place, consider **NOTE:** In case your homeserver has no working federation, step 5 will not happen. If step 4 took place, consider
your installation validated. your installation validated.
1. Log in using your Matrix client and set `https://example.org` as your Identity server URL, replacing `example.org` by 1. Log in using your Matrix client and set `https://matrix.example.org` as your Identity server URL, replacing `matrix.example.org`
the relevant hostname which you configured in your reverse proxy. by the relevant hostname which you configured in your reverse proxy.
2. Create a new empty room. All further actions will take place in this room. 2. Create a new empty room. All further actions will take place in this room.
3. Invite `mxisd-federation-test@kamax.io` 3. Invite `mxisd-federation-test@kamax.io`
4. The 3PID invite should be turned into a Matrix invite to `@mxisd-lookup-test:kamax.io`. 4. The 3PID invite should be turned into a Matrix invite to `@mxisd-lookup-test:kamax.io`.
@@ -148,7 +144,8 @@ 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

View File

@@ -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

View File

@@ -39,7 +39,7 @@
| [Authentication](../features/authentication.md) | Yes | | [Authentication](../features/authentication.md) | Yes |
| [Directory](../features/directory.md) | Yes | | [Directory](../features/directory.md) | Yes |
| [Identity](../features/identity.md) | Yes | | [Identity](../features/identity.md) | Yes |
| [Profile](#profile) | Yes | | [Profile](../features/profile.md) | Yes |
This Identity Store lets you run arbitrary commands to handle the various requests in each support feature. This Identity Store lets you run arbitrary commands to handle the various requests in each support feature.
It is the most versatile Identity store of mxisd, allowing you to connect any kind of logic with any executable/script. It is the most versatile Identity store of mxisd, allowing you to connect any kind of logic with any executable/script.
@@ -199,7 +199,7 @@ exec:
DOMAIN: '{domain}' DOMAIN: '{domain}'
``` ```
With Authentication enabled, run `/opt/mxisd-exec/auth.sh` when validating credentials, providing: With Authentication enabled, run `/opt/mxisd-exec/auth.sh` when validating credentials, providing:
- A single command-line argument to provide the `localoart` as username - A single command-line argument to provide the `localpart` as username
- A plain text string with the password token for standard input, which will be replaced by the password to check - A plain text string with the password token for standard input, which will be replaced by the password to check
- A single environment variable `DOMAIN` containing Matrix ID domain, if given - A single environment variable `DOMAIN` containing Matrix ID domain, if given
@@ -207,26 +207,34 @@ The command will use the default values for:
- Success exit status of `0` - Success exit status of `0`
- Failure exit status of `1` - Failure exit status of `1`
- Any other exit status considered as error - Any other exit status considered as error
- The standard output processing as not processed - Standard output will not be processed
#### 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

View File

@@ -2,12 +2,12 @@
https://firebase.google.com/ https://firebase.google.com/
## Features ## Features
| Name | Supported? | | Name | Supported |
|----------------|------------| |-------------------------------------------------|-----------|
| Authentication | Yes | | [Authentication](../features/authentication.md) | Yes |
| Directory | No | | [Directory](../features/directory.md) | No |
| Identity | Yes | | [Identity](../features/identity.md) | Yes |
| Profile | No | | [Profile](../features/profile.md) | No |
## Requirements ## Requirements
This backend requires a suitable Matrix client capable of performing Firebase authentication and passing the following This backend requires a suitable Matrix client capable of performing Firebase authentication and passing the following

View File

@@ -8,12 +8,12 @@
For NetIQ, replace all the `ldap` prefix in the configuration by `netiq`. For NetIQ, replace all the `ldap` prefix in the configuration by `netiq`.
## Features ## Features
| Name | Supported? | | Name | Supported |
|----------------|------------| |-------------------------------------------------|-----------|
| Authentication | Yes | | [Authentication](../features/authentication.md) | Yes |
| Directory | Yes | | [Directory](../features/directory.md) | Yes |
| Identity | Yes | | [Identity](../features/identity.md) | Yes |
| Profile | Yes | | [Profile](../features/profile.md) | Yes |
## Getting started ## Getting started
### Base ### Base
@@ -113,16 +113,18 @@ configuration item is needed to get started.
- `ldap.identity.medium`: Namespace to overwrite generated queries from the list of attributes for each 3PID medium. - `ldap.identity.medium`: Namespace to overwrite generated queries from the list of attributes for each 3PID medium.
### Authentication ### Authentication
No further configuration is needed to use the Authentication feature with LDAP once globally enabled and configured. After you have configured and enabled the [feature itself](../features/authentication.md), no further configuration is
needed with this identity store to make it work.
Profile auto-fill is enabled by default. It will use the `ldap.attribute.name` and `ldap.attribute.threepid` configuration Profile auto-fill is enabled by default. It will use the `ldap.attribute.name` and `ldap.attribute.threepid` configuration
options to get a lit of attributes to be used to build the user profile to pass on to synapse during authentication. options to get a lit of attributes to be used to build the user profile to pass on to synapse during authentication.
#### Configuration #### Configuration
- `ldap.auth.filter`: Specific user filter applied during identity search. Global filter is used if blank/not set. - `ldap.auth.filter`: Specific user filter applied during username search. Global filter is used if blank/not set.
### Directory ### Directory
No further configuration is needed to use the Directory feature with LDAP once globally enabled and configured. After you have configured and enabled the [feature itself](../features/directory.md), no further configuration is
needed with this identity store to make it work.
#### Configuration #### Configuration
To set a specific filter applied during directory search, use `ldap.directory.filter` To set a specific filter applied during directory search, use `ldap.directory.filter`

View File

@@ -6,12 +6,12 @@
- SQLite - SQLite
## Features ## Features
| Name | Supported? | | Name | Supported |
|----------------|------------| |-------------------------------------------------|-----------|
| Authentication | No | | [Authentication](../features/authentication.md) | No |
| Directory | Yes | | [Directory](../features/directory.md) | Yes |
| Identity | Yes | | [Identity](../features/identity.md) | Yes |
| Profile | Yes | | [Profile](../features/profile.md) | Yes |
Due to the implementation complexity of supporting arbitrary hashing/encoding mechanisms or auth flow, Authentication Due to the implementation complexity of supporting arbitrary hashing/encoding mechanisms or auth flow, Authentication
will be out of scope of SQL Identity stores and should be done via one of the other identity stores, typically will be out of scope of SQL Identity stores and should be done via one of the other identity stores, typically

View File

@@ -2,12 +2,12 @@
Synapse's Database itself can be used as an Identity store. Synapse's Database itself can be used as an Identity store.
## Features ## Features
| Name | Supported? | | Name | Supported |
|----------------|------------| |-------------------------------------------------|-----------|
| Authentication | No | | [Authentication](../features/authentication.md) | No |
| Directory | Yes | | [Directory](../features/directory.md) | Yes |
| Identity | Yes | | [Identity](../features/identity.md) | Yes |
| Profile | Yes | | [Profile](../features/profile.md) | Yes |
Authentication is done by Synapse itself. Authentication is done by Synapse itself.

View File

@@ -5,12 +5,12 @@ Two types of connections are required for full support:
- Direct SQL access - Direct SQL access
## Features ## Features
| Name | Supported? | | Name | Supported |
|----------------|------------| |-------------------------------------------------|-----------|
| Authentication | Yes | | [Authentication](../features/authentication.md) | Yes |
| Directory | Yes | | [Directory](../features/directory.md) | Yes |
| Identity | Yes | | [Identity](../features/identity.md) | Yes |
| Profile | No | | [Profile](../features/profile.md) | No |
## Requirements ## Requirements
- [Wordpress](https://wordpress.org/download/) >= 4.4 - [Wordpress](https://wordpress.org/download/) >= 4.4

View File

@@ -1,6 +1,4 @@
# Email notifications - SMTP connector # Email notifications - SMTP connector
Enabled by default.
Connector ID: `smtp` Connector ID: `smtp`
## Configuration ## Configuration

View File

@@ -1,6 +1,4 @@
# SMS notifications - Twilio connector # SMS notifications - Twilio connector
Enabled by default.
Connector ID: `twilio` Connector ID: `twilio`
## Configuration ## Configuration

View File

@@ -26,16 +26,10 @@ notification:
html: <Path to file containing the HTML part of the email. Do not set to not use one> html: <Path to file containing the HTML part of the email. Do not set to not use one>
session: session:
validation: validation:
local: subject: <Subject of the email notification sent for 3PID sessions>
subject: <Subject of the email notification sent for local 3PID sessions> body:
body: text: <Path to file containing the raw text part of the email. Do not set to not use one>
text: <Path to file containing the raw text part of the email. Do not set to not use one> html: <Path to file containing the HTML part of the email. Do not set to not use one>
html: <Path to file containing the HTML part of the email. Do not set to not use one>
remote:
subject: <Subject of the email notification sent for remote 3PID sessions>
body:
text: <Path to file containing the raw text part of the email. Do not set to not use one>
html: <Path to file containing the HTML part of the email. Do not set to not use one>
unbind: unbind:
fraudulent: fraudulent:
subject: <Subject of the email notification sent for potentially fraudulent 3PID unbinds> subject: <Subject of the email notification sent for potentially fraudulent 3PID unbinds>

View File

@@ -18,9 +18,7 @@ threepid:
template: template:
invite: '/path/to/invite-template.eml' invite: '/path/to/invite-template.eml'
session: session:
validation: validation: '/path/to/validate-template.eml'
local: '/path/to/validate-local-template.eml'
remote: '/path/to/validate-remote-template.eml'
unbind: unbind:
frandulent: '/path/to/unbind-fraudulent-template.eml' frandulent: '/path/to/unbind-fraudulent-template.eml'
generic: generic:
@@ -53,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.
@@ -61,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
View 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).

View File

@@ -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

View File

@@ -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;

View File

@@ -53,6 +53,7 @@ public class HttpMxisd {
public void start() { public void start() {
m.start(); m.start();
HttpHandler helloHandler = SaneHandler.around(new HelloHandler());
HttpHandler asNotFoundHandler = SaneHandler.around(new AsNotFoundHandler(m.getAs())); HttpHandler asNotFoundHandler = SaneHandler.around(new AsNotFoundHandler(m.getAs()));
HttpHandler asTxnHandler = SaneHandler.around(new AsTransactionHandler(m.getAs())); HttpHandler asTxnHandler = SaneHandler.around(new AsTransactionHandler(m.getAs()));
HttpHandler storeInvHandler = SaneHandler.around(new StoreInviteHandler(m.getConfig().getServer(), m.getInvitationManager(), m.getKeyManager())); HttpHandler storeInvHandler = SaneHandler.around(new StoreInviteHandler(m.getConfig().getServer(), m.getInvitationManager(), m.getKeyManager()));
@@ -79,8 +80,9 @@ public class HttpMxisd {
.get(EphemeralKeyIsValidHandler.Path, SaneHandler.around(new EphemeralKeyIsValidHandler())) .get(EphemeralKeyIsValidHandler.Path, SaneHandler.around(new EphemeralKeyIsValidHandler()))
// Identity endpoints // Identity endpoints
.get(HelloHandler.Path, SaneHandler.around(new HelloHandler())) .get(HelloHandler.Path, helloHandler)
.get(SingleLookupHandler.Path, SaneHandler.around(new SingleLookupHandler(m.getIdentity(), m.getSign()))) .get(HelloHandler.Path + "/", helloHandler) // Be lax with possibly trailing slash
.get(SingleLookupHandler.Path, SaneHandler.around(new SingleLookupHandler(m.getConfig(), m.getIdentity(), m.getSign())))
.post(BulkLookupHandler.Path, SaneHandler.around(new BulkLookupHandler(m.getIdentity()))) .post(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())))
@@ -109,7 +111,6 @@ public class HttpMxisd {
public void stop() { public void stop() {
httpSrv.stop(); httpSrv.stop();
m.stop(); m.stop();
} }

View File

@@ -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;
@@ -40,6 +38,7 @@ import io.kamax.mxisd.lookup.provider.BridgeFetcher;
import io.kamax.mxisd.lookup.provider.RemoteIdentityServerFetcher; import io.kamax.mxisd.lookup.provider.RemoteIdentityServerFetcher;
import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.lookup.strategy.LookupStrategy;
import io.kamax.mxisd.lookup.strategy.RecursivePriorityLookupStrategy; import io.kamax.mxisd.lookup.strategy.RecursivePriorityLookupStrategy;
import io.kamax.mxisd.matrix.IdentityServerUtils;
import io.kamax.mxisd.notification.NotificationHandlerSupplier; import io.kamax.mxisd.notification.NotificationHandlerSupplier;
import io.kamax.mxisd.notification.NotificationHandlers; import io.kamax.mxisd.notification.NotificationHandlers;
import io.kamax.mxisd.notification.NotificationManager; import io.kamax.mxisd.notification.NotificationManager;
@@ -47,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;
@@ -55,42 +57,43 @@ import java.util.ServiceLoader;
public class Mxisd { public class Mxisd {
protected MxisdConfig cfg; private MxisdConfig cfg;
protected CloseableHttpClient httpClient; private CloseableHttpClient httpClient;
protected IRemoteIdentityServerFetcher srvFetcher; private IRemoteIdentityServerFetcher srvFetcher;
protected IStorage store; private IStorage store;
protected KeyManager keyMgr; private Ed25519KeyManager keyMgr;
protected SignatureManager signMgr; private SignatureManager signMgr;
// Features // Features
protected AuthManager authMgr; private AuthManager authMgr;
protected DirectoryManager dirMgr; private DirectoryManager dirMgr;
protected LookupStrategy idStrategy; private LookupStrategy idStrategy;
protected InvitationManager invMgr; private InvitationManager invMgr;
protected ProfileManager pMgr; private ProfileManager pMgr;
protected AppSvcManager asHander; private AppSvcManager asHander;
protected SessionManager sessMgr; private SessionManager sessMgr;
protected NotificationManager notifMgr; private NotificationManager notifMgr;
public Mxisd(MxisdConfig cfg) { public Mxisd(MxisdConfig cfg) {
this.cfg = cfg.build(); this.cfg = cfg.build();
} }
protected void build() { private void build() {
httpClient = HttpClients.custom() httpClient = HttpClients.custom()
.setUserAgent("mxisd") .setUserAgent("mxisd")
.setMaxConnPerRoute(Integer.MAX_VALUE) .setMaxConnPerRoute(Integer.MAX_VALUE)
.setMaxConnTotal(Integer.MAX_VALUE) .setMaxConnTotal(Integer.MAX_VALUE)
.build(); .build();
IdentityServerUtils.setHttpClient(httpClient);
srvFetcher = new RemoteIdentityServerFetcher(httpClient); srvFetcher = new RemoteIdentityServerFetcher(httpClient);
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());
@@ -103,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);

View File

@@ -22,44 +22,52 @@ 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.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 {
public static void main(String[] args) throws IOException { private static final Logger log = LoggerFactory.getLogger("App");
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);
System.out.println("Loaded configuration from " + cfgFile);
} else {
System.out.println("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();
System.out.println("------------- mxisd stopped -------------"); log.info("------------- mxisd stopped -------------");
})); }));
mxisd.start(); mxisd.start();
System.out.println("------------- 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);

View File

@@ -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());
} }

View File

@@ -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;
} }

View File

@@ -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;
} }

View File

@@ -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) {

View File

@@ -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)));
} }

View File

@@ -37,4 +37,5 @@ public class LookupSingleRequestJson {
public String getAddress() { public String getAddress() {
return address; return address;
} }
} }

View File

@@ -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());

View File

@@ -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() {
@@ -64,8 +63,8 @@ public class DirectoryConfig {
public void build() { public void build() {
log.info("--- Directory config ---"); log.info("--- Directory config ---");
log.info("Exclude:"); log.info("Exclude:");
log.info("\tHomeserver: {}", getExclude().getHomeserver()); log.info(" Homeserver: {}", getExclude().getHomeserver());
log.info("\t3PID: {}", getExclude().getThreepid()); log.info(" 3PID: {}", getExclude().getThreepid());
} }
} }

View File

@@ -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();

View File

@@ -21,10 +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.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;
@@ -32,21 +38,38 @@ import java.util.Optional;
public class YamlConfigLoader { public class YamlConfigLoader {
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 {
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);
return GsonUtil.get().fromJson(GsonUtil.get().toJson(o), MxisdConfig.class); log.debug("Read config in memory from {}", path);
// 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);
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");
} }
} }
public static Optional<MxisdConfig> tryLoadFromFile(String path) { public static Optional<MxisdConfig> tryLoadFromFile(String path) {
log.debug("Attempting to read config from {}", path);
try { try {
return Optional.of(loadFromFile(path)); return Optional.of(loadFromFile(path));
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
log.info("No config file at {}", path);
return Optional.empty(); return Optional.empty();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);

View File

@@ -421,9 +421,9 @@ public abstract class LdapConfig {
log.info("Port: {}", connection.getPort()); log.info("Port: {}", connection.getPort());
log.info("TLS: {}", connection.isTls()); log.info("TLS: {}", connection.isTls());
log.info("Bind DN: {}", connection.getBindDn()); log.info("Bind DN: {}", connection.getBindDn());
log.info("Base DNs: {}"); log.info("Base DNs:");
for (String baseDN : connection.getBaseDNs()) { for (String baseDN : connection.getBaseDNs()) {
log.info("\t- {}", baseDN); log.info(" - {}", baseDN);
} }
log.info("Attribute: {}", GsonUtil.get().toJson(attribute)); log.info("Attribute: {}", GsonUtil.get().toJson(attribute));

View File

@@ -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());
} }
} }

View File

@@ -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());

View File

@@ -115,28 +115,6 @@ public class EmailSendGridConfig {
public static class Templates { public static class Templates {
public static class TemplateSessionValidation {
private EmailTemplate local = new EmailTemplate();
private EmailTemplate remote = new EmailTemplate();
public EmailTemplate getLocal() {
return local;
}
public void setLocal(EmailTemplate local) {
this.local = local;
}
public EmailTemplate getRemote() {
return remote;
}
public void setRemote(EmailTemplate remote) {
this.remote = remote;
}
}
public static class TemplateSessionUnbind { public static class TemplateSessionUnbind {
private EmailTemplate fraudulent = new EmailTemplate(); private EmailTemplate fraudulent = new EmailTemplate();
@@ -153,14 +131,14 @@ public class EmailSendGridConfig {
public static class TemplateSession { public static class TemplateSession {
private TemplateSessionValidation validation = new TemplateSessionValidation(); private EmailTemplate validation = new EmailTemplate();
private TemplateSessionUnbind unbind = new TemplateSessionUnbind(); private TemplateSessionUnbind unbind = new TemplateSessionUnbind();
public TemplateSessionValidation getValidation() { public EmailTemplate getValidation() {
return validation; return validation;
} }
public void setValidation(TemplateSessionValidation validation) { public void setValidation(EmailTemplate validation) {
this.validation = validation; this.validation = validation;
} }

View File

@@ -30,17 +30,17 @@ public class EmailTemplateConfig extends GenericTemplateConfig {
public EmailTemplateConfig() { public EmailTemplateConfig() {
setInvite("classpath:/threepids/email/invite-template.eml"); setInvite("classpath:/threepids/email/invite-template.eml");
getGeneric().put("matrixId", "classpath:/threepids/email/mxid-template.eml"); getGeneric().put("matrixId", "classpath:/threepids/email/mxid-template.eml");
getSession().getValidation().setLocal("classpath:/threepids/email/validate-local-template.eml"); getSession().setValidation("classpath:/threepids/email/validate-template.eml");
getSession().getValidation().setRemote("classpath:/threepids/email/validate-remote-template.eml");
getSession().getUnbind().setFraudulent("classpath:/threepids/email/unbind-fraudulent.eml"); getSession().getUnbind().setFraudulent("classpath:/threepids/email/unbind-fraudulent.eml");
} }
public EmailTemplateConfig build() { public EmailTemplateConfig build() {
log.info("--- E-mail Generator templates config ---"); log.info("--- E-mail Generator templates config ---");
log.info("Invite: {}", getName(getInvite())); log.info("Invite: {}", getName(getInvite()));
log.info("Session validation:"); log.info("Session:");
log.info("\tLocal: {}", getName(getSession().getValidation().getLocal())); log.info(" Validation: {}", getSession().getValidation());
log.info("\tRemote: {}", getName(getSession().getValidation().getRemote())); log.info(" Unbind:");
log.info(" Fraudulent: {}", getSession().getUnbind().getFraudulent());
return this; return this;
} }

View File

@@ -39,29 +39,6 @@ public class GenericTemplateConfig {
public static class Session { public static class Session {
public static class SessionValidation {
private String local;
private String remote;
public String getLocal() {
return local;
}
public void setLocal(String local) {
this.local = local;
}
public String getRemote() {
return remote;
}
public void setRemote(String remote) {
this.remote = remote;
}
}
public static class SessionUnbind { public static class SessionUnbind {
private String fraudulent; private String fraudulent;
@@ -76,14 +53,14 @@ public class GenericTemplateConfig {
} }
private SessionValidation validation = new SessionValidation(); private String validation;
private SessionUnbind unbind = new SessionUnbind(); private SessionUnbind unbind = new SessionUnbind();
public SessionValidation getValidation() { public String getValidation() {
return validation; return validation;
} }
public void setValidation(SessionValidation validation) { public void setValidation(String validation) {
this.validation = validation; this.validation = validation;
} }

View File

@@ -29,18 +29,17 @@ public class PhoneSmsTemplateConfig extends GenericTemplateConfig {
public PhoneSmsTemplateConfig() { public PhoneSmsTemplateConfig() {
setInvite("classpath:/threepids/sms/invite-template.txt"); setInvite("classpath:/threepids/sms/invite-template.txt");
getGeneric().put("matrixId", "classpath:/threepids/email/mxid-template.eml"); getSession().setValidation("classpath:/threepids/sms/validate-template.txt");
getSession().getValidation().setLocal("classpath:/threepids/sms/validate-local-template.txt");
getSession().getValidation().setRemote("classpath:/threepids/sms/validate-remote-template.txt");
getSession().getUnbind().setFraudulent("classpath:/threepids/sms/unbind-fraudulent.txt"); getSession().getUnbind().setFraudulent("classpath:/threepids/sms/unbind-fraudulent.txt");
} }
public PhoneSmsTemplateConfig build() { public PhoneSmsTemplateConfig build() {
log.info("--- SMS Generator templates config ---"); log.info("--- SMS Generator templates config ---");
log.info("Invite: {}", getName(getInvite())); log.info("Invite: {}", getName(getInvite()));
log.info("Session validation:"); log.info("Session:");
log.info("\tLocal: {}", getName(getSession().getValidation().getLocal())); log.info(" Validation: {}", getSession().getValidation());
log.info("\tRemote: {}", getName(getSession().getValidation().getRemote())); log.info(" Unbind:");
log.info(" Fraudulent: {}", getSession().getUnbind().getFraudulent());
return this; return this;
} }

View File

@@ -61,7 +61,7 @@ public class NotificationConfig {
public void build() { public void build() {
log.info("--- Notification config ---"); log.info("--- Notification config ---");
log.info("Handlers:"); log.info("Handlers:");
handler.forEach((k, v) -> log.info("\t{}: {}", k, v)); handler.forEach((k, v) -> log.info(" {}: {}", k, v));
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -62,7 +62,7 @@ public class DirectoryManager {
this.providers = new ArrayList<>(providers); this.providers = new ArrayList<>(providers);
log.info("Directory providers:"); log.info("Directory providers:");
this.providers.forEach(p -> log.info("\t- {}", p.getClass().getName())); this.providers.forEach(p -> log.info(" - {}", p.getClass().getName()));
} }
public UserDirectorySearchResult search(URI target, String accessToken, String query) { public UserDirectorySearchResult search(URI target, String accessToken, String query) {

View File

@@ -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;
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -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));
} }

View File

@@ -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();
} }

View File

@@ -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);
} }

View File

@@ -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);
} }
} }

View File

@@ -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);
} }

View File

@@ -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()));
} }
} }

View File

@@ -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");

View File

@@ -25,6 +25,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import io.kamax.matrix.json.GsonUtil; import io.kamax.matrix.json.GsonUtil;
import io.kamax.mxisd.exception.InvalidResponseJsonException; import io.kamax.mxisd.exception.InvalidResponseJsonException;
import io.kamax.mxisd.http.IsAPIv1;
import io.kamax.mxisd.http.io.identity.ClientBulkLookupRequest; import io.kamax.mxisd.http.io.identity.ClientBulkLookupRequest;
import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.SingleLookupRequest;
@@ -73,7 +74,7 @@ public class RemoteIdentityServerFetcher implements IRemoteIdentityServerFetcher
try { try {
URIBuilder b = new URIBuilder(remote); URIBuilder b = new URIBuilder(remote);
b.setPath("/_matrix/identity/api/v1/lookup"); b.setPath(IsAPIv1.Base + "/lookup");
b.addParameter("medium", request.getType()); b.addParameter("medium", request.getType());
b.addParameter("address", request.getThreePid()); b.addParameter("address", request.getThreePid());
HttpGet req = new HttpGet(b.build()); HttpGet req = new HttpGet(b.build());
@@ -116,7 +117,7 @@ public class RemoteIdentityServerFetcher implements IRemoteIdentityServerFetcher
ClientBulkLookupRequest mappingRequest = new ClientBulkLookupRequest(); ClientBulkLookupRequest mappingRequest = new ClientBulkLookupRequest();
mappingRequest.setMappings(mappings); mappingRequest.setMappings(mappings);
String url = remote + "/_matrix/identity/api/v1/bulk_lookup"; String url = remote + IsAPIv1.Base + "/bulk_lookup";
try { try {
HttpPost request = RestClientUtils.post(url, mappingRequest); HttpPost request = RestClientUtils.post(url, mappingRequest);
try (CloseableHttpResponse response = client.execute(request)) { try (CloseableHttpResponse response = client.execute(request)) {

View File

@@ -57,7 +57,7 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
try { try {
log.info("Found {} providers", providers.size()); log.info("Found {} providers", providers.size());
providers.forEach(p -> log.info("\t- {}", p.getClass().getName())); providers.forEach(p -> log.info(" - {}", p.getClass().getName()));
providers.sort((o1, o2) -> Integer.compare(o2.getPriority(), o1.getPriority())); providers.sort((o1, o2) -> Integer.compare(o2.getPriority(), o1.getPriority()));
log.info("Recursive lookup enabled: {}", cfg.getRecursive().isEnabled()); log.info("Recursive lookup enabled: {}", cfg.getRecursive().isEnabled());

View File

@@ -1,17 +1,42 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Kamax Sarl
*
* https://www.kamax.io/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.matrix; package io.kamax.mxisd.matrix;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import io.kamax.mxisd.http.IsAPIv1;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.xbill.DNS.*; import org.xbill.DNS.*;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@@ -20,31 +45,41 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
// FIXME placeholder, this must go in matrix-java-sdk for 1.0 // FIXME placeholder, this must go in matrix-java-sdk for 1.0
// FIXME this class is just a mistake and should never have happened. Make sure to get rid of for v2.x
public class IdentityServerUtils { public class IdentityServerUtils {
private static Logger log = LoggerFactory.getLogger(IdentityServerUtils.class); private static Logger log = LoggerFactory.getLogger(IdentityServerUtils.class);
private static JsonParser parser = new JsonParser(); private static JsonParser parser = new JsonParser();
private static CloseableHttpClient client;
public static void setHttpClient(CloseableHttpClient client) {
IdentityServerUtils.client = client;
}
public static boolean isUsable(String remote) { public static boolean isUsable(String remote) {
if (StringUtils.isBlank(remote)) { if (StringUtils.isBlank(remote)) {
log.info("IS URL is blank, not usable");
return false; return false;
} }
try { HttpGet req = new HttpGet(URI.create(remote + IsAPIv1.Base));
// FIXME use Apache HTTP client req.setConfig(RequestConfig.custom()
HttpURLConnection rootSrvConn = (HttpURLConnection) new URL(remote + "/_matrix/identity/api/v1/").openConnection(); .setConnectTimeout(2000)
// TODO turn this into a configuration property .setConnectionRequestTimeout(2000)
rootSrvConn.setConnectTimeout(2000); .build()
);
int status = rootSrvConn.getResponseCode(); try (CloseableHttpResponse res = client.execute(req)) {
int status = res.getStatusLine().getStatusCode();
if (status != 200) { if (status != 200) {
log.info("Usability of {} as Identity server: answer status: {}", remote, status); log.info("Usability of {} as Identity server: answer status: {}", remote, status);
return false; return false;
} }
JsonElement el = parser.parse(IOUtils.toString(rootSrvConn.getInputStream(), StandardCharsets.UTF_8)); JsonElement el = parser.parse(IOUtils.toString(res.getEntity().getContent(), StandardCharsets.UTF_8));
if (!el.isJsonObject()) { if (!el.isJsonObject()) {
log.debug("IS {} did not send back a JSON object for single 3PID lookup"); log.debug("IS {} did not send back an empty JSON object as per spec, not a valid IS");
return false; return false;
} }

View File

@@ -37,8 +37,6 @@ public interface NotificationHandler {
void sendForValidation(IThreePidSession session); void sendForValidation(IThreePidSession session);
void sendForRemoteValidation(IThreePidSession session);
void sendForFraudulentUnbind(ThreePid tpid); void sendForFraudulentUnbind(ThreePid tpid);
} }

View File

@@ -78,10 +78,6 @@ public class NotificationManager {
ensureMedium(session.getThreePid().getMedium()).sendForValidation(session); ensureMedium(session.getThreePid().getMedium()).sendForValidation(session);
} }
public void sendForRemoteValidation(IThreePidSession session) {
ensureMedium(session.getThreePid().getMedium()).sendForRemoteValidation(session);
}
public void sendForFraudulentUnbind(ThreePid tpid) throws NotImplementedException { public void sendForFraudulentUnbind(ThreePid tpid) throws NotImplementedException {
ensureMedium(tpid.getMedium()).sendForFraudulentUnbind(tpid); ensureMedium(tpid.getMedium()).sendForFraudulentUnbind(tpid);
} }

View File

@@ -58,7 +58,7 @@ public class ProfileManager {
this.providers = new ArrayList<>(providers); this.providers = new ArrayList<>(providers);
log.info("Profile Providers:"); log.info("Profile Providers:");
providers.forEach(p -> log.info("\t- {}", p.getClass().getSimpleName())); providers.forEach(p -> log.info(" - {}", p.getClass().getSimpleName()));
} }
public <T> List<T> getList(Function<ProfileProvider, List<T>> function) { public <T> List<T> getList(Function<ProfileProvider, List<T>> function) {

View File

@@ -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.");
} }
} }

View 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 class Ed2219RegularKeyIdentifier extends RegularKeyIdentifier {
public Ed2219RegularKeyIdentifier(String serial) {
super(KeyAlgorithm.Ed25519, serial);
}
}

View 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;
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}

View 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;
}
}

View File

@@ -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;
}
}

View 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();
}

View File

@@ -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";
}

View File

@@ -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();
}

View 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);
}

View 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();
}

View 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
}

View 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);
}
}

View 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 class RegularKeyIdentifier extends GenericKeyIdentifier {
public RegularKeyIdentifier(String algo, String serial) {
super(KeyType.Regular, algo, serial);
}
}

View 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();
}

View File

@@ -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);
}

View File

@@ -74,13 +74,7 @@ public abstract class GenericTemplateNotificationGenerator extends PlaceholderNo
@Override @Override
public String getForValidation(IThreePidSession session) { public String getForValidation(IThreePidSession session) {
log.info("Generating notification content for 3PID Session validation"); log.info("Generating notification content for 3PID Session validation");
return populateForValidation(session, getTemplateContent(cfg.getSession().getValidation().getLocal())); return populateForValidation(session, getTemplateContent(cfg.getSession().getValidation()));
}
@Override
public String getForRemoteValidation(IThreePidSession session) {
log.info("Generating notification content for remote-only 3PID session");
return populateForRemoteValidation(session, getTemplateContent(cfg.getSession().getValidation().getRemote()));
} }
@Override @Override

View File

@@ -37,8 +37,6 @@ public interface NotificationGenerator {
String getForValidation(IThreePidSession session); String getForValidation(IThreePidSession session);
String getForRemoteValidation(IThreePidSession session);
String getForFraudulentUnbind(ThreePid tpid); String getForFraudulentUnbind(ThreePid tpid);
} }

View File

@@ -72,11 +72,6 @@ public abstract class GenericNotificationHandler<A extends ThreePidConnector, B
send(connector, session.getThreePid().getAddress(), generator.getForValidation(session)); send(connector, session.getThreePid().getAddress(), generator.getForValidation(session));
} }
@Override
public void sendForRemoteValidation(IThreePidSession session) {
send(connector, session.getThreePid().getAddress(), generator.getForRemoteValidation(session));
}
@Override @Override
public void sendForFraudulentUnbind(ThreePid tpid) { public void sendForFraudulentUnbind(ThreePid tpid) {
send(connector, tpid.getAddress(), generator.getForFraudulentUnbind(tpid)); send(connector, tpid.getAddress(), generator.getForFraudulentUnbind(tpid));

View File

@@ -108,7 +108,7 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
@Override @Override
public void sendForValidation(IThreePidSession session) { public void sendForValidation(IThreePidSession session) {
EmailTemplate template = cfg.getTemplates().getSession().getValidation().getLocal(); EmailTemplate template = cfg.getTemplates().getSession().getValidation();
Email email = getEmail(); Email email = getEmail();
email.setSubject(populateForValidation(session, template.getSubject())); email.setSubject(populateForValidation(session, template.getSubject()));
email.setText(populateForValidation(session, getFromFile(template.getBody().getText()))); email.setText(populateForValidation(session, getFromFile(template.getBody().getText())));
@@ -117,17 +117,6 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
send(session.getThreePid().getAddress(), email); send(session.getThreePid().getAddress(), email);
} }
@Override
public void sendForRemoteValidation(IThreePidSession session) {
EmailTemplate template = cfg.getTemplates().getSession().getValidation().getRemote();
Email email = getEmail();
email.setSubject(populateForRemoteValidation(session, template.getSubject()));
email.setText(populateForRemoteValidation(session, getFromFile(template.getBody().getText())));
email.setHtml(populateForRemoteValidation(session, getFromFile(template.getBody().getHtml())));
send(session.getThreePid().getAddress(), email);
}
@Override @Override
public void sendForFraudulentUnbind(ThreePid tpid) { public void sendForFraudulentUnbind(ThreePid tpid) {
EmailTemplate template = cfg.getTemplates().getSession().getUnbind().getFraudulent(); EmailTemplate template = cfg.getTemplates().getSession().getUnbind().getFraudulent();

View File

@@ -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);
}
}

View File

@@ -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");

View File

@@ -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);
} }

View File

@@ -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);
} }

View File

@@ -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());
}
} }

View File

@@ -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));
}
}

View File

@@ -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";
}

View File

@@ -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);
}
}