Compare commits

...

23 Commits

Author SHA1 Message Date
Max Dor
a964b073bf Restart mxisd service on Debian package upgrade/install if possible 2019-06-12 00:18:25 +02:00
Max Dor
f85345bc97 Update code and links following Matrix 1.0 release
- Support 3PID unbind via 3PID sessions
2019-06-12 00:17:43 +02:00
Max Dor
29603682e5 Clarify how to serve static assets for 3PID session views 2019-06-04 17:06:25 +02:00
Max Dor
d54f1dcb88 Fix various typos in the Registration feature docs 2019-05-30 17:29:47 +02:00
Max Dor
92f10347d1 Fix #123 2019-05-30 14:18:11 +02:00
Max Dor
0298f66212 Fix #128 2019-05-30 13:58:40 +02:00
Max Dor
0ddd086bda Fix response body of /3pid/bind to match spec
- synapse did not check/validate the response as per spec until 0.99.5 it seems
- mxisd was never compliant also
2019-05-30 13:26:38 +02:00
Max Dor
544f8e59f0 Add check for legality of the returned Matrix ID in Auth
- Helps troubleshoot reported issues that might not be obvious at first
- Add basic unit test for auth manager
2019-05-28 19:28:46 +02:00
Max Dor
917f87bf8c Fix broken HTML tag in 3PID template 2019-05-28 16:01:01 +02:00
Max Dor
774795c203 Fix various logging/variable scopes 2019-05-27 17:12:52 +02:00
Max Dor
27b2976e42 Provide URL encoded placeholders in notification template for 3PID data 2019-05-18 02:20:13 +02:00
Max Dor
f16f184253 Minor internal changes
- Fix log statement to include expected value
- Change access level to method
2019-05-18 01:57:40 +02:00
Max Dor
cd890d114a Add warning about possibly unresolvable 3PID invites 2019-05-14 00:49:07 +02:00
Max Dor
321ba1e325 Code formatting (cosmetic, no-op) 2019-05-14 00:39:12 +02:00
Max Dor
c3ce0a17f6 Avoid conflict between 3PID expired user and Matrix ID users event 2019-05-13 16:08:35 +02:00
Max Dor
0fcc0d9bb2 Properly inform about bad configuration for 3PID builtin configs 2019-05-13 14:04:11 +02:00
Max Dor
ce7f900543 Make various optimisations/clarifications
- Change some log levels to be less verbose
- Add privacy link
- Remove unused code
2019-05-06 23:28:38 +02:00
Max Dor
c7c009f9af Fix indentation in builtin 3PID templates (cosmetic) 2019-05-06 19:16:20 +02:00
Max Dor
3b01663245 Switch to Gradle 5 build 2019-05-05 15:56:51 +02:00
Max Dor
9cc601d582 Fix custom config for custom notification handlers 2019-05-05 13:54:12 +02:00
Max Dor
e6272b1827 Improve detection and fast-fail on empty Sendgrid template paths 2019-05-05 13:48:14 +02:00
Max Dor
8243354f39 Remove unused but bug-triggering code block (Fix #172) 2019-05-04 11:17:36 +02:00
Max Dor
25968e0737 Log denied requests due to invalid credentials in AS 2019-05-04 11:16:19 +02:00
49 changed files with 634 additions and 372 deletions

View File

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

View File

@@ -48,6 +48,8 @@ def dockerImageTag = "${dockerImageName}:${mxisdVersion()}"
group = 'io.kamax' group = 'io.kamax'
mainClassName = 'io.kamax.mxisd.MxisdStandaloneExec' mainClassName = 'io.kamax.mxisd.MxisdStandaloneExec'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
String mxisdVersion() { String mxisdVersion() {
def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?") def versionPattern = Pattern.compile("v(\\d+\\.)?(\\d+\\.)?(\\d+)(-.*)?")
@@ -87,10 +89,10 @@ repositories {
dependencies { dependencies {
// Logging // Logging
compile 'org.slf4j:slf4j-simple:1.7.25' compile 'org.slf4j:slf4j-simple:1.7.25'
// Easy file management // Easy file management
compile 'commons-io:commons-io:2.5' compile 'commons-io:commons-io:2.5'
// Config management // Config management
compile 'org.yaml:snakeyaml:1.23' compile 'org.yaml:snakeyaml:1.23'
@@ -145,7 +147,7 @@ dependencies {
// HTTP server // HTTP server
compile 'io.undertow:undertow-core:2.0.16.Final' compile 'io.undertow:undertow-core:2.0.16.Final'
// Command parser for AS interface // Command parser for AS interface
implementation 'commons-cli:commons-cli:1.4' implementation 'commons-cli:commons-cli:1.4'
@@ -158,7 +160,7 @@ dependencies {
jar { jar {
manifest { manifest {
attributes( attributes(
'Implementation-Version': mxisdVersion() 'Implementation-Version': mxisdVersion()
) )
} }
} }
@@ -227,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'

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

@@ -1,6 +1,5 @@
#Fri Aug 11 17:19:02 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0.2-bin.zip

18
gradlew vendored
View File

@@ -1,5 +1,21 @@
#!/usr/bin/env sh #!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
############################################################################## ##############################################################################
## ##
## Gradle start up script for UN*X ## Gradle start up script for UN*X
@@ -28,7 +44,7 @@ APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS="" DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD="maximum"

184
gradlew.bat vendored
View File

@@ -1,84 +1,100 @@
@if "%DEBUG%" == "" @echo off @rem
@rem ########################################################################## @rem Copyright 2015 the original author or authors.
@rem @rem
@rem Gradle startup script for Windows @rem Licensed under the Apache License, Version 2.0 (the "License");
@rem @rem you may not use this file except in compliance with the License.
@rem ########################################################################## @rem You may obtain a copy of the License at
@rem
@rem Set local scope for the variables with windows NT shell @rem http://www.apache.org/licenses/LICENSE-2.0
if "%OS%"=="Windows_NT" setlocal @rem
@rem Unless required by applicable law or agreed to in writing, software
set DIRNAME=%~dp0 @rem distributed under the License is distributed on an "AS IS" BASIS,
if "%DIRNAME%" == "" set DIRNAME=. @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
set APP_BASE_NAME=%~n0 @rem See the License for the specific language governing permissions and
set APP_HOME=%DIRNAME% @rem limitations under the License.
@rem
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS= @if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem Find java.exe @rem
if defined JAVA_HOME goto findJavaFromJavaHome @rem Gradle startup script for Windows
@rem
set JAVA_EXE=java.exe @rem ##########################################################################
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. set DIRNAME=%~dp0
echo. if "%DIRNAME%" == "" set DIRNAME=.
echo Please set the JAVA_HOME variable in your environment to match the set APP_BASE_NAME=%~n0
echo location of your Java installation. set APP_HOME=%DIRNAME%
goto fail @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% @rem Find java.exe
set JAVA_EXE=%JAVA_HOME%/bin/java.exe if defined JAVA_HOME goto findJavaFromJavaHome
if exist "%JAVA_EXE%" goto init set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
echo. if "%ERRORLEVEL%" == "0" goto init
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo location of your Java installation. echo.
echo Please set the JAVA_HOME variable in your environment to match the
goto fail echo location of your Java installation.
:init goto fail
@rem Get command-line arguments, handling Windows variants
:findJavaFromJavaHome
if not "%OS%" == "Windows_NT" goto win9xME_args set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
:win9xME_args
@rem Slurp the command line arguments. if exist "%JAVA_EXE%" goto init
set CMD_LINE_ARGS=
set _SKIP=2 echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
:win9xME_args_slurp echo.
if "x%~1" == "x" goto execute echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
set CMD_LINE_ARGS=%*
goto fail
:execute
@rem Setup the command line :init
@rem Get command-line arguments, handling Windows variants
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
if not "%OS%" == "Windows_NT" goto win9xME_args
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :win9xME_args
@rem Slurp the command line arguments.
:end set CMD_LINE_ARGS=
@rem End local scope for the variables with windows NT shell set _SKIP=2
if "%ERRORLEVEL%"=="0" goto mainEnd
:win9xME_args_slurp
:fail if "x%~1" == "x" goto execute
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! set CMD_LINE_ARGS=%*
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1 :execute
@rem Setup the command line
:mainEnd
if "%OS%"=="Windows_NT" endlocal set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:omega @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

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

View File

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

View File

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

View File

@@ -176,10 +176,12 @@ public class AppSvcManager {
ensureEnabled(); ensureEnabled();
if (StringUtils.isBlank(token)) { if (StringUtils.isBlank(token)) {
log.info("Denying request without a HS token");
throw new HttpMatrixException(401, "M_UNAUTHORIZED", "No HS token"); throw new HttpMatrixException(401, "M_UNAUTHORIZED", "No HS token");
} }
if (!StringUtils.equals(cfg.getEndpoint().getToAS().getToken(), token)) { if (!StringUtils.equals(cfg.getEndpoint().getToAS().getToken(), token)) {
log.info("Denying request with an invalid HS token");
throw new NotAllowedException("Invalid HS token"); throw new NotAllowedException("Invalid HS token");
} }

View File

@@ -27,7 +27,6 @@ import io.kamax.matrix._MatrixID;
import io.kamax.matrix._ThreePid; import io.kamax.matrix._ThreePid;
import io.kamax.matrix.client.as.MatrixApplicationServiceClient; import io.kamax.matrix.client.as.MatrixApplicationServiceClient;
import io.kamax.matrix.event.EventKey; import io.kamax.matrix.event.EventKey;
import io.kamax.matrix.hs._MatrixRoom;
import io.kamax.mxisd.Mxisd; import io.kamax.mxisd.Mxisd;
import io.kamax.mxisd.backend.sql.synapse.Synapse; import io.kamax.mxisd.backend.sql.synapse.Synapse;
import io.kamax.mxisd.config.MxisdConfig; import io.kamax.mxisd.config.MxisdConfig;
@@ -81,7 +80,7 @@ public class MembershipEventProcessor implements EventTypeProcessor {
_MatrixID target = MatrixID.asAcceptable(targetId); _MatrixID target = MatrixID.asAcceptable(targetId);
if (!StringUtils.equals(target.getDomain(), cfg.getMatrix().getDomain())) { if (!StringUtils.equals(target.getDomain(), cfg.getMatrix().getDomain())) {
log.debug("Ignoring invite for {}: not a local user"); log.debug("Ignoring invite for {}: not a local user", targetId);
return; return;
} }
@@ -89,10 +88,9 @@ public class MembershipEventProcessor implements EventTypeProcessor {
boolean isForMainUser = StringUtils.equals(target.getLocalPart(), cfg.getAppsvc().getUser().getMain()); boolean isForMainUser = StringUtils.equals(target.getLocalPart(), cfg.getAppsvc().getUser().getMain());
boolean isForExpInvUser = StringUtils.equals(target.getLocalPart(), cfg.getAppsvc().getUser().getInviteExpired()); boolean isForExpInvUser = StringUtils.equals(target.getLocalPart(), cfg.getAppsvc().getUser().getInviteExpired());
boolean isUs = isForMainUser || isForExpInvUser;
if (StringUtils.equals("join", EventKey.Membership.getStringOrNull(content))) { if (StringUtils.equals("join", EventKey.Membership.getStringOrNull(content))) {
if (!isForMainUser) { if (isForExpInvUser) {
log.warn("We joined the room {} for another identity as the main user, which is not supported. Leaving...", roomId); log.warn("We joined the room {} for another identity as the main user, which is not supported. Leaving...", roomId);
client.getUser(target.getLocalPart()).getRoom(roomId).tryLeave().ifPresent(err -> { client.getUser(target.getLocalPart()).getRoom(roomId).tryLeave().ifPresent(err -> {
@@ -108,10 +106,7 @@ public class MembershipEventProcessor implements EventTypeProcessor {
processForUserIdInvite(roomId, sender, target); processForUserIdInvite(roomId, sender, target);
} }
} else if (StringUtils.equals("leave", EventKey.Membership.getStringOrNull(content))) { } else if (StringUtils.equals("leave", EventKey.Membership.getStringOrNull(content))) {
_MatrixRoom room = client.getRoom(roomId); // TODO we need to find out if this is only us remaining and leave the room if so, using the right client for it
if (!isUs && room.getJoinedUsers().size() == 1) {
// TODO we need to find out if this is only us remaining and leave the room if so, using the right client for it
}
} else { } else {
log.debug("This is not an supported type of membership event, skipping"); log.debug("This is not an supported type of membership event, skipping");
} }

View File

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

View File

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

View File

@@ -20,7 +20,6 @@
package io.kamax.mxisd.config.threepid.notification; package io.kamax.mxisd.config.threepid.notification;
import com.google.gson.JsonObject;
import io.kamax.matrix.ThreePidMedium; import io.kamax.matrix.ThreePidMedium;
import io.kamax.mxisd.threepid.notification.email.EmailRawNotificationHandler; import io.kamax.mxisd.threepid.notification.email.EmailRawNotificationHandler;
import io.kamax.mxisd.threepid.notification.phone.PhoneNotificationHandler; import io.kamax.mxisd.threepid.notification.phone.PhoneNotificationHandler;
@@ -35,7 +34,7 @@ public class NotificationConfig {
private transient final Logger log = LoggerFactory.getLogger(NotificationConfig.class); private transient final Logger log = LoggerFactory.getLogger(NotificationConfig.class);
private Map<String, String> handler = new HashMap<>(); private Map<String, String> handler = new HashMap<>();
private Map<String, JsonObject> handlers = new HashMap<>(); private Map<String, Object> handlers = new HashMap<>();
public NotificationConfig() { public NotificationConfig() {
handler.put(ThreePidMedium.Email.getId(), EmailRawNotificationHandler.ID); handler.put(ThreePidMedium.Email.getId(), EmailRawNotificationHandler.ID);
@@ -50,11 +49,11 @@ public class NotificationConfig {
this.handler = handler; this.handler = handler;
} }
public Map<String, JsonObject> getHandlers() { public Map<String, Object> getHandlers() {
return handlers; return handlers;
} }
public void setHandlers(Map<String, JsonObject> handlers) { public void setHandlers(Map<String, Object> handlers) {
this.handlers = handlers; this.handlers = handlers;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -111,11 +111,11 @@ public class InvitationManager {
this.notifMgr = notifMgr; this.notifMgr = notifMgr;
this.profileMgr = profileMgr; this.profileMgr = profileMgr;
log.info("Loading saved invites"); log.debug("Loading saved invites");
Collection<ThreePidInviteIO> ioList = storage.getInvites(); Collection<ThreePidInviteIO> ioList = storage.getInvites();
ioList.forEach(io -> { ioList.forEach(io -> {
io.getProperties().putIfAbsent(CreatedAtPropertyKey, defaultCreateTs); io.getProperties().putIfAbsent(CreatedAtPropertyKey, defaultCreateTs);
log.info("Processing invite {}", GsonUtil.get().toJson(io)); log.debug("Processing invite {}", GsonUtil.get().toJson(io));
ThreePidInvite invite = new ThreePidInvite( ThreePidInvite invite = new ThreePidInvite(
MatrixID.asAcceptable(io.getSender()), MatrixID.asAcceptable(io.getSender()),
io.getMedium(), io.getMedium(),
@@ -127,6 +127,7 @@ public class InvitationManager {
ThreePidInviteReply reply = new ThreePidInviteReply(io.getId(), invite, io.getToken(), "", Collections.emptyList()); ThreePidInviteReply reply = new ThreePidInviteReply(io.getId(), invite, io.getToken(), "", Collections.emptyList());
invitations.put(reply.getId(), reply); invitations.put(reply.getId(), reply);
}); });
log.info("Loaded saved invites");
// FIXME export such madness into matrix-java-sdk with a nice wrapper to talk to a homeserver // FIXME export such madness into matrix-java-sdk with a nice wrapper to talk to a homeserver
try { try {
@@ -511,6 +512,9 @@ public class InvitationManager {
publishMapping(reply, lookup.getMxid().getId()); publishMapping(reply, lookup.getMxid().getId());
} else { } else {
log.info("No mapping for pending invite {}", getIdForLog(reply)); log.info("No mapping for pending invite {}", getIdForLog(reply));
if (lookupMgr.getLocalProviders().isEmpty()) {
log.warn("No Identity store has been configured, this invite may never resolve");
}
} }
} catch (Throwable t) { } catch (Throwable t) {
log.error("Unable to process invite", t); log.error("Unable to process invite", t);

View File

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

View File

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

View File

@@ -27,20 +27,19 @@ 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;
import io.kamax.mxisd.threepid.session.ThreePidSession; import io.kamax.mxisd.threepid.session.ThreePidSession;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -56,25 +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;
// FIXME export into central class, set version
private CloseableHttpClient client;
public SessionManager( public SessionManager(
SessionConfig cfg, SessionConfig cfg,
MatrixConfig mxCfg, MatrixConfig mxCfg,
IStorage storage, IStorage storage,
NotificationManager notifMgr, NotificationManager notifMgr
LookupStrategy lookupMgr,
CloseableHttpClient client
) { ) {
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;
this.client = client;
} }
private ThreePidSession getSession(String sid, String secret) { private ThreePidSession getSession(String sid, String secret) {
@@ -128,7 +119,7 @@ public class SessionManager {
log.info("Generated new session {} to validate {} from server {}", sessionId, tpid, server); log.info("Generated new session {} to validate {} from server {}", sessionId, tpid, server);
storage.insertThreePidSession(session.getDao()); storage.insertThreePidSession(session.getDao());
log.info("Stored session {}", sessionId, tpid, server); log.info("Stored session {}", sessionId);
log.info("Session {} for {}: sending validation notification", sessionId, tpid); log.info("Session {} for {}: sending validation notification", sessionId, tpid);
notifMgr.sendForValidation(session); notifMgr.sendForValidation(session);
@@ -157,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");
@@ -171,60 +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");
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());
} }
} }

View File

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

View File

@@ -27,8 +27,9 @@ 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 org.apache.commons.lang.StringUtils; 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 static io.kamax.mxisd.http.io.identity.StoreInviteRequest.Keys.RoomName; import static io.kamax.mxisd.http.io.identity.StoreInviteRequest.Keys.RoomName;
import static io.kamax.mxisd.http.io.identity.StoreInviteRequest.Keys.SenderDisplayName; import static io.kamax.mxisd.http.io.identity.StoreInviteRequest.Keys.SenderDisplayName;
@@ -46,16 +47,26 @@ public abstract class PlaceholderNotificationGenerator {
} }
protected String populateForCommon(ThreePid recipient, String input) { protected String populateForCommon(ThreePid recipient, String input) {
if (StringUtils.isBlank(input)) {
return input;
}
String domainPretty = WordUtils.capitalizeFully(mxCfg.getDomain()); String domainPretty = WordUtils.capitalizeFully(mxCfg.getDomain());
return input return input
.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) {
if (StringUtils.isBlank(input)) {
return input;
}
String senderName = invite.getProperties().getOrDefault(SenderDisplayName, ""); String senderName = invite.getProperties().getOrDefault(SenderDisplayName, "");
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getSender().getId()); String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getSender().getId());
String roomName = invite.getProperties().getOrDefault(RoomName, ""); String roomName = invite.getProperties().getOrDefault(RoomName, "");
@@ -72,6 +83,10 @@ public abstract class PlaceholderNotificationGenerator {
} }
protected String populateForReply(IThreePidInviteReply invite, String input) { protected String populateForReply(IThreePidInviteReply invite, String input) {
if (StringUtils.isBlank(input)) {
return input;
}
ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress()); ThreePid tpid = new ThreePid(invite.getInvite().getMedium(), invite.getInvite().getAddress());
String senderName = invite.getInvite().getProperties().getOrDefault(SenderDisplayName, ""); String senderName = invite.getInvite().getProperties().getOrDefault(SenderDisplayName, "");
@@ -86,13 +101,19 @@ 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);
} }
protected String populateForValidation(IThreePidSession session, String input) { protected String populateForValidation(IThreePidSession session, String input) {
if (StringUtils.isBlank(input)) {
return input;
}
String validationLink = srvCfg.getPublicUrl() + IsAPIv1.getValidate( String validationLink = srvCfg.getPublicUrl() + IsAPIv1.getValidate(
session.getThreePid().getMedium(), session.getThreePid().getMedium(),
session.getId(), session.getId(),

View File

@@ -20,7 +20,7 @@
package io.kamax.mxisd.threepid.notification; package io.kamax.mxisd.threepid.notification;
import com.google.gson.JsonObject; import com.google.gson.JsonSyntaxException;
import io.kamax.matrix.ThreePidMedium; import io.kamax.matrix.ThreePidMedium;
import io.kamax.matrix.json.GsonUtil; import io.kamax.matrix.json.GsonUtil;
import io.kamax.mxisd.Mxisd; import io.kamax.mxisd.Mxisd;
@@ -65,13 +65,18 @@ public class BuiltInNotificationHandlerSupplier implements NotificationHandlerSu
if (StringUtils.equals(EmailRawNotificationHandler.ID, handler)) { if (StringUtils.equals(EmailRawNotificationHandler.ID, handler)) {
Object o = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.Email.getId()); Object o = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.Email.getId());
if (Objects.nonNull(o)) { if (Objects.nonNull(o)) {
EmailConfig emailCfg = GsonUtil.get().fromJson(GsonUtil.makeObj(o), EmailConfig.class); EmailConfig emailCfg;
try {
emailCfg = GsonUtil.get().fromJson(GsonUtil.makeObj(o), EmailConfig.class);
} catch (JsonSyntaxException e) {
throw new ConfigurationException("Invalid configuration for threepid email notification");
}
if (org.apache.commons.lang.StringUtils.isBlank(emailCfg.getGenerator())) { if (StringUtils.isBlank(emailCfg.getGenerator())) {
throw new ConfigurationException("notification.email.generator"); throw new ConfigurationException("notification.email.generator");
} }
if (org.apache.commons.lang.StringUtils.isBlank(emailCfg.getConnector())) { if (StringUtils.isBlank(emailCfg.getConnector())) {
throw new ConfigurationException("notification.email.connector"); throw new ConfigurationException("notification.email.connector");
} }
@@ -94,9 +99,15 @@ public class BuiltInNotificationHandlerSupplier implements NotificationHandlerSu
} }
if (StringUtils.equals(EmailSendGridNotificationHandler.ID, handler)) { if (StringUtils.equals(EmailSendGridNotificationHandler.ID, handler)) {
JsonObject cfgJson = mxisd.getConfig().getNotification().getHandlers().get(EmailSendGridNotificationHandler.ID); Object cfgJson = mxisd.getConfig().getNotification().getHandlers().get(EmailSendGridNotificationHandler.ID);
if (Objects.nonNull(cfgJson)) { if (Objects.nonNull(cfgJson)) {
EmailSendGridConfig cfg = GsonUtil.get().fromJson(cfgJson, EmailSendGridConfig.class); EmailSendGridConfig cfg;
try {
cfg = GsonUtil.get().fromJson(GsonUtil.get().toJson(cfgJson), EmailSendGridConfig.class);
} catch (JsonSyntaxException e) {
throw new ConfigurationException("Invalid configuration for threepid email sendgrid handler");
}
NotificationHandlers.register(() -> new EmailSendGridNotificationHandler(mxisd.getConfig(), cfg)); NotificationHandlers.register(() -> new EmailSendGridNotificationHandler(mxisd.getConfig(), cfg));
} }
} }
@@ -107,7 +118,12 @@ public class BuiltInNotificationHandlerSupplier implements NotificationHandlerSu
if (StringUtils.equals(PhoneNotificationHandler.ID, handler)) { if (StringUtils.equals(PhoneNotificationHandler.ID, handler)) {
Object o = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.PhoneNumber.getId()); Object o = mxisd.getConfig().getThreepid().getMedium().get(ThreePidMedium.PhoneNumber.getId());
if (Objects.nonNull(o)) { if (Objects.nonNull(o)) {
PhoneConfig cfg = GsonUtil.get().fromJson(GsonUtil.makeObj(o), PhoneConfig.class); PhoneConfig cfg;
try {
cfg = GsonUtil.get().fromJson(GsonUtil.makeObj(o), PhoneConfig.class);
} catch (JsonSyntaxException e) {
throw new ConfigurationException("Invalid configuration for threepid msisdn notification");
}
List<PhoneGenerator> generators = StreamSupport List<PhoneGenerator> generators = StreamSupport
.stream(ServiceLoader.load(PhoneGeneratorSupplier.class).spliterator(), false) .stream(ServiceLoader.load(PhoneGeneratorSupplier.class).spliterator(), false)

View File

@@ -33,7 +33,7 @@ import io.kamax.mxisd.notification.NotificationHandler;
import io.kamax.mxisd.threepid.generator.PlaceholderNotificationGenerator; import io.kamax.mxisd.threepid.generator.PlaceholderNotificationGenerator;
import io.kamax.mxisd.threepid.session.IThreePidSession; import io.kamax.mxisd.threepid.session.IThreePidSession;
import io.kamax.mxisd.util.FileUtil; import io.kamax.mxisd.util.FileUtil;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -86,6 +86,9 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
@Override @Override
public void sendForInvite(IMatrixIdInvite invite) { public void sendForInvite(IMatrixIdInvite invite) {
EmailTemplate template = cfg.getTemplates().getGeneric().get("matrixId"); EmailTemplate template = cfg.getTemplates().getGeneric().get("matrixId");
if (StringUtils.isAllBlank(template.getBody().getText(), template.getBody().getHtml())) {
throw new FeatureNotAvailable("No template has been configured for Matrix ID invite notifications");
}
Email email = getEmail(); Email email = getEmail();
email.setSubject(populateForInvite(invite, template.getSubject())); email.setSubject(populateForInvite(invite, template.getSubject()));
@@ -98,6 +101,10 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
@Override @Override
public void sendForReply(IThreePidInviteReply invite) { public void sendForReply(IThreePidInviteReply invite) {
EmailTemplate template = cfg.getTemplates().getInvite(); EmailTemplate template = cfg.getTemplates().getInvite();
if (StringUtils.isAllBlank(template.getBody().getText(), template.getBody().getHtml())) {
throw new FeatureNotAvailable("No template has been configured for 3PID invite notifications");
}
Email email = getEmail(); Email email = getEmail();
email.setSubject(populateForReply(invite, template.getSubject())); email.setSubject(populateForReply(invite, template.getSubject()));
email.setText(populateForReply(invite, getFromFile(template.getBody().getText()))); email.setText(populateForReply(invite, getFromFile(template.getBody().getText())));
@@ -109,6 +116,10 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
@Override @Override
public void sendForValidation(IThreePidSession session) { public void sendForValidation(IThreePidSession session) {
EmailTemplate template = cfg.getTemplates().getSession().getValidation(); EmailTemplate template = cfg.getTemplates().getSession().getValidation();
if (StringUtils.isAllBlank(template.getBody().getText(), template.getBody().getHtml())) {
throw new FeatureNotAvailable("No template has been configured for validation notifications");
}
Email email = getEmail(); Email email = getEmail();
email.setSubject(populateForValidation(session, template.getSubject())); email.setSubject(populateForValidation(session, template.getSubject()));
email.setText(populateForValidation(session, getFromFile(template.getBody().getText()))); email.setText(populateForValidation(session, getFromFile(template.getBody().getText())));
@@ -120,6 +131,10 @@ public class EmailSendGridNotificationHandler extends PlaceholderNotificationGen
@Override @Override
public void sendForFraudulentUnbind(ThreePid tpid) { public void sendForFraudulentUnbind(ThreePid tpid) {
EmailTemplate template = cfg.getTemplates().getSession().getUnbind().getFraudulent(); EmailTemplate template = cfg.getTemplates().getSession().getUnbind().getFraudulent();
if (StringUtils.isAllBlank(template.getBody().getText(), template.getBody().getHtml())) {
throw new FeatureNotAvailable("No template has been configured for fraudulent unbind notifications");
}
Email email = getEmail(); Email email = getEmail();
email.setSubject(populateForCommon(tpid, template.getSubject())); email.setSubject(populateForCommon(tpid, template.getSubject()));
email.setText(populateForCommon(tpid, getFromFile(template.getBody().getText()))); email.setText(populateForCommon(tpid, getFromFile(template.getBody().getText())));

View File

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

View File

@@ -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
@@ -39,26 +32,26 @@ Content-Disposition: inline
<html lang="en"> <html lang="en">
<head> <head>
<style type="text/css"> <style type="text/css">
body { body {
margin: 0px; margin: 0px;
} }
pre, code { pre, code {
word-break: break-word; word-break: break-word;
white-space: pre-wrap; white-space: pre-wrap;
} }
#page { #page {
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif; font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
font-color: #454545; font-color: #454545;
font-size: 12pt; font-size: 12pt;
width: 100%%; width: 100%%;
padding: 20px; padding: 20px;
} }
#inner { #inner {
width: 640px; width: 640px;
} }
</style> </style>
</head> </head>
<body> <body>
@@ -66,24 +59,17 @@ pre, code {
<tr> <tr>
<td> </td> <td> </td>
<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>Thanks,</p>
<p>About Matrix:</p>
<p>Matrix is an open standard for interoperable, decentralised, real-time communication <p>%DOMAIN_PRETTY% Admins</p>
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>%DOMAIN_PRETTY% Admins</p>
</td> </td>
<td> </td> <td> </td>
</tr> </tr>

View File

@@ -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,
@@ -28,26 +28,26 @@ Content-Disposition: inline
<html lang="en"> <html lang="en">
<head> <head>
<style type="text/css"> <style type="text/css">
body { body {
margin: 0px; margin: 0px;
} }
pre, code { pre, code {
word-break: break-word; word-break: break-word;
white-space: pre-wrap; white-space: pre-wrap;
} }
#page { #page {
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif; font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
font-color: #454545; font-color: #454545;
font-size: 12pt; font-size: 12pt;
width: 100%%; width: 100%%;
padding: 20px; padding: 20px;
} }
#inner { #inner {
width: 640px; width: 640px;
} }
</style> </style>
</head> </head>
<body> <body>
@@ -55,13 +55,13 @@ pre, code {
<tr> <tr>
<td> </td> <td> </td>
<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>
<p>%DOMAIN_PRETTY% Admins</p> <p>%DOMAIN_PRETTY% Admins</p>
</td> </td>
<td> </td> <td> </td>
</tr> </tr>

View File

@@ -61,26 +61,26 @@ Content-Disposition: inline
<html lang="en"> <html lang="en">
<head> <head>
<style type="text/css"> <style type="text/css">
body { body {
margin: 0px; margin: 0px;
} }
pre, code { pre, code {
word-break: break-word; word-break: break-word;
white-space: pre-wrap; white-space: pre-wrap;
} }
#page { #page {
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif; font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
font-color: #454545; font-color: #454545;
font-size: 12pt; font-size: 12pt;
width: 100%%; width: 100%%;
padding: 20px; padding: 20px;
} }
#inner { #inner {
width: 640px; width: 640px;
} }
</style> </style>
</head> </head>
<body> <body>
@@ -88,42 +88,42 @@ pre, code {
<tr> <tr>
<td> </td> <td> </td>
<td id="inner"> <td id="inner">
<p>Hi,</p> <p>Hi,</p>
<p><b>THIS IS IMPORTANT, PLEASE READ CAREFULLY</b>.<br/> <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> 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 <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> 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>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> <p>If you do not understand this email, please forward it to your System administrator.</p>
<hr> <hr>
<p>As the system administrator:</p> <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> <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 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> 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>) (<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> 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 <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/> 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> 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 <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 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> the attack and take relevant actions following your policy.</p>
<p>If you would like to disable these notifications, please see the <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> <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>Thanks,</p>
<p>%DOMAIN_PRETTY% Admins</p> <p>%DOMAIN_PRETTY% Admins</p>
</td> </td>
<td> </td> <td> </td>
</tr> </tr>

View File

@@ -33,30 +33,30 @@ Content-Disposition: inline
<html lang="en"> <html lang="en">
<head> <head>
<style type="text/css"> <style type="text/css">
body { body {
margin: 0px; margin: 0px;
} }
pre, code { pre, code {
word-break: break-word; word-break: break-word;
white-space: pre-wrap; white-space: pre-wrap;
} }
#page { #page {
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif; font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
font-color: #454545; font-color: #454545;
font-size: 12pt; font-size: 12pt;
width: 100%%; width: 100%%;
padding: 20px; padding: 20px;
} }
#inner { #inner {
width: 640px; width: 640px;
} }
.notif_link a, .footer a { .notif_link a, .footer a {
color: #76CFA6 ! important; color: #76CFA6 ! important;
} }
</style> </style>
</head> </head>
<body> <body>

View File

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

View File

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

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

View File

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

View File

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