Compare commits

...

9 Commits

Author SHA1 Message Date
Max Dor
5c660fdcaf Add forgotten CORS headers from Spring port 2019-02-05 19:09:47 +01:00
Max Dor
fbbafeb769 Cache processing of bulk lookups and de-dup concurrent requests 2019-02-04 06:04:39 +01:00
Max Dor
559f6a7401 Fix docs 2019-02-04 06:03:15 +01:00
Max Dor
3bebb33147 Revamp 3PID sessions
- Fix #93
- Fix #98
2019-02-04 05:26:33 +01:00
Max Dor
3e240fe34d Improve fraudulent unbind notification 2019-02-01 15:41:44 +01:00
Max Dor
635f6fdbe7 Implementation for blocking fraudulent 3PID /unbind attempts 2019-02-01 02:34:52 +01:00
Max Dor
4237eeb3b6 Skeleton for blocking fraudulent 3PID /unbind attempts 2019-01-30 00:29:51 +01:00
Max Dor
a0e91e7896 Use proper return codes for session errors 2019-01-30 00:28:55 +01:00
Max Dor
aab0b86646 Talk about projects using mxisd under the hood 2019-01-23 18:50:00 +01:00
56 changed files with 741 additions and 1293 deletions

View File

@@ -8,6 +8,7 @@ mxisd - Federated Matrix Identity Server
- [Getting Started](#getting-started)
- [Support](#support)
- [Contribute](#contribute)
- [Powered by mxisd](#powered-by-mxisd)
- [FAQ](#faq)
- [Contact](#contact)
@@ -96,6 +97,11 @@ You can contribute as an organisation/corporation by:
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.
# 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
See the [dedicated document](docs/faq.md)

View File

@@ -38,12 +38,12 @@ matrix:
- 'https://other1.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.name`: Public hostname of mxisd, if different from the Matrix domain.
- `server.port`: HTTP port to listen on (unencrypted)
- `server.publicUrl`: Defaults to `https://${server.name}`
- `server.publicUrl`: Defaults to `https://{server.name}`
## Storage
### SQLite

View File

@@ -1,5 +1,5 @@
# 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).

View File

@@ -10,6 +10,15 @@ Following these quick start instructions, you will have a basic setup that can p
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.
---
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
You will need:
- Working Homeserver, ideally with working federation

View File

@@ -36,4 +36,10 @@ notification:
body:
text: <Path to file containing the raw text part of the email. Do not set to not use one>
html: <Path to file containing the HTML part of the email. Do not set to not use one>
unbind:
fraudulent:
subject: <Subject of the email notification sent for potentially fraudulent 3PID unbinds>
body:
text: <Path to file containing the raw text part of the email. Do not set to not use one>
html: <Path to file containing the raw text part of the email. Do not set to not use one>
```

View File

@@ -20,7 +20,9 @@ threepid:
session:
validation:
local: '/path/to/validate-local-template.eml'
remote: 'path/to/validate-remote-template.eml'
remote: '/path/to/validate-remote-template.eml'
unbind:
frandulent: '/path/to/unbind-fraudulent-template.eml'
generic:
matrixId: '/path/to/mxid-invite-template.eml'
```

View File

@@ -8,81 +8,27 @@ Pseudo-configuration to illustrate the structure:
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
view:
session:
local:
onTokenSubmit:
success: '/path/to/session/local/tokenSubmitSuccess-page.html'
failure: '/path/to/session/local/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'
onTokenSubmit:
success: '/path/to/session/tokenSubmitSuccess-page.html'
failure: '/path/to/session/tokenSubmitFailure-page.html'
# CONFIGURATION EXAMPLE
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
```
3PID session are divided into three config sections:
- `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
`view.session`:
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 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.
## Local & Remote
### 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
### Failure
No object/placeholder are currently available.

View File

@@ -1,9 +1,8 @@
# 3PID Sessions
- [Overview](#overview)
- [Purpose](#purpose)
- [Federation](#federation)
- [3PID scope](#3pid-scope)
- [Session scope](#session-scope)
- [Restrictions](#restrictions)
- [Bindings](#bindings)
- [Federation](#federation)
- [Notifications](#notifications)
- [Email](#email)
- [Phone numbers](#msisdn-(phone-numbers))
@@ -11,28 +10,39 @@
- [Configuration](#configuration)
- [Web views](#web-views)
- [Scenarios](#scenarios)
- [Default](#default)
- [Local sessions only](#local-sessions-only)
- [Remote sessions only](#remote-sessions-only)
- [Sessions disabled](#sessions-disabled)
## Overview
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.
To validate the 3PID the identity server sends a message to the 3PID (e.g. an
email) with a hyperlink back to a web-page managed by the identity server to
confirm ownership of the 3PID.
To validate the 3PID, the identity server creates a session associated with a secret token. That token is sent via a message
to the 3PID (e.g. an email) with a the necessary info so the user can submit them to the Identity Server, confirm ownership
of the 3PID.
Once this 3PID is validated, the Homeserver will publish the user Matrix ID on the Identity Server and
add this 3PID to the Matrix account which initiated the request.
Once this 3PID is validated, the Homeserver will request that the Identity Server links the provided user Matrix ID with
the 3PID session and finally add the 3PID to its own data store.
This serves two purposes:
- 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.
## 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.
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`.
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).
Such 3PIDs cannot be resolved in a federated way.
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 and will not be found from other servers.
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
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
- 3PIDs that cannot be resolved locally or using federation, are fowarded to another global identity server.
- 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.
As mxisd is built for self-hosted use cases, mainly for orgs/corps, this is usually not a problem for emails.
Sadly, there is currently no mechanism to make this work for phone numbers.
## Notifications
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
### 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
file unless you want to specifically overwrite them.
```yaml
# CONFIGURATION EXAMPLE
# DO NOT COPY/PASTE THIS IN YOUR CONFIGURATION
# DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION
session:
policy:
validation:
enabled: true
forLocal:
enabled: true
toLocal: true
toRemote:
enabled: true
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
unbind:
fraudulent:
sendWarning: true
# DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION
# CONFIGURATION EXAMPLE
```
`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:
- global on/off switch for 3PID sessions using `.enabled`
- `toLocal` allowing or not local 3PID session validations
- `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.
`unbind.fraudulent` controls warning notifications if an illegal/fraudulent 3PID removal is attempted on the Identity server.
This is directly related to synapse disregard for privacy and new GDPR laws in Europe in an attempt to inform users about
potential privacy leaks.
If both `toLocal` and `toRemote` are enabled, the user will be offered to initiate a remote session once their 3PID
locally validated.
For more information, see the corresponding [synapse issue](https://github.com/matrix-org/synapse/issues/4540).
### Web views
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.
### 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
This configuration would disable 3PID session altogether, preventing users from adding emails and/or phone numbers to
their profiles.
This configuration would disable 3PID sessions altogether, preventing users from validating emails and/or phone numbers
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
[REST password provider](https://github.com/kamax-io/matrix-synapse-rest-auth).
**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.
[REST password provider](https://github.com/kamax-matrix/matrix-synapse-rest-auth), where 3PID mappings would be
auto-populated.
Use the following values to enable this mode:
```yaml

View File

@@ -21,6 +21,7 @@
package io.kamax.mxisd;
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.as.v1.AsNotFoundHandler;
import io.kamax.mxisd.http.undertow.handler.as.v1.AsTransactionHandler;
@@ -59,6 +60,8 @@ public class HttpMxisd {
httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(Handlers.routing()
.add("OPTIONS", "/**", SaneHandler.around(new OptionsHandler()))
// Status endpoints
.get(StatusHandler.Path, SaneHandler.around(new StatusHandler()))
@@ -85,8 +88,7 @@ public class HttpMxisd {
.post(SessionValidateHandler.Path, sessValidateHandler)
.get(SessionTpidGetValidatedHandler.Path, SaneHandler.around(new SessionTpidGetValidatedHandler(m.getSession())))
.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())))
.get(RemoteIdentityAPIv1.SESSION_CHECK, SaneHandler.around(new RemoteSessionCheckHandler(m.getSession(), m.getConfig().getView())))
.post(SessionTpidUnbindHandler.Path, SaneHandler.around(new SessionTpidUnbindHandler(m.getSession())))
// Profile endpoints
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))

View File

@@ -45,7 +45,7 @@ import io.kamax.mxisd.notification.NotificationHandlers;
import io.kamax.mxisd.notification.NotificationManager;
import io.kamax.mxisd.profile.ProfileManager;
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.ormlite.OrmLiteSqlStorage;
import org.apache.http.impl.client.CloseableHttpClient;
@@ -72,7 +72,7 @@ public class Mxisd {
protected InvitationManager invMgr;
protected ProfileManager pMgr;
protected AppSvcManager asHander;
protected SessionMananger sessMgr;
protected SessionManager sessMgr;
protected NotificationManager notifMgr;
public Mxisd(MxisdConfig cfg) {
@@ -102,7 +102,7 @@ public class Mxisd {
idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher);
pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient);
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);
authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient);
dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get());
@@ -137,7 +137,7 @@ public class Mxisd {
return authMgr;
}
public SessionMananger getSession() {
public SessionManager getSession() {
return sessMgr;
}

View File

@@ -32,68 +32,7 @@ public class SessionConfig {
public static class PolicyTemplate {
public static class PolicySource {
public static class PolicySourceRemote {
private boolean enabled;
private String server;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getServer() {
return server;
}
public void setServer(String server) {
this.server = server;
}
}
private boolean enabled;
private boolean toLocal;
private PolicySourceRemote toRemote = new PolicySourceRemote();
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
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;
@@ -103,42 +42,42 @@ public class SessionConfig {
this.enabled = enabled;
}
public PolicySource getForLocal() {
return forLocal;
}
public static class PolicyUnbind {
public static class PolicyUnbindFraudulent {
private boolean sendWarning = true;
public boolean getSendWarning() {
return sendWarning;
}
public void setSendWarning(boolean sendWarning) {
this.sendWarning = sendWarning;
}
}
public PolicySource forLocal() {
return forLocal;
private PolicyUnbindFraudulent fraudulent = new PolicyUnbindFraudulent();
public PolicyUnbindFraudulent getFraudulent() {
return fraudulent;
}
public PolicySource getForRemote() {
return forRemote;
}
public PolicySource forRemote() {
return forRemote;
}
public PolicySource forIf(boolean isLocal) {
return isLocal ? forLocal : forRemote;
public void setFraudulent(PolicyUnbindFraudulent fraudulent) {
this.fraudulent = fraudulent;
}
}
public Policy() {
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 PolicyUnbind unbind = new PolicyUnbind();
public PolicyTemplate getValidation() {
return validation;
@@ -148,6 +87,14 @@ public class SessionConfig {
this.validation = validation;
}
public PolicyUnbind getUnbind() {
return unbind;
}
public void setUnbind(PolicyUnbind unbind) {
this.unbind = unbind;
}
}
private Policy policy = new Policy();

View File

@@ -21,12 +21,13 @@
package io.kamax.mxisd.config;
import io.kamax.matrix.json.GsonUtil;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 {
@@ -67,45 +68,13 @@ public class ViewConfig {
}
public static class Remote {
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;
}
}
// Legacy option
private Local local = new Local();
private Local localRemote = new Local();
private Remote remote = new Remote();
private Paths onTokenSubmit = new Paths();
public Session() {
local.onTokenSubmit.success = "classpath:/templates/session/local/tokenSubmitSuccess.html";
local.onTokenSubmit.failure = "classpath:/templates/session/local/tokenSubmitFailure.html";
localRemote.onTokenSubmit.success = "classpath:/templates/session/localRemote/tokenSubmitSuccess.html";
localRemote.onTokenSubmit.failure = "classpath:/templates/session/local/tokenSubmitFailure.html";
remote.onRequest.success = "classpath:/templates/session/remote/requestSuccess.html";
remote.onRequest.failure = "classpath:/templates/session/remote/requestFailure.html";
remote.onCheck.success = "classpath:/templates/session/remote/checkSuccess.html";
remote.onCheck.failure = "classpath:/templates/session/remote/checkFailure.html";
onTokenSubmit.success = "classpath:/templates/session/tokenSubmitSuccess.html";
onTokenSubmit.failure = "classpath:/templates/session/tokenSubmitFailure.html";
}
public Local getLocal() {
@@ -116,21 +85,14 @@ public class ViewConfig {
this.local = local;
}
public Local getLocalRemote() {
return localRemote;
public Paths getOnTokenSubmit() {
return onTokenSubmit;
}
public void setLocalRemote(Local localRemote) {
this.localRemote = localRemote;
public void setOnTokenSubmit(Paths onTokenSubmit) {
this.onTokenSubmit = onTokenSubmit;
}
public Remote getRemote() {
return remote;
}
public void setRemote(Remote remote) {
this.remote = remote;
}
}
private Session session = new Session();
@@ -144,6 +106,17 @@ public class ViewConfig {
}
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("Session: {}", GsonUtil.get().toJson(session));
}

View File

@@ -115,7 +115,7 @@ public class EmailSendGridConfig {
public static class Templates {
public static class TemplateSession {
public static class TemplateSessionValidation {
private EmailTemplate local = new EmailTemplate();
private EmailTemplate remote = new EmailTemplate();
@@ -137,6 +137,43 @@ public class EmailSendGridConfig {
}
}
public static class TemplateSessionUnbind {
private EmailTemplate fraudulent = new EmailTemplate();
public EmailTemplate getFraudulent() {
return fraudulent;
}
public void setFraudulent(EmailTemplate fraudulent) {
this.fraudulent = fraudulent;
}
}
public static class TemplateSession {
private TemplateSessionValidation validation = new TemplateSessionValidation();
private TemplateSessionUnbind unbind = new TemplateSessionUnbind();
public TemplateSessionValidation getValidation() {
return validation;
}
public void setValidation(TemplateSessionValidation validation) {
this.validation = validation;
}
public TemplateSessionUnbind getUnbind() {
return unbind;
}
public void setUnbind(TemplateSessionUnbind unbind) {
this.unbind = unbind;
}
}
private EmailTemplate invite = new EmailTemplate();
private TemplateSession session = new TemplateSession();
private Map<String, EmailTemplate> generic = new HashMap<>();

View File

@@ -32,6 +32,7 @@ public class EmailTemplateConfig extends GenericTemplateConfig {
getGeneric().put("matrixId", "classpath:/threepids/email/mxid-template.eml");
getSession().getValidation().setLocal("classpath:/threepids/email/validate-local-template.eml");
getSession().getValidation().setRemote("classpath:/threepids/email/validate-remote-template.eml");
getSession().getUnbind().setFraudulent("classpath:/threepids/email/unbind-fraudulent.eml");
}
public EmailTemplateConfig build() {

View File

@@ -62,7 +62,22 @@ public class GenericTemplateConfig {
}
public static class SessionUnbind {
private String fraudulent;
public String getFraudulent() {
return fraudulent;
}
public void setFraudulent(String fraudulent) {
this.fraudulent = fraudulent;
}
}
private SessionValidation validation = new SessionValidation();
private SessionUnbind unbind = new SessionUnbind();
public SessionValidation getValidation() {
return validation;
@@ -72,6 +87,14 @@ public class GenericTemplateConfig {
this.validation = validation;
}
public SessionUnbind getUnbind() {
return unbind;
}
public void setUnbind(SessionUnbind unbind) {
this.unbind = unbind;
}
}
private String invite;

View File

@@ -32,6 +32,7 @@ public class PhoneSmsTemplateConfig extends GenericTemplateConfig {
getGeneric().put("matrixId", "classpath:/threepids/email/mxid-template.eml");
getSession().getValidation().setLocal("classpath:/threepids/sms/validate-local-template.txt");
getSession().getValidation().setRemote("classpath:/threepids/sms/validate-remote-template.txt");
getSession().getUnbind().setFraudulent("classpath:/threepids/sms/unbind-fraudulent.txt");
}
public PhoneSmsTemplateConfig build() {

View File

@@ -25,7 +25,7 @@ import org.apache.http.HttpStatus;
public class SessionNotValidatedException extends HttpMatrixException {
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");
}
}

View File

@@ -20,6 +20,8 @@
package io.kamax.mxisd.exception;
import org.apache.http.HttpStatus;
public class SessionUnknownException extends HttpMatrixException {
public SessionUnknownException() {
@@ -27,7 +29,7 @@ public class SessionUnknownException extends HttpMatrixException {
}
public SessionUnknownException(String error) {
super(200, "M_NO_VALID_SESSION", error);
super(HttpStatus.SC_NOT_FOUND, "M_NO_VALID_SESSION", error);
}
}

View File

@@ -98,11 +98,7 @@ public abstract class BasicHttpHandler implements HttpHandler {
}
protected JsonObject parseJsonObject(HttpServerExchange exchange) {
try {
return GsonUtil.parseObj(IOUtils.toString(exchange.getInputStream(), StandardCharsets.UTF_8));
} catch (IOException e) {
throw new RuntimeException(e);
}
return GsonUtil.parseObj(getBodyUtf8(exchange));
}
protected void respond(HttpServerExchange ex, int statusCode, JsonElement bodyJson) {

View File

@@ -1,6 +1,6 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2017 Kamax Sarl
* Copyright (C) 2019 Kamax Sarl
*
* https://www.kamax.io/
*
@@ -18,20 +18,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.http.undertow.handler.identity.v1;
package io.kamax.mxisd.http.undertow.handler;
public class RemoteIdentityAPIv1 {
import io.undertow.server.HttpServerExchange;
public static final String BASE = "/_matrix/identity/remote/api/v1";
public static final String SESSION_REQUEST_TOKEN = BASE + "/validate/requestToken";
public static final String SESSION_CHECK = BASE + "/validate/check";
public class OptionsHandler extends BasicHttpHandler {
public static String getRequestToken(String id, String secret) {
return SESSION_REQUEST_TOKEN + "?sid=" + id + "&client_secret=" + secret;
}
public static String getSessionCheck(String id, String secret) {
return SESSION_CHECK + "?sid=" + id + "&client_secret=" + secret;
@Override
public void handleRequest(HttpServerExchange exchange) {
// no-op
}
}

View File

@@ -27,6 +27,7 @@ import io.kamax.matrix.json.InvalidJsonException;
import io.kamax.mxisd.exception.*;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.HttpString;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
@@ -56,6 +57,11 @@ public class SaneHandler extends BasicHttpHandler {
exchange.dispatch(this);
} else {
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);
} catch (IllegalArgumentException e) {
respond(exchange, HttpStatus.SC_BAD_REQUEST, GsonUtil.makeObj("error", e.getMessage()));

View File

@@ -46,7 +46,7 @@ public class BulkLookupHandler extends LookupHandler {
}
@Override
public void handleRequest(HttpServerExchange exchange) {
public void handleRequest(HttpServerExchange exchange) throws Exception {
ClientBulkLookupRequest input = parseJsonTo(exchange, ClientBulkLookupRequest.class);
BulkLookupRequest lookupRequest = new BulkLookupRequest();
setRequesterInfo(lookupRequest, exchange);
@@ -63,7 +63,9 @@ public class BulkLookupHandler extends LookupHandler {
lookupRequest.setMappings(mappings);
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);
}

View File

@@ -1,56 +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.kamax.mxisd.util.FileUtil;
import io.undertow.server.HttpServerExchange;
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");
String viewData;
try {
mgr.validateRemote(sid, secret);
viewData = FileUtil.load(viewCfg.getSession().getRemote().getOnCheck().getSuccess());
} catch (SessionNotValidatedException e) {
viewData = FileUtil.load(viewCfg.getSession().getRemote().getOnCheck().getFailure());
}
writeBodyAsUtf8(exchange, viewData);
}
}

View File

@@ -1,51 +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.kamax.mxisd.util.FileUtil;
import io.undertow.server.HttpServerExchange;
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);
String rawData = FileUtil.load(viewCfg.getSession().getRemote().getOnRequest().getSuccess());
String data = rawData.replace("${checkLink}", RemoteIdentityAPIv1.getSessionCheck(session.getId(), session.getSecret()));
writeBodyAsUtf8(exchange, data);
}
}

View File

@@ -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.SessionPhoneTokenRequestJson;
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 org.apache.http.HttpStatus;
import org.slf4j.Logger;
@@ -41,9 +41,9 @@ public class SessionStartHandler extends BasicHttpHandler {
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;
}

View File

@@ -26,7 +26,7 @@ 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.invitation.InvitationManager;
import io.kamax.mxisd.session.SessionMananger;
import io.kamax.mxisd.session.SessionManager;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.QueryParameterUtils;
import org.apache.commons.lang.StringUtils;
@@ -44,10 +44,10 @@ public class SessionTpidBindHandler extends BasicHttpHandler {
private transient final Logger log = LoggerFactory.getLogger(SessionTpidBindHandler.class);
private SessionMananger mgr;
private SessionManager mgr;
private InvitationManager invMgr;
public SessionTpidBindHandler(SessionMananger mgr, InvitationManager invMgr) {
public SessionTpidBindHandler(SessionManager mgr, InvitationManager invMgr) {
this.mgr = mgr;
this.invMgr = invMgr;
}

View File

@@ -25,7 +25,7 @@ import io.kamax.mxisd.exception.SessionNotValidatedException;
import io.kamax.mxisd.http.IsAPIv1;
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
import io.kamax.mxisd.lookup.ThreePidValidation;
import io.kamax.mxisd.session.SessionMananger;
import io.kamax.mxisd.session.SessionManager;
import io.undertow.server.HttpServerExchange;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,11 +34,11 @@ public class SessionTpidGetValidatedHandler extends BasicHttpHandler {
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;
}

View File

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

View File

@@ -25,7 +25,7 @@ import io.kamax.mxisd.config.ViewConfig;
import io.kamax.mxisd.http.IsAPIv1;
import io.kamax.mxisd.http.io.identity.SuccessStatusJson;
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.util.FileUtil;
import io.undertow.server.HttpServerExchange;
@@ -44,11 +44,11 @@ public class SessionValidateHandler extends BasicHttpHandler {
private transient final Logger log = LoggerFactory.getLogger(SessionValidateHandler.class);
private SessionMananger mgr;
private SessionManager mgr;
private ServerConfig srvCfg;
private ViewConfig viewCfg;
public SessionValidateHandler(SessionMananger mgr, ServerConfig srvCfg, ViewConfig viewCfg) {
public SessionValidateHandler(SessionManager mgr, ServerConfig srvCfg, ViewConfig viewCfg) {
this.mgr = mgr;
this.srvCfg = srvCfg;
this.viewCfg = viewCfg;
@@ -72,11 +72,11 @@ public class SessionValidateHandler extends BasicHttpHandler {
if (isHtmlRequest) {
handleHtmlRequest(exchange, medium, sid, secret, token);
} 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);
ValidationResult r = mgr.validate(sid, secret, token);
log.info("Session {} was validated", sid);
@@ -93,24 +93,18 @@ public class SessionValidateHandler extends BasicHttpHandler {
exchange.getResponseHeaders().add(HttpString.tryFromString("Location"), url);
} else {
try {
String rawData = FileUtil.load(viewCfg.getSession().getLocalRemote().getOnTokenSubmit().getSuccess());
if (r.isCanRemote()) {
String url = srvCfg.getPublicUrl() + RemoteIdentityAPIv1.getRequestToken(r.getSession().getId(), r.getSession().getSecret());
String data = rawData.replace("${remoteSessionLink}", url);
writeBodyAsUtf8(exchange, data);
} else {
writeBodyAsUtf8(exchange, rawData);
}
String data = FileUtil.load(viewCfg.getSession().getOnTokenSubmit().getSuccess());
writeBodyAsUtf8(exchange, data);
} catch (IOException 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());
ValidationResult r = mgr.validate(sid, secret, token);
mgr.validate(sid, secret, token);
log.info("Session {} was validated", sid);
respondJson(exchange, new SuccessStatusJson(true));

View File

@@ -28,6 +28,7 @@ import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
public interface LookupStrategy {
@@ -43,6 +44,6 @@ public interface LookupStrategy {
Optional<SingleLookupReply> findRecursive(SingleLookupRequest request);
List<ThreePidMapping> find(BulkLookupRequest requests);
CompletableFuture<List<ThreePidMapping>> find(BulkLookupRequest requests);
}

View File

@@ -21,19 +21,21 @@
package io.kamax.mxisd.lookup.strategy;
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.exception.ConfigurationException;
import io.kamax.mxisd.lookup.*;
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher;
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class RecursivePriorityLookupStrategy implements LookupStrategy {
@@ -44,6 +46,8 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
private List<IThreePidProvider> providers;
private IBridgeFetcher bridge;
private Map<String, CompletableFuture<List<ThreePidMapping>>> bulkLookupInProgress = new ConcurrentHashMap<>();
private List<CIDRUtils> allowedCidr = new ArrayList<>();
public RecursivePriorityLookupStrategy(MxisdConfig.Lookup cfg, List<? extends IThreePidProvider> providers, IBridgeFetcher bridge) {
@@ -182,11 +186,27 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
}
@Override
public List<ThreePidMapping> find(BulkLookupRequest request) {
public CompletableFuture<List<ThreePidMapping>> find(BulkLookupRequest request) {
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> mapFoundAll = new ArrayList<>();
@@ -205,7 +225,9 @@ public class RecursivePriorityLookupStrategy implements LookupStrategy {
mapToDo.removeAll(mapFound);
}
return mapFoundAll;
log.info("Processed Payload ID {}", payloadId);
result.complete(mapFoundAll);
return bulkLookupInProgress.remove(payloadId);
}
}

View File

@@ -20,6 +20,7 @@
package io.kamax.mxisd.notification;
import io.kamax.matrix.ThreePid;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession;
@@ -38,4 +39,6 @@ public interface NotificationHandler {
void sendForRemoteValidation(IThreePidSession session);
void sendForFraudulentUnbind(ThreePid tpid);
}

View File

@@ -20,6 +20,7 @@
package io.kamax.mxisd.notification;
import io.kamax.matrix.ThreePid;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.config.threepid.notification.NotificationConfig;
import io.kamax.mxisd.exception.NotImplementedException;
@@ -81,4 +82,8 @@ public class NotificationManager {
ensureMedium(session.getThreePid().getMedium()).sendForRemoteValidation(session);
}
public void sendForFraudulentUnbind(ThreePid tpid) throws NotImplementedException {
ensureMedium(tpid.getMedium()).sendForFraudulentUnbind(tpid);
}
}

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

View File

@@ -1,396 +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) {
if (StringUtils.isEmpty(mxidRaw)) {
throw new IllegalArgumentException("No Matrix User ID provided");
}
_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());
}
}
}

View File

@@ -27,22 +27,16 @@ import java.util.Optional;
public class ValidationResult {
private IThreePidSession session;
private boolean canRemote;
private String nextUrl;
public ValidationResult(IThreePidSession session, boolean canRemote) {
public ValidationResult(IThreePidSession session) {
this.session = session;
this.canRemote = canRemote;
}
public IThreePidSession getSession() {
return session;
}
public boolean isCanRemote() {
return canRemote;
}
public Optional<String> getNextUrl() {
return Optional.ofNullable(nextUrl);
}

View File

@@ -20,6 +20,7 @@
package io.kamax.mxisd.threepid.generator;
import io.kamax.matrix.ThreePid;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.config.MatrixConfig;
import io.kamax.mxisd.config.ServerConfig;
@@ -82,4 +83,10 @@ public abstract class GenericTemplateNotificationGenerator extends PlaceholderNo
return populateForRemoteValidation(session, getTemplateContent(cfg.getSession().getValidation().getRemote()));
}
@Override
public String getForFraudulentUnbind(ThreePid tpid) {
log.info("Generating notification content for fraudulent unbind");
return populateForFraudulentUndind(tpid, getTemplateContent(cfg.getSession().getUnbind().getFraudulent()));
}
}

View File

@@ -20,6 +20,7 @@
package io.kamax.mxisd.threepid.generator;
import io.kamax.matrix.ThreePid;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.threepid.session.IThreePidSession;
@@ -38,4 +39,6 @@ public interface NotificationGenerator {
String getForRemoteValidation(IThreePidSession session);
String getForFraudulentUnbind(ThreePid tpid);
}

View File

@@ -106,4 +106,8 @@ public abstract class PlaceholderNotificationGenerator {
return populateForValidation(session, input);
}
protected String populateForFraudulentUndind(ThreePid tpid, String input) {
return populateForCommon(tpid, input);
}
}

View File

@@ -20,6 +20,7 @@
package io.kamax.mxisd.threepid.notification;
import io.kamax.matrix.ThreePid;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.exception.ConfigurationException;
import io.kamax.mxisd.invitation.IThreePidInviteReply;
@@ -76,4 +77,9 @@ public abstract class GenericNotificationHandler<A extends ThreePidConnector, B
send(connector, session.getThreePid().getAddress(), generator.getForRemoteValidation(session));
}
@Override
public void sendForFraudulentUnbind(ThreePid tpid) {
send(connector, tpid.getAddress(), generator.getForFraudulentUnbind(tpid));
}
}

View File

@@ -22,6 +22,7 @@ package io.kamax.mxisd.threepid.notification.email;
import com.sendgrid.SendGrid;
import com.sendgrid.SendGridException;
import io.kamax.matrix.ThreePid;
import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.as.IMatrixIdInvite;
import io.kamax.mxisd.config.MxisdConfig;
@@ -107,7 +108,7 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
@Override
public void sendForValidation(IThreePidSession session) {
EmailTemplate template = cfg.getTemplates().getSession().getLocal();
EmailTemplate template = cfg.getTemplates().getSession().getValidation().getLocal();
Email email = getEmail();
email.setSubject(populateForValidation(session, template.getSubject()));
email.setText(populateForValidation(session, getFromFile(template.getBody().getText())));
@@ -118,7 +119,7 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
@Override
public void sendForRemoteValidation(IThreePidSession session) {
EmailTemplate template = cfg.getTemplates().getSession().getLocal();
EmailTemplate template = cfg.getTemplates().getSession().getValidation().getRemote();
Email email = getEmail();
email.setSubject(populateForRemoteValidation(session, template.getSubject()));
email.setText(populateForRemoteValidation(session, getFromFile(template.getBody().getText())));
@@ -127,6 +128,17 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
send(session.getThreePid().getAddress(), email);
}
@Override
public void sendForFraudulentUnbind(ThreePid tpid) {
EmailTemplate template = cfg.getTemplates().getSession().getUnbind().getFraudulent();
Email email = getEmail();
email.setSubject(populateForCommon(tpid, template.getSubject()));
email.setText(populateForCommon(tpid, getFromFile(template.getBody().getText())));
email.setHtml(populateForCommon(tpid, getFromFile(template.getBody().getHtml())));
send(tpid.getAddress(), email);
}
private void send(String recipient, Email email) {
if (StringUtils.isBlank(cfg.getIdentity().getFrom())) {
throw new FeatureNotAvailable("3PID Email identity: sender address is empty - " +

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,10 +10,9 @@ Content-Disposition: inline
Hi,
%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
https://riot.im/app/#/register?%INVITE_MEDIUM%=%INVITE_ADDRESS%
You can also register an account on a public server and get in touch with them.
About Matrix:
@@ -70,10 +69,9 @@ pre, code {
<p>Hi,</p>
<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
<a href="https://riot.im/app/#/register?%INVITE_MEDIUM%=%INVITE_ADDRESS%">this link</a>.</p>
<pYou can also register an account on a public server and get in touch with them.</p>
<br>
<p>About Matrix:</p>

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

View File

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

View File

@@ -9,7 +9,7 @@ Content-Disposition: inline
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
complete the verification of your email address:
@@ -66,7 +66,7 @@ pre, code {
<td id="inner">
<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
complete the verification of your email address:</p>

View File

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

View File

@@ -0,0 +1 @@
INFORMATIONAL ONLY - Someone attempted to change your Matrix 3PIDs, with a potential data leak. Please contact your system administrator.

View File

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