Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
a964b073bf | ||
|
f85345bc97 | ||
|
29603682e5 | ||
|
d54f1dcb88 | ||
|
92f10347d1 | ||
|
0298f66212 | ||
|
0ddd086bda | ||
|
544f8e59f0 | ||
|
917f87bf8c | ||
|
774795c203 | ||
|
27b2976e42 | ||
|
f16f184253 |
14
README.md
14
README.md
@@ -14,7 +14,7 @@ mxisd - Federated Matrix Identity Server
|
|||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures with [enhanced features](#features).
|
mxisd is a Federated Matrix Identity server for self-hosted Matrix infrastructures with [enhanced features](#features).
|
||||||
As an enhanced Identity service, it implements the [Identity service API](https://matrix.org/docs/spec/identity_service/r0.1.0.html)
|
As an enhanced Identity service, it implements the [Identity service API](https://matrix.org/docs/spec/identity_service/r0.2.0.html)
|
||||||
and several [extra features](#features) that greatly enhance user experience within Matrix.
|
and several [extra features](#features) that greatly enhance user experience within Matrix.
|
||||||
It is the one stop shop for anything regarding Authentication, Directory and Identity management in Matrix built in a
|
It is the one stop shop for anything regarding Authentication, Directory and Identity management in Matrix built in a
|
||||||
single coherent product.
|
single coherent product.
|
||||||
@@ -34,15 +34,15 @@ users. 3PIDs can be anything that uniquely and globally identify a user, like:
|
|||||||
If you are unfamiliar with the Identity vocabulary and concepts in Matrix, **please read this [introduction](docs/concepts.md)**.
|
If you are unfamiliar with the Identity vocabulary and concepts in Matrix, **please read this [introduction](docs/concepts.md)**.
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
[Identity](docs/features/identity.md): As a [regular Matrix Identity service](https://matrix.org/docs/spec/identity_service/r0.1.0.html#general-principles):
|
[Identity](docs/features/identity.md): As a [regular Matrix Identity service](https://matrix.org/docs/spec/identity_service/r0.2.0.html#general-principles):
|
||||||
- Search for people by 3PID using its own Identity stores
|
- Search for people by 3PID using its own Identity stores
|
||||||
([Spec](https://matrix.org/docs/spec/identity_service/r0.1.0.html#association-lookup))
|
([Spec](https://matrix.org/docs/spec/identity_service/r0.2.0.html#association-lookup))
|
||||||
- Invite people to rooms by 3PID using its own Identity stores, with notifications to the invitee (Email, SMS, etc.)
|
- Invite people to rooms by 3PID using its own Identity stores, with notifications to the invitee (Email, SMS, etc.)
|
||||||
([Spec](https://matrix.org/docs/spec/identity_service/r0.1.0.html#post-matrix-identity-api-v1-store-invite))
|
([Spec](https://matrix.org/docs/spec/identity_service/r0.2.0.html#invitation-storage))
|
||||||
- Allow users to add 3PIDs to their settings/profile
|
- Allow users to add/remove 3PIDs to their settings/profile via 3PID sessions
|
||||||
([Spec](https://matrix.org/docs/spec/identity_service/r0.1.0.html#establishing-associations))
|
([Spec](https://matrix.org/docs/spec/identity_service/r0.2.0.html#establishing-associations))
|
||||||
- Register accounts on your Homeserver with 3PIDs
|
- Register accounts on your Homeserver with 3PIDs
|
||||||
([Spec](https://matrix.org/docs/spec/identity_service/r0.1.0.html#establishing-associations))
|
([Spec](https://matrix.org/docs/spec/identity_service/r0.2.0.html#establishing-associations))
|
||||||
|
|
||||||
As an enhanced Identity service:
|
As an enhanced Identity service:
|
||||||
- [Federation](docs/features/federation.md): Use a recursive lookup mechanism when searching and inviting people by 3PID,
|
- [Federation](docs/features/federation.md): Use a recursive lookup mechanism when searching and inviting people by 3PID,
|
||||||
|
@@ -229,6 +229,12 @@ task debBuild(dependsOn: shadowJar) {
|
|||||||
value: debDataPath
|
value: debDataPath
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ant.replace(
|
||||||
|
file: "${debBuildDebianPath}/postinst",
|
||||||
|
token: '%DEB_CONF_FILE%',
|
||||||
|
value: "${debConfPath}/mxisd.yaml"
|
||||||
|
)
|
||||||
|
|
||||||
ant.chmod(
|
ant.chmod(
|
||||||
file: "${debBuildDebianPath}/postinst",
|
file: "${debBuildDebianPath}/postinst",
|
||||||
perm: 'a+x'
|
perm: 'a+x'
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Application Service
|
# Application Service
|
||||||
**WARNING:** These features are currently highly experimental. They can be removed or modified without notice.
|
**WARNING:** These features are currently highly experimental. They can be removed or modified without notice.
|
||||||
All the features requires a Homeserver capable of connecting [Application Services](https://matrix.org/docs/spec/application_service/r0.1.0.html).
|
All the features requires a Homeserver capable of connecting [Application Services](https://matrix.org/docs/spec/application_service/r0.1.1.html).
|
||||||
|
|
||||||
The following capabilities are provided in this feature:
|
The following capabilities are provided in this feature:
|
||||||
- [Admin commands](#admin-commands)
|
- [Admin commands](#admin-commands)
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# Identity
|
# Identity
|
||||||
Implementation of the [Identity Service API r0.1.0](https://matrix.org/docs/spec/identity_service/r0.1.0.html).
|
Implementation of the [Identity Service API r0.2.0](https://matrix.org/docs/spec/identity_service/r0.2.0.html).
|
||||||
|
|
||||||
- [Lookups](#lookups)
|
- [Lookups](#lookups)
|
||||||
- [Invitations](#invitations)
|
- [Invitations](#invitations)
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
- [Integration](#integration)
|
- [Integration](#integration)
|
||||||
- [Reverse Proxy](#reverse-proxy)
|
- [Reverse Proxy](#reverse-proxy)
|
||||||
- [nginx](#nginx)
|
- [nginx](#nginx)
|
||||||
- [Apache](#apache)
|
- [Apache2](#apache2)
|
||||||
- [Homeserver](#homeserver)
|
- [Homeserver](#homeserver)
|
||||||
- [synapse](#synapse)
|
- [synapse](#synapse)
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
Registration is an enhanced feature of mxisd to control registrations involving 3PIDs on a Homeserver based on policies:
|
Registration is an enhanced feature of mxisd to control registrations involving 3PIDs on a Homeserver based on policies:
|
||||||
- Match pending 3PID invites on the server
|
- Match pending 3PID invites on the server
|
||||||
- Match 3PID pattern, like a specific set of domains for emails
|
- Match 3PID pattern, like a specific set of domains for emails
|
||||||
- In futher releases, use 3PIDs found in Identity stores
|
- In further releases, use 3PIDs found in Identity stores
|
||||||
|
|
||||||
It aims to help open or invite-only registration servers control what is possible to do and ensure only approved people
|
It aims to help open or invite-only registration servers control what is possible to do and ensure only approved people
|
||||||
can register on a given server in a implementation-agnostic manner.
|
can register on a given server in a implementation-agnostic manner.
|
||||||
@@ -36,14 +36,14 @@ Later version(s) of this feature may directly control registration itself to cre
|
|||||||
### Reverse Proxy
|
### Reverse Proxy
|
||||||
#### nginx
|
#### nginx
|
||||||
```nginx
|
```nginx
|
||||||
location ^/_matrix/client/r0/register/[^/]/?$ {
|
location ~* ^/_matrix/client/r0/register/[^/]+/requestToken$ {
|
||||||
proxy_pass http://127.0.0.1:8090;
|
proxy_pass http://localhost:8090;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Forwarded-For $remote_addr;
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### apache
|
#### Apache2
|
||||||
> TBC
|
> TBC
|
||||||
|
|
||||||
### Homeserver
|
### Homeserver
|
||||||
@@ -55,8 +55,8 @@ registrations_require_3pid:
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
See the [Configuration](../configuration.md) introduction doc on how to read the configuration keys.
|
See the [Configuration](../configure.md) introduction doc on how to read the configuration keys.
|
||||||
An example of working configuration is avaiable at the end of this section.
|
An example of working configuration is available at the end of this section.
|
||||||
### Enable/Disable
|
### Enable/Disable
|
||||||
`register.allowed`, taking a boolean, can be used to enable/disable registration if the attempt is not 3PID-based.
|
`register.allowed`, taking a boolean, can be used to enable/disable registration if the attempt is not 3PID-based.
|
||||||
`false` is the default value to prevent open registration, as you must allow it on the homeserver side.
|
`false` is the default value to prevent open registration, as you must allow it on the homeserver side.
|
||||||
@@ -72,7 +72,7 @@ At this time, only `email` is supported with 3PID specific configuration with th
|
|||||||
**Base key**: `register.threepid.email`
|
**Base key**: `register.threepid.email`
|
||||||
|
|
||||||
##### Domain whitelist/blacklist
|
##### Domain whitelist/blacklist
|
||||||
If you would like to control which domains are allowed to be used when registrating with an email, the following sub-keys
|
If you would like to control which domains are allowed to be used when registering with an email, the following sub-keys
|
||||||
are available:
|
are available:
|
||||||
- `domain.whitelist`
|
- `domain.whitelist`
|
||||||
- `domain.blacklist`
|
- `domain.blacklist`
|
||||||
@@ -82,7 +82,7 @@ The value format is an hybrid between glob patterns and postfix configuration fi
|
|||||||
- `.<domain>` will only match sub-domain(s)
|
- `.<domain>` will only match sub-domain(s)
|
||||||
- `<domain>` will only match the exact domain
|
- `<domain>` will only match the exact domain
|
||||||
|
|
||||||
The following table illustrates pattern and maching status against example values:
|
The following table illustrates pattern and matching status against example values:
|
||||||
|
|
||||||
| Config value | Matches `example.org` | Matches `sub.example.org` |
|
| Config value | Matches `example.org` | Matches `sub.example.org` |
|
||||||
|--------------- |-----------------------|---------------------------|
|
|--------------- |-----------------------|---------------------------|
|
||||||
|
@@ -19,29 +19,33 @@ All placeholders **MUST** be surrounded with `%` in the template. Per example, t
|
|||||||
### Global
|
### Global
|
||||||
The following placeholders are available in every template:
|
The following placeholders are available in every template:
|
||||||
|
|
||||||
| Placeholder | Purpose |
|
| Placeholder | Purpose |
|
||||||
|---------------------|------------------------------------------------------------------------------|
|
|---------------------------------|------------------------------------------------------------------------------|
|
||||||
| `DOMAIN` | Identity server authoritative domain, as configured in `matrix.domain` |
|
| `DOMAIN` | Identity server authoritative domain, as configured in `matrix.domain` |
|
||||||
| `DOMAIN_PRETTY` | Same as `DOMAIN` with the first letter upper case and all other lower case |
|
| `DOMAIN_PRETTY` | Same as `DOMAIN` with the first letter upper case and all other lower case |
|
||||||
| `FROM_EMAIL` | Email address configured in `threepid.medium.<3PID medium>.identity.from` |
|
| `FROM_EMAIL` | Email address configured in `threepid.medium.<3PID medium>.identity.from` |
|
||||||
| `FROM_NAME` | Name configured in `threepid.medium.<3PID medium>.identity.name` |
|
| `FROM_NAME` | Name configured in `threepid.medium.<3PID medium>.identity.name` |
|
||||||
| `RECIPIENT_MEDIUM` | The 3PID medium, like `email` or `msisdn` |
|
| `RECIPIENT_MEDIUM` | The 3PID medium, like `email` or `msisdn` |
|
||||||
| `RECIPIENT_ADDRESS` | The address to which the notification is sent |
|
| `RECIPIENT_MEDIUM_URL_ENCODED` | URL encoded value of `RECIPIENT_MEDIUM` |
|
||||||
|
| `RECIPIENT_ADDRESS` | The address to which the notification is sent |
|
||||||
|
| `RECIPIENT_ADDRESS_URL_ENCODED` | URL encoded value of `RECIPIENT_ADDRESS` |
|
||||||
|
|
||||||
### Room invitation
|
### Room invitation
|
||||||
Specific placeholders:
|
Specific placeholders:
|
||||||
|
|
||||||
| Placeholder | Purpose |
|
| Placeholder | Purpose |
|
||||||
|---------------------|------------------------------------------------------------------------------------------|
|
|------------------------------|-----------------------------------------------------------------------------------|
|
||||||
| `SENDER_ID` | Matrix ID of the user who made the invite |
|
| `SENDER_ID` | Matrix ID of the user who made the invite |
|
||||||
| `SENDER_NAME` | Display name of the user who made the invite, if not available/set, empty |
|
| `SENDER_NAME` | Display name of the user who made the invite, if not available/set, empty |
|
||||||
| `SENDER_NAME_OR_ID` | Display name of the user who made the invite. If not available/set, its Matrix ID |
|
| `SENDER_NAME_OR_ID` | Display name of the user who made the invite. If not available/set, its Matrix ID |
|
||||||
| `INVITE_MEDIUM` | The 3PID medium for the invite. |
|
| `INVITE_MEDIUM` | The 3PID medium for the invite. |
|
||||||
| `INVITE_ADDRESS` | The 3PID address for the invite. |
|
| `INVITE_MEDIUM_URL_ENCODED` | URL encoded value of `INVITE_MEDIUM` |
|
||||||
| `ROOM_ID` | The Matrix ID of the Room in which the invite took place |
|
| `INVITE_ADDRESS` | The 3PID address for the invite. |
|
||||||
| `ROOM_NAME` | The Name of the room in which the invite took place. If not available/set, empty |
|
| `INVITE_ADDRESS_URL_ENCODED` | URL encoded value of `INVITE_ADDRESS` |
|
||||||
| `ROOM_NAME_OR_ID` | The Name of the room in which the invite took place. If not available/set, its Matrix ID |
|
| `ROOM_ID` | The Matrix ID of the Room in which the invite took place |
|
||||||
| `REGISTER_URL` | The URL to provide to the user allowing them to register their account, if needed |
|
| `ROOM_NAME` | The Name of the room in which the invite took place. If not available/set, empty |
|
||||||
|
| `ROOM_NAME_OR_ID` | The Name of the room in which the invite took place. If not available/set, its ID |
|
||||||
|
| `REGISTER_URL` | The URL to provide to the user allowing them to register their account, if needed |
|
||||||
|
|
||||||
### Validation of 3PID Session
|
### Validation of 3PID Session
|
||||||
Specific placeholders:
|
Specific placeholders:
|
||||||
|
@@ -26,6 +26,14 @@ Two configuration keys are available that accept paths to HTML templates:
|
|||||||
- `success`
|
- `success`
|
||||||
- `failure`
|
- `failure`
|
||||||
|
|
||||||
|
### Serving static assets
|
||||||
|
mxisd will not serve any static asset (images, JS, CSS, etc.). If such are needed, you will need to serve them using the
|
||||||
|
reverse proxy sitting in front of mxisd using a path outside of the `/_matrix/identity/` namespace. We advise using
|
||||||
|
the base path `/static/` for such use cases, allowing to remain under the same hostname/origin.
|
||||||
|
|
||||||
|
You can also serve such assets using absolute URL, possibly under other domains, but be aware of Cross-Origin restrictions
|
||||||
|
in browsers which are out of scope of mxisd.
|
||||||
|
|
||||||
## Placeholders
|
## Placeholders
|
||||||
### Success
|
### Success
|
||||||
No object/placeholder are currently available.
|
No object/placeholder are currently available.
|
||||||
|
@@ -11,3 +11,9 @@ ln -sfT /usr/lib/mxisd/mxisd /usr/bin/mxisd
|
|||||||
|
|
||||||
# Enable systemd service
|
# Enable systemd service
|
||||||
systemctl enable mxisd.service
|
systemctl enable mxisd.service
|
||||||
|
|
||||||
|
# If we already have a config file setup, we attempt to run mxisd automatically
|
||||||
|
# Specifically targeted at upgrades where the service needs to be restarted
|
||||||
|
if [ -f "%DEB_CONF_FILE%" ]; then
|
||||||
|
systemctl restart mxisd.service
|
||||||
|
fi
|
||||||
|
@@ -105,7 +105,7 @@ public class HttpMxisd {
|
|||||||
.get(SessionValidateHandler.Path, SaneHandler.around(new SessionValidationGetHandler(m.getSession(), m.getConfig())))
|
.get(SessionValidateHandler.Path, SaneHandler.around(new SessionValidationGetHandler(m.getSession(), m.getConfig())))
|
||||||
.post(SessionValidateHandler.Path, SaneHandler.around(new SessionValidationPostHandler(m.getSession())))
|
.post(SessionValidateHandler.Path, SaneHandler.around(new SessionValidationPostHandler(m.getSession())))
|
||||||
.get(SessionTpidGetValidatedHandler.Path, SaneHandler.around(new SessionTpidGetValidatedHandler(m.getSession())))
|
.get(SessionTpidGetValidatedHandler.Path, SaneHandler.around(new SessionTpidGetValidatedHandler(m.getSession())))
|
||||||
.post(SessionTpidBindHandler.Path, SaneHandler.around(new SessionTpidBindHandler(m.getSession(), m.getInvite())))
|
.post(SessionTpidBindHandler.Path, SaneHandler.around(new SessionTpidBindHandler(m.getSession(), m.getInvite(), m.getSign())))
|
||||||
.post(SessionTpidUnbindHandler.Path, SaneHandler.around(new SessionTpidUnbindHandler(m.getSession())))
|
.post(SessionTpidUnbindHandler.Path, SaneHandler.around(new SessionTpidUnbindHandler(m.getSession())))
|
||||||
.post(SignEd25519Handler.Path, SaneHandler.around(new SignEd25519Handler(m.getConfig(), m.getInvite(), m.getSign())))
|
.post(SignEd25519Handler.Path, SaneHandler.around(new SignEd25519Handler(m.getConfig(), m.getInvite(), m.getSign())))
|
||||||
|
|
||||||
|
@@ -107,7 +107,7 @@ public class Mxisd {
|
|||||||
|
|
||||||
store = new OrmLiteSqlStorage(cfg);
|
store = new OrmLiteSqlStorage(cfg);
|
||||||
keyMgr = CryptoFactory.getKeyManager(cfg.getKey());
|
keyMgr = CryptoFactory.getKeyManager(cfg.getKey());
|
||||||
signMgr = CryptoFactory.getSignatureManager(keyMgr);
|
signMgr = CryptoFactory.getSignatureManager(cfg, keyMgr);
|
||||||
clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite());
|
clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite());
|
||||||
|
|
||||||
synapse = new Synapse(cfg.getSynapseSql());
|
synapse = new Synapse(cfg.getSynapseSql());
|
||||||
@@ -118,7 +118,7 @@ public class Mxisd {
|
|||||||
idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher);
|
idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher);
|
||||||
pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient);
|
pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient);
|
||||||
notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get());
|
notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get());
|
||||||
sessMgr = new SessionManager(cfg.getSession(), cfg.getMatrix(), store, notifMgr, idStrategy);
|
sessMgr = new SessionManager(cfg.getSession(), cfg.getMatrix(), store, notifMgr);
|
||||||
invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, resolver, notifMgr, pMgr);
|
invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, resolver, notifMgr, pMgr);
|
||||||
authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient);
|
authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient);
|
||||||
dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get());
|
dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get());
|
||||||
|
@@ -64,6 +64,8 @@ import java.util.Objects;
|
|||||||
|
|
||||||
public class AuthManager {
|
public class AuthManager {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AuthManager.class);
|
||||||
|
|
||||||
private static final String TypeKey = "type";
|
private static final String TypeKey = "type";
|
||||||
private static final String UserKey = "user";
|
private static final String UserKey = "user";
|
||||||
private static final String IdentifierKey = "identifier";
|
private static final String IdentifierKey = "identifier";
|
||||||
@@ -72,7 +74,6 @@ public class AuthManager {
|
|||||||
private static final String UserIdTypeValue = "m.id.user";
|
private static final String UserIdTypeValue = "m.id.user";
|
||||||
private static final String ThreepidTypeValue = "m.id.thirdparty";
|
private static final String ThreepidTypeValue = "m.id.thirdparty";
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(AuthManager.class);
|
|
||||||
private final Gson gson = GsonUtil.get(); // FIXME replace
|
private final Gson gson = GsonUtil.get(); // FIXME replace
|
||||||
|
|
||||||
private List<AuthenticatorProvider> providers;
|
private List<AuthenticatorProvider> providers;
|
||||||
@@ -138,6 +139,12 @@ public class AuthManager {
|
|||||||
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, mxId));
|
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, mxId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
MatrixID.asValid(mxId);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.warn("The returned User ID {} is not a valid Matrix ID. Login might fail at the Homeserver level", mxId);
|
||||||
|
}
|
||||||
|
|
||||||
invMgr.lookupMappingsForInvites();
|
invMgr.lookupMappingsForInvites();
|
||||||
|
|
||||||
return authResult;
|
return authResult;
|
||||||
|
@@ -83,6 +83,12 @@ public class MxisdConfig {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static MxisdConfig forDomain(String domain) {
|
||||||
|
MxisdConfig cfg = new MxisdConfig();
|
||||||
|
cfg.getMatrix().setDomain(domain);
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
private AppServiceConfig appsvc = new AppServiceConfig();
|
private AppServiceConfig appsvc = new AppServiceConfig();
|
||||||
private AuthenticationConfig auth = new AuthenticationConfig();
|
private AuthenticationConfig auth = new AuthenticationConfig();
|
||||||
private DirectoryConfig directory = new DirectoryConfig();
|
private DirectoryConfig directory = new DirectoryConfig();
|
||||||
@@ -309,6 +315,13 @@ public class MxisdConfig {
|
|||||||
this.wordpress = wordpress;
|
this.wordpress = wordpress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MxisdConfig inMemory() {
|
||||||
|
getKey().setPath(":memory:");
|
||||||
|
getStorage().getProvider().getSqlite().setDatabase(":memory:");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public MxisdConfig build() {
|
public MxisdConfig build() {
|
||||||
if (StringUtils.isBlank(getServer().getName())) {
|
if (StringUtils.isBlank(getServer().getName())) {
|
||||||
getServer().setName(getMatrix().getDomain());
|
getServer().setName(getMatrix().getDomain());
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
package io.kamax.mxisd.crypto;
|
package io.kamax.mxisd.crypto;
|
||||||
|
|
||||||
import io.kamax.mxisd.config.KeyConfig;
|
import io.kamax.mxisd.config.KeyConfig;
|
||||||
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
import io.kamax.mxisd.crypto.ed25519.Ed25519KeyManager;
|
import io.kamax.mxisd.crypto.ed25519.Ed25519KeyManager;
|
||||||
import io.kamax.mxisd.crypto.ed25519.Ed25519SignatureManager;
|
import io.kamax.mxisd.crypto.ed25519.Ed25519SignatureManager;
|
||||||
import io.kamax.mxisd.storage.crypto.FileKeyStore;
|
import io.kamax.mxisd.storage.crypto.FileKeyStore;
|
||||||
@@ -54,8 +55,8 @@ public class CryptoFactory {
|
|||||||
return new Ed25519KeyManager(store);
|
return new Ed25519KeyManager(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SignatureManager getSignatureManager(Ed25519KeyManager keyMgr) {
|
public static SignatureManager getSignatureManager(MxisdConfig cfg, Ed25519KeyManager keyMgr) {
|
||||||
return new Ed25519SignatureManager(keyMgr);
|
return new Ed25519SignatureManager(cfg, keyMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@ package io.kamax.mxisd.crypto;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Types of keys used by an Identity server.
|
* Types of keys used by an Identity server.
|
||||||
* See https://matrix.org/docs/spec/identity_service/r0.1.0.html#key-management
|
* See https://matrix.org/docs/spec/identity_service/r0.2.0.html#key-management
|
||||||
*/
|
*/
|
||||||
public enum KeyType {
|
public enum KeyType {
|
||||||
|
|
||||||
|
@@ -30,6 +30,18 @@ import java.util.Objects;
|
|||||||
|
|
||||||
public interface SignatureManager {
|
public interface SignatureManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign the message with the default domain and add the signature to the <code>signatures</code> key.
|
||||||
|
* <p>
|
||||||
|
* If the key does not exist yet, it is created. If the key exist, the produced signature will be merged with any
|
||||||
|
* existing ones.
|
||||||
|
*
|
||||||
|
* @param message The message to sign with the default domain and add the produced signature to
|
||||||
|
* @return The provided message with the new signature
|
||||||
|
* @throws IllegalArgumentException If the <code>signatures</code> key exists and its value is not a JSON object
|
||||||
|
*/
|
||||||
|
JsonObject signMessageGson(JsonObject message) throws IllegalArgumentException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign the message and add the signature to the <code>signatures</code> key.
|
* Sign the message and add the signature to the <code>signatures</code> key.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -39,7 +51,7 @@ public interface SignatureManager {
|
|||||||
* @param domain The domain under which the signature should be added
|
* @param domain The domain under which the signature should be added
|
||||||
* @param message The message to sign and add the produced signature to
|
* @param message The message to sign and add the produced signature to
|
||||||
* @return The provided message with the new signature
|
* @return The provided message with the new signature
|
||||||
* @throws IllegalArgumentException If the <code>signatures</code> value is not a JSON object
|
* @throws IllegalArgumentException If the <code>signatures</code> key exists and its value is not a JSON object
|
||||||
*/
|
*/
|
||||||
default JsonObject signMessageGson(String domain, JsonObject message) throws IllegalArgumentException {
|
default JsonObject signMessageGson(String domain, JsonObject message) throws IllegalArgumentException {
|
||||||
JsonElement signEl = message.remove(EventKey.Signatures.get());
|
JsonElement signEl = message.remove(EventKey.Signatures.get());
|
||||||
|
@@ -23,6 +23,8 @@ package io.kamax.mxisd.crypto.ed25519;
|
|||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import io.kamax.matrix.codec.MxBase64;
|
import io.kamax.matrix.codec.MxBase64;
|
||||||
import io.kamax.matrix.json.MatrixJson;
|
import io.kamax.matrix.json.MatrixJson;
|
||||||
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
|
import io.kamax.mxisd.config.ServerConfig;
|
||||||
import io.kamax.mxisd.crypto.KeyIdentifier;
|
import io.kamax.mxisd.crypto.KeyIdentifier;
|
||||||
import io.kamax.mxisd.crypto.Signature;
|
import io.kamax.mxisd.crypto.Signature;
|
||||||
import io.kamax.mxisd.crypto.SignatureManager;
|
import io.kamax.mxisd.crypto.SignatureManager;
|
||||||
@@ -35,12 +37,19 @@ import java.security.SignatureException;
|
|||||||
|
|
||||||
public class Ed25519SignatureManager implements SignatureManager {
|
public class Ed25519SignatureManager implements SignatureManager {
|
||||||
|
|
||||||
|
private final ServerConfig cfg;
|
||||||
private final Ed25519KeyManager keyMgr;
|
private final Ed25519KeyManager keyMgr;
|
||||||
|
|
||||||
public Ed25519SignatureManager(Ed25519KeyManager keyMgr) {
|
public Ed25519SignatureManager(MxisdConfig cfg, Ed25519KeyManager keyMgr) {
|
||||||
|
this.cfg = cfg.getServer();
|
||||||
this.keyMgr = keyMgr;
|
this.keyMgr = keyMgr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonObject signMessageGson(JsonObject message) throws IllegalArgumentException {
|
||||||
|
return signMessageGson(cfg.getName(), message);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonObject signMessageGson(String domain, String message) {
|
public JsonObject signMessageGson(String domain, String message) {
|
||||||
Signature sign = sign(message);
|
Signature sign = sign(message);
|
||||||
|
@@ -33,8 +33,7 @@ public class InternalServerError extends HttpMatrixException {
|
|||||||
super(
|
super(
|
||||||
HttpStatus.SC_INTERNAL_SERVER_ERROR,
|
HttpStatus.SC_INTERNAL_SERVER_ERROR,
|
||||||
"M_UNKNOWN",
|
"M_UNKNOWN",
|
||||||
"An internal server error occured. If this error persists, please contact support with reference #" +
|
"An internal server error occurred. Contact your administrator with reference Transaction #" + Instant.now().toEpochMilli()
|
||||||
Instant.now().toEpochMilli()
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -97,7 +97,7 @@ public class SaneHandler extends BasicHttpHandler {
|
|||||||
if (StringUtils.isNotBlank(e.getInternalReason())) {
|
if (StringUtils.isNotBlank(e.getInternalReason())) {
|
||||||
log.error("Transaction #{} - {}", e.getReference(), e.getInternalReason());
|
log.error("Transaction #{} - {}", e.getReference(), e.getInternalReason());
|
||||||
} else {
|
} else {
|
||||||
log.error("Transaction #{}", e);
|
log.error("Transaction #{}", e.getReference(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleException(exchange, e);
|
handleException(exchange, e);
|
||||||
|
@@ -36,7 +36,7 @@ public class RestAuthHandler extends BasicHttpHandler {
|
|||||||
|
|
||||||
public static final String Path = "/_matrix-internal/identity/v1/check_credentials";
|
public static final String Path = "/_matrix-internal/identity/v1/check_credentials";
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(RestAuthHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(RestAuthHandler.class);
|
||||||
|
|
||||||
private AuthManager mgr;
|
private AuthManager mgr;
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ public class RestAuthHandler extends BasicHttpHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
public void handleRequest(HttpServerExchange exchange) {
|
||||||
JsonObject authData = parseJsonObject(exchange, "user");
|
JsonObject authData = parseJsonObject(exchange, "user");
|
||||||
if (!authData.has("id") || !authData.has("password")) {
|
if (!authData.has("id") || !authData.has("password")) {
|
||||||
throw new JsonMemberNotFoundException("Missing id or password keys");
|
throw new JsonMemberNotFoundException("Missing id or password keys");
|
||||||
|
@@ -40,7 +40,7 @@ public class UserDirectorySearchHandler extends HomeserverProxyHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleRequest(HttpServerExchange exchange) throws Exception {
|
public void handleRequest(HttpServerExchange exchange) {
|
||||||
String accessToken = getAccessToken(exchange);
|
String accessToken = getAccessToken(exchange);
|
||||||
UserDirectorySearchRequest searchQuery = parseJsonTo(exchange, UserDirectorySearchRequest.class);
|
UserDirectorySearchRequest searchQuery = parseJsonTo(exchange, UserDirectorySearchRequest.class);
|
||||||
URI target = URI.create(exchange.getRequestURL());
|
URI target = URI.create(exchange.getRequestURL());
|
||||||
|
@@ -37,7 +37,7 @@ public class BulkLookupHandler extends LookupHandler {
|
|||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/bulk_lookup";
|
public static final String Path = IsAPIv1.Base + "/bulk_lookup";
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(SingleLookupHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(SingleLookupHandler.class);
|
||||||
|
|
||||||
private LookupStrategy strategy;
|
private LookupStrategy strategy;
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ public class EphemeralKeyIsValidHandler extends KeyIsValidHandler {
|
|||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/pubkey/ephemeral/isvalid";
|
public static final String Path = IsAPIv1.Base + "/pubkey/ephemeral/isvalid";
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(EphemeralKeyIsValidHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(EphemeralKeyIsValidHandler.class);
|
||||||
|
|
||||||
private KeyManager mgr;
|
private KeyManager mgr;
|
||||||
|
|
||||||
|
@@ -21,11 +21,15 @@
|
|||||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
|
import io.kamax.mxisd.crypto.SignatureManager;
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
import io.kamax.mxisd.exception.BadRequestException;
|
||||||
import io.kamax.mxisd.http.IsAPIv1;
|
import io.kamax.mxisd.http.IsAPIv1;
|
||||||
import io.kamax.mxisd.http.io.identity.BindRequest;
|
import io.kamax.mxisd.http.io.identity.BindRequest;
|
||||||
|
import io.kamax.mxisd.http.io.identity.SingeLookupReplyJson;
|
||||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||||
import io.kamax.mxisd.invitation.InvitationManager;
|
import io.kamax.mxisd.invitation.InvitationManager;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
import io.kamax.mxisd.session.SessionManager;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import io.undertow.util.QueryParameterUtils;
|
import io.undertow.util.QueryParameterUtils;
|
||||||
@@ -42,14 +46,16 @@ public class SessionTpidBindHandler extends BasicHttpHandler {
|
|||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/3pid/bind";
|
public static final String Path = IsAPIv1.Base + "/3pid/bind";
|
||||||
|
|
||||||
private transient final Logger log = LoggerFactory.getLogger(SessionTpidBindHandler.class);
|
private static final Logger log = LoggerFactory.getLogger(SessionTpidBindHandler.class);
|
||||||
|
|
||||||
private SessionManager mgr;
|
private SessionManager mgr;
|
||||||
private InvitationManager invMgr;
|
private InvitationManager invMgr;
|
||||||
|
private SignatureManager signMgr;
|
||||||
|
|
||||||
public SessionTpidBindHandler(SessionManager mgr, InvitationManager invMgr) {
|
public SessionTpidBindHandler(SessionManager mgr, InvitationManager invMgr, SignatureManager signMgr) {
|
||||||
this.mgr = mgr;
|
this.mgr = mgr;
|
||||||
this.invMgr = invMgr;
|
this.invMgr = invMgr;
|
||||||
|
this.signMgr = signMgr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -74,8 +80,9 @@ public class SessionTpidBindHandler extends BasicHttpHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
mgr.bind(bindReq.getSid(), bindReq.getSecret(), bindReq.getUserId());
|
SingleLookupReply lookup = mgr.bind(bindReq.getSid(), bindReq.getSecret(), bindReq.getUserId());
|
||||||
respond(exchange, new JsonObject());
|
JsonObject response = signMgr.signMessageGson(GsonUtil.makeObj(new SingeLookupReplyJson(lookup)));
|
||||||
|
respond(exchange, response);
|
||||||
} catch (BadRequestException e) {
|
} catch (BadRequestException e) {
|
||||||
log.info("requested session was not validated");
|
log.info("requested session was not validated");
|
||||||
|
|
||||||
|
@@ -21,15 +21,22 @@
|
|||||||
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
package io.kamax.mxisd.http.undertow.handler.identity.v1;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
|
import io.kamax.mxisd.exception.BadRequestException;
|
||||||
|
import io.kamax.mxisd.exception.NotAllowedException;
|
||||||
import io.kamax.mxisd.http.IsAPIv1;
|
import io.kamax.mxisd.http.IsAPIv1;
|
||||||
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
|
||||||
import io.kamax.mxisd.session.SessionManager;
|
import io.kamax.mxisd.session.SessionManager;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
||||||
|
|
||||||
public static final String Path = IsAPIv1.Base + "/3pid/unbind";
|
public static final String Path = IsAPIv1.Base + "/3pid/unbind";
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SessionTpidUnbindHandler.class);
|
||||||
|
|
||||||
private final SessionManager sessionMgr;
|
private final SessionManager sessionMgr;
|
||||||
|
|
||||||
public SessionTpidUnbindHandler(SessionManager sessionMgr) {
|
public SessionTpidUnbindHandler(SessionManager sessionMgr) {
|
||||||
@@ -38,6 +45,18 @@ public class SessionTpidUnbindHandler extends BasicHttpHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleRequest(HttpServerExchange exchange) {
|
public void handleRequest(HttpServerExchange exchange) {
|
||||||
|
String auth = exchange.getRequestHeaders().getFirst("Authorization");
|
||||||
|
if (StringUtils.isNotEmpty(auth)) {
|
||||||
|
// We have a auth header to process
|
||||||
|
if (StringUtils.startsWith(auth, "X-Matrix ")) {
|
||||||
|
log.warn("A remote host attempted to unbind without proper authorization. Request was denied");
|
||||||
|
log.warn("See https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy for more info");
|
||||||
|
throw new NotAllowedException("3PID can only be removed via 3PID sessions, not via Homeserver signature");
|
||||||
|
} else {
|
||||||
|
throw new BadRequestException("Illegal authorization type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
JsonObject body = parseJsonObject(exchange);
|
JsonObject body = parseJsonObject(exchange);
|
||||||
sessionMgr.unbind(body);
|
sessionMgr.unbind(body);
|
||||||
writeBodyAsUtf8(exchange, "{}");
|
writeBodyAsUtf8(exchange, "{}");
|
||||||
|
@@ -72,7 +72,7 @@ public class SingleLookupReply {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SingleLookupReply(SingleLookupRequest request, _MatrixID mxid) {
|
public SingleLookupReply(SingleLookupRequest request, _MatrixID mxid) {
|
||||||
this(request, mxid, Instant.now(), Instant.ofEpochMilli(0), Instant.ofEpochMilli(253402300799000L));
|
this(request, mxid, Instant.now(), Instant.now().minusSeconds(60), Instant.now().plusSeconds(5 * 60));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SingleLookupReply(SingleLookupRequest request, _MatrixID mxid, Instant timestamp, Instant notBefore, Instant notAfter) {
|
public SingleLookupReply(SingleLookupRequest request, _MatrixID mxid, Instant timestamp, Instant notBefore, Instant notAfter) {
|
||||||
|
@@ -79,7 +79,7 @@ public class IdentityServerUtils {
|
|||||||
|
|
||||||
JsonElement el = parser.parse(IOUtils.toString(res.getEntity().getContent(), StandardCharsets.UTF_8));
|
JsonElement el = parser.parse(IOUtils.toString(res.getEntity().getContent(), StandardCharsets.UTF_8));
|
||||||
if (!el.isJsonObject()) {
|
if (!el.isJsonObject()) {
|
||||||
log.debug("IS {} did not send back an empty JSON object as per spec, not a valid IS");
|
log.debug("IS {} did not send back an empty JSON object as per spec, not a valid IS", remote);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ public class IdentityServerUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getSrvRecordName(String domain) {
|
private static String getSrvRecordName(String domain) {
|
||||||
return "_matrix-identity._tcp." + domain;
|
return "_matrix-identity._tcp." + domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -27,13 +27,13 @@ import io.kamax.matrix._MatrixID;
|
|||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
import io.kamax.mxisd.config.SessionConfig;
|
import io.kamax.mxisd.config.SessionConfig;
|
||||||
|
import io.kamax.mxisd.exception.BadRequestException;
|
||||||
import io.kamax.mxisd.exception.NotAllowedException;
|
import io.kamax.mxisd.exception.NotAllowedException;
|
||||||
import io.kamax.mxisd.exception.NotImplementedException;
|
|
||||||
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
||||||
import io.kamax.mxisd.exception.SessionUnknownException;
|
import io.kamax.mxisd.exception.SessionUnknownException;
|
||||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
|
||||||
import io.kamax.mxisd.notification.NotificationManager;
|
import io.kamax.mxisd.notification.NotificationManager;
|
||||||
import io.kamax.mxisd.storage.IStorage;
|
import io.kamax.mxisd.storage.IStorage;
|
||||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||||
@@ -55,20 +55,17 @@ public class SessionManager {
|
|||||||
private MatrixConfig mxCfg;
|
private MatrixConfig mxCfg;
|
||||||
private IStorage storage;
|
private IStorage storage;
|
||||||
private NotificationManager notifMgr;
|
private NotificationManager notifMgr;
|
||||||
private LookupStrategy lookupMgr;
|
|
||||||
|
|
||||||
public SessionManager(
|
public SessionManager(
|
||||||
SessionConfig cfg,
|
SessionConfig cfg,
|
||||||
MatrixConfig mxCfg,
|
MatrixConfig mxCfg,
|
||||||
IStorage storage,
|
IStorage storage,
|
||||||
NotificationManager notifMgr,
|
NotificationManager notifMgr
|
||||||
LookupStrategy lookupMgr
|
|
||||||
) {
|
) {
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
this.mxCfg = mxCfg;
|
this.mxCfg = mxCfg;
|
||||||
this.storage = storage;
|
this.storage = storage;
|
||||||
this.notifMgr = notifMgr;
|
this.notifMgr = notifMgr;
|
||||||
this.lookupMgr = lookupMgr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ThreePidSession getSession(String sid, String secret) {
|
private ThreePidSession getSession(String sid, String secret) {
|
||||||
@@ -151,7 +148,7 @@ public class SessionManager {
|
|||||||
return new ThreePidValidation(session.getThreePid(), session.getValidationTime());
|
return new ThreePidValidation(session.getThreePid(), session.getValidationTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(String sid, String secret, String mxidRaw) {
|
public SingleLookupReply bind(String sid, String secret, String mxidRaw) {
|
||||||
// We make sure we have an acceptable User ID
|
// We make sure we have an acceptable User ID
|
||||||
if (StringUtils.isEmpty(mxidRaw)) {
|
if (StringUtils.isEmpty(mxidRaw)) {
|
||||||
throw new IllegalArgumentException("No Matrix User ID provided");
|
throw new IllegalArgumentException("No Matrix User ID provided");
|
||||||
@@ -165,61 +162,45 @@ public class SessionManager {
|
|||||||
|
|
||||||
// Only accept binds if the domain matches our own
|
// Only accept binds if the domain matches our own
|
||||||
if (!StringUtils.equalsIgnoreCase(mxCfg.getDomain(), mxid.getDomain())) {
|
if (!StringUtils.equalsIgnoreCase(mxCfg.getDomain(), mxid.getDomain())) {
|
||||||
throw new NotAllowedException("Only Matrix IDs from domain " + mxCfg + " can be bound");
|
throw new NotAllowedException("Only Matrix IDs from domain " + mxCfg.getDomain() + " can be bound");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Session {}: Binding of {}:{} to Matrix ID {} is accepted",
|
log.info("Session {}: Binding of {}:{} to Matrix ID {} is accepted",
|
||||||
session.getId(), session.getThreePid().getMedium(), session.getThreePid().getAddress(), mxid.getId());
|
session.getId(), session.getThreePid().getMedium(), session.getThreePid().getAddress(), mxid.getId());
|
||||||
|
|
||||||
|
SingleLookupRequest request = new SingleLookupRequest();
|
||||||
|
request.setType(session.getThreePid().getMedium());
|
||||||
|
request.setThreePid(session.getThreePid().getAddress());
|
||||||
|
return new SingleLookupReply(request, mxid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void unbind(JsonObject reqData) {
|
public void unbind(JsonObject reqData) {
|
||||||
if (reqData.entrySet().size() == 2 && reqData.has("mxid") && reqData.has("threepid")) {
|
_MatrixID mxid;
|
||||||
/* This is a HS request to remove a 3PID and is considered:
|
try {
|
||||||
* - An attack on user privacy
|
mxid = MatrixID.asAcceptable(GsonUtil.getStringOrThrow(reqData, "mxid"));
|
||||||
* - A baffling spec breakage requiring IS and HS 3PID info to be independent [1]
|
} catch (IllegalArgumentException e) {
|
||||||
* - A baffling spec breakage that 3PID (un)bind is only one way [2]
|
throw new BadRequestException(e.getMessage());
|
||||||
*
|
|
||||||
* 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");
|
|
||||||
log.warn("See https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy for more info");
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new NotAllowedException("You have attempted to alter 3PID bindings, which can only be done by the 3PID owner directly. " +
|
|
||||||
"We have informed the 3PID owner of your fraudulent attempt.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Denying unbind request as the endpoint is not defined in the spec.");
|
String sid = GsonUtil.getStringOrNull(reqData, "sid");
|
||||||
throw new NotAllowedException(499, "This endpoint does not exist in the spec and therefore is not supported.");
|
String secret = GsonUtil.getStringOrNull(reqData, "client_secret");
|
||||||
|
ThreePid tpid = GsonUtil.get().fromJson(GsonUtil.getObj(reqData, "threepid"), ThreePid.class);
|
||||||
|
|
||||||
|
// We ensure the session was validated
|
||||||
|
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||||
|
|
||||||
|
// As per spec, we can only allow if the provided 3PID matches the validated session
|
||||||
|
if (!session.getThreePid().equals(tpid)) {
|
||||||
|
throw new BadRequestException("3PID to unbind does not match the one from the validated session");
|
||||||
|
}
|
||||||
|
|
||||||
|
// We only allow unbind for the domain we manage, mirroring bind
|
||||||
|
if (!StringUtils.equalsIgnoreCase(mxCfg.getDomain(), mxid.getDomain())) {
|
||||||
|
throw new NotAllowedException("Only Matrix IDs from domain " + mxCfg.getDomain() + " can be unbound");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Session {}: Unbinding of {}:{} to Matrix ID {} is accepted",
|
||||||
|
session.getId(), session.getThreePid().getMedium(), session.getThreePid().getAddress(), mxid.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -21,10 +21,12 @@
|
|||||||
package io.kamax.mxisd.threepid.connector.phone;
|
package io.kamax.mxisd.threepid.connector.phone;
|
||||||
|
|
||||||
import com.twilio.Twilio;
|
import com.twilio.Twilio;
|
||||||
|
import com.twilio.exception.ApiException;
|
||||||
import com.twilio.rest.api.v2010.account.Message;
|
import com.twilio.rest.api.v2010.account.Message;
|
||||||
import com.twilio.type.PhoneNumber;
|
import com.twilio.type.PhoneNumber;
|
||||||
import io.kamax.mxisd.config.threepid.connector.PhoneTwilioConfig;
|
import io.kamax.mxisd.config.threepid.connector.PhoneTwilioConfig;
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
|
import io.kamax.mxisd.exception.NotImplementedException;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -52,12 +54,17 @@ public class PhoneSmsTwilioConnector implements PhoneConnector {
|
|||||||
@Override
|
@Override
|
||||||
public void send(String recipient, String content) {
|
public void send(String recipient, String content) {
|
||||||
if (StringUtils.isBlank(cfg.getAccountSid()) || StringUtils.isBlank(cfg.getAuthToken()) || StringUtils.isBlank(cfg.getNumber())) {
|
if (StringUtils.isBlank(cfg.getAccountSid()) || StringUtils.isBlank(cfg.getAuthToken()) || StringUtils.isBlank(cfg.getNumber())) {
|
||||||
throw new BadRequestException("Phone numbers cannot be validated at this time. Contact your administrator.");
|
log.error("Twilio connector in not fully configured and is missing mandatory configuration values.");
|
||||||
|
throw new NotImplementedException("Phone numbers cannot be validated at this time. Contact your administrator.");
|
||||||
}
|
}
|
||||||
|
|
||||||
recipient = "+" + recipient;
|
recipient = "+" + recipient;
|
||||||
log.info("Sending SMS notification from {} to {} with {} characters", cfg.getNumber(), recipient, content.length());
|
log.info("Sending SMS notification from {} to {} with {} characters", cfg.getNumber(), recipient, content.length());
|
||||||
Message.creator(new PhoneNumber("+" + recipient), new PhoneNumber(cfg.getNumber()), content).create();
|
try {
|
||||||
|
Message.creator(new PhoneNumber("+" + recipient), new PhoneNumber(cfg.getNumber()), content).create();
|
||||||
|
} catch (ApiException e) {
|
||||||
|
throw new InternalServerError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,7 @@ import io.kamax.mxisd.http.IsAPIv1;
|
|||||||
import io.kamax.mxisd.invitation.IMatrixIdInvite;
|
import io.kamax.mxisd.invitation.IMatrixIdInvite;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||||
|
import io.kamax.mxisd.util.RestClientUtils;
|
||||||
import org.apache.commons.lang.WordUtils;
|
import org.apache.commons.lang.WordUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
@@ -56,7 +57,9 @@ public abstract class PlaceholderNotificationGenerator {
|
|||||||
.replace("%DOMAIN%", mxCfg.getDomain())
|
.replace("%DOMAIN%", mxCfg.getDomain())
|
||||||
.replace("%DOMAIN_PRETTY%", domainPretty)
|
.replace("%DOMAIN_PRETTY%", domainPretty)
|
||||||
.replace("%RECIPIENT_MEDIUM%", recipient.getMedium())
|
.replace("%RECIPIENT_MEDIUM%", recipient.getMedium())
|
||||||
.replace("%RECIPIENT_ADDRESS%", recipient.getAddress());
|
.replace("%RECIPIENT_MEDIUM_URL_ENCODED%", RestClientUtils.urlEncode(recipient.getMedium()))
|
||||||
|
.replace("%RECIPIENT_ADDRESS%", recipient.getAddress())
|
||||||
|
.replace("%RECIPIENT_ADDRESS_URL_ENCODED%", RestClientUtils.urlEncode(recipient.getAddress()));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String populateForInvite(IMatrixIdInvite invite, String input) {
|
protected String populateForInvite(IMatrixIdInvite invite, String input) {
|
||||||
@@ -98,7 +101,9 @@ public abstract class PlaceholderNotificationGenerator {
|
|||||||
.replace("%SENDER_NAME%", senderName)
|
.replace("%SENDER_NAME%", senderName)
|
||||||
.replace("%SENDER_NAME_OR_ID%", senderNameOrId)
|
.replace("%SENDER_NAME_OR_ID%", senderNameOrId)
|
||||||
.replace("%INVITE_MEDIUM%", tpid.getMedium())
|
.replace("%INVITE_MEDIUM%", tpid.getMedium())
|
||||||
|
.replace("%INVITE_MEDIUM_URL_ENCODED%", RestClientUtils.urlEncode(tpid.getMedium()))
|
||||||
.replace("%INVITE_ADDRESS%", tpid.getAddress())
|
.replace("%INVITE_ADDRESS%", tpid.getAddress())
|
||||||
|
.replace("%INVITE_ADDRESS_URL_ENCODED%", RestClientUtils.urlEncode(tpid.getAddress()))
|
||||||
.replace("%ROOM_ID%", invite.getInvite().getRoomId())
|
.replace("%ROOM_ID%", invite.getInvite().getRoomId())
|
||||||
.replace("%ROOM_NAME%", roomName)
|
.replace("%ROOM_NAME%", roomName)
|
||||||
.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
|
.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
|
||||||
|
@@ -25,12 +25,22 @@ import org.apache.http.client.methods.HttpPost;
|
|||||||
import org.apache.http.entity.ContentType;
|
import org.apache.http.entity.ContentType;
|
||||||
import org.apache.http.entity.StringEntity;
|
import org.apache.http.entity.StringEntity;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
public class RestClientUtils {
|
public class RestClientUtils {
|
||||||
|
|
||||||
private static Gson gson = GsonUtil.build();
|
private static Gson gson = GsonUtil.build();
|
||||||
|
|
||||||
|
public static String urlEncode(String value) {
|
||||||
|
try {
|
||||||
|
return URLEncoder.encode(value, StandardCharsets.UTF_8.name());
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static HttpPost post(String url, String body) {
|
public static HttpPost post(String url, String body) {
|
||||||
StringEntity entity = new StringEntity(body, StandardCharsets.UTF_8);
|
StringEntity entity = new StringEntity(body, StandardCharsets.UTF_8);
|
||||||
entity.setContentType(ContentType.APPLICATION_JSON.toString());
|
entity.setContentType(ContentType.APPLICATION_JSON.toString());
|
||||||
|
@@ -9,19 +9,12 @@ Content-Disposition: inline
|
|||||||
|
|
||||||
Hi,
|
Hi,
|
||||||
|
|
||||||
%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
%SENDER_NAME_OR_ID% has invited you to the Matrix room [%ROOM_NAME_OR_ID%].
|
||||||
Matrix. To join the conversation, register an account on %REGISTER_URL%
|
To join the conversation, register an account using %REGISTER_URL%
|
||||||
|
|
||||||
|
You may be required to provide the same email used for this invite during registration.
|
||||||
You can also register an account on a public server and get in touch with them.
|
You can also register an account on a public server and get in touch with them.
|
||||||
|
|
||||||
|
|
||||||
About Matrix:
|
|
||||||
|
|
||||||
Matrix is an open standard for interoperable, decentralised, real-time communication
|
|
||||||
over IP, supporting group chat, file transfer, voice and video calling, integrations to
|
|
||||||
other apps, bridges to other communication systems and much more. It can be used to power
|
|
||||||
Instant Messaging, VoIP/WebRTC signalling, Internet of Things communication.
|
|
||||||
|
|
||||||
Thanks,
|
Thanks,
|
||||||
|
|
||||||
%DOMAIN_PRETTY% Admins
|
%DOMAIN_PRETTY% Admins
|
||||||
@@ -68,18 +61,11 @@ Content-Disposition: inline
|
|||||||
<td id="inner">
|
<td id="inner">
|
||||||
<p>Hi,</p>
|
<p>Hi,</p>
|
||||||
|
|
||||||
<p>%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
<p>%SENDER_NAME_OR_ID% has invited you to the Matrix room [%ROOM_NAME_OR_ID%].<br/>
|
||||||
Matrix. To join the conversation, register an account on <a href="%REGISTER_URL%">%DOMAIN%</a>.</p>
|
To join the conversation, register an account on <a href="%REGISTER_URL%">%DOMAIN%</a>.</p>
|
||||||
|
|
||||||
<pYou can also register an account on a public server and get in touch with them.</p>
|
<p>You may be required to provide the same email used for this invite during registration.<br/>
|
||||||
|
You can also register an account on a public server and get in touch with them.</p>
|
||||||
<br>
|
|
||||||
<p>About Matrix:</p>
|
|
||||||
|
|
||||||
<p>Matrix is an open standard for interoperable, decentralised, real-time communication
|
|
||||||
over IP, supporting group chat, file transfer, voice and video calling, integrations to
|
|
||||||
other apps, bridges to other communication systems and much more. It can be used to power
|
|
||||||
Instant Messaging, VoIP/WebRTC signalling, Internet of Things communication.</p>
|
|
||||||
|
|
||||||
<p>Thanks,</p>
|
<p>Thanks,</p>
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@ Content-Disposition: inline
|
|||||||
|
|
||||||
Hi,
|
Hi,
|
||||||
|
|
||||||
%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on Matrix.
|
%SENDER_NAME_OR_ID% has invited you to the Matrix room [%ROOM_NAME_OR_ID%].
|
||||||
|
|
||||||
Thanks,
|
Thanks,
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ Content-Disposition: inline
|
|||||||
<td id="inner">
|
<td id="inner">
|
||||||
<p>Hi,</p>
|
<p>Hi,</p>
|
||||||
|
|
||||||
<p>%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on Matrix.</p>
|
<p>%SENDER_NAME_OR_ID% has invited you to the Matrix room [%ROOM_NAME_OR_ID%].</p>
|
||||||
|
|
||||||
<p>Thanks,</p>
|
<p>Thanks,</p>
|
||||||
|
|
||||||
|
@@ -33,11 +33,7 @@ public class MxisdDefaultTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void defaultConfig() {
|
public void defaultConfig() {
|
||||||
MxisdConfig cfg = new MxisdConfig();
|
MxisdConfig cfg = MxisdConfig.forDomain(domain).inMemory();
|
||||||
cfg.getMatrix().setDomain(domain);
|
|
||||||
cfg.getKey().setPath(":memory:");
|
|
||||||
cfg.getStorage().getProvider().getSqlite().setDatabase(":memory:");
|
|
||||||
|
|
||||||
Mxisd m = new Mxisd(cfg);
|
Mxisd m = new Mxisd(cfg);
|
||||||
m.start();
|
m.start();
|
||||||
|
|
||||||
|
@@ -41,10 +41,7 @@ public class MxisdTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void before() {
|
public void before() {
|
||||||
MxisdConfig cfg = new MxisdConfig();
|
MxisdConfig cfg = MxisdConfig.forDomain("localhost").inMemory();
|
||||||
cfg.getMatrix().setDomain("localhost");
|
|
||||||
cfg.getKey().setPath(":memory:");
|
|
||||||
cfg.getStorage().getProvider().getSqlite().setDatabase(":memory:");
|
|
||||||
|
|
||||||
MemoryThreePid mem3pid = new MemoryThreePid();
|
MemoryThreePid mem3pid = new MemoryThreePid();
|
||||||
mem3pid.setMedium("email");
|
mem3pid.setMedium("email");
|
||||||
|
77
src/test/java/io/kamax/mxisd/test/auth/AuthManagerTest.java
Normal file
77
src/test/java/io/kamax/mxisd/test/auth/AuthManagerTest.java
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sarl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.test.auth;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.Mxisd;
|
||||||
|
import io.kamax.mxisd.auth.AuthManager;
|
||||||
|
import io.kamax.mxisd.auth.UserAuthResult;
|
||||||
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
|
import io.kamax.mxisd.config.memory.MemoryIdentityConfig;
|
||||||
|
import io.kamax.mxisd.config.memory.MemoryThreePid;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class AuthManagerTest {
|
||||||
|
|
||||||
|
private static AuthManager mgr;
|
||||||
|
|
||||||
|
// FIXME we should be able to easily build the class ourselves
|
||||||
|
// FIXME use constants
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() {
|
||||||
|
MxisdConfig cfg = new MxisdConfig();
|
||||||
|
cfg.getMatrix().setDomain("localhost");
|
||||||
|
cfg.getKey().setPath(":memory:");
|
||||||
|
cfg.getStorage().getProvider().getSqlite().setDatabase(":memory:");
|
||||||
|
|
||||||
|
MemoryThreePid mem3pid = new MemoryThreePid();
|
||||||
|
mem3pid.setMedium("email");
|
||||||
|
mem3pid.setAddress("john@localhost");
|
||||||
|
MemoryIdentityConfig validCfg = new MemoryIdentityConfig();
|
||||||
|
validCfg.setUsername("john");
|
||||||
|
validCfg.setPassword("doe");
|
||||||
|
validCfg.getThreepids().add(mem3pid);
|
||||||
|
MemoryIdentityConfig illegalUser = new MemoryIdentityConfig();
|
||||||
|
illegalUser.setUsername("JANE");
|
||||||
|
illegalUser.setPassword("doe");
|
||||||
|
cfg.getMemory().setEnabled(true);
|
||||||
|
cfg.getMemory().getIdentities().add(validCfg);
|
||||||
|
cfg.getMemory().getIdentities().add(illegalUser);
|
||||||
|
|
||||||
|
Mxisd m = new Mxisd(cfg);
|
||||||
|
m.start();
|
||||||
|
mgr = m.getAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void basic() {
|
||||||
|
UserAuthResult result = mgr.authenticate("@john:localhost", "doe");
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
|
||||||
|
// For backward-compatibility as per instructed by the spec, we do not fail on an illegal username
|
||||||
|
// This makes sure we don't break it
|
||||||
|
result = mgr.authenticate("@JANE:localhost", "doe");
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -24,6 +24,7 @@ import com.google.gson.JsonObject;
|
|||||||
import io.kamax.matrix.event.EventKey;
|
import io.kamax.matrix.event.EventKey;
|
||||||
import io.kamax.matrix.json.GsonUtil;
|
import io.kamax.matrix.json.GsonUtil;
|
||||||
import io.kamax.matrix.json.MatrixJson;
|
import io.kamax.matrix.json.MatrixJson;
|
||||||
|
import io.kamax.mxisd.config.MxisdConfig;
|
||||||
import io.kamax.mxisd.crypto.Signature;
|
import io.kamax.mxisd.crypto.Signature;
|
||||||
import io.kamax.mxisd.crypto.SignatureManager;
|
import io.kamax.mxisd.crypto.SignatureManager;
|
||||||
import io.kamax.mxisd.crypto.ed25519.Ed25519Key;
|
import io.kamax.mxisd.crypto.ed25519.Ed25519Key;
|
||||||
@@ -52,7 +53,7 @@ public class SignatureManagerTest {
|
|||||||
KeyStore store = new MemoryKeyStore();
|
KeyStore store = new MemoryKeyStore();
|
||||||
store.add(key);
|
store.add(key);
|
||||||
|
|
||||||
return new Ed25519SignatureManager(new Ed25519KeyManager(store));
|
return new Ed25519SignatureManager(MxisdConfig.forDomain("localhost").inMemory().build(), new Ed25519KeyManager(store));
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
|
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* mxisd - Matrix Identity Server Daemon
|
||||||
|
* Copyright (C) 2019 Kamax Sarl
|
||||||
|
*
|
||||||
|
* https://www.kamax.io/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.kamax.mxisd.test.util;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.util.RestClientUtils;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
public class RestClientUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void urlEncode() {
|
||||||
|
String encoded = RestClientUtils.urlEncode("john+doe@example.org");
|
||||||
|
assertEquals("john%2Bdoe%40example.org", encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user