Compare commits
29 Commits
v1.3.0-alp
...
v1.3.0-rc.
Author | SHA1 | Date | |
---|---|---|---|
|
6fa36ea092 | ||
|
471e06536b | ||
|
3a6b75996c | ||
|
566e4f3137 | ||
|
a4c18dee5d | ||
|
8d6850d346 | ||
|
67bc18af7d | ||
|
5c660fdcaf | ||
|
fbbafeb769 | ||
|
559f6a7401 | ||
|
3bebb33147 | ||
|
3e240fe34d | ||
|
635f6fdbe7 | ||
|
4237eeb3b6 | ||
|
a0e91e7896 | ||
|
aab0b86646 | ||
|
3e22301af7 | ||
|
2b202323c0 | ||
|
4ec05f518e | ||
|
6da68298b0 | ||
|
aecaafdeca | ||
|
d885932f45 | ||
|
c689a3f161 | ||
|
7805112548 | ||
|
3e89f0bc5e | ||
|
c6b8f7d48e | ||
|
83377ebee0 | ||
|
2aa6e4d142 | ||
|
82a1a3df68 |
21
README.md
21
README.md
@@ -8,18 +8,20 @@ mxisd - Federated Matrix Identity Server
|
|||||||
- [Getting Started](#getting-started)
|
- [Getting Started](#getting-started)
|
||||||
- [Support](#support)
|
- [Support](#support)
|
||||||
- [Contribute](#contribute)
|
- [Contribute](#contribute)
|
||||||
|
- [Powered by mxisd](#powered-by-mxisd)
|
||||||
- [FAQ](#faq)
|
- [FAQ](#faq)
|
||||||
- [Contact](#contact)
|
- [Contact](#contact)
|
||||||
|
|
||||||
# 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:
|
||||||
@@ -32,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,
|
||||||
@@ -66,6 +68,8 @@ 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)
|
||||||
|
|
||||||
@@ -96,6 +100,11 @@ You can contribute as an organisation/corporation by:
|
|||||||
maintained regularly and you get direct access to the support team.
|
maintained regularly and you get direct access to the support team.
|
||||||
- Sponsoring new features or bug fixes. [Get in touch](#contact) so we can discuss it further.
|
- Sponsoring new features or bug fixes. [Get in touch](#contact) so we can discuss it further.
|
||||||
|
|
||||||
|
# Powered by mxisd
|
||||||
|
The following projects use mxisd under the hood for some or all their features. Check them out!
|
||||||
|
- [matrix-docker-ansible-deploy](https://github.com/spantaleev/matrix-docker-ansible-deploy)
|
||||||
|
- [matrix-register-bot](https://github.com/krombel/matrix-register-bot)
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
See the [dedicated document](docs/faq.md)
|
See the [dedicated document](docs/faq.md)
|
||||||
|
|
||||||
|
@@ -129,7 +129,7 @@ dependencies {
|
|||||||
compile 'org.xerial:sqlite-jdbc:3.20.0'
|
compile 'org.xerial:sqlite-jdbc:3.20.0'
|
||||||
|
|
||||||
// PostgreSQL
|
// PostgreSQL
|
||||||
compile 'org.postgresql:postgresql:42.1.4'
|
compile 'org.postgresql:postgresql:42.2.5'
|
||||||
|
|
||||||
// MariaDB/MySQL
|
// MariaDB/MySQL
|
||||||
compile 'org.mariadb.jdbc:mariadb-java-client:2.1.2'
|
compile 'org.mariadb.jdbc:mariadb-java-client:2.1.2'
|
||||||
|
@@ -1 +1 @@
|
|||||||
theme: jekyll-theme-cayman
|
theme: jekyll-theme-hacker
|
@@ -38,12 +38,12 @@ matrix:
|
|||||||
- 'https://other1.example.org'
|
- 'https://other1.example.org'
|
||||||
- 'https://other2.example.org'
|
- 'https://other2.example.org'
|
||||||
```
|
```
|
||||||
Create a list under the label `root` containing a single Identity server, `https://matrix.org`
|
Create a list under the label `myOtherServers` containing two Identity servers: `https://other1.example.org` and `https://other2.example.org`.
|
||||||
|
|
||||||
## Server
|
## Server
|
||||||
- `server.name`: Public hostname of mxisd, if different from the Matrix domain.
|
- `server.name`: Public hostname of mxisd, if different from the Matrix domain.
|
||||||
- `server.port`: HTTP port to listen on (unencrypted)
|
- `server.port`: HTTP port to listen on (unencrypted)
|
||||||
- `server.publicUrl`: Defaults to `https://${server.name}`
|
- `server.publicUrl`: Defaults to `https://{server.name}`
|
||||||
|
|
||||||
## Storage
|
## Storage
|
||||||
### SQLite
|
### SQLite
|
||||||
|
22
docs/faq.md
22
docs/faq.md
@@ -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.
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# Identity
|
# Identity
|
||||||
**WARNING**: This document is incomplete and can be missleading.
|
**WARNING**: This document is incomplete and can be misleading.
|
||||||
|
|
||||||
Implementation of the [Unofficial Matrix Identity Service API](https://kamax.io/matrix/api/identity_service/unstable.html).
|
Implementation of the [Identity Service API r0.1.0](https://matrix.org/docs/spec/identity_service/r0.1.0.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
|
||||||
|
@@ -6,22 +6,34 @@
|
|||||||
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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If you would like a more fully integrated setup out of the box, the [matrix-docker-ansible-deploy](https://github.com/spantaleev/matrix-docker-ansible-deploy)
|
||||||
|
project provides a turn-key full-stack solution, including LDAP and the various mxisd features enabled and ready.
|
||||||
|
We work closely with the project owner so the latest mxisd version is always supported.
|
||||||
|
|
||||||
|
If you choose to use it, this Getting Started guide is not applicable - See the project documentation. You may then
|
||||||
|
directly go to the [Next steps](#next-steps).
|
||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
You will need:
|
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
|
||||||
@@ -74,7 +86,7 @@ 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
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -98,7 +110,7 @@ Typical configuration would look like:
|
|||||||
```nginx
|
```nginx
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
server_name example.org;
|
server_name matrix.example.org;
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -121,17 +133,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`.
|
||||||
|
@@ -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,7 +207,7 @@ 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:
|
||||||
|
@@ -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
|
||||||
|
@@ -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`
|
||||||
|
@@ -268,3 +268,10 @@ Structure with example values:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
The base `profile` key is mandatory. `display_name`, `threepids` and `roles` are only to be returned on the relevant request.
|
The base `profile` key is mandatory. `display_name`, `threepids` and `roles` are only to be returned on the relevant request.
|
||||||
|
|
||||||
|
If there is no profile, the following response is expected:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"profile": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@@ -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
|
||||||
|
@@ -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.
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -26,14 +26,14 @@ 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:
|
unbind:
|
||||||
subject: <Subject of the email notification sent for remote 3PID sessions>
|
fraudulent:
|
||||||
|
subject: <Subject of the email notification sent for potentially fraudulent 3PID unbinds>
|
||||||
body:
|
body:
|
||||||
text: <Path to file containing the raw text part of the email. Do not set to not use one>
|
text: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||||
html: <Path to file containing the HTML part of the email. Do not set to not use one>
|
html: <Path to file containing the raw text part of the email. Do not set to not use one>
|
||||||
```
|
```
|
||||||
|
@@ -18,9 +18,9 @@ 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'
|
unbind:
|
||||||
remote: 'path/to/validate-remote-template.eml'
|
frandulent: '/path/to/unbind-fraudulent-template.eml'
|
||||||
generic:
|
generic:
|
||||||
matrixId: '/path/to/mxid-invite-template.eml'
|
matrixId: '/path/to/mxid-invite-template.eml'
|
||||||
```
|
```
|
||||||
|
@@ -8,81 +8,27 @@ Pseudo-configuration to illustrate the structure:
|
|||||||
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
|
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
|
||||||
view:
|
view:
|
||||||
session:
|
session:
|
||||||
local:
|
|
||||||
onTokenSubmit:
|
onTokenSubmit:
|
||||||
success: '/path/to/session/local/tokenSubmitSuccess-page.html'
|
success: '/path/to/session/tokenSubmitSuccess-page.html'
|
||||||
failure: '/path/to/session/local/tokenSubmitFailure-page.html'
|
failure: '/path/to/session/tokenSubmitFailure-page.html'
|
||||||
localRemote:
|
|
||||||
onTokenSubmit:
|
|
||||||
success: '/path/to/session/localRemote/tokenSubmitSuccess-page.html'
|
|
||||||
failure: '/path/to/session/local/tokenSubmitFailure-page.html'
|
|
||||||
remote:
|
|
||||||
onRequest:
|
|
||||||
success: '/path/to/session/remote/requestSuccess-page.html'
|
|
||||||
failure: '/path/to/session/remote/requestFailure-page.html'
|
|
||||||
onCheck:
|
|
||||||
success: '/path/to/session/remote/checkSuccess-page.html'
|
|
||||||
failure: '/path/to/session/remote/checkFailure-page.html'
|
|
||||||
# CONFIGURATION EXAMPLE
|
# CONFIGURATION EXAMPLE
|
||||||
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
|
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
|
||||||
```
|
```
|
||||||
|
|
||||||
3PID session are divided into three config sections:
|
`view.session`:
|
||||||
- `local` for local-only 3PID sessions
|
|
||||||
- `localRemote` for local 3PID sessions that can also be turned into remote sessions, if the user so desires
|
|
||||||
- `remote` for remote-only 3PID sessions
|
|
||||||
|
|
||||||
Each section contains a sub-key per support event. Finally, a `success` and `failure` key is available depending on the
|
|
||||||
outcome of the request.
|
|
||||||
|
|
||||||
## Local
|
|
||||||
### onTokenSubmit
|
|
||||||
This is triggered when a user submit a validation token for a 3PID session. It is typically visited when clicking the
|
This is triggered when a user submit a validation token for a 3PID session. It is typically visited when clicking the
|
||||||
link in a validation email.
|
link in a validation email.
|
||||||
|
|
||||||
The template should typically inform the user that the validation was successful and to go back in their Matrix client
|
The template should typically inform the user that the validation was successful and to go back in their Matrix client
|
||||||
to finish the validation process.
|
to finish the validation process, or that the validation failed.
|
||||||
|
|
||||||
#### Placeholders
|
Two configuration keys are available that accept paths to HTML templates:
|
||||||
|
- `success`
|
||||||
|
- `failure`
|
||||||
|
|
||||||
|
## Placeholders
|
||||||
|
### Success
|
||||||
No object/placeholder are currently available.
|
No object/placeholder are currently available.
|
||||||
|
|
||||||
## Local & Remote
|
### Failure
|
||||||
### onTokenSubmit
|
|
||||||
This is triggered when a user submit a validation token for a 3PID session. It is typically visited when clicking the
|
|
||||||
link in a validation email.
|
|
||||||
|
|
||||||
The template should typically inform the user that their 3PID address will not yet be publicly/globally usable. In case
|
|
||||||
they want to make it, they should start a Remote 3PID session with a given link or that they can go back to their Matrix
|
|
||||||
client if they do not wish to proceed any further.
|
|
||||||
|
|
||||||
#### Placeholders
|
|
||||||
##### Success
|
|
||||||
`<a href="${remoteSessionLink}">text</a>` can be used to display the link to start a Remote 3PID session.
|
|
||||||
|
|
||||||
##### Failure
|
|
||||||
No object/placeholder are currently available.
|
|
||||||
|
|
||||||
## Remote
|
|
||||||
### onRequest
|
|
||||||
This is triggered when a user starts a Remote 3PID session, usually from a link produced in the `local.onTokenSubmit`
|
|
||||||
view or in a remote-only 3PID notification.
|
|
||||||
|
|
||||||
The template should typically inform the user that the remote creation was successful, followed the instructions sent by
|
|
||||||
the remote Identity server and, once that is done, click a link to validate the session.
|
|
||||||
|
|
||||||
#### Placeholders
|
|
||||||
##### Success
|
|
||||||
`<a href="${checkLink}">text</a>` can be used to display the link to validate the Remote 3PID session.
|
|
||||||
|
|
||||||
##### Failure
|
|
||||||
No object/placeholder are currently available.
|
|
||||||
|
|
||||||
### onCheck
|
|
||||||
This is triggered when a user attempts to inform the Identity server that the Remote 3PID session has been validated
|
|
||||||
with the remote Identity server.
|
|
||||||
|
|
||||||
The template should typically inform the user that the validation was successful and to go back in their Matrix client
|
|
||||||
to finish the validation process.
|
|
||||||
|
|
||||||
#### Placeholders
|
|
||||||
No object/placeholder are currently available.
|
No object/placeholder are currently available.
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
# 3PID Sessions
|
# 3PID Sessions
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Purpose](#purpose)
|
- [Restrictions](#restrictions)
|
||||||
- [Federation](#federation)
|
- [Bindings](#bindings)
|
||||||
- [3PID scope](#3pid-scope)
|
- [Federation](#federation)
|
||||||
- [Session scope](#session-scope)
|
|
||||||
- [Notifications](#notifications)
|
- [Notifications](#notifications)
|
||||||
- [Email](#email)
|
- [Email](#email)
|
||||||
- [Phone numbers](#msisdn-(phone-numbers))
|
- [Phone numbers](#msisdn-(phone-numbers))
|
||||||
@@ -11,28 +10,39 @@
|
|||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Web views](#web-views)
|
- [Web views](#web-views)
|
||||||
- [Scenarios](#scenarios)
|
- [Scenarios](#scenarios)
|
||||||
- [Default](#default)
|
|
||||||
- [Local sessions only](#local-sessions-only)
|
|
||||||
- [Remote sessions only](#remote-sessions-only)
|
|
||||||
- [Sessions disabled](#sessions-disabled)
|
- [Sessions disabled](#sessions-disabled)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
When adding an email, a phone number or any other kind of 3PID (Third-Party Identifier) in a Matrix client,
|
When adding an email, a phone number or any other kind of 3PID (Third-Party Identifier) in a Matrix client,
|
||||||
the identity server is contacted to validate the 3PID.
|
the identity server is contacted to validate the 3PID.
|
||||||
|
|
||||||
To validate the 3PID the identity server sends a message to the 3PID (e.g. an
|
To validate the 3PID, the identity server creates a session associated with a secret token. That token is sent via a message
|
||||||
email) with a hyperlink back to a web-page managed by the identity server to
|
to the 3PID (e.g. an email) with a the necessary info so the user can submit them to the Identity Server, confirm ownership
|
||||||
confirm ownership of the 3PID.
|
of the 3PID.
|
||||||
|
|
||||||
Once this 3PID is validated, the Homeserver will publish the user Matrix ID on the Identity Server and
|
Once this 3PID is validated, the Homeserver will request that the Identity Server links the provided user Matrix ID with
|
||||||
add this 3PID to the Matrix account which initiated the request.
|
the 3PID session and finally add the 3PID to its own data store.
|
||||||
|
|
||||||
This serves two purposes:
|
This serves two purposes:
|
||||||
- Add the 3PID as an administrative/login info for the Homeserver directly
|
- Add the 3PID as an administrative/login info for the Homeserver directly
|
||||||
- Publish, or *Bind*, the 3PID so it can be queried from Homeservers and clients when inviting someone in a room
|
- Links, called *Bind*, the 3PID so it can be queried from Homeservers and clients when inviting someone in a room
|
||||||
by a 3PID, allowing it to be resolved to a Matrix ID.
|
by a 3PID, allowing it to be resolved to a Matrix ID.
|
||||||
|
|
||||||
## Federation
|
## Restrictions
|
||||||
|
### Bindings
|
||||||
|
mxisd does not store bindings directly. While a user can see its email, phone number or any other 3PID in its
|
||||||
|
settings/profile, it does **NOT** mean it is published/saved anywhere or can be used to invite/search the user.
|
||||||
|
|
||||||
|
Identity stores are the ones holding such data, irrelevant if a user added a 3PID to their profile. When queried for
|
||||||
|
bindings, mxisd will query Identity stores which are responsible to store this kind of information.
|
||||||
|
|
||||||
|
Therefore, by default, any 3PID added to a user profile which is NOT within a configured and enabled Identity backend
|
||||||
|
will simply not be usable for search or invites, **even on the same Homeserver!**
|
||||||
|
|
||||||
|
To have such 3PID bindings available for search and invite queries on synapse, use its dedicated
|
||||||
|
[Identity store](../../stores/synapse.md).
|
||||||
|
|
||||||
|
### Federation
|
||||||
In a federated set up, identity servers must cooperate to find the Matrix ID associated with a 3PID.
|
In a federated set up, identity servers must cooperate to find the Matrix ID associated with a 3PID.
|
||||||
|
|
||||||
Federation is based on the principle that each server is responsible for its own (dns) domain.
|
Federation is based on the principle that each server is responsible for its own (dns) domain.
|
||||||
@@ -43,61 +53,15 @@ Example: a user from Homeserver `example.org` adds an email `john@example.com`.
|
|||||||
Federated identity servers would try to find the identity server at `example.com` and ask it for the Matrix ID of associated with `john@example.com`.
|
Federated identity servers would try to find the identity server at `example.com` and ask it for the Matrix ID of associated with `john@example.com`.
|
||||||
|
|
||||||
Nevertheless, Matrix users might add 3PIDs that are not associated to a domain, for example telephone numbers.
|
Nevertheless, Matrix users might add 3PIDs that are not associated to a domain, for example telephone numbers.
|
||||||
Or they might even add 3PIDs associated to a different domain (such as an email address hosted by gmail).
|
Or they might even add 3PIDs associated to a different domain (such as an email address hosted by Gmail).
|
||||||
Such 3PIDs cannot be resolved in a federated way.
|
Such 3PIDs cannot be resolved in a federated way and will not be found from other servers.
|
||||||
|
|
||||||
Example: a user from Homeserver `example.org` adds an email `john@gmail.com`.
|
Example: a user from Homeserver `example.org` adds an email `john@gmail.com`.
|
||||||
If a federated lookup was performed, Identity servers would try to find the 3PID bind at the `gmail.com` server, and
|
If a federated lookup was performed, Identity servers would try to find the 3PID bind at the `gmail.com` server, and
|
||||||
not `example.org`.
|
not `example.org`.
|
||||||
|
|
||||||
In order to resolve such 3PIDs, i.e. 3PIDs that cannot be resolved in a Federated way, an identity server can be configured such that
|
As mxisd is built for self-hosted use cases, mainly for orgs/corps, this is usually not a problem for emails.
|
||||||
- 3PIDs that cannot be resolved locally or using federation, are fowarded to another global identity server.
|
Sadly, there is currently no mechanism to make this work for phone numbers.
|
||||||
- registration of new 3PIDs that cannot be looked up in a federated fashion, is forwarded to another global identity server.
|
|
||||||
|
|
||||||
By forwarding a 3PIDs registration the identity creates a *Remote session* and *Remote bind*, effectively starting a new 3PID session with another Identity server on
|
|
||||||
behalf of the user.
|
|
||||||
|
|
||||||
To ensure lookup works consistency within the current Matrix network, the central Matrix.org Identity Server should be
|
|
||||||
used to store *remote* sessions and binds.
|
|
||||||
|
|
||||||
However, at the time of writing, the Matrix specification and the central Matrix.org servers do not allow to remote a 3PID bind.
|
|
||||||
This means that once a 3PID is published (email, phone number, etc.), it cannot be easily removed
|
|
||||||
and would require contacting the Matrix.org administrators for each bind individually.
|
|
||||||
This poses a privacy, control and security concern, especially for groups/corporations that want to keep a tight control
|
|
||||||
on where such identifiers can be made publicly visible.
|
|
||||||
|
|
||||||
To ensure full control, validation management relies on two concepts:
|
|
||||||
- The scope of 3PID being validated
|
|
||||||
- The scope of 3PID sessions that should be possible/offered
|
|
||||||
|
|
||||||
### 3PID scope
|
|
||||||
3PID can either be scoped as local or remote.
|
|
||||||
|
|
||||||
Local means that they can be looked up using federation and that such a federation call would end up on the local
|
|
||||||
Identity Server.
|
|
||||||
Remote means that they cannot be lookup using federation or that a federation call would not end up on the local
|
|
||||||
Identity Server.
|
|
||||||
|
|
||||||
Email addresses can either be local or remote 3PID, depending on the domain. If the address is one from the configured
|
|
||||||
domain in the Identity server, it will be scoped as local. If it is from another domain, it will be as remote.
|
|
||||||
|
|
||||||
Phone number can only be scoped as remote, since there is currently no way to perform DNS queries that would lead back
|
|
||||||
to the Identity server who validated the phone number.
|
|
||||||
|
|
||||||
### Session scope
|
|
||||||
Sessions can be scoped as:
|
|
||||||
- Local only - validate 3PIDs directly, do not allow the creation of 3PID sessions on a remote Identity server.
|
|
||||||
- Local and Remote - validate 3PIDs directly, offer users to option to also validate and bind 3PID on another server.
|
|
||||||
- Remote only - validate and bind 3PIDs on another server, no validation or bind done locally.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**IMPORTANT NOTE:** mxisd does not store bindings directly. While a user can see its email, phone number or any other
|
|
||||||
3PID in its settings/profile, it does **NOT** mean it is published anywhere and can be used to invite/search the user.
|
|
||||||
Identity stores are the ones holding such data.
|
|
||||||
If you still want added arbitrary 3PIDs to be discoverable on a synapse Homeserver, use the corresponding [Identity store](../../stores/synapse.md).
|
|
||||||
|
|
||||||
See the [Scenarios](#scenarios) for more info on how and why.
|
|
||||||
|
|
||||||
## Notifications
|
## Notifications
|
||||||
3PIDs are validated by sending a pre-formatted message containing a token to that 3PID address, which must be given to the
|
3PIDs are validated by sending a pre-formatted message containing a token to that 3PID address, which must be given to the
|
||||||
@@ -126,47 +90,36 @@ Connectors:
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
### Configuration
|
### Configuration
|
||||||
The following example of configuration (incomplete extract) shows which items are relevant for 3PID sessions.
|
The following example of configuration shows which items are relevant for 3PID sessions.
|
||||||
|
|
||||||
**IMPORTANT:** Most configuration items shown have default values and should not be included in your own configuration
|
**IMPORTANT:** Most configuration items shown have default values and should not be included in your own configuration
|
||||||
file unless you want to specifically overwrite them.
|
file unless you want to specifically overwrite them.
|
||||||
```yaml
|
```yaml
|
||||||
# CONFIGURATION EXAMPLE
|
# CONFIGURATION EXAMPLE
|
||||||
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
|
# DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION
|
||||||
|
|
||||||
session:
|
session:
|
||||||
policy:
|
policy:
|
||||||
validation:
|
validation:
|
||||||
enabled: true
|
enabled: true
|
||||||
forLocal:
|
unbind:
|
||||||
enabled: true
|
fraudulent:
|
||||||
toLocal: true
|
sendWarning: true
|
||||||
toRemote:
|
|
||||||
enabled: true
|
# DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION
|
||||||
server: 'configExample' # Not to be included in config! Already present in default config!
|
|
||||||
forRemote:
|
|
||||||
enabled: true
|
|
||||||
toLocal: true
|
|
||||||
toRemote:
|
|
||||||
enabled: true
|
|
||||||
server: 'configExample' # Not to be included in config! Already present in default config!
|
|
||||||
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
|
|
||||||
# CONFIGURATION EXAMPLE
|
# CONFIGURATION EXAMPLE
|
||||||
```
|
```
|
||||||
|
|
||||||
`session.policy.validation` is the core configuration to control what users configured to use your Identity server
|
`session.policy.validation` is the core configuration to control what users configured to use your Identity server
|
||||||
are allowed to do in terms of 3PID sessions.
|
are allowed to do in terms of 3PID sessions. The policy has a global on/off switch for 3PID sessions using `.enabled`
|
||||||
|
|
||||||
The policy has a global on/off switch for 3PID sessions using `.enabled`
|
---
|
||||||
It is also divided into two sections: `forLocal` and `forRemote` which refers to the 3PID scopes.
|
|
||||||
|
|
||||||
Each scope is divided into three parts:
|
`unbind.fraudulent` controls warning notifications if an illegal/fraudulent 3PID removal is attempted on the Identity server.
|
||||||
- global on/off switch for 3PID sessions using `.enabled`
|
This is directly related to synapse disregard for privacy and new GDPR laws in Europe in an attempt to inform users about
|
||||||
- `toLocal` allowing or not local 3PID session validations
|
potential privacy leaks.
|
||||||
- `toRemote` allowing or not remote 3PID session validations and to which server such sessions should be sent.
|
|
||||||
`.server` takes a Matrix Identity server list label. Only the first server in the list is currently used.
|
|
||||||
|
|
||||||
If both `toLocal` and `toRemote` are enabled, the user will be offered to initiate a remote session once their 3PID
|
For more information, see the corresponding [synapse issue](https://github.com/matrix-org/synapse/issues/4540).
|
||||||
locally validated.
|
|
||||||
|
|
||||||
### Web views
|
### Web views
|
||||||
Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submitted.
|
Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submitted.
|
||||||
@@ -177,107 +130,13 @@ See [the dedicated document](session-views.md)
|
|||||||
on how to configure/customize/brand those pages to your liking.
|
on how to configure/customize/brand those pages to your liking.
|
||||||
|
|
||||||
### Scenarios
|
### Scenarios
|
||||||
It is important to keep in mind that mxisd does not create bindings, irrelevant if a user added a 3PID to their profile.
|
|
||||||
Instead, when queried for bindings, mxisd will query Identity stores which are responsible to store this kind of information.
|
|
||||||
|
|
||||||
This has the side effect that any 3PID added to a user profile which is NOT within a configured and enabled Identity backend
|
|
||||||
will simply not be usable for search or invites, **even on the same Homeserver!**
|
|
||||||
mxisd does not store binds on purpose, as one of its primary goal is to ensure maximum compatibility with federation
|
|
||||||
and the rest of the Matrix ecosystem is preserved.
|
|
||||||
|
|
||||||
Nonetheless, because mxisd also aims at offering support for tight control over identity data, it is possible to have
|
|
||||||
such 3PID bindings available for search and invite queries on synapse with the corresponding [Identity store](../../stores/synapse.md).
|
|
||||||
|
|
||||||
See the [Local sessions only](#local-sessions-only) use case for more information on how to configure.
|
|
||||||
|
|
||||||
#### Default
|
|
||||||
By default, mxisd allows the following:
|
|
||||||
|
|
||||||
| | Local Session | Remote Session |
|
|
||||||
|-----------------|-------------------|----------------|
|
|
||||||
| **Local 3PID** | Yes | Yes, offered |
|
|
||||||
| **Remote 3PID** | No, Remote forced | Yes |
|
|
||||||
|
|
||||||
This is usually what people expect and will feel natural to users and does not involve further integration.
|
|
||||||
|
|
||||||
This allows to stay in control for e-mail addresses which domain matches your Matrix environment, still making them
|
|
||||||
discoverable with federation but not recorded in a 3rd party Identity server which is not under your control.
|
|
||||||
Users still get the possibility to publish globally their address if needed.
|
|
||||||
|
|
||||||
Other e-mail addresses and phone number will be redirected to remote sessions to ensure full compatibility with the Matrix
|
|
||||||
ecosystem and other federated servers.
|
|
||||||
|
|
||||||
#### Local sessions only
|
|
||||||
**NOTE:** This does not affect 3PID lookups (queries to find Matrix IDs). See [Federation](../../features/federation.md)
|
|
||||||
to disable remote lookup for those.
|
|
||||||
|
|
||||||
This configuration ensures maximum confidentiality and privacy.
|
|
||||||
Typical use cases:
|
|
||||||
- Private Homeserver, not federated
|
|
||||||
- Internal Homeserver without direct Internet access
|
|
||||||
- Custom product based on Matrix which does not federate
|
|
||||||
|
|
||||||
No 3PID will be sent to a remote Identity server and all validation will be performed locally.
|
|
||||||
On the flip side, people with *Remote* 3PID scopes will not be found from other servers.
|
|
||||||
|
|
||||||
Use the following values:
|
|
||||||
```yaml
|
|
||||||
session:
|
|
||||||
policy:
|
|
||||||
validation:
|
|
||||||
enabled: true
|
|
||||||
forLocal:
|
|
||||||
enabled: true
|
|
||||||
toLocal: true
|
|
||||||
toRemote:
|
|
||||||
enabled: false
|
|
||||||
forRemote:
|
|
||||||
enabled: true
|
|
||||||
toLocal: true
|
|
||||||
toRemote:
|
|
||||||
enabled: false
|
|
||||||
```
|
|
||||||
|
|
||||||
**IMPORTANT**: When using local-only mode and if you are using synapse, you will also need to enable its dedicated Identity
|
|
||||||
store if you want user searches and invites to work. To do so, see the [dedicated document](../../stores/synapse.md).
|
|
||||||
|
|
||||||
#### Remote sessions only
|
|
||||||
This configuration ensures all 3PID are made public for maximum compatibility and reach within the Matrix ecosystem, at
|
|
||||||
the cost of confidentiality and privacy.
|
|
||||||
|
|
||||||
Typical use cases:
|
|
||||||
- Public Homeserver
|
|
||||||
- Homeserver with registration enabled
|
|
||||||
|
|
||||||
Use the following values:
|
|
||||||
```yaml
|
|
||||||
session:
|
|
||||||
policy:
|
|
||||||
validation:
|
|
||||||
enabled: true
|
|
||||||
forLocal:
|
|
||||||
enabled: true
|
|
||||||
toLocal: false
|
|
||||||
toRemote:
|
|
||||||
enabled: true
|
|
||||||
forRemote:
|
|
||||||
enabled: true
|
|
||||||
toLocal: false
|
|
||||||
toRemote:
|
|
||||||
enabled: true
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Sessions disabled
|
#### Sessions disabled
|
||||||
This configuration would disable 3PID session altogether, preventing users from adding emails and/or phone numbers to
|
This configuration would disable 3PID sessions altogether, preventing users from validating emails and/or phone numbers
|
||||||
their profiles.
|
and any subsequent actions that requires them, like adding them to their profiles.
|
||||||
|
|
||||||
This would be used if mxisd is also performing authentication for the Homeserver, typically with synapse and the
|
This would be used if mxisd is also performing authentication for the Homeserver, typically with synapse and the
|
||||||
[REST password provider](https://github.com/kamax-io/matrix-synapse-rest-auth).
|
[REST password provider](https://github.com/kamax-matrix/matrix-synapse-rest-auth), where 3PID mappings would be
|
||||||
|
auto-populated.
|
||||||
**This mode comes with several important restrictions:**
|
|
||||||
- This does not prevent users from removing 3PID from their profile. They would be unable to add them back!
|
|
||||||
- This prevents users from initiating remote session to make their 3PID binds globally visible
|
|
||||||
|
|
||||||
It is therefore recommended to not fully disable sessions but instead restrict specific set of 3PID and Session scopes.
|
|
||||||
|
|
||||||
Use the following values to enable this mode:
|
Use the following values to enable this mode:
|
||||||
```yaml
|
```yaml
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
package io.kamax.mxisd;
|
package io.kamax.mxisd;
|
||||||
|
|
||||||
import io.kamax.mxisd.config.MxisdConfig;
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
|
import io.kamax.mxisd.http.undertow.handler.OptionsHandler;
|
||||||
import io.kamax.mxisd.http.undertow.handler.SaneHandler;
|
import io.kamax.mxisd.http.undertow.handler.SaneHandler;
|
||||||
import io.kamax.mxisd.http.undertow.handler.as.v1.AsNotFoundHandler;
|
import io.kamax.mxisd.http.undertow.handler.as.v1.AsNotFoundHandler;
|
||||||
import io.kamax.mxisd.http.undertow.handler.as.v1.AsTransactionHandler;
|
import io.kamax.mxisd.http.undertow.handler.as.v1.AsTransactionHandler;
|
||||||
@@ -52,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()));
|
||||||
@@ -59,6 +61,8 @@ public class HttpMxisd {
|
|||||||
|
|
||||||
httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(Handlers.routing()
|
httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(Handlers.routing()
|
||||||
|
|
||||||
|
.add("OPTIONS", "/**", SaneHandler.around(new OptionsHandler()))
|
||||||
|
|
||||||
// Status endpoints
|
// Status endpoints
|
||||||
.get(StatusHandler.Path, SaneHandler.around(new StatusHandler()))
|
.get(StatusHandler.Path, SaneHandler.around(new StatusHandler()))
|
||||||
|
|
||||||
@@ -76,7 +80,8 @@ 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(HelloHandler.Path + "/", helloHandler) // Be lax with possibly trailing slash
|
||||||
.get(SingleLookupHandler.Path, SaneHandler.around(new SingleLookupHandler(m.getIdentity(), m.getSign())))
|
.get(SingleLookupHandler.Path, SaneHandler.around(new SingleLookupHandler(m.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)
|
||||||
@@ -85,8 +90,7 @@ public class HttpMxisd {
|
|||||||
.post(SessionValidateHandler.Path, sessValidateHandler)
|
.post(SessionValidateHandler.Path, sessValidateHandler)
|
||||||
.get(SessionTpidGetValidatedHandler.Path, SaneHandler.around(new SessionTpidGetValidatedHandler(m.getSession())))
|
.get(SessionTpidGetValidatedHandler.Path, SaneHandler.around(new SessionTpidGetValidatedHandler(m.getSession())))
|
||||||
.post(SessionTpidBindHandler.Path, SaneHandler.around(new SessionTpidBindHandler(m.getSession(), m.getInvitationManager())))
|
.post(SessionTpidBindHandler.Path, SaneHandler.around(new SessionTpidBindHandler(m.getSession(), m.getInvitationManager())))
|
||||||
.get(RemoteIdentityAPIv1.SESSION_REQUEST_TOKEN, SaneHandler.around(new RemoteSessionStartHandler(m.getSession(), m.getConfig().getView())))
|
.post(SessionTpidUnbindHandler.Path, SaneHandler.around(new SessionTpidUnbindHandler(m.getSession())))
|
||||||
.get(RemoteIdentityAPIv1.SESSION_CHECK, SaneHandler.around(new RemoteSessionCheckHandler(m.getSession(), m.getConfig().getView())))
|
|
||||||
|
|
||||||
// Profile endpoints
|
// Profile endpoints
|
||||||
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
|
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
|
||||||
@@ -107,7 +111,6 @@ public class HttpMxisd {
|
|||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
httpSrv.stop();
|
httpSrv.stop();
|
||||||
|
|
||||||
m.stop();
|
m.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -40,14 +40,15 @@ 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;
|
||||||
import io.kamax.mxisd.profile.ProfileManager;
|
import io.kamax.mxisd.profile.ProfileManager;
|
||||||
import io.kamax.mxisd.profile.ProfileProviders;
|
import io.kamax.mxisd.profile.ProfileProviders;
|
||||||
import io.kamax.mxisd.session.SessionMananger;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
import io.kamax.mxisd.storage.IStorage;
|
import io.kamax.mxisd.storage.IStorage;
|
||||||
import io.kamax.mxisd.storage.ormlite.OrmLiteSqliteStorage;
|
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,40 +56,41 @@ 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 KeyManager 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 SessionMananger 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 OrmLiteSqliteStorage(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, cfg.getServer());
|
||||||
ClientDnsOverwrite clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite());
|
ClientDnsOverwrite clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite());
|
||||||
@@ -102,7 +104,7 @@ public class Mxisd {
|
|||||||
idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher);
|
idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher);
|
||||||
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 SessionMananger(cfg.getSession(), cfg.getMatrix(), store, notifMgr, 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.getInvite(), 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());
|
||||||
@@ -137,7 +139,7 @@ public class Mxisd {
|
|||||||
return authMgr;
|
return authMgr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SessionMananger getSession() {
|
public SessionManager getSession() {
|
||||||
return sessMgr;
|
return sessMgr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,6 +23,8 @@ 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 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.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -31,7 +33,10 @@ import java.util.Objects;
|
|||||||
|
|
||||||
public class MxisdStandaloneExec {
|
public class MxisdStandaloneExec {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger("");
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
|
log.info("------------- mxisd starting -------------");
|
||||||
MxisdConfig cfg = null;
|
MxisdConfig cfg = null;
|
||||||
|
|
||||||
Iterator<String> argsIt = Arrays.asList(args).iterator();
|
Iterator<String> argsIt = Arrays.asList(args).iterator();
|
||||||
@@ -40,9 +45,8 @@ public class MxisdStandaloneExec {
|
|||||||
if (StringUtils.equals("-c", arg)) {
|
if (StringUtils.equals("-c", arg)) {
|
||||||
String cfgFile = argsIt.next();
|
String cfgFile = argsIt.next();
|
||||||
cfg = YamlConfigLoader.loadFromFile(cfgFile);
|
cfg = YamlConfigLoader.loadFromFile(cfgFile);
|
||||||
System.out.println("Loaded configuration from " + cfgFile);
|
|
||||||
} else {
|
} else {
|
||||||
System.out.println("Invalid argument: " + arg);
|
log.info("Invalid argument: {}", arg);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,11 +59,11 @@ public class MxisdStandaloneExec {
|
|||||||
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 (Throwable t) {
|
} catch (Throwable t) {
|
||||||
t.printStackTrace();
|
t.printStackTrace();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
|
@@ -60,7 +60,9 @@ public class GoogleFirebaseBackend {
|
|||||||
|
|
||||||
private FirebaseCredential getCreds(String credsPath) throws IOException {
|
private FirebaseCredential getCreds(String credsPath) throws IOException {
|
||||||
if (StringUtils.isNotBlank(credsPath)) {
|
if (StringUtils.isNotBlank(credsPath)) {
|
||||||
return FirebaseCredentials.fromCertificate(new FileInputStream(credsPath));
|
try (FileInputStream is = new FileInputStream(credsPath)) {
|
||||||
|
return FirebaseCredentials.fromCertificate(is);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return FirebaseCredentials.applicationDefault();
|
return FirebaseCredentials.applicationDefault();
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.backend.sql;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
public class BuiltInDriverLoader implements DriverLoader {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void accept(String s) {
|
||||||
|
String className = null;
|
||||||
|
if (StringUtils.equals("sqlite", s)) {
|
||||||
|
className = "org.sqlite.JDBC";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.equals("postgresql", s)) {
|
||||||
|
className = "org.postgresql.Driver";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.equals("mariadb", s)) {
|
||||||
|
className = "org.mariadb.jdbc.Driver";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.equals("mysql", s)) {
|
||||||
|
className = "org.mariadb.jdbc.Driver";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotEmpty(className)) {
|
||||||
|
try {
|
||||||
|
Class.forName(className);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
src/main/java/io/kamax/mxisd/backend/sql/DriverLoader.java
Normal file
26
src/main/java/io/kamax/mxisd/backend/sql/DriverLoader.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.backend.sql;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public interface DriverLoader extends Consumer<String> {
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* mxisd - Matrix Identity Server Daemon
|
* mxisd - Matrix Identity Server Daemon
|
||||||
* Copyright (C) 2017 Kamax Sarl
|
* Copyright (C) 2019 Kamax Sàrl
|
||||||
*
|
*
|
||||||
* https://www.kamax.io/
|
* https://www.kamax.io/
|
||||||
*
|
*
|
||||||
@@ -18,20 +18,21 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
package io.kamax.mxisd.backend.sql;
|
||||||
|
|
||||||
public class RemoteIdentityAPIv1 {
|
import java.util.Objects;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
|
||||||
public static final String BASE = "/_matrix/identity/remote/api/v1";
|
public class Drivers {
|
||||||
public static final String SESSION_REQUEST_TOKEN = BASE + "/validate/requestToken";
|
|
||||||
public static final String SESSION_CHECK = BASE + "/validate/check";
|
|
||||||
|
|
||||||
public static String getRequestToken(String id, String secret) {
|
private static ServiceLoader<DriverLoader> svcLoader;
|
||||||
return SESSION_REQUEST_TOKEN + "?sid=" + id + "&client_secret=" + secret;
|
|
||||||
|
public static void load(String type) {
|
||||||
|
if (Objects.isNull(svcLoader)) {
|
||||||
|
svcLoader = ServiceLoader.load(DriverLoader.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getSessionCheck(String id, String secret) {
|
svcLoader.iterator().forEachRemaining(drv -> drv.accept(type));
|
||||||
return SESSION_CHECK + "?sid=" + id + "&client_secret=" + secret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@@ -37,11 +37,15 @@ public class SqlConnectionPool {
|
|||||||
private ComboPooledDataSource ds;
|
private ComboPooledDataSource ds;
|
||||||
|
|
||||||
public SqlConnectionPool(SqlConfig cfg) {
|
public SqlConnectionPool(SqlConfig cfg) {
|
||||||
|
Drivers.load(cfg.getType());
|
||||||
|
|
||||||
ds = new ComboPooledDataSource();
|
ds = new ComboPooledDataSource();
|
||||||
ds.setJdbcUrl("jdbc:" + cfg.getType() + ":" + cfg.getConnection());
|
ds.setJdbcUrl("jdbc:" + cfg.getType() + ":" + cfg.getConnection());
|
||||||
ds.setMinPoolSize(1);
|
ds.setMinPoolSize(1);
|
||||||
ds.setMaxPoolSize(10);
|
ds.setMaxPoolSize(10);
|
||||||
ds.setAcquireIncrement(2);
|
ds.setAcquireIncrement(2);
|
||||||
|
ds.setAcquireRetryAttempts(10);
|
||||||
|
ds.setAcquireRetryDelay(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Connection get() throws SQLException {
|
public Connection get() throws SQLException {
|
||||||
|
@@ -64,8 +64,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());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -32,12 +32,7 @@ public class SessionConfig {
|
|||||||
|
|
||||||
public static class PolicyTemplate {
|
public static class PolicyTemplate {
|
||||||
|
|
||||||
public static class PolicySource {
|
|
||||||
|
|
||||||
public static class PolicySourceRemote {
|
|
||||||
|
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private String server;
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
@@ -47,98 +42,42 @@ public class SessionConfig {
|
|||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getServer() {
|
|
||||||
return server;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setServer(String server) {
|
public static class PolicyUnbind {
|
||||||
this.server = server;
|
|
||||||
|
public static class PolicyUnbindFraudulent {
|
||||||
|
|
||||||
|
private boolean sendWarning = true;
|
||||||
|
|
||||||
|
public boolean getSendWarning() {
|
||||||
|
return sendWarning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSendWarning(boolean sendWarning) {
|
||||||
|
this.sendWarning = sendWarning;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean enabled;
|
|
||||||
private boolean toLocal;
|
|
||||||
private PolicySourceRemote toRemote = new PolicySourceRemote();
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
private PolicyUnbindFraudulent fraudulent = new PolicyUnbindFraudulent();
|
||||||
return enabled;
|
|
||||||
|
public PolicyUnbindFraudulent getFraudulent() {
|
||||||
|
return fraudulent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEnabled(boolean enabled) {
|
public void setFraudulent(PolicyUnbindFraudulent fraudulent) {
|
||||||
this.enabled = enabled;
|
this.fraudulent = fraudulent;
|
||||||
}
|
|
||||||
|
|
||||||
public boolean toLocal() {
|
|
||||||
return toLocal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setToLocal(boolean toLocal) {
|
|
||||||
this.toLocal = toLocal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean toRemote() {
|
|
||||||
return toRemote.isEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PolicySourceRemote getToRemote() {
|
|
||||||
return toRemote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setToRemote(PolicySourceRemote toRemote) {
|
|
||||||
this.toRemote = toRemote;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean enabled;
|
|
||||||
private PolicySource forLocal = new PolicySource();
|
|
||||||
private PolicySource forRemote = new PolicySource();
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEnabled(boolean enabled) {
|
|
||||||
this.enabled = enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PolicySource getForLocal() {
|
|
||||||
return forLocal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PolicySource forLocal() {
|
|
||||||
return forLocal;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PolicySource getForRemote() {
|
|
||||||
return forRemote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PolicySource forRemote() {
|
|
||||||
return forRemote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PolicySource forIf(boolean isLocal) {
|
|
||||||
return isLocal ? forLocal : forRemote;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Policy() {
|
public Policy() {
|
||||||
validation.enabled = true;
|
validation.enabled = true;
|
||||||
validation.forLocal.enabled = true;
|
|
||||||
validation.forLocal.toLocal = true;
|
|
||||||
validation.forLocal.toRemote.enabled = true;
|
|
||||||
validation.forLocal.toRemote.server = "matrix-org";
|
|
||||||
|
|
||||||
validation.forRemote.enabled = true;
|
|
||||||
validation.forRemote.toLocal = false;
|
|
||||||
validation.forRemote.toRemote.enabled = true;
|
|
||||||
validation.forRemote.toRemote.server = "matrix-org";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PolicyTemplate validation = new PolicyTemplate();
|
private PolicyTemplate validation = new PolicyTemplate();
|
||||||
|
private PolicyUnbind unbind = new PolicyUnbind();
|
||||||
|
|
||||||
public PolicyTemplate getValidation() {
|
public PolicyTemplate getValidation() {
|
||||||
return validation;
|
return validation;
|
||||||
@@ -148,6 +87,14 @@ public class SessionConfig {
|
|||||||
this.validation = validation;
|
this.validation = validation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PolicyUnbind getUnbind() {
|
||||||
|
return unbind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnbind(PolicyUnbind unbind) {
|
||||||
|
this.unbind = unbind;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Policy policy = new Policy();
|
private Policy policy = new Policy();
|
||||||
|
@@ -21,12 +21,13 @@
|
|||||||
package io.kamax.mxisd.config;
|
package io.kamax.mxisd.config;
|
||||||
|
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class ViewConfig {
|
public class ViewConfig {
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(ViewConfig.class);
|
private static final Logger log = LoggerFactory.getLogger(ViewConfig.class);
|
||||||
|
|
||||||
public static class Session {
|
public static class Session {
|
||||||
|
|
||||||
@@ -67,45 +68,13 @@ public class ViewConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Remote {
|
// Legacy option
|
||||||
|
|
||||||
private Paths onRequest = new Paths();
|
|
||||||
private Paths onCheck = new Paths();
|
|
||||||
|
|
||||||
public Paths getOnRequest() {
|
|
||||||
return onRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnRequest(Paths onRequest) {
|
|
||||||
this.onRequest = onRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Paths getOnCheck() {
|
|
||||||
return onCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnCheck(Paths onCheck) {
|
|
||||||
this.onCheck = onCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Local local = new Local();
|
private Local local = new Local();
|
||||||
private Local localRemote = new Local();
|
private Paths onTokenSubmit = new Paths();
|
||||||
private Remote remote = new Remote();
|
|
||||||
|
|
||||||
public Session() {
|
public Session() {
|
||||||
local.onTokenSubmit.success = "session/local/tokenSubmitSuccess";
|
onTokenSubmit.success = "classpath:/templates/session/tokenSubmitSuccess.html";
|
||||||
local.onTokenSubmit.failure = "session/local/tokenSubmitFailure";
|
onTokenSubmit.failure = "classpath:/templates/session/tokenSubmitFailure.html";
|
||||||
|
|
||||||
localRemote.onTokenSubmit.success = "session/localRemote/tokenSubmitSuccess";
|
|
||||||
localRemote.onTokenSubmit.failure = "session/local/tokenSubmitFailure";
|
|
||||||
|
|
||||||
remote.onRequest.success = "session/remote/requestSuccess";
|
|
||||||
remote.onRequest.failure = "session/remote/requestFailure";
|
|
||||||
|
|
||||||
remote.onCheck.success = "session/remote/checkSuccess";
|
|
||||||
remote.onCheck.failure = "session/remote/checkFailure";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Local getLocal() {
|
public Local getLocal() {
|
||||||
@@ -116,21 +85,14 @@ public class ViewConfig {
|
|||||||
this.local = local;
|
this.local = local;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Local getLocalRemote() {
|
public Paths getOnTokenSubmit() {
|
||||||
return localRemote;
|
return onTokenSubmit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLocalRemote(Local localRemote) {
|
public void setOnTokenSubmit(Paths onTokenSubmit) {
|
||||||
this.localRemote = localRemote;
|
this.onTokenSubmit = onTokenSubmit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Remote getRemote() {
|
|
||||||
return remote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRemote(Remote remote) {
|
|
||||||
this.remote = remote;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Session session = new Session();
|
private Session session = new Session();
|
||||||
@@ -144,6 +106,17 @@ public class ViewConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void build() {
|
public void build() {
|
||||||
|
if (StringUtils.isNotBlank(session.local.onTokenSubmit.success) && StringUtils.isBlank(session.onTokenSubmit.success)) {
|
||||||
|
log.warn("Legacy option session.local.onTokenSubmit.success in use, please switch to session.onTokenSubmit.success");
|
||||||
|
session.onTokenSubmit.success = session.local.onTokenSubmit.success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isNotBlank(session.local.onTokenSubmit.failure) && StringUtils.isBlank(session.onTokenSubmit.failure)) {
|
||||||
|
log.warn("Legacy option session.local.onTokenSubmit.failure in use, please switch to session.onTokenSubmit.failure");
|
||||||
|
session.onTokenSubmit.failure = session.local.onTokenSubmit.failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
log.info("--- View config ---");
|
log.info("--- View config ---");
|
||||||
log.info("Session: {}", GsonUtil.get().toJson(session));
|
log.info("Session: {}", GsonUtil.get().toJson(session));
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,8 @@
|
|||||||
package io.kamax.mxisd.config;
|
package io.kamax.mxisd.config;
|
||||||
|
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
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.representer.Representer;
|
import org.yaml.snakeyaml.representer.Representer;
|
||||||
@@ -32,19 +34,29 @@ 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 {
|
||||||
|
log.debug("Reading config from {}", path);
|
||||||
Representer rep = new Representer();
|
Representer rep = new Representer();
|
||||||
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);
|
||||||
Object o = yaml.load(new FileInputStream(path));
|
try (FileInputStream is = new FileInputStream(path)) {
|
||||||
return GsonUtil.get().fromJson(GsonUtil.get().toJson(o), MxisdConfig.class);
|
Object o = yaml.load(is);
|
||||||
|
log.debug("Read config in memory from {}", path);
|
||||||
|
MxisdConfig cfg = GsonUtil.get().fromJson(GsonUtil.get().toJson(o), MxisdConfig.class);
|
||||||
|
log.info("Loaded config from {}", path);
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
@@ -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));
|
||||||
|
@@ -34,8 +34,8 @@ public class RestBackendConfig {
|
|||||||
|
|
||||||
public static class IdentityEndpoints {
|
public static class IdentityEndpoints {
|
||||||
|
|
||||||
private String single = "/_mxisd/backend/api/v1/identity/lookup/single";
|
private String single = "/_mxisd/backend/api/v1/identity/single";
|
||||||
private String bulk = "/_mxisd/backend/api/v1/identity/lookup/bulk";
|
private String bulk = "/_mxisd/backend/api/v1/identity/bulk";
|
||||||
|
|
||||||
public String getSingle() {
|
public String getSingle() {
|
||||||
return single;
|
return single;
|
||||||
|
@@ -20,7 +20,6 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.config.threepid;
|
package io.kamax.mxisd.config.threepid;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import io.kamax.matrix.ThreePidMedium;
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
||||||
@@ -31,18 +30,18 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class ThreePidConfig {
|
public class ThreePidConfig {
|
||||||
|
|
||||||
private Map<String, JsonObject> medium = new HashMap<>();
|
private Map<String, Object> medium = new HashMap<>();
|
||||||
|
|
||||||
public ThreePidConfig() {
|
public ThreePidConfig() { // TODO Check if this is still needed
|
||||||
medium.put(ThreePidMedium.Email.getId(), GsonUtil.makeObj(new EmailConfig()));
|
medium.put(ThreePidMedium.Email.getId(), GsonUtil.makeObj(new EmailConfig()));
|
||||||
medium.put(ThreePidMedium.PhoneNumber.getId(), GsonUtil.makeObj(new PhoneConfig()));
|
medium.put(ThreePidMedium.PhoneNumber.getId(), GsonUtil.makeObj(new PhoneConfig()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, JsonObject> getMedium() {
|
public Map<String, Object> getMedium() {
|
||||||
return medium;
|
return medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMedium(Map<String, JsonObject> medium) {
|
public void setMedium(Map<String, Object> medium) {
|
||||||
this.medium = medium;
|
this.medium = medium;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -115,26 +115,41 @@ public class EmailSendGridConfig {
|
|||||||
|
|
||||||
public static class Templates {
|
public static class Templates {
|
||||||
|
|
||||||
|
public static class TemplateSessionUnbind {
|
||||||
|
|
||||||
|
private EmailTemplate fraudulent = new EmailTemplate();
|
||||||
|
|
||||||
|
public EmailTemplate getFraudulent() {
|
||||||
|
return fraudulent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFraudulent(EmailTemplate fraudulent) {
|
||||||
|
this.fraudulent = fraudulent;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public static class TemplateSession {
|
public static class TemplateSession {
|
||||||
|
|
||||||
private EmailTemplate local = new EmailTemplate();
|
private EmailTemplate validation = new EmailTemplate();
|
||||||
private EmailTemplate remote = new EmailTemplate();
|
private TemplateSessionUnbind unbind = new TemplateSessionUnbind();
|
||||||
|
|
||||||
public EmailTemplate getLocal() {
|
public EmailTemplate getValidation() {
|
||||||
return local;
|
return validation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLocal(EmailTemplate local) {
|
public void setValidation(EmailTemplate validation) {
|
||||||
this.local = local;
|
this.validation = validation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EmailTemplate getRemote() {
|
public TemplateSessionUnbind getUnbind() {
|
||||||
return remote;
|
return unbind;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRemote(EmailTemplate remote) {
|
public void setUnbind(TemplateSessionUnbind unbind) {
|
||||||
this.remote = remote;
|
this.unbind = unbind;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private EmailTemplate invite = new EmailTemplate();
|
private EmailTemplate invite = new EmailTemplate();
|
||||||
|
@@ -30,16 +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");
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@@ -39,39 +39,39 @@ public class GenericTemplateConfig {
|
|||||||
|
|
||||||
public static class Session {
|
public static class Session {
|
||||||
|
|
||||||
public static class SessionValidation {
|
public static class SessionUnbind {
|
||||||
|
|
||||||
private String local;
|
private String fraudulent;
|
||||||
private String remote;
|
|
||||||
|
|
||||||
public String getLocal() {
|
public String getFraudulent() {
|
||||||
return local;
|
return fraudulent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLocal(String local) {
|
public void setFraudulent(String fraudulent) {
|
||||||
this.local = local;
|
this.fraudulent = fraudulent;
|
||||||
}
|
|
||||||
|
|
||||||
public String getRemote() {
|
|
||||||
return remote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRemote(String remote) {
|
|
||||||
this.remote = remote;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private SessionValidation validation = new SessionValidation();
|
private String validation;
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SessionUnbind getUnbind() {
|
||||||
|
return unbind;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUnbind(SessionUnbind unbind) {
|
||||||
|
this.unbind = unbind;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String invite;
|
private String invite;
|
||||||
|
@@ -29,17 +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().getUnbind().setFraudulent("classpath:/threepids/sms/unbind-fraudulent.txt");
|
||||||
getSession().getValidation().setRemote("classpath:/threepids/sms/validate-remote-template.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;
|
||||||
}
|
}
|
||||||
|
@@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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) {
|
||||||
|
@@ -28,7 +28,7 @@ public class ConfigurationException extends RuntimeException {
|
|||||||
private String detailedMsg;
|
private String detailedMsg;
|
||||||
|
|
||||||
public ConfigurationException(String key) {
|
public ConfigurationException(String key) {
|
||||||
super("Invalid or empty value for configuration item " + key);
|
super("Invalid or empty value for configuration item: " + key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigurationException(Throwable t) {
|
public ConfigurationException(Throwable t) {
|
||||||
|
@@ -25,7 +25,7 @@ import org.apache.http.HttpStatus;
|
|||||||
public class SessionNotValidatedException extends HttpMatrixException {
|
public class SessionNotValidatedException extends HttpMatrixException {
|
||||||
|
|
||||||
public SessionNotValidatedException() {
|
public SessionNotValidatedException() {
|
||||||
super(HttpStatus.SC_OK, "M_SESSION_NOT_VALIDATED", "This validation session has not yet been completed");
|
super(HttpStatus.SC_BAD_REQUEST, "M_SESSION_NOT_VALIDATED", "This validation session has not yet been completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,8 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.exception;
|
package io.kamax.mxisd.exception;
|
||||||
|
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
|
||||||
public class SessionUnknownException extends HttpMatrixException {
|
public class SessionUnknownException extends HttpMatrixException {
|
||||||
|
|
||||||
public SessionUnknownException() {
|
public SessionUnknownException() {
|
||||||
@@ -27,7 +29,7 @@ public class SessionUnknownException extends HttpMatrixException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SessionUnknownException(String error) {
|
public SessionUnknownException(String error) {
|
||||||
super(200, "M_NO_VALID_SESSION", error);
|
super(HttpStatus.SC_NOT_FOUND, "M_NO_VALID_SESSION", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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.http.io.identity;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class BindRequest {
|
||||||
|
|
||||||
|
public static class Keys {
|
||||||
|
|
||||||
|
public static final String SessionID = "sid";
|
||||||
|
public static final String Secret = "client_secret";
|
||||||
|
public static final String UserID = "mxid";
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName(Keys.SessionID)
|
||||||
|
private String sid;
|
||||||
|
|
||||||
|
@SerializedName(Keys.Secret)
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@SerializedName(Keys.UserID)
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
public String getSid() {
|
||||||
|
return sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSid(String sid) {
|
||||||
|
this.sid = sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecret() {
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecret(String secret) {
|
||||||
|
this.secret = secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userId) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,179 @@
|
|||||||
|
/*
|
||||||
|
* 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.http.io.identity;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class StoreInviteRequest {
|
||||||
|
|
||||||
|
// Available keys from Spec + HS implementations reverse-engineering
|
||||||
|
//
|
||||||
|
// Synapse: https://github.com/matrix-org/synapse/blob/a219ce87263ad9be887cf039a04b4a1f06b7b0b8/synapse/handlers/room_member.py#L826
|
||||||
|
public static class Keys {
|
||||||
|
|
||||||
|
public static final String Medium = "medium";
|
||||||
|
public static final String Address = "address";
|
||||||
|
public static final String RoomID = "room_id";
|
||||||
|
public static final String RoomAlias = "room_alias"; // Not in the spec, arbitrary
|
||||||
|
public static final String RoomAvatarURL = "room_avatar_url"; // Not in the spec, arbitrary
|
||||||
|
public static final String RoomJoinRules = "room_join_rules"; // Not in the spec, arbitrary
|
||||||
|
public static final String RoomName = "room_name"; // Not in the spec, arbitrary
|
||||||
|
public static final String Sender = "sender";
|
||||||
|
public static final String SenderDisplayName = "sender_display_name"; // Not in the spec, arbitrary
|
||||||
|
public static final String SenderAvatarURL = "sender_avatar_url"; // Not in the spec, arbitrary
|
||||||
|
public static final String GuestAccessToken = "guest_access_token"; // Not in the spec, arbitrary
|
||||||
|
public static final String GuestUserID = "guest_user_id"; // Not in the spec, arbitrary
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName(Keys.Medium)
|
||||||
|
private String medium;
|
||||||
|
|
||||||
|
@SerializedName(Keys.Address)
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
@SerializedName(Keys.RoomID)
|
||||||
|
private String roomId;
|
||||||
|
|
||||||
|
@SerializedName(Keys.RoomAlias)
|
||||||
|
private String roomAlias;
|
||||||
|
|
||||||
|
@SerializedName(Keys.RoomAvatarURL)
|
||||||
|
private String roomAvatarUrl;
|
||||||
|
|
||||||
|
@SerializedName(Keys.RoomJoinRules)
|
||||||
|
private String roomJoinRules;
|
||||||
|
|
||||||
|
@SerializedName(Keys.RoomName)
|
||||||
|
private String roomName;
|
||||||
|
|
||||||
|
@SerializedName(Keys.Sender)
|
||||||
|
private String sender;
|
||||||
|
|
||||||
|
@SerializedName(Keys.SenderDisplayName)
|
||||||
|
private String senderDisplayName;
|
||||||
|
|
||||||
|
@SerializedName(Keys.SenderAvatarURL)
|
||||||
|
private String senderAvatarUrl;
|
||||||
|
|
||||||
|
@SerializedName(Keys.GuestAccessToken)
|
||||||
|
private String guestAccessToken;
|
||||||
|
|
||||||
|
@SerializedName(Keys.GuestUserID)
|
||||||
|
private String guestUserId;
|
||||||
|
|
||||||
|
public String getMedium() {
|
||||||
|
return medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMedium(String medium) {
|
||||||
|
this.medium = medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(String address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomId() {
|
||||||
|
return roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoomId(String roomId) {
|
||||||
|
this.roomId = roomId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomAlias() {
|
||||||
|
return roomAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoomAlias(String roomAlias) {
|
||||||
|
this.roomAlias = roomAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomAvatarUrl() {
|
||||||
|
return roomAvatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoomAvatarUrl(String roomAvatarUrl) {
|
||||||
|
this.roomAvatarUrl = roomAvatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomJoinRules() {
|
||||||
|
return roomJoinRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoomJoinRules(String roomJoinRules) {
|
||||||
|
this.roomJoinRules = roomJoinRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRoomName() {
|
||||||
|
return roomName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRoomName(String roomName) {
|
||||||
|
this.roomName = roomName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSender() {
|
||||||
|
return sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSender(String sender) {
|
||||||
|
this.sender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSenderDisplayName() {
|
||||||
|
return senderDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSenderDisplayName(String senderDisplayName) {
|
||||||
|
this.senderDisplayName = senderDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSenderAvatarUrl() {
|
||||||
|
return senderAvatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSenderAvatarUrl(String senderAvatarUrl) {
|
||||||
|
this.senderAvatarUrl = senderAvatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGuestAccessToken() {
|
||||||
|
return guestAccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGuestAccessToken(String guestAccessToken) {
|
||||||
|
this.guestAccessToken = guestAccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGuestUserId() {
|
||||||
|
return guestUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGuestUserId(String guestUserId) {
|
||||||
|
this.guestUserId = guestUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -30,6 +30,7 @@ import io.undertow.server.HttpHandler;
|
|||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import io.undertow.util.HttpString;
|
import io.undertow.util.HttpString;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -38,7 +39,10 @@ import java.io.UnsupportedEncodingException;
|
|||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Deque;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public abstract class BasicHttpHandler implements HttpHandler {
|
public abstract class BasicHttpHandler implements HttpHandler {
|
||||||
|
|
||||||
@@ -49,8 +53,16 @@ public abstract class BasicHttpHandler implements HttpHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected String getQueryParameter(HttpServerExchange exchange, String name) {
|
protected String getQueryParameter(HttpServerExchange exchange, String name) {
|
||||||
|
return getQueryParameter(exchange.getQueryParameters(), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getQueryParameter(Map<String, Deque<String>> parms, String name) {
|
||||||
try {
|
try {
|
||||||
String raw = exchange.getQueryParameters().getOrDefault(name, new LinkedList<>()).peekFirst();
|
String raw = parms.getOrDefault(name, new LinkedList<>()).peekFirst();
|
||||||
|
if (StringUtils.isEmpty(raw)) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
return URLDecoder.decode(raw, StandardCharsets.UTF_8.name());
|
return URLDecoder.decode(raw, StandardCharsets.UTF_8.name());
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
throw new InternalServerError(e);
|
throw new InternalServerError(e);
|
||||||
@@ -61,25 +73,32 @@ public abstract class BasicHttpHandler implements HttpHandler {
|
|||||||
return getQueryParameter(exchange, name);
|
return getQueryParameter(exchange, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected Optional<String> getContentType(HttpServerExchange exchange) {
|
||||||
|
return Optional.ofNullable(exchange.getRequestHeaders().getFirst("Content-Type"));
|
||||||
|
}
|
||||||
|
|
||||||
protected void writeBodyAsUtf8(HttpServerExchange exchange, String body) {
|
protected void writeBodyAsUtf8(HttpServerExchange exchange, String body) {
|
||||||
exchange.getResponseSender().send(body, StandardCharsets.UTF_8);
|
exchange.getResponseSender().send(body, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T> T parseJsonTo(HttpServerExchange exchange, Class<T> type) {
|
protected String getBodyUtf8(HttpServerExchange exchange) {
|
||||||
try {
|
try {
|
||||||
return GsonUtil.get().fromJson(IOUtils.toString(exchange.getInputStream(), StandardCharsets.UTF_8), type);
|
return IOUtils.toString(exchange.getInputStream(), StandardCharsets.UTF_8);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected JsonObject parseJsonObject(HttpServerExchange exchange, String key) {
|
protected <T> T parseJsonTo(HttpServerExchange exchange, Class<T> type) {
|
||||||
try {
|
return GsonUtil.get().fromJson(getBodyUtf8(exchange), type);
|
||||||
JsonObject base = GsonUtil.parseObj(IOUtils.toString(exchange.getInputStream(), StandardCharsets.UTF_8));
|
|
||||||
return GsonUtil.getObj(base, key);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected JsonObject parseJsonObject(HttpServerExchange exchange, String key) {
|
||||||
|
return GsonUtil.getObj(parseJsonObject(exchange), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected JsonObject parseJsonObject(HttpServerExchange exchange) {
|
||||||
|
return GsonUtil.parseObj(getBodyUtf8(exchange));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void respond(HttpServerExchange ex, int statusCode, JsonElement bodyJson) {
|
protected void respond(HttpServerExchange ex, int statusCode, JsonElement bodyJson) {
|
||||||
|
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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.http.undertow.handler;
|
||||||
|
|
||||||
|
import io.undertow.server.HttpServerExchange;
|
||||||
|
|
||||||
|
public class OptionsHandler extends BasicHttpHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleRequest(HttpServerExchange exchange) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -21,10 +21,13 @@
|
|||||||
package io.kamax.mxisd.http.undertow.handler;
|
package io.kamax.mxisd.http.undertow.handler;
|
||||||
|
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
import com.google.gson.stream.MalformedJsonException;
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
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.lang.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;
|
||||||
@@ -54,6 +57,11 @@ public class SaneHandler extends BasicHttpHandler {
|
|||||||
exchange.dispatch(this);
|
exchange.dispatch(this);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
// CORS headers as per spec
|
||||||
|
exchange.getResponseHeaders().put(HttpString.tryFromString("Access-Control-Allow-Origin"), "*");
|
||||||
|
exchange.getResponseHeaders().put(HttpString.tryFromString("Access-Control-Allow-Methods"), "GET, POST, PUT, DELETE, OPTIONS");
|
||||||
|
exchange.getResponseHeaders().put(HttpString.tryFromString("Access-Control-Allow-Headers"), "Origin, X-Requested-With, Content-Type, Accept, Authorization");
|
||||||
|
|
||||||
child.handleRequest(exchange);
|
child.handleRequest(exchange);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
respond(exchange, HttpStatus.SC_BAD_REQUEST, GsonUtil.makeObj("error", e.getMessage()));
|
respond(exchange, HttpStatus.SC_BAD_REQUEST, GsonUtil.makeObj("error", e.getMessage()));
|
||||||
@@ -63,8 +71,10 @@ public class SaneHandler extends BasicHttpHandler {
|
|||||||
respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_ALREADY_EXISTS", e.getMessage());
|
respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_ALREADY_EXISTS", e.getMessage());
|
||||||
} catch (JsonMemberNotFoundException e) {
|
} catch (JsonMemberNotFoundException e) {
|
||||||
respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_JSON_MISSING_KEYS", e.getMessage());
|
respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_JSON_MISSING_KEYS", e.getMessage());
|
||||||
} catch (InvalidResponseJsonException | JsonSyntaxException e) {
|
} catch (InvalidResponseJsonException | JsonSyntaxException | MalformedJsonException e) {
|
||||||
respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_INVALID_JSON", e.getMessage());
|
respond(exchange, HttpStatus.SC_BAD_REQUEST, "M_INVALID_JSON", e.getMessage());
|
||||||
|
} catch (InvalidJsonException e) {
|
||||||
|
respond(exchange, HttpStatus.SC_BAD_REQUEST, e.getErrorCode(), e.getError());
|
||||||
} catch (InvalidCredentialsException e) {
|
} catch (InvalidCredentialsException e) {
|
||||||
respond(exchange, HttpStatus.SC_UNAUTHORIZED, "M_UNAUTHORIZED", e.getMessage());
|
respond(exchange, HttpStatus.SC_UNAUTHORIZED, "M_UNAUTHORIZED", e.getMessage());
|
||||||
} catch (ObjectNotFoundException e) {
|
} catch (ObjectNotFoundException e) {
|
||||||
|
@@ -46,7 +46,7 @@ public class BulkLookupHandler extends LookupHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleRequest(HttpServerExchange exchange) {
|
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
||||||
ClientBulkLookupRequest input = parseJsonTo(exchange, ClientBulkLookupRequest.class);
|
ClientBulkLookupRequest input = parseJsonTo(exchange, ClientBulkLookupRequest.class);
|
||||||
BulkLookupRequest lookupRequest = new BulkLookupRequest();
|
BulkLookupRequest lookupRequest = new BulkLookupRequest();
|
||||||
setRequesterInfo(lookupRequest, exchange);
|
setRequesterInfo(lookupRequest, exchange);
|
||||||
@@ -63,7 +63,9 @@ public class BulkLookupHandler extends LookupHandler {
|
|||||||
lookupRequest.setMappings(mappings);
|
lookupRequest.setMappings(mappings);
|
||||||
|
|
||||||
ClientBulkLookupAnswer answer = new ClientBulkLookupAnswer();
|
ClientBulkLookupAnswer answer = new ClientBulkLookupAnswer();
|
||||||
answer.addAll(strategy.find(lookupRequest));
|
answer.addAll(strategy.find(lookupRequest).get());
|
||||||
|
log.info("Finished bulk lookup request from {}", lookupRequest.getRequester());
|
||||||
|
|
||||||
respondJson(exchange, answer);
|
respondJson(exchange, answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* mxisd - Matrix Identity Server Daemon
|
|
||||||
* Copyright (C) 2018 Kamax Sarl
|
|
||||||
*
|
|
||||||
* https://www.kamax.io/
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
|
||||||
|
|
||||||
import io.kamax.mxisd.config.ViewConfig;
|
|
||||||
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
|
||||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
|
||||||
import io.kamax.mxisd.session.SessionMananger;
|
|
||||||
import io.undertow.server.HttpServerExchange;
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class RemoteSessionCheckHandler extends BasicHttpHandler {
|
|
||||||
|
|
||||||
private SessionMananger mgr;
|
|
||||||
private ViewConfig viewCfg;
|
|
||||||
|
|
||||||
public RemoteSessionCheckHandler(SessionMananger mgr, ViewConfig viewCfg) {
|
|
||||||
this.mgr = mgr;
|
|
||||||
this.viewCfg = viewCfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
|
||||||
String sid = getQueryParameter(exchange, "sid");
|
|
||||||
String secret = getQueryParameter(exchange, "client_secret");
|
|
||||||
|
|
||||||
try {
|
|
||||||
FileInputStream f = new FileInputStream(viewCfg.getSession().getRemote().getOnCheck().getSuccess());
|
|
||||||
String viewData = IOUtils.toString(f, StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
mgr.validateRemote(sid, secret);
|
|
||||||
|
|
||||||
writeBodyAsUtf8(exchange, viewData);
|
|
||||||
} catch (SessionNotValidatedException e) {
|
|
||||||
FileInputStream f = new FileInputStream(viewCfg.getSession().getRemote().getOnCheck().getFailure());
|
|
||||||
String viewData = IOUtils.toString(f, StandardCharsets.UTF_8);
|
|
||||||
writeBodyAsUtf8(exchange, viewData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* mxisd - Matrix Identity Server Daemon
|
|
||||||
* Copyright (C) 2018 Kamax Sarl
|
|
||||||
*
|
|
||||||
* https://www.kamax.io/
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
|
||||||
|
|
||||||
import io.kamax.mxisd.config.ViewConfig;
|
|
||||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
|
||||||
import io.kamax.mxisd.session.SessionMananger;
|
|
||||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
|
||||||
import io.undertow.server.HttpServerExchange;
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class RemoteSessionStartHandler extends BasicHttpHandler {
|
|
||||||
|
|
||||||
private SessionMananger mgr;
|
|
||||||
private ViewConfig viewCfg;
|
|
||||||
|
|
||||||
public RemoteSessionStartHandler(SessionMananger mgr, ViewConfig viewCfg) {
|
|
||||||
this.mgr = mgr;
|
|
||||||
this.viewCfg = viewCfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
|
||||||
String sid = getQueryParameter(exchange, "sid");
|
|
||||||
String secret = getQueryParameter(exchange, "client_secret");
|
|
||||||
IThreePidSession session = mgr.createRemote(sid, secret);
|
|
||||||
|
|
||||||
FileInputStream f = new FileInputStream(viewCfg.getSession().getRemote().getOnRequest().getSuccess());
|
|
||||||
String rawData = IOUtils.toString(f, StandardCharsets.UTF_8);
|
|
||||||
String data = rawData.replace("${checkLink}", RemoteIdentityAPIv1.getSessionCheck(session.getId(), session.getSecret()));
|
|
||||||
writeBodyAsUtf8(exchange, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -28,7 +28,7 @@ import io.kamax.mxisd.http.io.identity.RequestTokenResponse;
|
|||||||
import io.kamax.mxisd.http.io.identity.SessionEmailTokenRequestJson;
|
import io.kamax.mxisd.http.io.identity.SessionEmailTokenRequestJson;
|
||||||
import io.kamax.mxisd.http.io.identity.SessionPhoneTokenRequestJson;
|
import io.kamax.mxisd.http.io.identity.SessionPhoneTokenRequestJson;
|
||||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||||
import io.kamax.mxisd.session.SessionMananger;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -41,9 +41,9 @@ public class SessionStartHandler extends BasicHttpHandler {
|
|||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(SessionStartHandler.class);
|
private transient final Logger log = LoggerFactory.getLogger(SessionStartHandler.class);
|
||||||
|
|
||||||
private SessionMananger mgr;
|
private SessionManager mgr;
|
||||||
|
|
||||||
public SessionStartHandler(SessionMananger mgr) {
|
public SessionStartHandler(SessionManager mgr) {
|
||||||
this.mgr = mgr;
|
this.mgr = mgr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,36 +23,58 @@ package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
|||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
import io.kamax.mxisd.exception.BadRequestException;
|
||||||
import io.kamax.mxisd.http.IsAPIv1;
|
import io.kamax.mxisd.http.IsAPIv1;
|
||||||
|
import io.kamax.mxisd.http.io.identity.BindRequest;
|
||||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||||
import io.kamax.mxisd.invitation.InvitationManager;
|
import io.kamax.mxisd.invitation.InvitationManager;
|
||||||
import io.kamax.mxisd.session.SessionMananger;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
|
import io.undertow.util.QueryParameterUtils;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class SessionTpidBindHandler extends BasicHttpHandler {
|
public class SessionTpidBindHandler extends BasicHttpHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/3pid/bind";
|
public static final String Path = IsAPIv1.Base + "/3pid/bind";
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(SessionTpidBindHandler.class);
|
private transient final Logger log = LoggerFactory.getLogger(SessionTpidBindHandler.class);
|
||||||
|
|
||||||
private SessionMananger mgr;
|
private SessionManager mgr;
|
||||||
private InvitationManager invMgr;
|
private InvitationManager invMgr;
|
||||||
|
|
||||||
public SessionTpidBindHandler(SessionMananger mgr, InvitationManager invMgr) {
|
public SessionTpidBindHandler(SessionManager mgr, InvitationManager invMgr) {
|
||||||
this.mgr = mgr;
|
this.mgr = mgr;
|
||||||
this.invMgr = invMgr;
|
this.invMgr = invMgr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleRequest(HttpServerExchange exchange) {
|
public void handleRequest(HttpServerExchange exchange) {
|
||||||
String sid = getQueryParameter(exchange, "sid");
|
BindRequest bindReq = new BindRequest();
|
||||||
String secret = getQueryParameter(exchange, "client_secret");
|
bindReq.setSid(getQueryParameter(exchange, BindRequest.Keys.SessionID));
|
||||||
String mxid = getQueryParameter(exchange, "mxid");
|
bindReq.setSecret(getQueryParameter(exchange, BindRequest.Keys.Secret));
|
||||||
|
bindReq.setUserId(getQueryParameter(exchange, BindRequest.Keys.UserID));
|
||||||
|
|
||||||
|
String reqContentType = getContentType(exchange).orElse("application/octet-stream");
|
||||||
|
if (StringUtils.equals("application/x-www-form-urlencoded", reqContentType)) {
|
||||||
|
String body = getBodyUtf8(exchange);
|
||||||
|
Map<String, Deque<String>> parms = QueryParameterUtils.parseQueryString(body, StandardCharsets.UTF_8.name());
|
||||||
|
bindReq.setSid(getQueryParameter(parms, BindRequest.Keys.SessionID));
|
||||||
|
bindReq.setSecret(getQueryParameter(parms, BindRequest.Keys.Secret));
|
||||||
|
bindReq.setUserId(getQueryParameter(parms, BindRequest.Keys.UserID));
|
||||||
|
} else if (StringUtils.equals("application/json", reqContentType)) {
|
||||||
|
bindReq = parseJsonTo(exchange, BindRequest.class);
|
||||||
|
} else {
|
||||||
|
log.warn("Unknown encoding in 3PID session bind: {}", reqContentType);
|
||||||
|
log.warn("The request will most likely fail");
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mgr.bind(sid, secret, mxid);
|
mgr.bind(bindReq.getSid(), bindReq.getSecret(), bindReq.getUserId());
|
||||||
respond(exchange, new JsonObject());
|
respond(exchange, new JsonObject());
|
||||||
} catch (BadRequestException e) {
|
} catch (BadRequestException e) {
|
||||||
log.info("requested session was not validated");
|
log.info("requested session was not validated");
|
||||||
|
@@ -25,7 +25,7 @@ import io.kamax.mxisd.exception.SessionNotValidatedException;
|
|||||||
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.lookup.ThreePidValidation;
|
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||||
import io.kamax.mxisd.session.SessionMananger;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -34,11 +34,11 @@ public class SessionTpidGetValidatedHandler extends BasicHttpHandler {
|
|||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/3pid/getValidated3pid";
|
public static final String Path = IsAPIv1.Base + "/3pid/getValidated3pid";
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(SessionTpidGetValidatedHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(SessionTpidGetValidatedHandler.class);
|
||||||
|
|
||||||
private SessionMananger mgr;
|
private SessionManager mgr;
|
||||||
|
|
||||||
public SessionTpidGetValidatedHandler(SessionMananger mgr) {
|
public SessionTpidGetValidatedHandler(SessionManager mgr) {
|
||||||
this.mgr = mgr;
|
this.mgr = mgr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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.http.undertow.handler.identity.v1;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kamax.mxisd.http.IsAPIv1;
|
||||||
|
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||||
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
|
import io.undertow.server.HttpServerExchange;
|
||||||
|
|
||||||
|
public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
||||||
|
|
||||||
|
public static final String Path = IsAPIv1.Base + "/3pid/unbind";
|
||||||
|
|
||||||
|
private final SessionManager sessionMgr;
|
||||||
|
|
||||||
|
public SessionTpidUnbindHandler(SessionManager sessionMgr) {
|
||||||
|
this.sessionMgr = sessionMgr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleRequest(HttpServerExchange exchange) {
|
||||||
|
JsonObject body = parseJsonObject(exchange);
|
||||||
|
sessionMgr.unbind(body);
|
||||||
|
writeBodyAsUtf8(exchange, "{}");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -25,20 +25,18 @@ import io.kamax.mxisd.config.ViewConfig;
|
|||||||
import io.kamax.mxisd.http.IsAPIv1;
|
import io.kamax.mxisd.http.IsAPIv1;
|
||||||
import io.kamax.mxisd.http.io.identity.SuccessStatusJson;
|
import io.kamax.mxisd.http.io.identity.SuccessStatusJson;
|
||||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||||
import io.kamax.mxisd.session.SessionMananger;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
import io.kamax.mxisd.session.ValidationResult;
|
import io.kamax.mxisd.session.ValidationResult;
|
||||||
|
import io.kamax.mxisd.util.FileUtil;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import io.undertow.util.HttpString;
|
import io.undertow.util.HttpString;
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public class SessionValidateHandler extends BasicHttpHandler {
|
public class SessionValidateHandler extends BasicHttpHandler {
|
||||||
|
|
||||||
@@ -46,11 +44,11 @@ public class SessionValidateHandler extends BasicHttpHandler {
|
|||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(SessionValidateHandler.class);
|
private transient final Logger log = LoggerFactory.getLogger(SessionValidateHandler.class);
|
||||||
|
|
||||||
private SessionMananger mgr;
|
private SessionManager mgr;
|
||||||
private ServerConfig srvCfg;
|
private ServerConfig srvCfg;
|
||||||
private ViewConfig viewCfg;
|
private ViewConfig viewCfg;
|
||||||
|
|
||||||
public SessionValidateHandler(SessionMananger mgr, ServerConfig srvCfg, ViewConfig viewCfg) {
|
public SessionValidateHandler(SessionManager mgr, ServerConfig srvCfg, ViewConfig viewCfg) {
|
||||||
this.mgr = mgr;
|
this.mgr = mgr;
|
||||||
this.srvCfg = srvCfg;
|
this.srvCfg = srvCfg;
|
||||||
this.viewCfg = viewCfg;
|
this.viewCfg = viewCfg;
|
||||||
@@ -74,11 +72,11 @@ public class SessionValidateHandler extends BasicHttpHandler {
|
|||||||
if (isHtmlRequest) {
|
if (isHtmlRequest) {
|
||||||
handleHtmlRequest(exchange, medium, sid, secret, token);
|
handleHtmlRequest(exchange, medium, sid, secret, token);
|
||||||
} else {
|
} else {
|
||||||
handleJsonRequest(exchange, medium, sid, secret, token);
|
handleJsonRequest(exchange, sid, secret, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleHtmlRequest(HttpServerExchange exchange, String medium, String sid, String secret, String token) {
|
private void handleHtmlRequest(HttpServerExchange exchange, String medium, String sid, String secret, String token) {
|
||||||
log.info("Validating session {} for medium {}", sid, medium);
|
log.info("Validating session {} for medium {}", sid, medium);
|
||||||
ValidationResult r = mgr.validate(sid, secret, token);
|
ValidationResult r = mgr.validate(sid, secret, token);
|
||||||
log.info("Session {} was validated", sid);
|
log.info("Session {} was validated", sid);
|
||||||
@@ -95,27 +93,18 @@ public class SessionValidateHandler extends BasicHttpHandler {
|
|||||||
exchange.getResponseHeaders().add(HttpString.tryFromString("Location"), url);
|
exchange.getResponseHeaders().add(HttpString.tryFromString("Location"), url);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
if (r.isCanRemote()) {
|
String data = FileUtil.load(viewCfg.getSession().getOnTokenSubmit().getSuccess());
|
||||||
FileInputStream f = new FileInputStream(viewCfg.getSession().getLocalRemote().getOnTokenSubmit().getSuccess());
|
|
||||||
String url = srvCfg.getPublicUrl() + RemoteIdentityAPIv1.getRequestToken(r.getSession().getId(), r.getSession().getSecret());
|
|
||||||
String rawData = IOUtils.toString(f, StandardCharsets.UTF_8);
|
|
||||||
String data = rawData.replace("${remoteSessionLink}", url);
|
|
||||||
writeBodyAsUtf8(exchange, data);
|
writeBodyAsUtf8(exchange, data);
|
||||||
} else {
|
|
||||||
FileInputStream f = new FileInputStream(viewCfg.getSession().getLocalRemote().getOnTokenSubmit().getSuccess());
|
|
||||||
String data = IOUtils.toString(f, StandardCharsets.UTF_8);
|
|
||||||
writeBodyAsUtf8(exchange, data);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleJsonRequest(HttpServerExchange exchange, String medium, String sid, String secret, String token) {
|
private void handleJsonRequest(HttpServerExchange exchange, String sid, String secret, String token) {
|
||||||
log.info("Requested: {}", exchange.getRequestURL());
|
log.info("Requested: {}", exchange.getRequestURL());
|
||||||
|
|
||||||
ValidationResult r = mgr.validate(sid, secret, token);
|
mgr.validate(sid, secret, token);
|
||||||
log.info("Session {} was validated", sid);
|
log.info("Session {} was validated", sid);
|
||||||
|
|
||||||
respondJson(exchange, new SuccessStatusJson(true));
|
respondJson(exchange, new SuccessStatusJson(true));
|
||||||
|
@@ -20,10 +20,16 @@
|
|||||||
|
|
||||||
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.reflect.TypeToken;
|
||||||
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.crypto.KeyManager;
|
||||||
|
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.http.IsAPIv1;
|
import io.kamax.mxisd.http.IsAPIv1;
|
||||||
|
import io.kamax.mxisd.http.io.identity.StoreInviteRequest;
|
||||||
import io.kamax.mxisd.http.io.identity.ThreePidInviteReplyIO;
|
import io.kamax.mxisd.http.io.identity.ThreePidInviteReplyIO;
|
||||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInvite;
|
import io.kamax.mxisd.invitation.IThreePidInvite;
|
||||||
@@ -31,11 +37,13 @@ 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.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
|
import io.undertow.util.QueryParameterUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class StoreInviteHandler extends BasicHttpHandler {
|
public class StoreInviteHandler extends BasicHttpHandler {
|
||||||
|
|
||||||
@@ -53,21 +61,39 @@ public class StoreInviteHandler extends BasicHttpHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleRequest(HttpServerExchange exchange) {
|
public void handleRequest(HttpServerExchange exchange) {
|
||||||
Map<String, String> parameters = new HashMap<>();
|
String reqContentType = getContentType(exchange).orElse("application/octet-stream");
|
||||||
|
JsonObject invJson = new JsonObject();
|
||||||
|
|
||||||
for (Map.Entry<String, Deque<String>> entry : exchange.getQueryParameters().entrySet()) {
|
if (StringUtils.startsWith(reqContentType, "application/json")) {
|
||||||
if (Objects.nonNull(entry.getValue().peekFirst())) {
|
invJson = parseJsonObject(exchange);
|
||||||
parameters.put(entry.getKey(), entry.getValue().peekFirst());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO test with missing parameters to see behaviour
|
// Backward compatibility for pre-r0.1.0 implementations
|
||||||
String sender = parameters.get("sender");
|
else if (StringUtils.startsWith(reqContentType, "application/x-www-form-urlencoded")) {
|
||||||
String medium = parameters.get("medium");
|
String body = getBodyUtf8(exchange);
|
||||||
String address = parameters.get("address");
|
Map<String, Deque<String>> parms = QueryParameterUtils.parseQueryString(body, StandardCharsets.UTF_8.name());
|
||||||
String roomId = parameters.get("room_id");
|
for (Map.Entry<String, Deque<String>> entry : parms.entrySet()) {
|
||||||
|
if (entry.getValue().size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
IThreePidInvite invite = new ThreePidInvite(MatrixID.asAcceptable(sender), medium, address, roomId, parameters);
|
if (entry.getValue().size() > 1) {
|
||||||
|
throw new BadRequestException("key " + entry.getKey() + " has more than one value");
|
||||||
|
}
|
||||||
|
|
||||||
|
invJson.addProperty(entry.getKey(), entry.getValue().peekFirst());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException("Unsupported Content-Type: " + reqContentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
Type parmType = new TypeToken<Map<String, String>>() {
|
||||||
|
}.getType();
|
||||||
|
Map<String, String> parameters = GsonUtil.get().fromJson(invJson, parmType);
|
||||||
|
StoreInviteRequest inv = GsonUtil.get().fromJson(invJson, StoreInviteRequest.class);
|
||||||
|
_MatrixID sender = MatrixID.asAcceptable(inv.getSender());
|
||||||
|
|
||||||
|
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()));
|
respondJson(exchange, new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), cfg.getPublicUrl()));
|
||||||
|
@@ -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)) {
|
||||||
|
@@ -28,6 +28,7 @@ import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
public interface LookupStrategy {
|
public interface LookupStrategy {
|
||||||
|
|
||||||
@@ -43,6 +44,6 @@ public interface LookupStrategy {
|
|||||||
|
|
||||||
Optional<SingleLookupReply> findRecursive(SingleLookupRequest request);
|
Optional<SingleLookupReply> findRecursive(SingleLookupRequest request);
|
||||||
|
|
||||||
List<ThreePidMapping> find(BulkLookupRequest requests);
|
CompletableFuture<List<ThreePidMapping>> find(BulkLookupRequest requests);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -21,19 +21,21 @@
|
|||||||
package io.kamax.mxisd.lookup.strategy;
|
package io.kamax.mxisd.lookup.strategy;
|
||||||
|
|
||||||
import edazdarevic.commons.net.CIDRUtils;
|
import edazdarevic.commons.net.CIDRUtils;
|
||||||
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
import io.kamax.matrix.json.MatrixJson;
|
||||||
import io.kamax.mxisd.config.MxisdConfig;
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
import io.kamax.mxisd.exception.ConfigurationException;
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
import io.kamax.mxisd.lookup.*;
|
import io.kamax.mxisd.lookup.*;
|
||||||
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher;
|
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher;
|
||||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.List;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class RecursivePriorityLookupStrategy implements LookupStrategy {
|
public class RecursivePriorityLookupStrategy implements LookupStrategy {
|
||||||
@@ -44,6 +46,8 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
|
|||||||
private List<IThreePidProvider> providers;
|
private List<IThreePidProvider> providers;
|
||||||
private IBridgeFetcher bridge;
|
private IBridgeFetcher bridge;
|
||||||
|
|
||||||
|
private Map<String, CompletableFuture<List<ThreePidMapping>>> bulkLookupInProgress = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private List<CIDRUtils> allowedCidr = new ArrayList<>();
|
private List<CIDRUtils> allowedCidr = new ArrayList<>();
|
||||||
|
|
||||||
public RecursivePriorityLookupStrategy(MxisdConfig.Lookup cfg, List<? extends IThreePidProvider> providers, IBridgeFetcher bridge) {
|
public RecursivePriorityLookupStrategy(MxisdConfig.Lookup cfg, List<? extends IThreePidProvider> providers, IBridgeFetcher bridge) {
|
||||||
@@ -53,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());
|
||||||
@@ -182,11 +186,27 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ThreePidMapping> find(BulkLookupRequest request) {
|
public CompletableFuture<List<ThreePidMapping>> find(BulkLookupRequest request) {
|
||||||
if (!cfg.getBulk().getEnabled()) {
|
if (!cfg.getBulk().getEnabled()) {
|
||||||
return Collections.emptyList();
|
return CompletableFuture.completedFuture(new ArrayList<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String payloadId = DigestUtils.md5Hex(MatrixJson.encodeCanonical(GsonUtil.makeObj(request)));
|
||||||
|
|
||||||
|
log.info("Computed Payload ID: {}", payloadId);
|
||||||
|
synchronized (this) {
|
||||||
|
CompletableFuture<List<ThreePidMapping>> f = bulkLookupInProgress.get(payloadId);
|
||||||
|
if (Objects.nonNull(f)) {
|
||||||
|
log.info("Returning existing future for Payload ID {}", payloadId);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
bulkLookupInProgress.put(payloadId, new CompletableFuture<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Processing Payload ID {}", payloadId);
|
||||||
|
|
||||||
|
CompletableFuture<List<ThreePidMapping>> result = bulkLookupInProgress.get(payloadId);
|
||||||
List<ThreePidMapping> mapToDo = new ArrayList<>(request.getMappings());
|
List<ThreePidMapping> mapToDo = new ArrayList<>(request.getMappings());
|
||||||
List<ThreePidMapping> mapFoundAll = new ArrayList<>();
|
List<ThreePidMapping> mapFoundAll = new ArrayList<>();
|
||||||
|
|
||||||
@@ -205,7 +225,9 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
|
|||||||
mapToDo.removeAll(mapFound);
|
mapToDo.removeAll(mapFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapFoundAll;
|
log.info("Processed Payload ID {}", payloadId);
|
||||||
|
result.complete(mapFoundAll);
|
||||||
|
return bulkLookupInProgress.remove(payloadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.notification;
|
package io.kamax.mxisd.notification;
|
||||||
|
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||||
@@ -36,6 +37,6 @@ public interface NotificationHandler {
|
|||||||
|
|
||||||
void sendForValidation(IThreePidSession session);
|
void sendForValidation(IThreePidSession session);
|
||||||
|
|
||||||
void sendForRemoteValidation(IThreePidSession session);
|
void sendForFraudulentUnbind(ThreePid tpid);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.notification;
|
package io.kamax.mxisd.notification;
|
||||||
|
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||||
import io.kamax.mxisd.config.threepid.notification.NotificationConfig;
|
import io.kamax.mxisd.config.threepid.notification.NotificationConfig;
|
||||||
import io.kamax.mxisd.exception.NotImplementedException;
|
import io.kamax.mxisd.exception.NotImplementedException;
|
||||||
@@ -77,8 +78,8 @@ public class NotificationManager {
|
|||||||
ensureMedium(session.getThreePid().getMedium()).sendForValidation(session);
|
ensureMedium(session.getThreePid().getMedium()).sendForValidation(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendForRemoteValidation(IThreePidSession session) {
|
public void sendForFraudulentUnbind(ThreePid tpid) throws NotImplementedException {
|
||||||
ensureMedium(session.getThreePid().getMedium()).sendForRemoteValidation(session);
|
ensureMedium(tpid.getMedium()).sendForFraudulentUnbind(tpid);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -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) {
|
||||||
|
228
src/main/java/io/kamax/mxisd/session/SessionManager.java
Normal file
228
src/main/java/io/kamax/mxisd/session/SessionManager.java
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* 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.session;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kamax.matrix.MatrixID;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
|
import io.kamax.matrix._MatrixID;
|
||||||
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
import io.kamax.mxisd.config.SessionConfig;
|
||||||
|
import io.kamax.mxisd.exception.NotAllowedException;
|
||||||
|
import io.kamax.mxisd.exception.NotImplementedException;
|
||||||
|
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
||||||
|
import io.kamax.mxisd.exception.SessionUnknownException;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
|
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||||
|
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||||
|
import io.kamax.mxisd.notification.NotificationManager;
|
||||||
|
import io.kamax.mxisd.storage.IStorage;
|
||||||
|
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||||
|
import io.kamax.mxisd.threepid.session.ThreePidSession;
|
||||||
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate;
|
||||||
|
|
||||||
|
public class SessionManager {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SessionManager.class);
|
||||||
|
|
||||||
|
private SessionConfig cfg;
|
||||||
|
private MatrixConfig mxCfg;
|
||||||
|
private IStorage storage;
|
||||||
|
private NotificationManager notifMgr;
|
||||||
|
private LookupStrategy lookupMgr;
|
||||||
|
|
||||||
|
// FIXME export into central class, set version
|
||||||
|
private CloseableHttpClient client;
|
||||||
|
|
||||||
|
public SessionManager(
|
||||||
|
SessionConfig cfg,
|
||||||
|
MatrixConfig mxCfg,
|
||||||
|
IStorage storage,
|
||||||
|
NotificationManager notifMgr,
|
||||||
|
LookupStrategy lookupMgr,
|
||||||
|
CloseableHttpClient client
|
||||||
|
) {
|
||||||
|
this.cfg = cfg;
|
||||||
|
this.mxCfg = mxCfg;
|
||||||
|
this.storage = storage;
|
||||||
|
this.notifMgr = notifMgr;
|
||||||
|
this.lookupMgr = lookupMgr;
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThreePidSession getSession(String sid, String secret) {
|
||||||
|
Optional<IThreePidSessionDao> dao = storage.getThreePidSession(sid);
|
||||||
|
if (!dao.isPresent() || !StringUtils.equals(dao.get().getSecret(), secret)) {
|
||||||
|
throw new SessionUnknownException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ThreePidSession(dao.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThreePidSession getSessionIfValidated(String sid, String secret) {
|
||||||
|
ThreePidSession session = getSession(sid, secret);
|
||||||
|
if (!session.isValidated()) {
|
||||||
|
throw new SessionNotValidatedException();
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String create(String server, ThreePid tpid, String secret, int attempt, String nextLink) {
|
||||||
|
PolicyTemplate policy = cfg.getPolicy().getValidation();
|
||||||
|
if (!policy.isEnabled()) {
|
||||||
|
throw new NotAllowedException("Validating 3PID is disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
|
log.info("Server {} is asking to create session for {} (Attempt #{}) - Next link: {}", server, tpid, attempt, nextLink);
|
||||||
|
Optional<IThreePidSessionDao> dao = storage.findThreePidSession(tpid, secret);
|
||||||
|
if (dao.isPresent()) {
|
||||||
|
ThreePidSession session = new ThreePidSession(dao.get());
|
||||||
|
log.info("We already have a session for {}: {}", tpid, session.getId());
|
||||||
|
if (session.getAttempt() < attempt) {
|
||||||
|
log.info("Received attempt {} is greater than stored attempt {}, sending validation communication", attempt, session.getAttempt());
|
||||||
|
notifMgr.sendForValidation(session);
|
||||||
|
log.info("Sent validation notification to {}", tpid);
|
||||||
|
session.increaseAttempt();
|
||||||
|
storage.updateThreePidSession(session.getDao());
|
||||||
|
}
|
||||||
|
|
||||||
|
return session.getId();
|
||||||
|
} else {
|
||||||
|
log.info("No existing session for {}", tpid);
|
||||||
|
|
||||||
|
String sessionId;
|
||||||
|
do {
|
||||||
|
sessionId = Long.toString(System.currentTimeMillis());
|
||||||
|
} while (storage.getThreePidSession(sessionId).isPresent());
|
||||||
|
|
||||||
|
String token = RandomStringUtils.randomNumeric(6);
|
||||||
|
ThreePidSession session = new ThreePidSession(sessionId, server, tpid, secret, attempt, nextLink, token);
|
||||||
|
log.info("Generated new session {} to validate {} from server {}", sessionId, tpid, server);
|
||||||
|
|
||||||
|
storage.insertThreePidSession(session.getDao());
|
||||||
|
log.info("Stored session {}", sessionId, tpid, server);
|
||||||
|
|
||||||
|
log.info("Session {} for {}: sending validation notification", sessionId, tpid);
|
||||||
|
notifMgr.sendForValidation(session);
|
||||||
|
|
||||||
|
return sessionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationResult validate(String sid, String secret, String token) {
|
||||||
|
ThreePidSession session = getSession(sid, secret);
|
||||||
|
log.info("Attempting validation for session {} from {}", session.getId(), session.getServer());
|
||||||
|
|
||||||
|
session.validate(token);
|
||||||
|
storage.updateThreePidSession(session.getDao());
|
||||||
|
log.info("Session {} has been validated locally", session.getId());
|
||||||
|
|
||||||
|
ValidationResult r = new ValidationResult(session);
|
||||||
|
session.getNextLink().ifPresent(r::setNextUrl);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThreePidValidation getValidated(String sid, String secret) {
|
||||||
|
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||||
|
return new ThreePidValidation(session.getThreePid(), session.getValidationTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(String sid, String secret, String mxidRaw) {
|
||||||
|
// We make sure we have an acceptable User ID
|
||||||
|
if (StringUtils.isEmpty(mxidRaw)) {
|
||||||
|
throw new IllegalArgumentException("No Matrix User ID provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We ensure the session was validated
|
||||||
|
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||||
|
|
||||||
|
// We parse the Matrix ID as acceptable
|
||||||
|
_MatrixID mxid = MatrixID.asAcceptable(mxidRaw);
|
||||||
|
|
||||||
|
// Only accept binds if the domain matches our own
|
||||||
|
if (!StringUtils.equalsIgnoreCase(mxCfg.getDomain(), mxid.getDomain())) {
|
||||||
|
throw new NotAllowedException("Only Matrix IDs from domain " + mxCfg + " can be bound");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Session {}: Binding of {}:{} to Matrix ID {} is accepted",
|
||||||
|
session.getId(), session.getThreePid().getMedium(), session.getThreePid().getAddress(), mxid.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
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")) {
|
||||||
|
/* This is a HS request to remove a 3PID and is considered:
|
||||||
|
* - An attack on user privacy
|
||||||
|
* - A baffling spec breakage requiring IS and HS 3PID info to be independent [1]
|
||||||
|
* - A baffling spec breakage that 3PID (un)bind is only one way [2]
|
||||||
|
*
|
||||||
|
* Given the lack of response on our extensive feedback on the proposal [3] which has not landed in the spec yet [4],
|
||||||
|
* We'll be denying such unbind requests and will inform users using their 3PID that a fraudulent attempt of
|
||||||
|
* removing their 3PID binding has been attempted and blocked.
|
||||||
|
*
|
||||||
|
* [1]: https://matrix.org/docs/spec/client_server/r0.4.0.html#adding-account-administrative-contact-information
|
||||||
|
* [2]: https://matrix.org/docs/spec/identity_service/r0.1.0.html#privacy
|
||||||
|
* [3]: https://docs.google.com/document/d/135g2muVxmuml0iUnLoTZxk8M2ZSt3kJzg81chGh51yg/edit
|
||||||
|
* [4]: https://github.com/matrix-org/matrix-doc/issues/1194
|
||||||
|
*/
|
||||||
|
|
||||||
|
log.warn("A remote host attempted to unbind without proper authorization. Request was denied");
|
||||||
|
|
||||||
|
if (!cfg.getPolicy().getUnbind().getFraudulent().getSendWarning()) {
|
||||||
|
log.info("Not sending notification to 3PID owner as per configuration");
|
||||||
|
} else {
|
||||||
|
log.info("Sending notification to 3PID owner as per configuration");
|
||||||
|
|
||||||
|
ThreePid tpid = GsonUtil.get().fromJson(GsonUtil.getObj(reqData, "threepid"), ThreePid.class);
|
||||||
|
Optional<SingleLookupReply> lookup = lookupMgr.findLocal(tpid.getMedium(), tpid.getAddress());
|
||||||
|
if (!lookup.isPresent()) {
|
||||||
|
log.info("No 3PID owner found, not sending any notification");
|
||||||
|
} else {
|
||||||
|
log.info("3PID owner found, sending notification");
|
||||||
|
try {
|
||||||
|
notifMgr.sendForFraudulentUnbind(tpid);
|
||||||
|
log.info("Notification sent");
|
||||||
|
} catch (NotImplementedException e) {
|
||||||
|
log.warn("Unable to send notification: {}", e.getMessage());
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
log.warn("Unable to send notification due to unknown error. See stacktrace below", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Denying request");
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,392 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.session;
|
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.i18n.phonenumbers.NumberParseException;
|
|
||||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
|
||||||
import com.google.i18n.phonenumbers.Phonenumber;
|
|
||||||
import io.kamax.matrix.MatrixID;
|
|
||||||
import io.kamax.matrix.ThreePid;
|
|
||||||
import io.kamax.matrix.ThreePidMedium;
|
|
||||||
import io.kamax.matrix._MatrixID;
|
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
|
||||||
import io.kamax.mxisd.config.SessionConfig;
|
|
||||||
import io.kamax.mxisd.exception.*;
|
|
||||||
import io.kamax.mxisd.http.io.identity.RequestTokenResponse;
|
|
||||||
import io.kamax.mxisd.http.undertow.handler.identity.v1.RemoteIdentityAPIv1;
|
|
||||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
|
||||||
import io.kamax.mxisd.matrix.IdentityServerUtils;
|
|
||||||
import io.kamax.mxisd.notification.NotificationManager;
|
|
||||||
import io.kamax.mxisd.storage.IStorage;
|
|
||||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
|
||||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
|
||||||
import io.kamax.mxisd.threepid.session.ThreePidSession;
|
|
||||||
import io.kamax.mxisd.util.GsonParser;
|
|
||||||
import io.kamax.mxisd.util.RestClientUtils;
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.commons.lang.RandomStringUtils;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
||||||
import org.apache.http.client.methods.HttpGet;
|
|
||||||
import org.apache.http.client.methods.HttpPost;
|
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate;
|
|
||||||
import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate.PolicySource;
|
|
||||||
|
|
||||||
public class SessionMananger {
|
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(SessionMananger.class);
|
|
||||||
|
|
||||||
private SessionConfig cfg;
|
|
||||||
private MatrixConfig mxCfg;
|
|
||||||
private IStorage storage;
|
|
||||||
private NotificationManager notifMgr;
|
|
||||||
|
|
||||||
private GsonParser parser = new GsonParser();
|
|
||||||
private PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); // FIXME refactor for sessions handling their own stuff
|
|
||||||
|
|
||||||
// FIXME export into central class, set version
|
|
||||||
private CloseableHttpClient client;
|
|
||||||
|
|
||||||
public SessionMananger(SessionConfig cfg, MatrixConfig mxCfg, IStorage storage, NotificationManager notifMgr, CloseableHttpClient client) {
|
|
||||||
this.cfg = cfg;
|
|
||||||
this.mxCfg = mxCfg;
|
|
||||||
this.storage = storage;
|
|
||||||
this.notifMgr = notifMgr;
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isLocal(ThreePid tpid) {
|
|
||||||
if (!ThreePidMedium.Email.is(tpid.getMedium())) { // We can only handle E-mails for now
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String domain = tpid.getAddress().split("@")[1];
|
|
||||||
return StringUtils.equalsIgnoreCase(mxCfg.getDomain(), domain);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ThreePidSession getSession(String sid, String secret) {
|
|
||||||
Optional<IThreePidSessionDao> dao = storage.getThreePidSession(sid);
|
|
||||||
if (!dao.isPresent() || !StringUtils.equals(dao.get().getSecret(), secret)) {
|
|
||||||
throw new SessionUnknownException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ThreePidSession(dao.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
private ThreePidSession getSessionIfValidated(String sid, String secret) {
|
|
||||||
ThreePidSession session = getSession(sid, secret);
|
|
||||||
if (!session.isValidated()) {
|
|
||||||
throw new SessionNotValidatedException();
|
|
||||||
}
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String create(String server, ThreePid tpid, String secret, int attempt, String nextLink) {
|
|
||||||
PolicyTemplate policy = cfg.getPolicy().getValidation();
|
|
||||||
if (!policy.isEnabled()) {
|
|
||||||
throw new NotAllowedException("Validating 3PID is disabled globally");
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized (this) {
|
|
||||||
log.info("Server {} is asking to create session for {} (Attempt #{}) - Next link: {}", server, tpid, attempt, nextLink);
|
|
||||||
Optional<IThreePidSessionDao> dao = storage.findThreePidSession(tpid, secret);
|
|
||||||
if (dao.isPresent()) {
|
|
||||||
ThreePidSession session = new ThreePidSession(dao.get());
|
|
||||||
log.info("We already have a session for {}: {}", tpid, session.getId());
|
|
||||||
if (session.getAttempt() < attempt) {
|
|
||||||
log.info("Received attempt {} is greater than stored attempt {}, sending validation communication", attempt, session.getAttempt());
|
|
||||||
notifMgr.sendForValidation(session);
|
|
||||||
log.info("Sent validation notification to {}", tpid);
|
|
||||||
session.increaseAttempt();
|
|
||||||
storage.updateThreePidSession(session.getDao());
|
|
||||||
}
|
|
||||||
|
|
||||||
return session.getId();
|
|
||||||
} else {
|
|
||||||
log.info("No existing session for {}", tpid);
|
|
||||||
|
|
||||||
boolean isLocal = isLocal(tpid);
|
|
||||||
log.info("Is 3PID bound to local domain? {}", isLocal);
|
|
||||||
|
|
||||||
// This might need a configuration by medium type?
|
|
||||||
PolicySource policySource = policy.forIf(isLocal);
|
|
||||||
if (!policySource.isEnabled() || (!policySource.toLocal() && !policySource.toRemote())) {
|
|
||||||
log.info("Session for {}: cancelled due to policy", tpid);
|
|
||||||
throw new NotAllowedException("Validating " + (isLocal ? "local" : "remote") + " 3PID is not allowed");
|
|
||||||
}
|
|
||||||
|
|
||||||
String sessionId;
|
|
||||||
do {
|
|
||||||
sessionId = Long.toString(System.currentTimeMillis());
|
|
||||||
} while (storage.getThreePidSession(sessionId).isPresent());
|
|
||||||
|
|
||||||
String token = RandomStringUtils.randomNumeric(6);
|
|
||||||
ThreePidSession session = new ThreePidSession(sessionId, server, tpid, secret, attempt, nextLink, token);
|
|
||||||
log.info("Generated new session {} to validate {} from server {}", sessionId, tpid, server);
|
|
||||||
|
|
||||||
// This might need a configuration by medium type?
|
|
||||||
if (policySource.toLocal()) {
|
|
||||||
log.info("Session {} for {}: sending local validation notification", sessionId, tpid);
|
|
||||||
notifMgr.sendForValidation(session);
|
|
||||||
} else {
|
|
||||||
log.info("Session {} for {}: sending remote-only validation notification", sessionId, tpid);
|
|
||||||
notifMgr.sendForRemoteValidation(session);
|
|
||||||
}
|
|
||||||
|
|
||||||
storage.insertThreePidSession(session.getDao());
|
|
||||||
log.info("Stored session {}", sessionId, tpid, server);
|
|
||||||
|
|
||||||
return sessionId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ValidationResult validate(String sid, String secret, String token) {
|
|
||||||
ThreePidSession session = getSession(sid, secret);
|
|
||||||
log.info("Attempting validation for session {} from {}", session.getId(), session.getServer());
|
|
||||||
|
|
||||||
boolean isLocal = isLocal(session.getThreePid());
|
|
||||||
PolicySource policy = cfg.getPolicy().getValidation().forIf(isLocal);
|
|
||||||
if (!policy.isEnabled()) {
|
|
||||||
throw new NotAllowedException("Validating " + (isLocal ? "local" : "remote") + " 3PID is not allowed");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ThreePidMedium.PhoneNumber.is(session.getThreePid().getMedium()) && session.isValidated() && session.isRemote()) {
|
|
||||||
submitRemote(session, token);
|
|
||||||
session.validateRemote();
|
|
||||||
return new ValidationResult(session, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
session.validate(token);
|
|
||||||
storage.updateThreePidSession(session.getDao());
|
|
||||||
log.info("Session {} has been validated locally", session.getId());
|
|
||||||
|
|
||||||
if (ThreePidMedium.PhoneNumber.is(session.getThreePid().getMedium()) && session.isValidated() && policy.toRemote()) {
|
|
||||||
createRemote(sid, secret);
|
|
||||||
// FIXME make the message configurable/customizable (templates?)
|
|
||||||
throw new MessageForClientException("You will receive a NEW code from another number. Enter it below");
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME definitely doable in a nicer way
|
|
||||||
ValidationResult r = new ValidationResult(session, policy.toRemote());
|
|
||||||
if (!policy.toLocal()) {
|
|
||||||
r.setNextUrl(RemoteIdentityAPIv1.getRequestToken(sid, secret));
|
|
||||||
} else {
|
|
||||||
session.getNextLink().ifPresent(r::setNextUrl);
|
|
||||||
}
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ThreePidValidation getValidated(String sid, String secret) {
|
|
||||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
|
||||||
return new ThreePidValidation(session.getThreePid(), session.getValidationTime());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bind(String sid, String secret, String mxidRaw) {
|
|
||||||
_MatrixID mxid = MatrixID.asAcceptable(mxidRaw);
|
|
||||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
|
||||||
|
|
||||||
if (!session.isRemote()) {
|
|
||||||
log.info("Session {} for {}: MXID {} was bound locally", sid, session.getThreePid(), mxid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Session {} for {}: MXID {} bind is remote", sid, session.getThreePid(), mxid);
|
|
||||||
if (!session.isRemoteValidated()) {
|
|
||||||
log.error("Session {} for {}: Not validated remotely", sid, session.getThreePid());
|
|
||||||
throw new SessionNotValidatedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Session {} for {}: Performing remote bind", sid, session.getThreePid());
|
|
||||||
|
|
||||||
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(
|
|
||||||
Arrays.asList(
|
|
||||||
new BasicNameValuePair("sid", session.getRemoteId()),
|
|
||||||
new BasicNameValuePair("client_secret", session.getRemoteSecret()),
|
|
||||||
new BasicNameValuePair("mxid", mxid.getId())
|
|
||||||
), StandardCharsets.UTF_8);
|
|
||||||
HttpPost bindReq = new HttpPost(session.getRemoteServer() + "/_matrix/identity/api/v1/3pid/bind");
|
|
||||||
bindReq.setEntity(entity);
|
|
||||||
|
|
||||||
try (CloseableHttpResponse response = client.execute(bindReq)) {
|
|
||||||
int status = response.getStatusLine().getStatusCode();
|
|
||||||
if (status < 200 || status >= 300) {
|
|
||||||
String body = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
|
||||||
log.error("Session {} for {}: Remote IS {} failed when trying to bind {} for remote session {}\n{}",
|
|
||||||
sid, session.getThreePid(), session.getRemoteServer(), mxid, session.getRemoteId(), body);
|
|
||||||
throw new RemoteIdentityServerException(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.error("Session {} for {}: MXID {} was bound remotely", sid, session.getThreePid(), mxid);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Session {} for {}: I/O Error when trying to bind mxid {}", sid, session.getThreePid(), mxid);
|
|
||||||
throw new RemoteIdentityServerException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IThreePidSession createRemote(String sid, String secret) {
|
|
||||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
|
||||||
log.info("Creating remote 3PID session for {} with local session [{}] to {}", session.getThreePid(), sid);
|
|
||||||
|
|
||||||
boolean isLocal = isLocal(session.getThreePid());
|
|
||||||
PolicySource policy = cfg.getPolicy().getValidation().forIf(isLocal);
|
|
||||||
if (!policy.isEnabled() || !policy.toRemote()) {
|
|
||||||
throw new NotAllowedException("Validating " + (isLocal ? "local" : "remote") + " 3PID is not allowed");
|
|
||||||
}
|
|
||||||
log.info("Remote 3PID is allowed by policy");
|
|
||||||
|
|
||||||
List<String> servers = mxCfg.getIdentity().getServers(policy.getToRemote().getServer());
|
|
||||||
if (servers.isEmpty()) {
|
|
||||||
throw new FeatureNotAvailable("Remote 3PID sessions are enabled but server list is " +
|
|
||||||
"misconstrued (invalid ID or empty list");
|
|
||||||
}
|
|
||||||
|
|
||||||
String is = servers.get(0);
|
|
||||||
String url = IdentityServerUtils.findIsUrlForDomain(is).orElse(is);
|
|
||||||
log.info("Will use IS endpoint {}", url);
|
|
||||||
|
|
||||||
String remoteSecret = session.isRemote() ? session.getRemoteSecret() : RandomStringUtils.randomAlphanumeric(16);
|
|
||||||
|
|
||||||
JsonObject body = new JsonObject();
|
|
||||||
body.addProperty("client_secret", remoteSecret);
|
|
||||||
body.addProperty(session.getThreePid().getMedium(), session.getThreePid().getAddress());
|
|
||||||
body.addProperty("send_attempt", session.increaseAndGetRemoteAttempt());
|
|
||||||
if (ThreePidMedium.PhoneNumber.is(session.getThreePid().getMedium())) {
|
|
||||||
try {
|
|
||||||
Phonenumber.PhoneNumber msisdn = phoneUtil.parse("+" + session.getThreePid().getAddress(), null);
|
|
||||||
String country = phoneUtil.getRegionCodeForNumber(msisdn).toUpperCase();
|
|
||||||
body.addProperty("phone_number", phoneUtil.format(msisdn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL));
|
|
||||||
body.addProperty("country", country);
|
|
||||||
} catch (NumberParseException e) {
|
|
||||||
throw new InternalServerError(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
body.addProperty(session.getThreePid().getMedium(), session.getThreePid().getAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Requesting remote session with attempt {}", session.getRemoteAttempt());
|
|
||||||
HttpPost tokenReq = RestClientUtils.post(url + "/_matrix/identity/api/v1/validate/" + session.getThreePid().getMedium() + "/requestToken", body);
|
|
||||||
try (CloseableHttpResponse response = client.execute(tokenReq)) {
|
|
||||||
int status = response.getStatusLine().getStatusCode();
|
|
||||||
if (status < 200 || status >= 300) {
|
|
||||||
JsonObject obj = parser.parseOptional(response).orElseThrow(() -> new RemoteIdentityServerException("Status " + status));
|
|
||||||
throw new RemoteIdentityServerException(obj.get("errcode").getAsString() + ": " + obj.get("error").getAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestTokenResponse data = new GsonParser().parse(response, RequestTokenResponse.class);
|
|
||||||
log.info("Remote Session ID: {}", data.getSid());
|
|
||||||
|
|
||||||
session.setRemoteData(url, data.getSid(), remoteSecret, 1);
|
|
||||||
storage.updateThreePidSession(session.getDao());
|
|
||||||
log.info("Updated Session {} with remote data", sid);
|
|
||||||
|
|
||||||
return session;
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.warn("Failed to create remote session with {} for {}: {}", url, session.getThreePid(), e.getMessage());
|
|
||||||
throw new RemoteIdentityServerException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void submitRemote(ThreePidSession session, String token) {
|
|
||||||
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(
|
|
||||||
Arrays.asList(
|
|
||||||
new BasicNameValuePair("sid", session.getRemoteId()),
|
|
||||||
new BasicNameValuePair("client_secret", session.getRemoteSecret()),
|
|
||||||
new BasicNameValuePair("token", token)
|
|
||||||
), StandardCharsets.UTF_8);
|
|
||||||
HttpPost submitReq = new HttpPost(session.getRemoteServer() + "/_matrix/identity/api/v1/submitToken");
|
|
||||||
submitReq.setEntity(entity);
|
|
||||||
|
|
||||||
try (CloseableHttpResponse response = client.execute(submitReq)) {
|
|
||||||
JsonObject o = new GsonParser().parse(response.getEntity().getContent());
|
|
||||||
if (!o.has("success") || !o.get("success").getAsBoolean()) {
|
|
||||||
String errcode = o.get("errcode").getAsString();
|
|
||||||
throw new RemoteIdentityServerException(errcode + ": " + o.get("error").getAsString());
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Successfully submitted validation token for {} to {}", session.getThreePid(), session.getRemoteServer());
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RemoteIdentityServerException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validateRemote(String sid, String secret) {
|
|
||||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
|
||||||
if (!session.isRemote()) {
|
|
||||||
throw new NotAllowedException("Cannot remotely validate a local session");
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Session {} for {}: Validating remote 3PID session {} on {}", sid, session.getThreePid(), session.getRemoteId(), session.getRemoteServer());
|
|
||||||
if (session.isRemoteValidated()) {
|
|
||||||
log.info("Session {} for {}: Already remotely validated", sid, session.getThreePid());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpGet validateReq = new HttpGet(session.getRemoteServer() + "/_matrix/identity/api/v1/3pid/getValidated3pid?sid=" + session.getRemoteId() + "&client_secret=" + session.getRemoteSecret());
|
|
||||||
try (CloseableHttpResponse response = client.execute(validateReq)) {
|
|
||||||
int status = response.getStatusLine().getStatusCode();
|
|
||||||
if (status < 200 || status >= 300) {
|
|
||||||
throw new RemoteIdentityServerException("Remote identity server returned with status " + status);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonObject o = new GsonParser().parse(response.getEntity().getContent());
|
|
||||||
if (o.has("errcode")) {
|
|
||||||
String errcode = o.get("errcode").getAsString();
|
|
||||||
if (StringUtils.equals("M_SESSION_NOT_VALIDATED", errcode)) {
|
|
||||||
throw new SessionNotValidatedException();
|
|
||||||
} else if (StringUtils.equals("M_NO_VALID_SESSION", errcode)) {
|
|
||||||
throw new SessionUnknownException();
|
|
||||||
} else {
|
|
||||||
throw new RemoteIdentityServerException("Unknown error while validating Remote 3PID session: " + errcode + " - " + o.get("error").getAsString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (o.has("validated_at")) {
|
|
||||||
ThreePid remoteThreePid = new ThreePid(o.get("medium").getAsString(), o.get("address").getAsString());
|
|
||||||
if (!session.getThreePid().equals(remoteThreePid)) { // sanity check
|
|
||||||
throw new InternalServerError("Local 3PID " + session.getThreePid() + " and remote 3PID " + remoteThreePid + " do not match for session " + session.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Session {} for {}: Remotely validated successfully", sid, session.getThreePid());
|
|
||||||
session.validateRemote();
|
|
||||||
storage.updateThreePidSession(session.getDao());
|
|
||||||
log.info("Session {} was updated in storage", sid);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.warn("Session {} for {}: Failed to validated remotely on {}: {}", sid, session.getThreePid(), session.getRemoteServer(), e.getMessage());
|
|
||||||
throw new RemoteIdentityServerException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -27,22 +27,16 @@ import java.util.Optional;
|
|||||||
public class ValidationResult {
|
public class ValidationResult {
|
||||||
|
|
||||||
private IThreePidSession session;
|
private IThreePidSession session;
|
||||||
private boolean canRemote;
|
|
||||||
private String nextUrl;
|
private String nextUrl;
|
||||||
|
|
||||||
public ValidationResult(IThreePidSession session, boolean canRemote) {
|
public ValidationResult(IThreePidSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.canRemote = canRemote;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IThreePidSession getSession() {
|
public IThreePidSession getSession() {
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCanRemote() {
|
|
||||||
return canRemote;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<String> getNextUrl() {
|
public Optional<String> getNextUrl() {
|
||||||
return Optional.ofNullable(nextUrl);
|
return Optional.ofNullable(nextUrl);
|
||||||
}
|
}
|
||||||
|
@@ -40,7 +40,6 @@ import org.apache.commons.lang.StringUtils;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -49,9 +48,9 @@ import java.util.Collection;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class OrmLiteSqliteStorage implements IStorage {
|
public class OrmLiteSqlStorage implements IStorage {
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(OrmLiteSqliteStorage.class);
|
private transient final Logger log = LoggerFactory.getLogger(OrmLiteSqlStorage.class);
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
private interface Getter<T> {
|
private interface Getter<T> {
|
||||||
@@ -71,11 +70,11 @@ public class OrmLiteSqliteStorage implements IStorage {
|
|||||||
private Dao<ThreePidSessionDao, String> sessionDao;
|
private Dao<ThreePidSessionDao, String> sessionDao;
|
||||||
private Dao<ASTransactionDao, String> asTxnDao;
|
private Dao<ASTransactionDao, String> asTxnDao;
|
||||||
|
|
||||||
public OrmLiteSqliteStorage(MxisdConfig cfg) {
|
public OrmLiteSqlStorage(MxisdConfig cfg) {
|
||||||
this(cfg.getStorage().getBackend(), cfg.getStorage().getProvider().getSqlite().getDatabase());
|
this(cfg.getStorage().getBackend(), cfg.getStorage().getProvider().getSqlite().getDatabase());
|
||||||
}
|
}
|
||||||
|
|
||||||
public OrmLiteSqliteStorage(String backend, String path) {
|
public OrmLiteSqlStorage(String backend, String path) {
|
||||||
if (StringUtils.isBlank(backend)) {
|
if (StringUtils.isBlank(backend)) {
|
||||||
throw new ConfigurationException("storage.backend");
|
throw new ConfigurationException("storage.backend");
|
||||||
}
|
}
|
||||||
@@ -85,13 +84,6 @@ public class OrmLiteSqliteStorage implements IStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withCatcher(() -> {
|
withCatcher(() -> {
|
||||||
if (path.startsWith("/") && !path.startsWith("//")) {
|
|
||||||
File parent = new File(path).getParentFile();
|
|
||||||
if (!parent.mkdirs() && !parent.isDirectory()) {
|
|
||||||
throw new RuntimeException("Unable to create DB parent directory: " + parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionSource connPool = new JdbcConnectionSource("jdbc:" + backend + ":" + path);
|
ConnectionSource connPool = new JdbcConnectionSource("jdbc:" + backend + ":" + path);
|
||||||
invDao = createDaoAndTable(connPool, ThreePidInviteIO.class);
|
invDao = createDaoAndTable(connPool, ThreePidInviteIO.class);
|
||||||
sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class);
|
sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class);
|
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.threepid.generator;
|
package io.kamax.mxisd.threepid.generator;
|
||||||
|
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
import io.kamax.mxisd.config.ServerConfig;
|
import io.kamax.mxisd.config.ServerConfig;
|
||||||
@@ -27,16 +28,12 @@ import io.kamax.mxisd.config.threepid.medium.GenericTemplateConfig;
|
|||||||
import io.kamax.mxisd.exception.InternalServerError;
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||||
import org.apache.commons.io.IOUtils;
|
import io.kamax.mxisd.util.FileUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
public abstract class GenericTemplateNotificationGenerator extends PlaceholderNotificationGenerator implements NotificationGenerator {
|
public abstract class GenericTemplateNotificationGenerator extends PlaceholderNotificationGenerator implements NotificationGenerator {
|
||||||
|
|
||||||
@@ -51,14 +48,7 @@ public abstract class GenericTemplateNotificationGenerator extends PlaceholderNo
|
|||||||
|
|
||||||
private String getTemplateContent(String location) {
|
private String getTemplateContent(String location) {
|
||||||
try {
|
try {
|
||||||
URI loc = URI.create(location);
|
return FileUtil.load(location);
|
||||||
InputStream is;
|
|
||||||
if (StringUtils.equals("classpath", loc.getScheme())) {
|
|
||||||
is = getClass().getResourceAsStream(loc.getSchemeSpecificPart());
|
|
||||||
} else {
|
|
||||||
is = new FileInputStream(location);
|
|
||||||
}
|
|
||||||
return IOUtils.toString(is, StandardCharsets.UTF_8);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new InternalServerError("Unable to read template content at " + location + ": " + e.getMessage());
|
throw new InternalServerError("Unable to read template content at " + location + ": " + e.getMessage());
|
||||||
}
|
}
|
||||||
@@ -84,13 +74,13 @@ 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
|
@Override
|
||||||
public String getForRemoteValidation(IThreePidSession session) {
|
public String getForFraudulentUnbind(ThreePid tpid) {
|
||||||
log.info("Generating notification content for remote-only 3PID session");
|
log.info("Generating notification content for fraudulent unbind");
|
||||||
return populateForRemoteValidation(session, getTemplateContent(cfg.getSession().getValidation().getRemote()));
|
return populateForFraudulentUndind(tpid, getTemplateContent(cfg.getSession().getUnbind().getFraudulent()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.threepid.generator;
|
package io.kamax.mxisd.threepid.generator;
|
||||||
|
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||||
@@ -36,6 +37,6 @@ public interface NotificationGenerator {
|
|||||||
|
|
||||||
String getForValidation(IThreePidSession session);
|
String getForValidation(IThreePidSession session);
|
||||||
|
|
||||||
String getForRemoteValidation(IThreePidSession session);
|
String getForFraudulentUnbind(ThreePid tpid);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,9 @@ import io.kamax.mxisd.threepid.session.IThreePidSession;
|
|||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.WordUtils;
|
import org.apache.commons.lang.WordUtils;
|
||||||
|
|
||||||
|
import static io.kamax.mxisd.http.io.identity.StoreInviteRequest.Keys.RoomName;
|
||||||
|
import static io.kamax.mxisd.http.io.identity.StoreInviteRequest.Keys.SenderDisplayName;
|
||||||
|
|
||||||
public abstract class PlaceholderNotificationGenerator {
|
public abstract class PlaceholderNotificationGenerator {
|
||||||
|
|
||||||
private MatrixConfig mxCfg;
|
private MatrixConfig mxCfg;
|
||||||
@@ -51,9 +54,9 @@ public abstract class PlaceholderNotificationGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected String populateForInvite(IMatrixIdInvite invite, String input) {
|
protected String populateForInvite(IMatrixIdInvite invite, String input) {
|
||||||
String senderName = invite.getProperties().getOrDefault("sender_display_name", "");
|
String senderName = invite.getProperties().getOrDefault(SenderDisplayName, "");
|
||||||
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getSender().getId());
|
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getSender().getId());
|
||||||
String roomName = invite.getProperties().getOrDefault("room_name", "");
|
String roomName = invite.getProperties().getOrDefault(RoomName, "");
|
||||||
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getRoomId());
|
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getRoomId());
|
||||||
|
|
||||||
return populateForCommon(new ThreePid(invite.getMedium(), invite.getAddress()), input)
|
return populateForCommon(new ThreePid(invite.getMedium(), invite.getAddress()), input)
|
||||||
@@ -69,9 +72,9 @@ public abstract class PlaceholderNotificationGenerator {
|
|||||||
protected String populateForReply(IThreePidInviteReply invite, String input) {
|
protected String populateForReply(IThreePidInviteReply invite, String input) {
|
||||||
ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress());
|
ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress());
|
||||||
|
|
||||||
String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", "");
|
String senderName = invite.getInvite().getProperties().getOrDefault(SenderDisplayName, "");
|
||||||
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
|
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
|
||||||
String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
|
String roomName = invite.getInvite().getProperties().getOrDefault(RoomName, "");
|
||||||
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
|
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
|
||||||
|
|
||||||
return populateForCommon(tpid, input)
|
return populateForCommon(tpid, input)
|
||||||
@@ -103,4 +106,8 @@ public abstract class PlaceholderNotificationGenerator {
|
|||||||
return populateForValidation(session, input);
|
return populateForValidation(session, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String populateForFraudulentUndind(ThreePid tpid, String input) {
|
||||||
|
return populateForCommon(tpid, input);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -26,6 +26,7 @@ import io.kamax.mxisd.config.ServerConfig;
|
|||||||
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
||||||
import io.kamax.mxisd.config.threepid.medium.EmailTemplateConfig;
|
import io.kamax.mxisd.config.threepid.medium.EmailTemplateConfig;
|
||||||
import io.kamax.mxisd.threepid.generator.GenericTemplateNotificationGenerator;
|
import io.kamax.mxisd.threepid.generator.GenericTemplateNotificationGenerator;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
public class GenericEmailNotificationGenerator extends GenericTemplateNotificationGenerator implements EmailGenerator {
|
public class GenericEmailNotificationGenerator extends GenericTemplateNotificationGenerator implements EmailGenerator {
|
||||||
|
|
||||||
@@ -46,8 +47,8 @@ public class GenericEmailNotificationGenerator extends GenericTemplateNotificati
|
|||||||
@Override
|
@Override
|
||||||
protected String populateForCommon(ThreePid recipient, String body) {
|
protected String populateForCommon(ThreePid recipient, String body) {
|
||||||
body = super.populateForCommon(recipient, body);
|
body = super.populateForCommon(recipient, body);
|
||||||
body = body.replace("%FROM_EMAIL%", cfg.getIdentity().getFrom());
|
body = body.replace("%FROM_EMAIL%", StringUtils.defaultIfEmpty(cfg.getIdentity().getFrom(), ""));
|
||||||
body = body.replace("%FROM_NAME%", cfg.getIdentity().getName());
|
body = body.replace("%FROM_NAME%", StringUtils.defaultIfEmpty(cfg.getIdentity().getName(), ""));
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -63,9 +63,9 @@ public class BuiltInNotificationHandlerSupplier implements NotificationHandlerSu
|
|||||||
|
|
||||||
private void acceptEmail(String handler, Mxisd mxisd) {
|
private void acceptEmail(String handler, Mxisd mxisd) {
|
||||||
if (StringUtils.equals(EmailRawNotificationHandler.ID, handler)) {
|
if (StringUtils.equals(EmailRawNotificationHandler.ID, handler)) {
|
||||||
JsonObject emailCfgJson = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.Email.getId());
|
Object o = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.Email.getId());
|
||||||
if (Objects.nonNull(emailCfgJson)) {
|
if (Objects.nonNull(o)) {
|
||||||
EmailConfig emailCfg = GsonUtil.get().fromJson(emailCfgJson, EmailConfig.class);
|
EmailConfig emailCfg = GsonUtil.get().fromJson(GsonUtil.makeObj(o), EmailConfig.class);
|
||||||
|
|
||||||
if (org.apache.commons.lang.StringUtils.isBlank(emailCfg.getGenerator())) {
|
if (org.apache.commons.lang.StringUtils.isBlank(emailCfg.getGenerator())) {
|
||||||
throw new ConfigurationException("notification.email.generator");
|
throw new ConfigurationException("notification.email.generator");
|
||||||
@@ -105,9 +105,9 @@ public class BuiltInNotificationHandlerSupplier implements NotificationHandlerSu
|
|||||||
|
|
||||||
private void acceptPhone(String handler, Mxisd mxisd) {
|
private void acceptPhone(String handler, Mxisd mxisd) {
|
||||||
if (StringUtils.equals(PhoneNotificationHandler.ID, handler)) {
|
if (StringUtils.equals(PhoneNotificationHandler.ID, handler)) {
|
||||||
JsonObject cfgJson = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.PhoneNumber.getId());
|
Object o = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.PhoneNumber.getId());
|
||||||
if (Objects.nonNull(cfgJson)) {
|
if (Objects.nonNull(o)) {
|
||||||
PhoneConfig cfg = GsonUtil.get().fromJson(cfgJson, PhoneConfig.class);
|
PhoneConfig cfg = GsonUtil.get().fromJson(GsonUtil.makeObj(o), PhoneConfig.class);
|
||||||
|
|
||||||
List<PhoneGenerator> generators = StreamSupport
|
List<PhoneGenerator> generators = StreamSupport
|
||||||
.stream(ServiceLoader.load(PhoneGeneratorSupplier.class).spliterator(), false)
|
.stream(ServiceLoader.load(PhoneGeneratorSupplier.class).spliterator(), false)
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.threepid.notification;
|
package io.kamax.mxisd.threepid.notification;
|
||||||
|
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||||
import io.kamax.mxisd.exception.ConfigurationException;
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
@@ -72,8 +73,8 @@ public abstract class GenericNotificationHandler<A extends ThreePidConnector, B
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendForRemoteValidation(IThreePidSession session) {
|
public void sendForFraudulentUnbind(ThreePid tpid) {
|
||||||
send(connector, session.getThreePid().getAddress(), generator.getForRemoteValidation(session));
|
send(connector, tpid.getAddress(), generator.getForFraudulentUnbind(tpid));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ package io.kamax.mxisd.threepid.notification.email;
|
|||||||
|
|
||||||
import com.sendgrid.SendGrid;
|
import com.sendgrid.SendGrid;
|
||||||
import com.sendgrid.SendGridException;
|
import com.sendgrid.SendGridException;
|
||||||
|
import io.kamax.matrix.ThreePid;
|
||||||
import io.kamax.matrix.ThreePidMedium;
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
import io.kamax.mxisd.as.IMatrixIdInvite;
|
import io.kamax.mxisd.as.IMatrixIdInvite;
|
||||||
import io.kamax.mxisd.config.MxisdConfig;
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
@@ -31,14 +32,12 @@ import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
|||||||
import io.kamax.mxisd.notification.NotificationHandler;
|
import io.kamax.mxisd.notification.NotificationHandler;
|
||||||
import io.kamax.mxisd.threepid.generator.PlaceholderNotificationGenerator;
|
import io.kamax.mxisd.threepid.generator.PlaceholderNotificationGenerator;
|
||||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||||
import org.apache.commons.io.IOUtils;
|
import io.kamax.mxisd.util.FileUtil;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
|
|
||||||
import static com.sendgrid.SendGrid.Email;
|
import static com.sendgrid.SendGrid.Email;
|
||||||
import static com.sendgrid.SendGrid.Response;
|
import static com.sendgrid.SendGrid.Response;
|
||||||
@@ -78,7 +77,7 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
|
|||||||
|
|
||||||
private String getFromFile(String path) {
|
private String getFromFile(String path) {
|
||||||
try {
|
try {
|
||||||
return IOUtils.toString(new FileInputStream(path), StandardCharsets.UTF_8);
|
return FileUtil.load(path);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("Couldn't create notification content using file " + path, e);
|
throw new RuntimeException("Couldn't create notification content using file " + path, e);
|
||||||
}
|
}
|
||||||
@@ -109,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().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())));
|
||||||
@@ -119,14 +118,14 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendForRemoteValidation(IThreePidSession session) {
|
public void sendForFraudulentUnbind(ThreePid tpid) {
|
||||||
EmailTemplate template = cfg.getTemplates().getSession().getLocal();
|
EmailTemplate template = cfg.getTemplates().getSession().getUnbind().getFraudulent();
|
||||||
Email email = getEmail();
|
Email email = getEmail();
|
||||||
email.setSubject(populateForRemoteValidation(session, template.getSubject()));
|
email.setSubject(populateForCommon(tpid, template.getSubject()));
|
||||||
email.setText(populateForRemoteValidation(session, getFromFile(template.getBody().getText())));
|
email.setText(populateForCommon(tpid, getFromFile(template.getBody().getText())));
|
||||||
email.setHtml(populateForRemoteValidation(session, getFromFile(template.getBody().getHtml())));
|
email.setHtml(populateForCommon(tpid, getFromFile(template.getBody().getHtml())));
|
||||||
|
|
||||||
send(session.getThreePid().getAddress(), email);
|
send(tpid.getAddress(), email);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void send(String recipient, Email email) {
|
private void send(String recipient, Email email) {
|
||||||
|
57
src/main/java/io/kamax/mxisd/util/FileUtil.java
Normal file
57
src/main/java/io/kamax/mxisd/util/FileUtil.java
Normal 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.util;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class FileUtil {
|
||||||
|
|
||||||
|
public static String load(String loc) throws IOException {
|
||||||
|
URI uri = URI.create(loc);
|
||||||
|
|
||||||
|
InputStream is;
|
||||||
|
if (StringUtils.equals("classpath", uri.getScheme())) {
|
||||||
|
String resource = uri.getSchemeSpecificPart();
|
||||||
|
is = FileUtil.class.getResourceAsStream(resource);
|
||||||
|
if (Objects.isNull(is)) {
|
||||||
|
throw new FileNotFoundException("No classpath resource: " + resource);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
is = new FileInputStream(loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return IOUtils.toString(is, StandardCharsets.UTF_8);
|
||||||
|
} finally {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1 @@
|
|||||||
|
io.kamax.mxisd.backend.sql.BuiltInDriverLoader
|
@@ -1,47 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<title>Matrix Token Verification</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 12pt;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
#message {
|
|
||||||
width: 1200px;
|
|
||||||
text-align: left;
|
|
||||||
padding: 1em;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-top: 50px;
|
|
||||||
|
|
||||||
-webkit-border-radius: 10px;
|
|
||||||
-moz-border-radius: 10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
border: 1px #ccc solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="message">
|
|
||||||
<p>Verification successful!</p>
|
|
||||||
<p>Your email will remain private and you will only be discoverable with it on your own server, or any related
|
|
||||||
servers configured by your system admin.<br/>
|
|
||||||
If you would like to be globally discoverable, start the process <a href="${remoteSessionLink}">here</a>.
|
|
||||||
<br/>If you chose to start the global publication process, wait until it is done before returning to your
|
|
||||||
client.</p>
|
|
||||||
<p>If the remote process is finished, or if you do not wish to start it at this time, you can now return to your
|
|
||||||
Matrix client to complete the process.</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,43 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<title>Matrix global token verification</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 12pt;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
#message {
|
|
||||||
width: 1200px;
|
|
||||||
text-align: left;
|
|
||||||
padding: 1em;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-top: 50px;
|
|
||||||
|
|
||||||
-webkit-border-radius: 10px;
|
|
||||||
-moz-border-radius: 10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
border: 1px #ccc solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="message">
|
|
||||||
<p>You do not seem to have validated your session with the global server. Please check your messages for one similar
|
|
||||||
to the one you received initially.<br/>
|
|
||||||
Once this is done, <a href="#">click here to continue</a></p>
|
|
||||||
<p>If this problem persists, contact your system administrator with the following info: Reference #ABC</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,41 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<title>Matrix global token verification</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 12pt;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
#message {
|
|
||||||
width: 1200px;
|
|
||||||
text-align: left;
|
|
||||||
padding: 1em;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-top: 50px;
|
|
||||||
|
|
||||||
-webkit-border-radius: 10px;
|
|
||||||
-moz-border-radius: 10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
border: 1px #ccc solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="message">
|
|
||||||
<p>Verification successful!</p>
|
|
||||||
<p>Return to your Matrix client to complete the process and make yourself globally discoverable.</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,42 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<title>Matrix global token verification</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 12pt;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
#message {
|
|
||||||
width: 1200px;
|
|
||||||
text-align: left;
|
|
||||||
padding: 1em;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-top: 50px;
|
|
||||||
|
|
||||||
-webkit-border-radius: 10px;
|
|
||||||
-moz-border-radius: 10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
border: 1px #ccc solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="message">
|
|
||||||
<p>The process to be globally discoverable has failed!<br/>You can try to refresh this page in a few seconds or
|
|
||||||
minutes.</p>
|
|
||||||
<p>If this problem persists, contact your system administrator with the following info: Reference #ABC</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -1,45 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html xmlns:th="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<title>Matrix global token verification</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 12pt;
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
#message {
|
|
||||||
width: 1200px;
|
|
||||||
text-align: left;
|
|
||||||
padding: 1em;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
margin-top: 50px;
|
|
||||||
|
|
||||||
-webkit-border-radius: 10px;
|
|
||||||
-moz-border-radius: 10px;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
|
||||||
|
|
||||||
background-color: #f8f8f8;
|
|
||||||
border: 1px #ccc solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="message">
|
|
||||||
<p>The process to be globally discoverable has started. A verification token has been requested on your behalf.</p>
|
|
||||||
<p>You will receive a similar communication as the first verification message.<br/>
|
|
||||||
Follow the instructions and come back to this page once you are told to return to your Matrix client or that the
|
|
||||||
verification was successful.</p>
|
|
||||||
<p>Once the validation was successful with the global server, please follow <a th:href="${checkLink}">this link</a>
|
|
||||||
to validate it with us.</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@@ -10,10 +10,9 @@ Content-Disposition: inline
|
|||||||
Hi,
|
Hi,
|
||||||
|
|
||||||
%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
||||||
Matrix. To join the conversation, register an account on http://%DOMAIN%
|
Matrix. To join the conversation, register an account on https://%DOMAIN%
|
||||||
|
|
||||||
You can also register an account on a public server, like Matrix.org, by going to
|
You can also register an account on a public server and get in touch with them.
|
||||||
https://riot.im/app/#/register?%INVITE_MEDIUM%=%INVITE_ADDRESS%
|
|
||||||
|
|
||||||
|
|
||||||
About Matrix:
|
About Matrix:
|
||||||
@@ -70,10 +69,9 @@ pre, code {
|
|||||||
<p>Hi,</p>
|
<p>Hi,</p>
|
||||||
|
|
||||||
<p>%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
<p>%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
||||||
Matrix. To join the conversation, register an account on <a href="http://%DOMAIN%">%DOMAIN%</a>.</p>
|
Matrix. To join the conversation, register an account on <a href="https://%DOMAIN%">%DOMAIN%</a>.</p>
|
||||||
|
|
||||||
<p>You can also register an account on a public server, like Matrix.org, by following
|
<pYou can also register an account on a public server and get in touch with them.</p>
|
||||||
<a href="https://riot.im/app/#/register?%INVITE_MEDIUM%=%INVITE_ADDRESS%">this link</a>.</p>
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<p>About Matrix:</p>
|
<p>About Matrix:</p>
|
||||||
|
135
src/main/resources/threepids/email/unbind-fraudulent.eml
Normal file
135
src/main/resources/threepids/email/unbind-fraudulent.eml
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
Subject: IMPORTANT - %DOMAIN% Matrix Identity Server - Unauthorized 3PID unbind blocked
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
|
||||||
|
|
||||||
|
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Disposition: inline
|
||||||
|
|
||||||
|
Hi,
|
||||||
|
|
||||||
|
**THIS IS IMPORTANT, PLEASE READ CAREFULLY**.
|
||||||
|
If you are the system administrator of the Matrix installation, read the second section.
|
||||||
|
|
||||||
|
This is a notification email that a possibly unauthorized entity has attempted to alter your
|
||||||
|
3PIDs (email, phone numbers, etc.) settings. The request was denied and no change has been made.
|
||||||
|
|
||||||
|
This is so you are aware of a possible failure in case you just tried to remove a 3PID from your account.
|
||||||
|
|
||||||
|
If you do not understand this email, please forward it to your System administrator.
|
||||||
|
|
||||||
|
-----------
|
||||||
|
|
||||||
|
As the system administrator:
|
||||||
|
|
||||||
|
If you are using synapse as a Homeserver, this is a known issue related to MSC1194 [1] and abuse of separation of concerns.
|
||||||
|
As a privacy-centric product and to protect your privacy, the request was actively blocked. We have written a more detailed
|
||||||
|
explanation on our Privacy wiki page [2] (Direct link [3]) so you can fully grasp the impact for you and your users.
|
||||||
|
|
||||||
|
We have open an issue [4] on the synapse repos to reflect the related privacy concerns and GDPR violation(s) and would
|
||||||
|
appreciate if you could comment on it or simply adds a thumbs up so the concerns are finally dealt with by the synapse dev team.
|
||||||
|
|
||||||
|
If you are using another Homeserver or this came following no action from your own users, then you have been the target
|
||||||
|
of an unbind attack from a rogue entity which was blocked. You may want to check your logs to see the exact source of
|
||||||
|
the attack and take relevant actions following your policy.
|
||||||
|
|
||||||
|
If you would like to disable these notifications, please see the 3PID sessions configuration documentation [5].
|
||||||
|
|
||||||
|
Thanks,
|
||||||
|
|
||||||
|
%DOMAIN_PRETTY% Admins
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[1] https://github.com/matrix-org/matrix-doc/issues/1194
|
||||||
|
[2] https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy
|
||||||
|
[3] https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy#msc1194-synapse-and-impacts-on-your-privacy
|
||||||
|
[4] https://github.com/matrix-org/synapse/issues/4540
|
||||||
|
[5] https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/session/session.md#configuration
|
||||||
|
|
||||||
|
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
||||||
|
Content-Type: multipart/related;
|
||||||
|
boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
|
||||||
|
type="text/html"
|
||||||
|
|
||||||
|
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR
|
||||||
|
Content-Type: text/html; charset=UTF-8
|
||||||
|
Content-Disposition: inline
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, code {
|
||||||
|
word-break: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#page {
|
||||||
|
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
|
||||||
|
font-color: #454545;
|
||||||
|
font-size: 12pt;
|
||||||
|
width: 100%%;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inner {
|
||||||
|
width: 640px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table id="page">
|
||||||
|
<tr>
|
||||||
|
<td> </td>
|
||||||
|
<td id="inner">
|
||||||
|
<p>Hi,</p>
|
||||||
|
|
||||||
|
<p><b>THIS IS IMPORTANT, PLEASE READ CAREFULLY</b>.<br/>
|
||||||
|
If you are the system administrator of the Matrix installation, read the second section.</p>
|
||||||
|
|
||||||
|
<p>This is a notification email that a possibly unauthorized entity has attempted to alter your
|
||||||
|
3PIDs (email, phone numbers, etc.) settings. The request was denied and no change has been made.</p>
|
||||||
|
|
||||||
|
<p>This is so you are aware of a possible failure in case you just tried to remove a 3PID from your account.</p>
|
||||||
|
|
||||||
|
<p>If you do not understand this email, please forward it to your System administrator.</p>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<p>As the system administrator:</p>
|
||||||
|
|
||||||
|
<p>If you are using synapse as a Homeserver, this is a known issue related to <a href="https://github.com/matrix-org/matrix-doc/issues/1194">MSC1194</a>
|
||||||
|
and abuse of separation of concerns. As a privacy-centric product and to protect your privacy, the request was actively
|
||||||
|
blocked. We have written a more detailed explanation on our <a href="https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy">Privacy wiki page</a>
|
||||||
|
(<a href="https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy#msc1194-synapse-and-impacts-on-your-privacy">Direct link to section</a>)
|
||||||
|
so you can fully grasp the impact for you and your users.</p>
|
||||||
|
|
||||||
|
<p>We have open an issue on the synapse repos to reflect the related privacy concerns and GDPR violation(s) and would
|
||||||
|
appreciate if you could comment on it or simply adds a thumbs up so the concerns are finally dealt with by the synapse dev team.<br/>
|
||||||
|
Issue: <a href="https://github.com/matrix-org/synapse/issues/4540">https://github.com/matrix-org/synapse/issues/4540</a></p>
|
||||||
|
|
||||||
|
<p>If you are using another Homeserver or this came following no action from your own users, then you have been the target
|
||||||
|
of an unbind attack from a rogue entity which was blocked. You may want to check your logs to see the exact source of
|
||||||
|
the attack and take relevant actions following your policy.</p>
|
||||||
|
|
||||||
|
<p>If you would like to disable these notifications, please see the
|
||||||
|
<a href="https://github.com/kamax-matrix/mxisd/blob/master/docs/threepids/session/session.md#configuration">3PID sessions configuration documentation.</a></p>
|
||||||
|
|
||||||
|
<p>Thanks,</p>
|
||||||
|
|
||||||
|
<p>%DOMAIN_PRETTY% Admins</p>
|
||||||
|
</td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR--
|
||||||
|
|
||||||
|
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--
|
@@ -1,102 +0,0 @@
|
|||||||
Subject: Linking your Email address to your Matrix account
|
|
||||||
MIME-Version: 1.0
|
|
||||||
Content-Type: multipart/alternative;
|
|
||||||
boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
|
|
||||||
|
|
||||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
|
||||||
Content-Type: text/plain; charset=UTF-8
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
Hello there!
|
|
||||||
|
|
||||||
We have received a request to link this email address with your Matrix account.
|
|
||||||
|
|
||||||
Due to the security policy in place, this email address can only be stored in the central Matrix Identity Server.
|
|
||||||
If you continue, your e-mail address and Matrix ID association will be made public without any current mean to be removed.
|
|
||||||
|
|
||||||
If you would still like to continue, you will need to:
|
|
||||||
1. Go to your private Public registration process page:
|
|
||||||
|
|
||||||
%VALIDATION_LINK%
|
|
||||||
|
|
||||||
2. Follow the registration process of the central Identity Server, usually another email with similar content
|
|
||||||
3. Once your email address validated with the central Identity Server, click on "Continue" on page of step #1
|
|
||||||
4. If your public association is found by our Identity server, the next step will be given to you.
|
|
||||||
|
|
||||||
|
|
||||||
If you didn't make this request, or do not want to make your address public, you can safely disregard this email.
|
|
||||||
|
|
||||||
%DOMAIN_PRETTY% Admins
|
|
||||||
|
|
||||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
|
||||||
Content-Type: multipart/related;
|
|
||||||
boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
|
|
||||||
type="text/html"
|
|
||||||
|
|
||||||
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR
|
|
||||||
Content-Type: text/html; charset=UTF-8
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<style type="text/css">
|
|
||||||
body {
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre, code {
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
#page {
|
|
||||||
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
|
|
||||||
font-color: #454545;
|
|
||||||
font-size: 12pt;
|
|
||||||
width: 100%%;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#inner {
|
|
||||||
width: 640px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notif_link a, .footer a {
|
|
||||||
color: #76CFA6 ! important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<table id="page">
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td id="inner">
|
|
||||||
<p>Hello there!</p>
|
|
||||||
|
|
||||||
<p>We have received a request to link this email address with your Matrix account.</p>
|
|
||||||
|
|
||||||
<p>Due to the security policy in place, this email address can only be stored in the central Matrix Identity Server.
|
|
||||||
If you continue, your e-mail address and Matrix ID association will be made public without any current mean to be removed.</p>
|
|
||||||
|
|
||||||
<p>If you would still like to continue, you will need to:
|
|
||||||
<ol>
|
|
||||||
<li>Go to your private <a href="%NEXT_URL%">Public registration process page</a></li>
|
|
||||||
<li>Follow the registration process of the central Identity Server, usually another email with similar content</li>
|
|
||||||
<li>Once your email address validated with the central Identity Server, click on "Continue" on page of step #1</li>
|
|
||||||
<li>If your public association is found by our Identity server, the next step will be given to you.</li>
|
|
||||||
</ol>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>If you didn't make this request, or do not want to make your address public, you can safely disregard this email.</p>
|
|
||||||
|
|
||||||
<p>%DOMAIN_PRETTY% Admins</p>
|
|
||||||
</td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR--
|
|
||||||
|
|
||||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--
|
|
@@ -9,7 +9,7 @@ Content-Disposition: inline
|
|||||||
|
|
||||||
Hello there!
|
Hello there!
|
||||||
|
|
||||||
We have received a request to link this email address with your Matrix account.
|
You or a server on your behalf requested to validate your email.
|
||||||
|
|
||||||
If it was really you who made this request, you can click on the following link to
|
If it was really you who made this request, you can click on the following link to
|
||||||
complete the verification of your email address:
|
complete the verification of your email address:
|
||||||
@@ -66,7 +66,7 @@ pre, code {
|
|||||||
<td id="inner">
|
<td id="inner">
|
||||||
<p>Hello there!</p>
|
<p>Hello there!</p>
|
||||||
|
|
||||||
<p>We have received a request to link this email address with your Matrix account.</p>
|
<p>You or a server on your behalf requested to validate your email.</p>
|
||||||
|
|
||||||
<p>If it was really you who made this request, you can click on the following link to
|
<p>If it was really you who made this request, you can click on the following link to
|
||||||
complete the verification of your email address:</p>
|
complete the verification of your email address:</p>
|
@@ -1 +1 @@
|
|||||||
You have been invited to a Matrix room by %SENDER_NAME_OR_ID%. Visit https://riot.im/ or any public server to join and start chatting!
|
You have been invited to a Matrix room by %SENDER_NAME_OR_ID%. Please get in touch with them for further instructions.
|
1
src/main/resources/threepids/sms/unbind-fraudulent.txt
Normal file
1
src/main/resources/threepids/sms/unbind-fraudulent.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
INFORMATIONAL ONLY - Someone attempted to change your Matrix 3PIDs, with a potential data leak. Please contact your system administrator.
|
@@ -1 +0,0 @@
|
|||||||
Your phone number will be made publicly searchable. To continue, you will need to enter two codes. Your first code is %VALIDATION_TOKEN%
|
|
@@ -20,23 +20,23 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.test.storage;
|
package io.kamax.mxisd.test.storage;
|
||||||
|
|
||||||
import io.kamax.mxisd.storage.ormlite.OrmLiteSqliteStorage;
|
import io.kamax.mxisd.storage.ormlite.OrmLiteSqlStorage;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
public class OrmLiteSqliteStorageTest {
|
public class OrmLiteSqlStorageTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void insertAsTxnDuplicate() {
|
public void insertAsTxnDuplicate() {
|
||||||
OrmLiteSqliteStorage store = new OrmLiteSqliteStorage("sqlite", ":memory:");
|
OrmLiteSqlStorage store = new OrmLiteSqlStorage("sqlite", ":memory:");
|
||||||
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
|
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
|
||||||
store.insertTransactionResult("mxisd", "2", Instant.now(), "{}");
|
store.insertTransactionResult("mxisd", "2", Instant.now(), "{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = RuntimeException.class)
|
@Test(expected = RuntimeException.class)
|
||||||
public void insertAsTxnSame() {
|
public void insertAsTxnSame() {
|
||||||
OrmLiteSqliteStorage store = new OrmLiteSqliteStorage("sqlite", ":memory:");
|
OrmLiteSqlStorage store = new OrmLiteSqlStorage("sqlite", ":memory:");
|
||||||
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
|
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
|
||||||
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
|
store.insertTransactionResult("mxisd", "1", Instant.now(), "{}");
|
||||||
}
|
}
|
Reference in New Issue
Block a user