5
.gitignore
vendored
5
.gitignore
vendored
@@ -7,4 +7,7 @@ out/
|
||||
.idea/
|
||||
|
||||
# Local dev config
|
||||
application.yaml
|
||||
/application.yaml
|
||||
|
||||
# Local dev storage
|
||||
/mxisd.db
|
||||
|
@@ -12,6 +12,12 @@ server:
|
||||
# e.g. domain name in e-mails.
|
||||
name: 'example.org'
|
||||
|
||||
# Public URL to reach this identity server
|
||||
#
|
||||
# This is used with 3PID invites in room and other Homeserver key verification workflow.
|
||||
# If left unconfigured, it will be generated from the server name
|
||||
# publicUrl: 'https://example.org'
|
||||
|
||||
|
||||
|
||||
key:
|
||||
@@ -125,7 +131,7 @@ ldap:
|
||||
baseDn: 'CN=Users,DC=example,DC=org'
|
||||
|
||||
# How to map Matrix attributes with LDAP attributes when performing lookup/auth
|
||||
attributes:
|
||||
attribute:
|
||||
|
||||
# The username/login that will be looked up or used to build Matrix IDs
|
||||
uid:
|
||||
@@ -190,3 +196,86 @@ forward:
|
||||
servers:
|
||||
- "https://matrix.org"
|
||||
- "https://vector.im"
|
||||
|
||||
|
||||
|
||||
# Configure the invite components
|
||||
invite:
|
||||
|
||||
# Configure invite senders for the various 3PID type
|
||||
sender:
|
||||
|
||||
# E-mail invite sender
|
||||
email:
|
||||
|
||||
# SMTP host
|
||||
host: "smtp.example.org"
|
||||
|
||||
# SMTP port
|
||||
port: 587
|
||||
|
||||
# TLS mode for the connection.
|
||||
#
|
||||
# Possible values:
|
||||
# 0 Disable TLS entirely
|
||||
# 1 Enable TLS if supported by server
|
||||
# 2 Force TLS and fail if not available
|
||||
tls: 1
|
||||
|
||||
# Login for SMTP
|
||||
login: "matrix-identity@example.org"
|
||||
|
||||
# Password for the account
|
||||
password: "ThePassword"
|
||||
|
||||
# The e-mail to send as. If empty, will be the same as login
|
||||
email: "matrix-identity@example.org"
|
||||
|
||||
# The display name used in the e-mail
|
||||
name: "Matrix Identity"
|
||||
|
||||
# The E-mail template to use.
|
||||
#
|
||||
# The template is expected to be a full e-mail body, including client headers, using MIME and UTF-8 encoding.
|
||||
# The following headers will be set by mxisd directly and should not be present in the template:
|
||||
# - From
|
||||
# - To
|
||||
# - Date
|
||||
# - Message-Id
|
||||
# - X-Mailer
|
||||
#
|
||||
# The following placeholders are available:
|
||||
# - %DOMAIN% Domain name as per server.name config item
|
||||
# - %DOMAIN_PRETTY% Word capitalize version of the domain. e.g. example.org -> Example.org
|
||||
# - %FROM_EMAIL% Value of this section's email config item
|
||||
# - %FROM_NAME% Value of this section's name config item
|
||||
# - %SENDER_ID% Matrix ID of the invitation sender
|
||||
# - %SENDER_NAME% Display name of the invitation sender, empty if not available
|
||||
# - %SENDER_NAME_OR_ID% Value of %SENDER_NAME% or, if empty, value of %SENDER_ID%
|
||||
# - %INVITE_MEDIUM% Medium of the invite (e.g. email, msisdn)
|
||||
# - %INVITE_ADDRESS% Address used to invite
|
||||
# - %ROOM_ID% ID of the room where the invitation took place
|
||||
# - %ROOM_NAME% Name of the room, empty if not available
|
||||
# - %ROOM_NAME_OR_ID% Value of %ROOM_NAME% or, if empty, value of %ROOM_ID%
|
||||
template: "/absolute/path/to/file"
|
||||
|
||||
|
||||
|
||||
# Configure persistence settings
|
||||
storage:
|
||||
|
||||
# Configure the storage backend, usually a DB
|
||||
# Possible built-in values:
|
||||
# sqlite SQLite backend, default
|
||||
#
|
||||
#backend: 'sqlite'
|
||||
|
||||
# Specific configuration for each provider, refer to their documentation for specifics.
|
||||
provider:
|
||||
|
||||
# Generic SQLite provider config
|
||||
sqlite:
|
||||
|
||||
# Path to the SQLite DB file, required
|
||||
#
|
||||
#database:'%SQLITE_DATABASE_PATH%'
|
||||
|
27
build.gradle
27
build.gradle
@@ -97,9 +97,19 @@ dependencies {
|
||||
// Phone numbers validation
|
||||
compile 'com.googlecode.libphonenumber:libphonenumber:8.7.1'
|
||||
|
||||
// E-mail sending
|
||||
compile 'com.sun.mail:javax.mail:1.5.6'
|
||||
compile 'javax.mail:javax.mail-api:1.5.6'
|
||||
|
||||
// Google Firebase Authentication backend
|
||||
compile 'com.google.firebase:firebase-admin:5.3.0'
|
||||
|
||||
// ORMLite
|
||||
compile 'com.j256.ormlite:ormlite-jdbc:5.0'
|
||||
|
||||
// SQLite
|
||||
compile 'org.xerial:sqlite-jdbc:3.20.0'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
||||
@@ -111,6 +121,17 @@ springBoot {
|
||||
]
|
||||
}
|
||||
|
||||
processResources {
|
||||
doLast {
|
||||
copy {
|
||||
from('build/resources/main/application.yaml') {
|
||||
rename 'application.yaml', 'mxisd.yaml'
|
||||
}
|
||||
into 'build/resources/main'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task buildDeb(dependsOn: build) {
|
||||
doLast {
|
||||
def v = gitVersion()
|
||||
@@ -146,6 +167,12 @@ task buildDeb(dependsOn: build) {
|
||||
value: "${debDataPath}/signing.key"
|
||||
)
|
||||
|
||||
ant.replaceregexp(
|
||||
file: "${debBuildConfPath}/${debConfFileName}",
|
||||
match: "#?database:\\s*'%SQLITE_DATABASE_PATH%'",
|
||||
replace: "database: '${debDataPath}/mxisd.db'"
|
||||
)
|
||||
|
||||
copy {
|
||||
from project.file('src/debian')
|
||||
into debBuildDebianPath
|
||||
|
BIN
media/mx-id-icon-reverse.png
Normal file
BIN
media/mx-id-icon-reverse.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
BIN
media/mx-id-icon.png
Normal file
BIN
media/mx-id-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
@@ -1,7 +0,0 @@
|
||||
package io.kamax.mxisd;
|
||||
|
||||
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||
|
||||
public interface GlobalProvider extends AuthenticatorProvider, IThreePidProvider {
|
||||
}
|
47
src/main/groovy/io/kamax/mxisd/ThreePid.java
Normal file
47
src/main/groovy/io/kamax/mxisd/ThreePid.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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;
|
||||
|
||||
// FIXME this should be in matrix-java-sdk
|
||||
public class ThreePid {
|
||||
|
||||
private String medium;
|
||||
private String address;
|
||||
|
||||
public ThreePid(String medium, String address) {
|
||||
this.medium = medium;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getMedium() {
|
||||
return medium;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getMedium() + ":" + getAddress();
|
||||
}
|
||||
|
||||
}
|
@@ -1,6 +1,29 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.auth;
|
||||
|
||||
import io.kamax.mxisd.ThreePid;
|
||||
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -17,6 +40,9 @@ public class AuthManager {
|
||||
@Autowired
|
||||
private List<AuthenticatorProvider> providers = new ArrayList<>();
|
||||
|
||||
@Autowired
|
||||
private InvitationManager invMgr;
|
||||
|
||||
public UserAuthResult authenticate(String id, String password) {
|
||||
for (AuthenticatorProvider provider : providers) {
|
||||
if (!provider.isEnabled()) {
|
||||
@@ -25,6 +51,14 @@ public class AuthManager {
|
||||
|
||||
UserAuthResult result = provider.authenticate(id, password);
|
||||
if (result.isSuccess()) {
|
||||
log.info("{} was authenticated by {}, publishing 3PID mappings, if any", id, provider.getClass().getSimpleName());
|
||||
for (ThreePid pid : result.getThreePids()) {
|
||||
log.info("Processing {} for {}", pid, id);
|
||||
invMgr.publishMappingIfInvited(new ThreePidMapping(pid, result.getMxid()));
|
||||
}
|
||||
|
||||
invMgr.lookupMappingsForInvites();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,38 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.auth;
|
||||
|
||||
import io.kamax.matrix.ThreePidMedium;
|
||||
import io.kamax.mxisd.ThreePid;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class UserAuthResult {
|
||||
|
||||
private boolean success;
|
||||
private String mxid;
|
||||
private String displayName;
|
||||
private List<ThreePid> threePids = new ArrayList<>();
|
||||
|
||||
public UserAuthResult failure() {
|
||||
success = false;
|
||||
@@ -46,4 +74,18 @@ public class UserAuthResult {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public UserAuthResult withThreePid(ThreePidMedium medium, String address) {
|
||||
return withThreePid(medium.getId(), address);
|
||||
}
|
||||
|
||||
public UserAuthResult withThreePid(String medium, String address) {
|
||||
threePids.add(new ThreePid(medium, address));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<ThreePid> getThreePids() {
|
||||
return Collections.unmodifiableList(threePids);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.auth.provider;
|
||||
|
||||
import io.kamax.mxisd.auth.UserAuthResult;
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.auth.provider
|
||||
|
||||
import com.google.firebase.FirebaseApp
|
||||
@@ -7,31 +27,36 @@ import com.google.firebase.internal.NonNull
|
||||
import com.google.firebase.tasks.OnFailureListener
|
||||
import com.google.firebase.tasks.OnSuccessListener
|
||||
import io.kamax.matrix.ThreePidMedium
|
||||
import io.kamax.mxisd.GlobalProvider
|
||||
import io.kamax.mxisd.auth.UserAuthResult
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.Consumer
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
public class GoogleFirebaseAuthenticator implements GlobalProvider {
|
||||
public class GoogleFirebaseAuthenticator implements AuthenticatorProvider {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(GoogleFirebaseAuthenticator.class);
|
||||
|
||||
private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)");
|
||||
private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)"); // FIXME use matrix-java-sdk
|
||||
|
||||
private boolean isEnabled;
|
||||
private String domain;
|
||||
private FirebaseApp fbApp;
|
||||
private FirebaseAuth fbAuth;
|
||||
|
||||
private void waitOnLatch(UserAuthResult result, CountDownLatch l, long timeout, TimeUnit unit, String purpose) {
|
||||
try {
|
||||
l.await(timeout, unit);
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("Interrupted while waiting for " + purpose);
|
||||
result.failure();
|
||||
}
|
||||
}
|
||||
|
||||
public GoogleFirebaseAuthenticator(boolean isEnabled) {
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
@@ -40,7 +65,7 @@ public class GoogleFirebaseAuthenticator implements GlobalProvider {
|
||||
this(true);
|
||||
this.domain = domain;
|
||||
try {
|
||||
fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db));
|
||||
fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "AuthenticationProvider");
|
||||
fbAuth = FirebaseAuth.getInstance(fbApp);
|
||||
|
||||
log.info("Google Firebase Authentication is ready");
|
||||
@@ -73,16 +98,6 @@ public class GoogleFirebaseAuthenticator implements GlobalProvider {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 25;
|
||||
}
|
||||
|
||||
private void waitOnLatch(CountDownLatch l) {
|
||||
try {
|
||||
l.await(30, TimeUnit.SECONDS);
|
||||
@@ -91,84 +106,6 @@ public class GoogleFirebaseAuthenticator implements GlobalProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<UserRecord> findInternal(String medium, String address) {
|
||||
UserRecord r;
|
||||
CountDownLatch l = new CountDownLatch(1);
|
||||
|
||||
OnSuccessListener<UserRecord> success = new OnSuccessListener<UserRecord>() {
|
||||
@Override
|
||||
void onSuccess(UserRecord result) {
|
||||
log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid())
|
||||
r = result;
|
||||
l.countDown()
|
||||
}
|
||||
};
|
||||
|
||||
OnFailureListener failure = new OnFailureListener() {
|
||||
@Override
|
||||
void onFailure(@NonNull Exception e) {
|
||||
log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage())
|
||||
r = null;
|
||||
l.countDown()
|
||||
}
|
||||
};
|
||||
|
||||
if (ThreePidMedium.Email.is(medium)) {
|
||||
log.info("Performing E-mail 3PID lookup for {}", address)
|
||||
fbAuth.getUserByEmail(address)
|
||||
.addOnSuccessListener(success)
|
||||
.addOnFailureListener(failure);
|
||||
waitOnLatch(l);
|
||||
} else if (ThreePidMedium.PhoneNumber.is(medium)) {
|
||||
log.info("Performing msisdn 3PID lookup for {}", address)
|
||||
fbAuth.getUserByPhoneNumber(address)
|
||||
.addOnSuccessListener(success)
|
||||
.addOnFailureListener(failure);
|
||||
waitOnLatch(l);
|
||||
} else {
|
||||
log.info("{} is not a supported 3PID medium", medium);
|
||||
r = null;
|
||||
}
|
||||
|
||||
return Optional.ofNullable(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<?> find(SingleLookupRequest request) {
|
||||
Optional<UserRecord> urOpt = findInternal(request.getType(), request.getThreePid())
|
||||
if (urOpt.isPresent()) {
|
||||
return [
|
||||
address : request.getThreePid(),
|
||||
medium : request.getType(),
|
||||
mxid : "@${urOpt.get().getUid()}:${domain}",
|
||||
not_before: 0,
|
||||
not_after : 9223372036854775807,
|
||||
ts : 0
|
||||
]
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
||||
List<ThreePidMapping> results = new ArrayList<>();
|
||||
mappings.parallelStream().forEach(new Consumer<ThreePidMapping>() {
|
||||
@Override
|
||||
void accept(ThreePidMapping o) {
|
||||
Optional<UserRecord> urOpt = findInternal(o.getMedium(), o.getValue());
|
||||
if (urOpt.isPresent()) {
|
||||
ThreePidMapping result = new ThreePidMapping();
|
||||
result.setMedium(o.getMedium())
|
||||
result.setValue(o.getValue())
|
||||
result.setMxid("@${urOpt.get().getUid()}:${domain}")
|
||||
results.add(result)
|
||||
}
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserAuthResult authenticate(String id, String password) {
|
||||
if (!isEnabled()) {
|
||||
@@ -190,37 +127,69 @@ public class GoogleFirebaseAuthenticator implements GlobalProvider {
|
||||
fbAuth.verifyIdToken(password).addOnSuccessListener(new OnSuccessListener<FirebaseToken>() {
|
||||
@Override
|
||||
void onSuccess(FirebaseToken token) {
|
||||
if (!StringUtils.equals(localpart, token.getUid())) {
|
||||
log.info("Failture to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", id, localpart, token.getUid());
|
||||
result.failure();
|
||||
}
|
||||
try {
|
||||
if (!StringUtils.equals(localpart, token.getUid())) {
|
||||
log.info("Failture to authenticate {}: Matrix ID localpart '{}' does not match Firebase UID '{}'", id, localpart, token.getUid());
|
||||
result.failure();
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("{} was successfully authenticated", id);
|
||||
result.success(id, token.getName());
|
||||
l.countDown()
|
||||
log.info("{} was successfully authenticated", id);
|
||||
result.success(id, token.getName());
|
||||
|
||||
log.info("Fetching profile for {}", id);
|
||||
CountDownLatch userRecordLatch = new CountDownLatch(1);
|
||||
fbAuth.getUser(token.getUid()).addOnSuccessListener(new OnSuccessListener<UserRecord>() {
|
||||
@Override
|
||||
void onSuccess(UserRecord user) {
|
||||
try {
|
||||
if (StringUtils.isNotBlank(user.getEmail())) {
|
||||
result.withThreePid(ThreePidMedium.Email, user.getEmail());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(user.getPhoneNumber())) {
|
||||
result.withThreePid(ThreePidMedium.PhoneNumber, user.getPhoneNumber());
|
||||
}
|
||||
} finally {
|
||||
userRecordLatch.countDown();
|
||||
}
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
void onFailure(@NonNull Exception e) {
|
||||
try {
|
||||
log.warn("Unable to fetch Firebase user profile for {}", id);
|
||||
result.failure();
|
||||
} finally {
|
||||
userRecordLatch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
waitOnLatch(result, userRecordLatch, 30, TimeUnit.SECONDS, "Firebase user profile");
|
||||
} finally {
|
||||
l.countDown()
|
||||
}
|
||||
}
|
||||
}).addOnFailureListener(new OnFailureListener() {
|
||||
@Override
|
||||
void onFailure(@NonNull Exception e) {
|
||||
if (e instanceof IllegalArgumentException) {
|
||||
log.info("Failure to authenticate {}: invalid firebase token", id);
|
||||
} else {
|
||||
log.info("Failure to authenticate {}: {}", id, e.getMessage(), e);
|
||||
log.info("Exception", e);
|
||||
}
|
||||
try {
|
||||
if (e instanceof IllegalArgumentException) {
|
||||
log.info("Failure to authenticate {}: invalid firebase token", id);
|
||||
} else {
|
||||
log.info("Failure to authenticate {}: {}", id, e.getMessage(), e);
|
||||
log.info("Exception", e);
|
||||
}
|
||||
|
||||
result.failure();
|
||||
l.countDown()
|
||||
result.failure();
|
||||
} finally {
|
||||
l.countDown()
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
l.await(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("Interrupted while waiting for Firebase auth check");
|
||||
result.failure();
|
||||
}
|
||||
|
||||
waitOnLatch(result, l, 30, TimeUnit.SECONDS, "Firebase auth check");
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.auth.provider;
|
||||
|
||||
import io.kamax.matrix.MatrixID;
|
||||
|
@@ -1,7 +1,29 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.config;
|
||||
|
||||
import io.kamax.mxisd.GlobalProvider;
|
||||
import io.kamax.mxisd.auth.provider.AuthenticatorProvider;
|
||||
import io.kamax.mxisd.auth.provider.GoogleFirebaseAuthenticator;
|
||||
import io.kamax.mxisd.lookup.provider.GoogleFirebaseProvider;
|
||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -56,16 +78,24 @@ public class FirebaseConfig {
|
||||
log.info("Credentials: {}", getCredentials());
|
||||
log.info("Database: {}", getDatabase());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GlobalProvider getProvider() {
|
||||
public AuthenticatorProvider getAuthProvider() {
|
||||
if (!enabled) {
|
||||
return new GoogleFirebaseAuthenticator(false);
|
||||
} else {
|
||||
return new GoogleFirebaseAuthenticator(credentials, database, srvCfg.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IThreePidProvider getLookupProvider() {
|
||||
if (!enabled) {
|
||||
return new GoogleFirebaseProvider(false);
|
||||
} else {
|
||||
return new GoogleFirebaseProvider(credentials, database, srvCfg.getName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.config
|
||||
|
||||
import org.slf4j.Logger
|
||||
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.config;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties("storage.provider.sqlite")
|
||||
public class SQLiteStorageConfig {
|
||||
|
||||
private String database;
|
||||
|
||||
public String getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
public void setDatabase(String database) {
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
}
|
@@ -22,6 +22,8 @@ package io.kamax.mxisd.config
|
||||
|
||||
import io.kamax.mxisd.exception.ConfigurationException
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.InitializingBean
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
@@ -30,7 +32,11 @@ import org.springframework.context.annotation.Configuration
|
||||
@ConfigurationProperties(prefix = "server")
|
||||
class ServerConfig implements InitializingBean {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(ServerConfig.class);
|
||||
|
||||
private String name
|
||||
private int port
|
||||
private String publicUrl
|
||||
|
||||
String getName() {
|
||||
return name
|
||||
@@ -40,11 +46,43 @@ class ServerConfig implements InitializingBean {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
int getPort() {
|
||||
return port
|
||||
}
|
||||
|
||||
void setPort(int port) {
|
||||
this.port = port
|
||||
}
|
||||
|
||||
String getPublicUrl() {
|
||||
return publicUrl
|
||||
}
|
||||
|
||||
void setPublicUrl(String publicUrl) {
|
||||
this.publicUrl = publicUrl
|
||||
}
|
||||
|
||||
@Override
|
||||
void afterPropertiesSet() throws Exception {
|
||||
if (StringUtils.isBlank(getName())) {
|
||||
throw new ConfigurationException("server.name")
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(getPublicUrl())) {
|
||||
log.warn("Public URL is empty, generating from name {}", getName())
|
||||
publicUrl = "https://${getName()}"
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(getPublicUrl())
|
||||
} catch (MalformedURLException e) {
|
||||
log.warn("Public URL is not valid: {}", StringUtils.defaultIfBlank(e.getMessage(), "<no reason provided>"))
|
||||
}
|
||||
|
||||
log.info("--- Server config ---")
|
||||
log.info("Name: {}", getName())
|
||||
log.info("Port: {}", getPort())
|
||||
log.info("Public URL: {}", getPublicUrl())
|
||||
}
|
||||
|
||||
}
|
||||
|
51
src/main/groovy/io/kamax/mxisd/config/StorageConfig.java
Normal file
51
src/main/groovy/io/kamax/mxisd/config/StorageConfig.java
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.config;
|
||||
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties("storage")
|
||||
public class StorageConfig {
|
||||
|
||||
private String backend;
|
||||
|
||||
public String getBackend() {
|
||||
return backend;
|
||||
}
|
||||
|
||||
public void setBackend(String backend) {
|
||||
this.backend = backend;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
if (StringUtils.isBlank(getBackend())) {
|
||||
throw new ConfigurationException("storage.backend");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.config.invite.sender;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.io.File;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "invite.sender.email")
|
||||
public class EmailSenderConfig {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(EmailSenderConfig.class);
|
||||
|
||||
private String host;
|
||||
private int port;
|
||||
private int tls;
|
||||
private String login;
|
||||
private String password;
|
||||
private String email;
|
||||
private String name;
|
||||
private String template;
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public int getTls() {
|
||||
return tls;
|
||||
}
|
||||
|
||||
public void setTls(int tls) {
|
||||
this.tls = tls;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public void setLogin(String login) {
|
||||
this.login = login;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getTemplate() {
|
||||
return template;
|
||||
}
|
||||
|
||||
public void setTemplate(String template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
log.info("--- E-mail Invite Sender config ---");
|
||||
log.info("Host: {}", getHost());
|
||||
log.info("Port: {}", getPort());
|
||||
log.info("TLS Mode: {}", getTls());
|
||||
log.info("Login: {}", getLogin());
|
||||
log.info("Has password: {}", StringUtils.isBlank(getPassword()));
|
||||
log.info("E-mail: {}", getEmail());
|
||||
if (!StringUtils.startsWith(getTemplate(), "classpath:")) {
|
||||
if (StringUtils.isBlank(getTemplate())) {
|
||||
log.warn("invite.sender.template is empty! Will not send invites");
|
||||
} else {
|
||||
File cp = new File(getTemplate()).getAbsoluteFile();
|
||||
log.info("Template: {}", cp.getAbsolutePath());
|
||||
if (!cp.exists() || !cp.isFile() || !cp.canRead()) {
|
||||
log.warn(getTemplate() + " does not exist, is not a file or cannot be read");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.info("Template: Built-in");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.config.ldap;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
@@ -1,12 +1,28 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.config.ldap;
|
||||
|
||||
import io.kamax.mxisd.lookup.provider.LdapProvider;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "ldap.attribute.uid")
|
||||
public class LdapAttributeUidConfig {
|
||||
@@ -30,11 +46,4 @@ public class LdapAttributeUidConfig {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
if (!StringUtils.equals(LdapProvider.UID, getType()) && !StringUtils.equals(LdapProvider.MATRIX_ID, getType())) {
|
||||
throw new IllegalArgumentException("Unsupported LDAP UID type: " + getType());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.config.ldap;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
@@ -21,6 +21,7 @@
|
||||
package io.kamax.mxisd.config.ldap
|
||||
|
||||
import groovy.json.JsonOutput
|
||||
import io.kamax.mxisd.lookup.provider.LdapProvider
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -110,8 +111,11 @@ class LdapConfig {
|
||||
throw new IllegalStateException("Attribute UID value cannot be empty")
|
||||
}
|
||||
|
||||
String uidType = attribute.getUid().getType();
|
||||
if (!StringUtils.equals(LdapProvider.UID, uidType) && !StringUtils.equals(LdapProvider.MATRIX_ID, uidType)) {
|
||||
throw new IllegalArgumentException("Unsupported LDAP UID type: " + uidType)
|
||||
}
|
||||
|
||||
log.info("Conn: {}", JsonOutput.toJson(conn))
|
||||
log.info("Host: {}", conn.getHost())
|
||||
log.info("Port: {}", conn.getPort())
|
||||
log.info("Bind DN: {}", conn.getBindDn())
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.config.ldap;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.config.ldap;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.controller.v1;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@ControllerAdvice
|
||||
@ResponseBody
|
||||
@RequestMapping(produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public class DefaultExceptionHandler {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(DefaultExceptionHandler.class);
|
||||
|
||||
private static Gson gson = new Gson();
|
||||
|
||||
static String handle(String erroCode, String error) {
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("errcode", erroCode);
|
||||
obj.addProperty("error", error);
|
||||
return gson.toJson(obj);
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public String handle(MissingServletRequestParameterException e) {
|
||||
return handle("M_INVALID_BODY", e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(MappingAlreadyExistsException.class)
|
||||
public String handle(MappingAlreadyExistsException e) {
|
||||
return handle("M_ALREADY_EXISTS", e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(BadRequestException.class)
|
||||
public String handle(BadRequestException e) {
|
||||
return handle("M_BAD_REQUEST", e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public String handle(HttpServletRequest req, RuntimeException e) {
|
||||
log.error("Unknown error when handling {}", req.getRequestURL(), e);
|
||||
return handle("M_UNKNOWN", StringUtils.defaultIfBlank(e.getMessage(), "An uknown error occured. Contact the server administrator if this persists."));
|
||||
}
|
||||
|
||||
}
|
@@ -20,10 +20,22 @@
|
||||
|
||||
package io.kamax.mxisd.controller.v1
|
||||
|
||||
import io.kamax.mxisd.exception.NotImplementedException
|
||||
import com.google.gson.Gson
|
||||
import io.kamax.matrix.MatrixID
|
||||
import io.kamax.mxisd.config.ServerConfig
|
||||
import io.kamax.mxisd.controller.v1.io.ThreePidInviteReplyIO
|
||||
import io.kamax.mxisd.invitation.IThreePidInvite
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply
|
||||
import io.kamax.mxisd.invitation.InvitationManager
|
||||
import io.kamax.mxisd.invitation.ThreePidInvite
|
||||
import io.kamax.mxisd.key.KeyManager
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.bind.annotation.CrossOrigin
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
@@ -31,15 +43,38 @@ import javax.servlet.http.HttpServletRequest
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.POST
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
class InvitationController {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(InvitationController.class)
|
||||
|
||||
@RequestMapping(value = "/_matrix/identity/api/v1/store-invite", method = POST)
|
||||
String store(HttpServletRequest request) {
|
||||
log.error("{} was requested but not implemented", request.getRequestURL())
|
||||
@Autowired
|
||||
private InvitationManager mgr
|
||||
|
||||
throw new NotImplementedException()
|
||||
@Autowired
|
||||
private KeyManager keyMgr
|
||||
|
||||
@Autowired
|
||||
private ServerConfig srvCfg
|
||||
|
||||
private Gson gson = new Gson()
|
||||
|
||||
@RequestMapping(value = "/store-invite", method = POST)
|
||||
String store(
|
||||
HttpServletRequest request,
|
||||
@RequestParam String sender,
|
||||
@RequestParam String medium,
|
||||
@RequestParam String address,
|
||||
@RequestParam("room_id") String roomId) {
|
||||
Map<String, String> parameters = new HashMap<>()
|
||||
for (String key : request.getParameterMap().keySet()) {
|
||||
parameters.put(key, request.getParameter(key));
|
||||
}
|
||||
IThreePidInvite invite = new ThreePidInvite(new MatrixID(sender), medium, address, roomId, parameters)
|
||||
IThreePidInviteReply reply = mgr.storeInvite(invite)
|
||||
|
||||
return gson.toJson(new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), srvCfg.getPublicUrl()))
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -24,18 +24,20 @@ import groovy.json.JsonOutput
|
||||
import io.kamax.mxisd.exception.BadRequestException
|
||||
import io.kamax.mxisd.exception.NotImplementedException
|
||||
import io.kamax.mxisd.key.KeyManager
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.GET
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
class KeyController {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(KeyController.class)
|
||||
@@ -43,29 +45,34 @@ class KeyController {
|
||||
@Autowired
|
||||
private KeyManager keyMgr
|
||||
|
||||
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/{keyType}:{keyId}", method = GET)
|
||||
@RequestMapping(value = "/pubkey/{keyType}:{keyId}", method = GET)
|
||||
String getKey(@PathVariable String keyType, @PathVariable int keyId) {
|
||||
if (!"ed25519".contentEquals(keyType)) {
|
||||
throw new BadRequestException("Invalid algorithm: " + keyType)
|
||||
}
|
||||
|
||||
log.info("Key {}:{} was requested", keyType, keyId)
|
||||
return JsonOutput.toJson([
|
||||
public_key: keyMgr.getPublicKeyBase64(keyId)
|
||||
])
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/ephemeral/isvalid", method = GET)
|
||||
@RequestMapping(value = "/pubkey/ephemeral/isvalid", method = GET)
|
||||
String checkEphemeralKeyValidity(HttpServletRequest request) {
|
||||
log.error("{} was requested but not implemented", request.getRequestURL())
|
||||
|
||||
throw new NotImplementedException()
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/isvalid", method = GET)
|
||||
String checkKeyValidity(HttpServletRequest request) {
|
||||
log.error("{} was requested but not implemented", request.getRequestURL())
|
||||
@RequestMapping(value = "/pubkey/isvalid", method = GET)
|
||||
String checkKeyValidity(HttpServletRequest request, @RequestParam("public_key") String pubKey) {
|
||||
log.info("Validating public key {}", pubKey)
|
||||
|
||||
throw new NotImplementedException()
|
||||
// TODO do in manager
|
||||
boolean valid = StringUtils.equals(pubKey, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()))
|
||||
return JsonOutput.toJson(
|
||||
valid: valid
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -20,18 +20,20 @@
|
||||
|
||||
package io.kamax.mxisd.controller.v1
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.json.JsonSlurper
|
||||
import io.kamax.mxisd.lookup.ALookupRequest
|
||||
import io.kamax.mxisd.lookup.BulkLookupRequest
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson
|
||||
import io.kamax.mxisd.lookup.*
|
||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy
|
||||
import io.kamax.mxisd.signature.SignatureManager
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.bind.annotation.CrossOrigin
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
@@ -42,10 +44,13 @@ import static org.springframework.web.bind.annotation.RequestMethod.GET
|
||||
import static org.springframework.web.bind.annotation.RequestMethod.POST
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
class MappingController {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(MappingController.class)
|
||||
private JsonSlurper json = new JsonSlurper()
|
||||
private Gson gson = new Gson()
|
||||
|
||||
@Autowired
|
||||
private LookupStrategy strategy
|
||||
@@ -60,37 +65,43 @@ class MappingController {
|
||||
if (lookupReq.isRecursive()) {
|
||||
lookupReq.setRecurseHosts(Arrays.asList(xff.split(",")))
|
||||
}
|
||||
|
||||
lookupReq.setUserAgent(req.getHeader("USER-AGENT"))
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/_matrix/identity/api/v1/lookup", method = GET)
|
||||
@RequestMapping(value = "/lookup", method = GET)
|
||||
String lookup(HttpServletRequest request, @RequestParam String medium, @RequestParam String address) {
|
||||
SingleLookupRequest lookupRequest = new SingleLookupRequest()
|
||||
setRequesterInfo(lookupRequest, request)
|
||||
lookupRequest.setType(medium)
|
||||
lookupRequest.setThreePid(address)
|
||||
|
||||
log.info("Got request from {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.isRecursive())
|
||||
log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive())
|
||||
|
||||
Optional<?> lookupOpt = strategy.find(lookupRequest)
|
||||
Optional<SingleLookupReply> lookupOpt = strategy.find(lookupRequest)
|
||||
if (!lookupOpt.isPresent()) {
|
||||
log.info("No mapping was found, return empty JSON object")
|
||||
return JsonOutput.toJson([])
|
||||
}
|
||||
|
||||
def lookup = lookupOpt.get()
|
||||
if (lookup['signatures'] == null) {
|
||||
log.info("lookup is not signed yet, we sign it")
|
||||
lookup['signatures'] = signMgr.signMessage(JsonOutput.toJson(lookup))
|
||||
}
|
||||
SingleLookupReply lookup = lookupOpt.get()
|
||||
if (lookup.isSigned()) {
|
||||
log.info("Lookup is already signed, sending as-is")
|
||||
return lookup.getBody();
|
||||
} else {
|
||||
log.info("Lookup is not signed, signing")
|
||||
JsonObject obj = new Gson().toJsonTree(new SingeLookupReplyJson(lookup)).getAsJsonObject()
|
||||
obj.add("signatures", signMgr.signMessageGson(gson.toJson(obj)))
|
||||
|
||||
return JsonOutput.toJson(lookup)
|
||||
return gson.toJson(obj)
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/_matrix/identity/api/v1/bulk_lookup", method = POST)
|
||||
@RequestMapping(value = "/bulk_lookup", method = POST)
|
||||
String bulkLookup(HttpServletRequest request) {
|
||||
BulkLookupRequest lookupRequest = new BulkLookupRequest()
|
||||
setRequesterInfo(lookupRequest, request)
|
||||
log.info("Got request from {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.isRecursive())
|
||||
log.info("Got single lookup request from {} with client {} - Is recursive? {}", lookupRequest.getRequester(), lookupRequest.getUserAgent(), lookupRequest.isRecursive())
|
||||
|
||||
ClientBulkLookupRequest input = (ClientBulkLookupRequest) json.parseText(request.getInputStream().getText())
|
||||
List<ThreePidMapping> mappings = new ArrayList<>()
|
||||
|
@@ -25,7 +25,7 @@ import com.google.gson.JsonObject
|
||||
import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson
|
||||
import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson
|
||||
import io.kamax.mxisd.exception.BadRequestException
|
||||
import io.kamax.mxisd.lookup.ThreePid
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation
|
||||
import io.kamax.mxisd.mapping.MappingManager
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang.StringUtils
|
||||
@@ -33,16 +33,16 @@ import org.apache.http.HttpStatus
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(path = "/_matrix/identity/api/v1", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
class SessionController {
|
||||
|
||||
@Autowired
|
||||
@@ -56,7 +56,7 @@ class SessionController {
|
||||
gson.fromJson(new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8), obj)
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/_matrix/identity/api/v1/validate/{medium}/requestToken")
|
||||
@RequestMapping(value = "/validate/{medium}/requestToken")
|
||||
String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) {
|
||||
log.info("Requested: {}", request.getRequestURL(), request.getQueryString())
|
||||
|
||||
@@ -77,7 +77,7 @@ class SessionController {
|
||||
return gson.toJson(obj)
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/_matrix/identity/api/v1/validate/{medium}/submitToken")
|
||||
@RequestMapping(value = "/validate/{medium}/submitToken")
|
||||
String validate(HttpServletRequest request,
|
||||
@RequestParam String sid,
|
||||
@RequestParam("client_secret") String secret, @RequestParam String token) {
|
||||
@@ -88,15 +88,15 @@ class SessionController {
|
||||
return "{}"
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/_matrix/identity/api/v1/3pid/getValidated3pid")
|
||||
@RequestMapping(value = "/3pid/getValidated3pid")
|
||||
String check(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam String sid, @RequestParam("client_secret") String secret) {
|
||||
log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString())
|
||||
|
||||
Optional<ThreePid> result = mgr.getValidated(sid, secret)
|
||||
Optional<ThreePidValidation> result = mgr.getValidated(sid, secret)
|
||||
if (result.isPresent()) {
|
||||
log.info("requested session was validated")
|
||||
ThreePid pid = result.get()
|
||||
ThreePidValidation pid = result.get()
|
||||
|
||||
JsonObject obj = new JsonObject()
|
||||
obj.addProperty("medium", pid.getMedium())
|
||||
@@ -115,7 +115,7 @@ class SessionController {
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/_matrix/identity/api/v1/3pid/bind")
|
||||
@RequestMapping(value = "/3pid/bind")
|
||||
String bind(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String mxid) {
|
||||
String data = IOUtils.toString(request.getReader())
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.controller.v1;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.controller.v1.io;
|
||||
|
||||
import io.kamax.mxisd.mapping.MappingSession;
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.controller.v1.io;
|
||||
|
||||
public class SessionEmailTokenRequestJson extends GenericTokenRequestJson {
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.controller.v1.io;
|
||||
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
|
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.controller.v1.io;
|
||||
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class SingeLookupReplyJson {
|
||||
|
||||
private String address;
|
||||
private String medium;
|
||||
private String mxid;
|
||||
private long not_after;
|
||||
private long not_before;
|
||||
private long ts;
|
||||
private Map<String, Map<String, String>> signatures = new HashMap<>();
|
||||
|
||||
public SingeLookupReplyJson(SingleLookupReply reply) {
|
||||
this.address = reply.getRequest().getThreePid();
|
||||
this.medium = reply.getRequest().getType();
|
||||
this.mxid = reply.getMxid().getId();
|
||||
this.not_after = reply.getNotAfter().toEpochMilli();
|
||||
this.not_before = reply.getNotBefore().toEpochMilli();
|
||||
this.ts = reply.getTimestamp().toEpochMilli();
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public String getMedium() {
|
||||
return medium;
|
||||
}
|
||||
|
||||
public String getMxid() {
|
||||
return mxid;
|
||||
}
|
||||
|
||||
public long getNot_after() {
|
||||
return not_after;
|
||||
}
|
||||
|
||||
public long getNot_before() {
|
||||
return not_before;
|
||||
}
|
||||
|
||||
public long getTs() {
|
||||
return ts;
|
||||
}
|
||||
|
||||
public boolean isSigned() {
|
||||
return signatures != null && !signatures.isEmpty();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.controller.v1.io;
|
||||
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ThreePidInviteReplyIO {
|
||||
|
||||
private String token;
|
||||
private List<Key> public_keys;
|
||||
private String display_name;
|
||||
|
||||
public ThreePidInviteReplyIO(IThreePidInviteReply reply, String pubKey, String publicUrl) {
|
||||
this.token = reply.getToken();
|
||||
this.public_keys = Collections.singletonList(new Key(pubKey, publicUrl));
|
||||
this.display_name = reply.getDisplayName();
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public List<Key> getPublic_keys() {
|
||||
return public_keys;
|
||||
}
|
||||
|
||||
public String getDisplay_name() {
|
||||
return display_name;
|
||||
}
|
||||
|
||||
public class Key {
|
||||
private String key_validity_url;
|
||||
private String public_key;
|
||||
|
||||
public Key(String key, String publicUrl) {
|
||||
this.key_validity_url = publicUrl + "/_matrix/identity/api/v1/pubkey/isvalid";
|
||||
this.public_key = key;
|
||||
}
|
||||
|
||||
public String getKey_validity_url() {
|
||||
return key_validity_url;
|
||||
}
|
||||
|
||||
public String getPublic_key() {
|
||||
return public_key;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,11 +1,47 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.exception;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ConfigurationException extends RuntimeException {
|
||||
|
||||
private String key;
|
||||
private String detailedMsg;
|
||||
|
||||
public ConfigurationException(String key) {
|
||||
super("Invalid or empty value for configuration key " + key);
|
||||
}
|
||||
|
||||
public ConfigurationException(Throwable t) {
|
||||
super(t.getMessage(), t);
|
||||
}
|
||||
|
||||
public ConfigurationException(String key, String detailedMsg) {
|
||||
this(key);
|
||||
this.detailedMsg = detailedMsg;
|
||||
}
|
||||
|
||||
public Optional<String> getDetailedMessage() {
|
||||
return Optional.ofNullable(detailedMsg);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.exception;
|
||||
|
||||
public class MappingAlreadyExistsException extends RuntimeException {
|
||||
|
||||
public MappingAlreadyExistsException() {
|
||||
super("A mapping already exists for this 3PID");
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.invitation;
|
||||
|
||||
import io.kamax.matrix._MatrixID;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface IThreePidInvite {
|
||||
|
||||
_MatrixID getSender();
|
||||
|
||||
String getMedium();
|
||||
|
||||
String getAddress();
|
||||
|
||||
String getRoomId();
|
||||
|
||||
Map<String, String> getProperties();
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.invitation;
|
||||
|
||||
public interface IThreePidInviteReply {
|
||||
|
||||
String getId();
|
||||
|
||||
IThreePidInvite getInvite();
|
||||
|
||||
String getToken();
|
||||
|
||||
String getDisplayName();
|
||||
|
||||
}
|
325
src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java
Normal file
325
src/main/groovy/io/kamax/mxisd/invitation/InvitationManager.java
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.invitation;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.kamax.matrix.MatrixID;
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
|
||||
import io.kamax.mxisd.invitation.sender.IInviteSender;
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||
import io.kamax.mxisd.signature.SignatureManager;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.conn.ssl.NoopHostnameVerifier;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.ssl.SSLContextBuilder;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.xbill.DNS.*;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class InvitationManager {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(InvitationManager.class);
|
||||
|
||||
private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private IStorage storage;
|
||||
|
||||
@Autowired
|
||||
private LookupStrategy lookupMgr;
|
||||
|
||||
@Autowired
|
||||
private SignatureManager signMgr;
|
||||
|
||||
private Map<String, IInviteSender> senders;
|
||||
|
||||
private CloseableHttpClient client;
|
||||
private Gson gson;
|
||||
private Timer refreshTimer;
|
||||
|
||||
private String getId(IThreePidInvite invite) {
|
||||
return invite.getSender().getDomain() + invite.getMedium() + invite.getAddress();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
gson = new Gson();
|
||||
|
||||
log.info("Loading saved invites");
|
||||
Collection<ThreePidInviteIO> ioList = storage.getInvites();
|
||||
ioList.forEach(io -> {
|
||||
log.info("Processing invite {}", gson.toJson(io));
|
||||
ThreePidInvite invite = new ThreePidInvite(
|
||||
new MatrixID(io.getSender()),
|
||||
io.getMedium(),
|
||||
io.getAddress(),
|
||||
io.getRoomId(),
|
||||
io.getProperties()
|
||||
);
|
||||
|
||||
ThreePidInviteReply reply = new ThreePidInviteReply(getId(invite), invite, io.getToken(), "");
|
||||
invitations.put(reply.getId(), reply);
|
||||
});
|
||||
|
||||
// FIXME export such madness into matrix-java-sdk with a nice wrapper to talk to a homeserver
|
||||
try {
|
||||
SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build();
|
||||
HostnameVerifier hostnameVerifier = new NoopHostnameVerifier();
|
||||
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
|
||||
client = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build();
|
||||
} catch (Exception e) {
|
||||
// FIXME do better...
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
log.info("Setting up invitation mapping refresh timer");
|
||||
refreshTimer = new Timer();
|
||||
refreshTimer.scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
lookupMappingsForInvites();
|
||||
} catch (Throwable t) {
|
||||
log.error("Error when running background mapping refresh", t);
|
||||
}
|
||||
}
|
||||
}, 5000L, TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES)); // FIXME make configurable
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
private void preDestroy() {
|
||||
ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
private String getId(IThreePidInviteReply reply) {
|
||||
return reply.getInvite().getSender().getId() + ":" + reply.getInvite().getRoomId() + ":" + reply.getInvite().getMedium() + ":" + reply.getInvite().getAddress();
|
||||
}
|
||||
|
||||
String getSrvRecordName(String domain) {
|
||||
return "_matrix._tcp." + domain;
|
||||
}
|
||||
|
||||
// TODO use caching mechanism
|
||||
// TODO export in matrix-java-sdk
|
||||
String findHomeserverForDomain(String domain) {
|
||||
log.debug("Performing SRV lookup for {}", domain);
|
||||
String lookupDns = getSrvRecordName(domain);
|
||||
log.info("Lookup name: {}", lookupDns);
|
||||
|
||||
try {
|
||||
List<SRVRecord> srvRecords = new ArrayList<>();
|
||||
Record[] rawRecords = new Lookup(lookupDns, Type.SRV).run();
|
||||
if (rawRecords != null && rawRecords.length > 0) {
|
||||
for (Record record : rawRecords) {
|
||||
if (Type.SRV == record.getType()) {
|
||||
srvRecords.add((SRVRecord) record);
|
||||
} else {
|
||||
log.info("Got non-SRV record: {}", record.toString());
|
||||
}
|
||||
}
|
||||
|
||||
srvRecords.sort(Comparator.comparingInt(SRVRecord::getPriority));
|
||||
for (SRVRecord record : srvRecords) {
|
||||
log.info("Found SRV record: {}", record.toString());
|
||||
return "https://" + record.getTarget().toString(true) + ":" + record.getPort();
|
||||
}
|
||||
} else {
|
||||
log.info("No SRV record for {}", lookupDns);
|
||||
}
|
||||
} catch (TextParseException e) {
|
||||
log.warn("Unable to perform DNS SRV query for {}: {}", lookupDns, e.getMessage());
|
||||
}
|
||||
|
||||
log.info("Performing basic lookup using domain name {}", domain);
|
||||
return "https://" + domain + ":8448";
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public InvitationManager(List<IInviteSender> senderList) {
|
||||
senders = new HashMap<>();
|
||||
senderList.forEach(sender -> senders.put(sender.getMedium(), sender));
|
||||
}
|
||||
|
||||
public synchronized IThreePidInviteReply storeInvite(IThreePidInvite invitation) { // TODO better sync
|
||||
IInviteSender sender = senders.get(invitation.getMedium());
|
||||
if (sender == null) {
|
||||
throw new BadRequestException("Medium type " + invitation.getMedium() + " is not supported");
|
||||
}
|
||||
|
||||
String invId = getId(invitation);
|
||||
log.info("Handling invite for {}:{} from {} in room {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender(), invitation.getRoomId());
|
||||
if (invitations.containsKey(invId)) { // FIXME we need to lookup using the HS domain too!!
|
||||
log.info("Invite is already pending for {}:{}, returning data", invitation.getMedium(), invitation.getAddress());
|
||||
return invitations.get(invId);
|
||||
}
|
||||
|
||||
Optional<?> result = lookupMgr.find(invitation.getMedium(), invitation.getAddress(), true);
|
||||
if (result.isPresent()) {
|
||||
log.info("Mapping for {}:{} already exists, refusing to store invite", invitation.getMedium(), invitation.getAddress());
|
||||
throw new MappingAlreadyExistsException();
|
||||
}
|
||||
|
||||
String token = RandomStringUtils.randomAlphanumeric(64);
|
||||
String displayName = invitation.getAddress().substring(0, 3) + "...";
|
||||
|
||||
IThreePidInviteReply reply = new ThreePidInviteReply(invId, invitation, token, displayName);
|
||||
|
||||
log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress());
|
||||
sender.send(reply);
|
||||
|
||||
log.info("Storing invite under ID {}", invId);
|
||||
storage.insertInvite(reply);
|
||||
invitations.put(invId, reply);
|
||||
log.info("A new invite has been created for {}:{} on HS {}", invitation.getMedium(), invitation.getAddress(), invitation.getSender().getDomain());
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public void lookupMappingsForInvites() {
|
||||
log.info("Checking for existing mapping for pending invites");
|
||||
for (IThreePidInviteReply reply : invitations.values()) {
|
||||
log.info("Processing invite {}", getId(reply));
|
||||
ForkJoinPool.commonPool().submit(new MappingChecker(reply));
|
||||
}
|
||||
}
|
||||
|
||||
public void publishMappingIfInvited(ThreePidMapping threePid) {
|
||||
log.info("Looking up possible pending invites for {}:{}", threePid.getMedium(), threePid.getValue());
|
||||
for (IThreePidInviteReply reply : invitations.values()) {
|
||||
if (StringUtils.equals(reply.getInvite().getMedium(), threePid.getMedium()) && StringUtils.equals(reply.getInvite().getAddress(), threePid.getValue())) {
|
||||
log.info("{}:{} has an invite pending on HS {}, publishing mapping", threePid.getMedium(), threePid.getValue(), reply.getInvite().getSender().getDomain());
|
||||
publishMapping(reply, threePid.getMxid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void publishMapping(IThreePidInviteReply reply, String mxid) {
|
||||
String medium = reply.getInvite().getMedium();
|
||||
String address = reply.getInvite().getAddress();
|
||||
String domain = reply.getInvite().getSender().getDomain();
|
||||
log.info("Discovering HS for domain {}", domain);
|
||||
String hsUrlOpt = findHomeserverForDomain(domain);
|
||||
|
||||
// TODO this is needed as this will block if called during authentication cycle due to synapse implementation
|
||||
new Thread(() -> { // FIXME need to make this retry-able and within a general background working pool
|
||||
HttpPost req = new HttpPost(hsUrlOpt + "/_matrix/federation/v1/3pid/onbind");
|
||||
// Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr
|
||||
JSONObject obj = new JSONObject(); // TODO use Gson instead
|
||||
obj.put("mxid", mxid);
|
||||
obj.put("token", reply.getToken());
|
||||
obj.put("signatures", signMgr.signMessageJson(obj.toString()));
|
||||
|
||||
JSONObject objUp = new JSONObject();
|
||||
objUp.put("mxid", mxid);
|
||||
objUp.put("medium", medium);
|
||||
objUp.put("address", address);
|
||||
objUp.put("sender", reply.getInvite().getSender().getId());
|
||||
objUp.put("room_id", reply.getInvite().getRoomId());
|
||||
objUp.put("signed", obj);
|
||||
|
||||
JSONObject content = new JSONObject(); // TODO use Gson instead
|
||||
JSONArray invites = new JSONArray();
|
||||
invites.put(objUp);
|
||||
content.put("invites", invites);
|
||||
content.put("medium", medium);
|
||||
content.put("address", address);
|
||||
content.put("mxid", mxid);
|
||||
|
||||
content.put("signatures", signMgr.signMessageJson(content.toString()));
|
||||
|
||||
StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8);
|
||||
entity.setContentType("application/json");
|
||||
req.setEntity(entity);
|
||||
try {
|
||||
log.info("Posting onBind event to {}", req.getURI());
|
||||
CloseableHttpResponse response = client.execute(req);
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
log.info("Answer code: {}", statusCode);
|
||||
if (statusCode >= 300) {
|
||||
log.warn("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8));
|
||||
} else {
|
||||
invitations.remove(getId(reply));
|
||||
storage.deleteInvite(reply.getId());
|
||||
log.info("Removed invite from internal store");
|
||||
}
|
||||
response.close();
|
||||
} catch (IOException e) {
|
||||
log.warn("Unable to tell HS {} about invite being mapped", domain, e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private class MappingChecker implements Runnable {
|
||||
|
||||
private IThreePidInviteReply reply;
|
||||
|
||||
public MappingChecker(IThreePidInviteReply reply) {
|
||||
this.reply = reply;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
log.info("Searching for mapping created since invite {} was created", getId(reply));
|
||||
Optional<SingleLookupReply> result = lookupMgr.find(reply.getInvite().getMedium(), reply.getInvite().getAddress(), true);
|
||||
if (result.isPresent()) {
|
||||
SingleLookupReply lookup = result.get();
|
||||
log.info("Found mapping for pending invite {}", getId(reply));
|
||||
publishMapping(reply, lookup.getMxid().getId());
|
||||
} else {
|
||||
log.info("No mapping for pending invite {}", getId(reply));
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
log.error("Unable to process invite", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.invitation;
|
||||
|
||||
import io.kamax.matrix._MatrixID;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ThreePidInvite implements IThreePidInvite {
|
||||
|
||||
private _MatrixID sender;
|
||||
private String medium;
|
||||
private String address;
|
||||
private String roomId;
|
||||
private Map<String, String> properties;
|
||||
|
||||
public ThreePidInvite(_MatrixID sender, String medium, String address, String roomId) {
|
||||
this.sender = sender;
|
||||
this.medium = medium;
|
||||
this.address = address;
|
||||
this.roomId = roomId;
|
||||
this.properties = new HashMap<>();
|
||||
}
|
||||
|
||||
public ThreePidInvite(_MatrixID sender, String medium, String address, String roomId, Map<String, String> properties) {
|
||||
this(sender, medium, address, roomId);
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public _MatrixID getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMedium() {
|
||||
return medium;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.invitation;
|
||||
|
||||
public class ThreePidInviteReply implements IThreePidInviteReply {
|
||||
|
||||
private String id;
|
||||
private IThreePidInvite invite;
|
||||
private String token;
|
||||
private String displayName;
|
||||
|
||||
public ThreePidInviteReply(String id, IThreePidInvite invite, String token, String displayName) {
|
||||
this.id = id;
|
||||
this.invite = invite;
|
||||
this.token = token;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IThreePidInvite getInvite() {
|
||||
return invite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.invitation.sender;
|
||||
|
||||
import com.sun.mail.smtp.SMTPTransport;
|
||||
import io.kamax.matrix.ThreePidMedium;
|
||||
import io.kamax.mxisd.config.ServerConfig;
|
||||
import io.kamax.mxisd.config.invite.sender.EmailSenderConfig;
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.WordUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.Session;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
|
||||
@Component
|
||||
public class EmailInviteSender implements IInviteSender {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(EmailInviteSender.class);
|
||||
|
||||
@Autowired
|
||||
private EmailSenderConfig cfg;
|
||||
|
||||
@Autowired
|
||||
private ServerConfig srvCfg;
|
||||
|
||||
@Autowired
|
||||
private ApplicationContext app;
|
||||
|
||||
private Session session;
|
||||
private InternetAddress sender;
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
try {
|
||||
session = Session.getInstance(System.getProperties());
|
||||
sender = new InternetAddress(cfg.getEmail(), cfg.getName());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// What are we supposed to do with this?!
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMedium() {
|
||||
return ThreePidMedium.Email.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(IThreePidInviteReply invite) {
|
||||
if (!ThreePidMedium.Email.is(invite.getInvite().getMedium())) {
|
||||
throw new IllegalArgumentException(invite.getInvite().getMedium() + " is not a supported 3PID type");
|
||||
}
|
||||
|
||||
try {
|
||||
String domainPretty = WordUtils.capitalizeFully(srvCfg.getName());
|
||||
String senderName = invite.getInvite().getProperties().getOrDefault("sender_display_name", "");
|
||||
String senderNameOrId = StringUtils.defaultIfBlank(senderName, invite.getInvite().getSender().getId());
|
||||
String roomName = invite.getInvite().getProperties().getOrDefault("room_name", "");
|
||||
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
|
||||
|
||||
String templateBody = IOUtils.toString(
|
||||
StringUtils.startsWith(cfg.getTemplate(), "classpath:") ?
|
||||
app.getResource(cfg.getTemplate()).getInputStream() : new FileInputStream(cfg.getTemplate()),
|
||||
StandardCharsets.UTF_8);
|
||||
templateBody = templateBody.replace("%DOMAIN%", srvCfg.getName());
|
||||
templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty);
|
||||
templateBody = templateBody.replace("%FROM_EMAIL%", cfg.getEmail());
|
||||
templateBody = templateBody.replace("%FROM_NAME%", cfg.getName());
|
||||
templateBody = templateBody.replace("%SENDER_ID%", invite.getInvite().getSender().getId());
|
||||
templateBody = templateBody.replace("%SENDER_NAME%", senderName);
|
||||
templateBody = templateBody.replace("%SENDER_NAME_OR_ID%", senderNameOrId);
|
||||
templateBody = templateBody.replace("%INVITE_MEDIUM%", invite.getInvite().getMedium());
|
||||
templateBody = templateBody.replace("%INVITE_ADDRESS%", invite.getInvite().getAddress());
|
||||
templateBody = templateBody.replace("%ROOM_ID%", invite.getInvite().getRoomId());
|
||||
templateBody = templateBody.replace("%ROOM_NAME%", roomName);
|
||||
templateBody = templateBody.replace("%ROOM_NAME_OR_ID%", roomNameOrId);
|
||||
|
||||
MimeMessage msg = new MimeMessage(session, IOUtils.toInputStream(templateBody, StandardCharsets.UTF_8));
|
||||
msg.setHeader("X-Mailer", "mxisd"); // TODO set version
|
||||
msg.setSentDate(new Date());
|
||||
msg.setFrom(sender);
|
||||
msg.setRecipients(Message.RecipientType.TO, invite.getInvite().getAddress());
|
||||
msg.saveChanges();
|
||||
|
||||
log.info("Sending invite to {} via SMTP using {}:{}", invite.getInvite().getAddress(), cfg.getHost(), cfg.getPort());
|
||||
SMTPTransport transport = (SMTPTransport) session.getTransport("smtp");
|
||||
transport.setStartTLS(cfg.getTls() > 0);
|
||||
transport.setRequireStartTLS(cfg.getTls() > 1);
|
||||
|
||||
log.info("Connecting to {}:{}", cfg.getHost(), cfg.getPort());
|
||||
transport.connect(cfg.getHost(), cfg.getPort(), cfg.getLogin(), cfg.getPassword());
|
||||
try {
|
||||
transport.sendMessage(msg, InternetAddress.parse(invite.getInvite().getAddress()));
|
||||
log.info("Invite to {} was sent", invite.getInvite().getAddress());
|
||||
} finally {
|
||||
transport.close();
|
||||
}
|
||||
} catch (IOException | MessagingException e) {
|
||||
throw new RuntimeException("Unable to send e-mail invite to " + invite.getInvite().getAddress(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.invitation.sender;
|
||||
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
|
||||
public interface IInviteSender {
|
||||
|
||||
String getMedium();
|
||||
|
||||
void send(IThreePidInviteReply invite);
|
||||
|
||||
}
|
@@ -26,6 +26,7 @@ public abstract class ALookupRequest {
|
||||
|
||||
private String id;
|
||||
private String requester;
|
||||
private String userAgent;
|
||||
private boolean isRecursive;
|
||||
private List<String> recurseHosts;
|
||||
|
||||
@@ -45,6 +46,14 @@ public abstract class ALookupRequest {
|
||||
this.requester = requester;
|
||||
}
|
||||
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
public void setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
public boolean isRecursive() {
|
||||
return isRecursive;
|
||||
}
|
||||
|
116
src/main/groovy/io/kamax/mxisd/lookup/SingleLookupReply.java
Normal file
116
src/main/groovy/io/kamax/mxisd/lookup/SingleLookupReply.java
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.lookup;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import io.kamax.matrix.MatrixID;
|
||||
import io.kamax.matrix._MatrixID;
|
||||
import io.kamax.mxisd.controller.v1.io.SingeLookupReplyJson;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class SingleLookupReply {
|
||||
|
||||
private static Gson gson = new Gson();
|
||||
|
||||
private boolean isRecursive;
|
||||
private boolean isSigned;
|
||||
private String body;
|
||||
private SingleLookupRequest request;
|
||||
private _MatrixID mxid;
|
||||
private Instant notBefore;
|
||||
private Instant notAfter;
|
||||
private Instant timestamp;
|
||||
|
||||
public static SingleLookupReply fromRecursive(SingleLookupRequest request, String body) {
|
||||
SingleLookupReply reply = new SingleLookupReply();
|
||||
reply.isRecursive = true;
|
||||
reply.request = request;
|
||||
reply.body = body;
|
||||
|
||||
try {
|
||||
SingeLookupReplyJson json = gson.fromJson(body, SingeLookupReplyJson.class);
|
||||
reply.mxid = new MatrixID(json.getMxid());
|
||||
reply.notAfter = Instant.ofEpochMilli(json.getNot_after());
|
||||
reply.notBefore = Instant.ofEpochMilli(json.getNot_before());
|
||||
reply.timestamp = Instant.ofEpochMilli(json.getTs());
|
||||
reply.isSigned = json.isSigned();
|
||||
} catch (JsonSyntaxException e) {
|
||||
// stub - we only want to try, nothing more
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
private SingleLookupReply() {
|
||||
// stub
|
||||
}
|
||||
|
||||
public SingleLookupReply(SingleLookupRequest request, String mxid) {
|
||||
this(request, new MatrixID(mxid));
|
||||
}
|
||||
|
||||
public SingleLookupReply(SingleLookupRequest request, _MatrixID mxid) {
|
||||
this(request, mxid, Instant.now(), Instant.ofEpochMilli(0), Instant.ofEpochMilli(253402300799000L));
|
||||
}
|
||||
|
||||
public SingleLookupReply(SingleLookupRequest request, _MatrixID mxid, Instant timestamp, Instant notBefore, Instant notAfter) {
|
||||
this.request = request;
|
||||
this.mxid = mxid;
|
||||
this.timestamp = timestamp;
|
||||
this.notBefore = notBefore;
|
||||
this.notAfter = notAfter;
|
||||
}
|
||||
|
||||
public boolean isRecursive() {
|
||||
return isRecursive;
|
||||
}
|
||||
|
||||
public boolean isSigned() {
|
||||
return isSigned;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public SingleLookupRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public _MatrixID getMxid() {
|
||||
return mxid;
|
||||
}
|
||||
|
||||
public Instant getNotBefore() {
|
||||
return notBefore;
|
||||
}
|
||||
|
||||
public Instant getNotAfter() {
|
||||
return notAfter;
|
||||
}
|
||||
|
||||
public Instant getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
package io.kamax.mxisd.lookup;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class ThreePid {
|
||||
|
||||
private String medium;
|
||||
private String address;
|
||||
private Instant validation;
|
||||
|
||||
public ThreePid(String medium, String address, Instant validation) {
|
||||
this.medium = medium;
|
||||
this.address = address;
|
||||
this.validation = validation;
|
||||
}
|
||||
|
||||
public String getMedium() {
|
||||
return medium;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public Instant getValidation() {
|
||||
return validation;
|
||||
}
|
||||
|
||||
}
|
@@ -21,6 +21,7 @@
|
||||
package io.kamax.mxisd.lookup;
|
||||
|
||||
import groovy.json.JsonOutput;
|
||||
import io.kamax.mxisd.ThreePid;
|
||||
|
||||
public class ThreePidMapping {
|
||||
|
||||
@@ -28,6 +29,20 @@ public class ThreePidMapping {
|
||||
private String value;
|
||||
private String mxid;
|
||||
|
||||
public ThreePidMapping() {
|
||||
// stub
|
||||
}
|
||||
|
||||
public ThreePidMapping(ThreePid threePid, String mxid) {
|
||||
this(threePid.getMedium(), threePid.getAddress(), mxid);
|
||||
}
|
||||
|
||||
public ThreePidMapping(String medium, String value, String mxid) {
|
||||
setMedium(medium);
|
||||
setValue(value);
|
||||
setMxid(mxid);
|
||||
}
|
||||
|
||||
public String getMedium() {
|
||||
return medium;
|
||||
}
|
||||
|
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.lookup;
|
||||
|
||||
import io.kamax.mxisd.ThreePid;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class ThreePidValidation extends ThreePid {
|
||||
|
||||
private Instant validation;
|
||||
|
||||
public ThreePidValidation(String medium, String address, Instant validation) {
|
||||
super(medium, address);
|
||||
this.validation = validation;
|
||||
}
|
||||
|
||||
public Instant getValidation() {
|
||||
return validation;
|
||||
}
|
||||
|
||||
}
|
@@ -20,12 +20,13 @@
|
||||
|
||||
package io.kamax.mxisd.lookup.fetcher
|
||||
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
|
||||
interface IBridgeFetcher {
|
||||
|
||||
Optional<?> find(SingleLookupRequest request)
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request)
|
||||
|
||||
List<ThreePidMapping> populate(List<ThreePidMapping> mappings)
|
||||
|
||||
|
@@ -20,13 +20,15 @@
|
||||
|
||||
package io.kamax.mxisd.lookup.fetcher
|
||||
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
|
||||
interface IRemoteIdentityServerFetcher {
|
||||
|
||||
boolean isUsable(String remote)
|
||||
|
||||
Optional<?> find(String remote, String type, String threePid)
|
||||
Optional<SingleLookupReply> find(String remote, SingleLookupRequest request)
|
||||
|
||||
List<ThreePidMapping> find(String remote, List<ThreePidMapping> mappings)
|
||||
|
||||
|
@@ -21,6 +21,7 @@
|
||||
package io.kamax.mxisd.lookup.provider;
|
||||
|
||||
import io.kamax.mxisd.config.RecursiveLookupBridgeConfig;
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher;
|
||||
@@ -46,16 +47,16 @@ public class BridgeFetcher implements IBridgeFetcher {
|
||||
private RemoteIdentityServerFetcher fetcher;
|
||||
|
||||
@Override
|
||||
public Optional<?> find(SingleLookupRequest request) {
|
||||
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||
Optional<String> mediumUrl = Optional.ofNullable(cfg.getMappings().get(request.getType()));
|
||||
if (mediumUrl.isPresent() && !StringUtils.isBlank(mediumUrl.get())) {
|
||||
log.info("Using specific medium bridge lookup URL {}", mediumUrl.get());
|
||||
|
||||
return fetcher.find(mediumUrl.get(), request.getType(), request.getThreePid());
|
||||
return fetcher.find(mediumUrl.get(), request);
|
||||
} else if (!StringUtils.isBlank(cfg.getServer())) {
|
||||
log.info("Using generic bridge lookup URL {}", cfg.getServer());
|
||||
|
||||
return fetcher.find(cfg.getServer(), request.getType(), request.getThreePid());
|
||||
return fetcher.find(cfg.getServer(), request);
|
||||
} else {
|
||||
log.info("No bridge lookup URL found/configured, skipping");
|
||||
|
||||
|
@@ -21,6 +21,7 @@
|
||||
package io.kamax.mxisd.lookup.provider
|
||||
|
||||
import io.kamax.mxisd.config.ServerConfig
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher
|
||||
@@ -124,7 +125,7 @@ class DnsLookupProvider implements IThreePidProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<?> find(SingleLookupRequest request) {
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||
if (!StringUtils.equals("email", request.getType())) { // TODO use enum
|
||||
log.info("Skipping unsupported type {} for {}", request.getType(), request.getThreePid())
|
||||
return Optional.empty()
|
||||
@@ -137,7 +138,7 @@ class DnsLookupProvider implements IThreePidProvider {
|
||||
Optional<String> baseUrl = findIdentityServerForDomain(domain)
|
||||
|
||||
if (baseUrl.isPresent()) {
|
||||
return fetcher.find(baseUrl.get(), request.getType().toString(), request.getThreePid())
|
||||
return fetcher.find(baseUrl.get(), request)
|
||||
}
|
||||
|
||||
return Optional.empty()
|
||||
|
@@ -21,6 +21,7 @@
|
||||
package io.kamax.mxisd.lookup.provider
|
||||
|
||||
import io.kamax.mxisd.config.ForwardConfig
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher
|
||||
@@ -56,9 +57,9 @@ class ForwarderProvider implements IThreePidProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<?> find(SingleLookupRequest request) {
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||
for (String root : cfg.getServers()) {
|
||||
Optional<?> answer = fetcher.find(root, request.getType(), request.getThreePid())
|
||||
Optional<SingleLookupReply> answer = fetcher.find(root, request)
|
||||
if (answer.isPresent()) {
|
||||
return answer
|
||||
}
|
||||
|
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.lookup.provider
|
||||
|
||||
import com.google.firebase.FirebaseApp
|
||||
import com.google.firebase.FirebaseOptions
|
||||
import com.google.firebase.auth.FirebaseAuth
|
||||
import com.google.firebase.auth.FirebaseCredential
|
||||
import com.google.firebase.auth.FirebaseCredentials
|
||||
import com.google.firebase.auth.UserRecord
|
||||
import com.google.firebase.internal.NonNull
|
||||
import com.google.firebase.tasks.OnFailureListener
|
||||
import com.google.firebase.tasks.OnSuccessListener
|
||||
import io.kamax.matrix.ThreePidMedium
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.function.Consumer
|
||||
import java.util.regex.Pattern
|
||||
|
||||
public class GoogleFirebaseProvider implements IThreePidProvider {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(GoogleFirebaseProvider.class);
|
||||
|
||||
private static final Pattern matrixIdLaxPattern = Pattern.compile("@(.*):(.+)");
|
||||
|
||||
private boolean isEnabled;
|
||||
private String domain;
|
||||
private FirebaseApp fbApp;
|
||||
private FirebaseAuth fbAuth;
|
||||
|
||||
public GoogleFirebaseProvider(boolean isEnabled) {
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
|
||||
public GoogleFirebaseProvider(String credsPath, String db, String domain) {
|
||||
this(true);
|
||||
this.domain = domain;
|
||||
try {
|
||||
fbApp = FirebaseApp.initializeApp(getOpts(credsPath, db), "ThreePidProvider");
|
||||
fbAuth = FirebaseAuth.getInstance(fbApp);
|
||||
|
||||
log.info("Google Firebase Authentication is ready");
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Error when initializing Firebase", e);
|
||||
}
|
||||
}
|
||||
|
||||
private FirebaseCredential getCreds(String credsPath) throws IOException {
|
||||
if (StringUtils.isNotBlank(credsPath)) {
|
||||
return FirebaseCredentials.fromCertificate(new FileInputStream(credsPath));
|
||||
} else {
|
||||
return FirebaseCredentials.applicationDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private FirebaseOptions getOpts(String credsPath, String db) throws IOException {
|
||||
if (StringUtils.isBlank(db)) {
|
||||
throw new IllegalArgumentException("Firebase database is not configured");
|
||||
}
|
||||
|
||||
return new FirebaseOptions.Builder()
|
||||
.setCredential(getCreds(credsPath))
|
||||
.setDatabaseUrl(db)
|
||||
.build();
|
||||
}
|
||||
|
||||
private String getMxid(UserRecord record) {
|
||||
return "@${record.getUid()}:${domain}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 25;
|
||||
}
|
||||
|
||||
private void waitOnLatch(CountDownLatch l) {
|
||||
try {
|
||||
l.await(30, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
log.warn("Interrupted while waiting for Firebase auth check");
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<UserRecord> findInternal(String medium, String address) {
|
||||
UserRecord r;
|
||||
CountDownLatch l = new CountDownLatch(1);
|
||||
|
||||
OnSuccessListener<UserRecord> success = new OnSuccessListener<UserRecord>() {
|
||||
@Override
|
||||
void onSuccess(UserRecord result) {
|
||||
log.info("Found 3PID match for {}:{} - UID is {}", medium, address, result.getUid())
|
||||
r = result;
|
||||
l.countDown()
|
||||
}
|
||||
};
|
||||
|
||||
OnFailureListener failure = new OnFailureListener() {
|
||||
@Override
|
||||
void onFailure(@NonNull Exception e) {
|
||||
log.info("No 3PID match for {}:{} - {}", medium, address, e.getMessage())
|
||||
r = null;
|
||||
l.countDown()
|
||||
}
|
||||
};
|
||||
|
||||
if (ThreePidMedium.Email.is(medium)) {
|
||||
log.info("Performing E-mail 3PID lookup for {}", address)
|
||||
fbAuth.getUserByEmail(address)
|
||||
.addOnSuccessListener(success)
|
||||
.addOnFailureListener(failure);
|
||||
waitOnLatch(l);
|
||||
} else if (ThreePidMedium.PhoneNumber.is(medium)) {
|
||||
log.info("Performing msisdn 3PID lookup for {}", address)
|
||||
fbAuth.getUserByPhoneNumber(address)
|
||||
.addOnSuccessListener(success)
|
||||
.addOnFailureListener(failure);
|
||||
waitOnLatch(l);
|
||||
} else {
|
||||
log.info("{} is not a supported 3PID medium", medium);
|
||||
r = null;
|
||||
}
|
||||
|
||||
return Optional.ofNullable(r);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||
Optional<UserRecord> urOpt = findInternal(request.getType(), request.getThreePid())
|
||||
if (urOpt.isPresent()) {
|
||||
return Optional.of(new SingleLookupReply(request, getMxid(urOpt.get())));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ThreePidMapping> populate(List<ThreePidMapping> mappings) {
|
||||
List<ThreePidMapping> results = new ArrayList<>();
|
||||
mappings.parallelStream().forEach(new Consumer<ThreePidMapping>() {
|
||||
@Override
|
||||
void accept(ThreePidMapping o) {
|
||||
Optional<UserRecord> urOpt = findInternal(o.getMedium(), o.getValue());
|
||||
if (urOpt.isPresent()) {
|
||||
ThreePidMapping result = new ThreePidMapping();
|
||||
result.setMedium(o.getMedium())
|
||||
result.setValue(o.getValue())
|
||||
result.setMxid(getMxid(urOpt.get()))
|
||||
results.add(result)
|
||||
}
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
@@ -20,6 +20,7 @@
|
||||
|
||||
package io.kamax.mxisd.lookup.provider
|
||||
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
|
||||
@@ -34,7 +35,7 @@ interface IThreePidProvider {
|
||||
*/
|
||||
int getPriority() // Should not be here but let's KISS for now
|
||||
|
||||
Optional<?> find(SingleLookupRequest request)
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request)
|
||||
|
||||
List<ThreePidMapping> populate(List<ThreePidMapping> mappings)
|
||||
|
||||
|
@@ -22,6 +22,7 @@ package io.kamax.mxisd.lookup.provider
|
||||
|
||||
import io.kamax.mxisd.config.ServerConfig
|
||||
import io.kamax.mxisd.config.ldap.LdapConfig
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import org.apache.commons.lang.StringUtils
|
||||
@@ -131,7 +132,7 @@ class LdapProvider implements IThreePidProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<?> find(SingleLookupRequest request) {
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||
log.info("Performing LDAP lookup ${request.getThreePid()} of type ${request.getType()}")
|
||||
|
||||
LdapConnection conn = getConn()
|
||||
@@ -140,14 +141,7 @@ class LdapProvider implements IThreePidProvider {
|
||||
|
||||
Optional<String> mxid = lookup(conn, request.getType(), request.getThreePid())
|
||||
if (mxid.isPresent()) {
|
||||
return Optional.of([
|
||||
address : request.getThreePid(),
|
||||
medium : request.getType(),
|
||||
mxid : mxid.get(),
|
||||
not_before: 0,
|
||||
not_after : 9223372036854775807,
|
||||
ts : 0
|
||||
])
|
||||
return Optional.of(new SingleLookupReply(request, mxid.get()));
|
||||
}
|
||||
} finally {
|
||||
conn.close()
|
||||
|
@@ -24,6 +24,8 @@ import groovy.json.JsonException
|
||||
import groovy.json.JsonOutput
|
||||
import groovy.json.JsonSlurper
|
||||
import io.kamax.mxisd.controller.v1.ClientBulkLookupRequest
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher
|
||||
import org.apache.http.HttpEntity
|
||||
@@ -77,24 +79,26 @@ public class RemoteIdentityServerFetcher implements IRemoteIdentityServerFetcher
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<?> find(String remote, String type, String threePid) {
|
||||
log.info("Looking up {} 3PID {} using {}", type, threePid, remote)
|
||||
Optional<SingleLookupReply> find(String remote, SingleLookupRequest request) {
|
||||
log.info("Looking up {} 3PID {} using {}", request.getType(), request.getThreePid(), remote)
|
||||
|
||||
HttpURLConnection rootSrvConn = (HttpURLConnection) new URL(
|
||||
"${remote}/_matrix/identity/api/v1/lookup?medium=${type}&address=${threePid}"
|
||||
"${remote}/_matrix/identity/api/v1/lookup?medium=${request.getType()}&address=${request.getThreePid()}"
|
||||
).openConnection()
|
||||
|
||||
try {
|
||||
def output = json.parseText(rootSrvConn.getInputStream().getText())
|
||||
String outputRaw = rootSrvConn.getInputStream().getText()
|
||||
def output = json.parseText(outputRaw)
|
||||
if (output['address']) {
|
||||
log.info("Found 3PID mapping: {}", output)
|
||||
return Optional.of(output)
|
||||
|
||||
return Optional.of(SingleLookupReply.fromRecursive(request, outputRaw))
|
||||
}
|
||||
|
||||
log.info("Empty 3PID mapping from {}", remote)
|
||||
return Optional.empty()
|
||||
} catch (IOException e) {
|
||||
log.warn("Error looking up 3PID mapping {}: {}", threePid, e.getMessage())
|
||||
log.warn("Error looking up 3PID mapping {}: {}", request.getThreePid(), e.getMessage())
|
||||
return Optional.empty()
|
||||
} catch (JsonException e) {
|
||||
log.warn("Invalid JSON answer from {}", remote)
|
||||
|
@@ -21,6 +21,7 @@
|
||||
package io.kamax.mxisd.lookup.strategy
|
||||
|
||||
import io.kamax.mxisd.lookup.BulkLookupRequest
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider
|
||||
@@ -29,7 +30,11 @@ interface LookupStrategy {
|
||||
|
||||
List<IThreePidProvider> getLocalProviders()
|
||||
|
||||
Optional<?> find(SingleLookupRequest request)
|
||||
Optional<SingleLookupReply> find(String medium, String address, boolean recursive)
|
||||
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request)
|
||||
|
||||
Optional<SingleLookupReply> findRecursive(SingleLookupRequest request)
|
||||
|
||||
List<ThreePidMapping> find(BulkLookupRequest requests)
|
||||
|
||||
|
@@ -22,10 +22,7 @@ package io.kamax.mxisd.lookup.strategy
|
||||
|
||||
import edazdarevic.commons.net.CIDRUtils
|
||||
import io.kamax.mxisd.config.RecursiveLookupConfig
|
||||
import io.kamax.mxisd.lookup.ALookupRequest
|
||||
import io.kamax.mxisd.lookup.BulkLookupRequest
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping
|
||||
import io.kamax.mxisd.lookup.*
|
||||
import io.kamax.mxisd.lookup.fetcher.IBridgeFetcher
|
||||
import io.kamax.mxisd.lookup.provider.IThreePidProvider
|
||||
import org.slf4j.Logger
|
||||
@@ -93,13 +90,17 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea
|
||||
}
|
||||
|
||||
List<IThreePidProvider> listUsableProviders(ALookupRequest request) {
|
||||
return listUsableProviders(request, false);
|
||||
}
|
||||
|
||||
List<IThreePidProvider> listUsableProviders(ALookupRequest request, boolean forceRecursive) {
|
||||
List<IThreePidProvider> usableProviders = new ArrayList<>()
|
||||
|
||||
boolean canRecurse = isAllowedForRecursive(request.getRequester())
|
||||
boolean canRecurse = forceRecursive || isAllowedForRecursive(request.getRequester())
|
||||
|
||||
log.info("Host {} allowed for recursion: {}", request.getRequester(), canRecurse)
|
||||
for (IThreePidProvider provider : providers) {
|
||||
if (provider.isEnabled() && (provider.isLocal() || canRecurse)) {
|
||||
if (provider.isEnabled() && (provider.isLocal() || canRecurse || forceRecursive)) {
|
||||
usableProviders.add(provider)
|
||||
}
|
||||
}
|
||||
@@ -118,15 +119,27 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<?> find(SingleLookupRequest request) {
|
||||
for (IThreePidProvider provider : listUsableProviders(request)) {
|
||||
Optional<?> lookupDataOpt = provider.find(request)
|
||||
Optional<SingleLookupReply> find(String medium, String address, boolean recursive) {
|
||||
SingleLookupRequest req = new SingleLookupRequest();
|
||||
req.setType(medium)
|
||||
req.setThreePid(address)
|
||||
req.setRequester("Internal")
|
||||
return find(req, recursive)
|
||||
}
|
||||
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request, boolean forceRecursive) {
|
||||
for (IThreePidProvider provider : listUsableProviders(request, forceRecursive)) {
|
||||
Optional<SingleLookupReply> lookupDataOpt = provider.find(request)
|
||||
if (lookupDataOpt.isPresent()) {
|
||||
return lookupDataOpt
|
||||
}
|
||||
}
|
||||
|
||||
if (recursiveCfg.getBridge().getEnabled() && (!recursiveCfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))) {
|
||||
if (
|
||||
recursiveCfg.getBridge() != null &&
|
||||
recursiveCfg.getBridge().getEnabled() &&
|
||||
(!recursiveCfg.getBridge().getRecursiveOnly() || isAllowedForRecursive(request.getRequester()))
|
||||
) {
|
||||
log.info("Using bridge failover for lookup")
|
||||
return bridge.find(request)
|
||||
}
|
||||
@@ -134,6 +147,16 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea
|
||||
return Optional.empty()
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<SingleLookupReply> find(SingleLookupRequest request) {
|
||||
return find(request, false)
|
||||
}
|
||||
|
||||
@Override
|
||||
Optional<SingleLookupReply> findRecursive(SingleLookupRequest request) {
|
||||
return find(request, true)
|
||||
}
|
||||
|
||||
@Override
|
||||
List<ThreePidMapping> find(BulkLookupRequest request) {
|
||||
List<ThreePidMapping> mapToDo = new ArrayList<>(request.getMappings())
|
||||
|
@@ -1,7 +1,27 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.mapping;
|
||||
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.lookup.ThreePid;
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -72,10 +92,10 @@ public class MappingManager {
|
||||
s.validationTimestamp = Instant.now();
|
||||
}
|
||||
|
||||
public Optional<ThreePid> getValidated(String sid, String secret) {
|
||||
public Optional<ThreePidValidation> getValidated(String sid, String secret) {
|
||||
Session s = sessions.get(sid);
|
||||
if (s != null && StringUtils.equals(s.secret, secret)) {
|
||||
return Optional.of(new ThreePid(s.medium, s.address, s.validationTimestamp));
|
||||
return Optional.of(new ThreePidValidation(s.medium, s.address, s.validationTimestamp));
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.mapping;
|
||||
|
||||
public interface MappingSession {
|
||||
|
@@ -20,9 +20,11 @@
|
||||
|
||||
package io.kamax.mxisd.signature
|
||||
|
||||
import com.google.gson.JsonObject
|
||||
import io.kamax.mxisd.config.ServerConfig
|
||||
import io.kamax.mxisd.key.KeyManager
|
||||
import net.i2p.crypto.eddsa.EdDSAEngine
|
||||
import org.json.JSONObject
|
||||
import org.springframework.beans.factory.InitializingBean
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Component
|
||||
@@ -40,14 +42,31 @@ class SignatureManager implements InitializingBean {
|
||||
|
||||
private EdDSAEngine signEngine
|
||||
|
||||
Map<?, ?> signMessage(String message) {
|
||||
private String sign(String message) {
|
||||
byte[] signRaw = signEngine.signOneShot(message.getBytes())
|
||||
String sign = Base64.getEncoder().encodeToString(signRaw)
|
||||
return [
|
||||
"${srvCfg.getName()}": [
|
||||
"ed25519:${keyMgr.getCurrentIndex()}": sign
|
||||
]
|
||||
]
|
||||
return Base64.getEncoder().encodeToString(signRaw)
|
||||
}
|
||||
|
||||
JSONObject signMessageJson(String message) {
|
||||
String sign = sign(message)
|
||||
|
||||
JSONObject keySignature = new JSONObject()
|
||||
keySignature.put("ed25519:${keyMgr.getCurrentIndex()}", sign)
|
||||
JSONObject signature = new JSONObject()
|
||||
signature.put("${srvCfg.getName()}", keySignature)
|
||||
|
||||
return signature
|
||||
}
|
||||
|
||||
JsonObject signMessageGson(String message) {
|
||||
String sign = sign(message)
|
||||
|
||||
JsonObject keySignature = new JsonObject()
|
||||
keySignature.addProperty("ed25519:${keyMgr.getCurrentIndex()}", sign)
|
||||
JsonObject signature = new JsonObject()
|
||||
signature.add("${srvCfg.getName()}", keySignature);
|
||||
|
||||
return signature
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -1,3 +1,23 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.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.spring;
|
||||
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
@@ -8,7 +28,11 @@ public class ConfigurationFailureAnalyzer extends AbstractFailureAnalyzer<Config
|
||||
|
||||
@Override
|
||||
protected FailureAnalysis analyze(Throwable rootFailure, ConfigurationException cause) {
|
||||
return new FailureAnalysis(cause.getMessage(), "Double check the key value", cause);
|
||||
String message = cause.getMessage();
|
||||
if (cause.getDetailedMessage().isPresent()) {
|
||||
message += " - " + cause.getDetailedMessage().get();
|
||||
}
|
||||
return new FailureAnalysis(message, "Double check the key value", cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
36
src/main/groovy/io/kamax/mxisd/storage/IStorage.java
Normal file
36
src/main/groovy/io/kamax/mxisd/storage/IStorage.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.storage;
|
||||
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface IStorage {
|
||||
|
||||
Collection<ThreePidInviteIO> getInvites();
|
||||
|
||||
void insertInvite(IThreePidInviteReply data);
|
||||
|
||||
void deleteInvite(String id);
|
||||
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.storage.ormlite;
|
||||
|
||||
import com.j256.ormlite.dao.CloseableWrappedIterable;
|
||||
import com.j256.ormlite.dao.Dao;
|
||||
import com.j256.ormlite.dao.DaoManager;
|
||||
import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
||||
import com.j256.ormlite.support.ConnectionSource;
|
||||
import com.j256.ormlite.table.TableUtils;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class OrmLiteSqliteStorage implements IStorage {
|
||||
|
||||
private Dao<ThreePidInviteIO, String> invDao;
|
||||
|
||||
OrmLiteSqliteStorage(String path) {
|
||||
try {
|
||||
ConnectionSource connPool = new JdbcConnectionSource("jdbc:sqlite:" + path);
|
||||
invDao = DaoManager.createDao(connPool, ThreePidInviteIO.class);
|
||||
TableUtils.createTableIfNotExists(connPool, ThreePidInviteIO.class);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e); // FIXME do better
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ThreePidInviteIO> getInvites() {
|
||||
try (CloseableWrappedIterable<ThreePidInviteIO> t = invDao.getWrappedIterable()) {
|
||||
List<ThreePidInviteIO> ioList = new ArrayList<>();
|
||||
t.forEach(ioList::add);
|
||||
return ioList;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e); // FIXME do better
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertInvite(IThreePidInviteReply data) {
|
||||
try {
|
||||
int updated = invDao.create(new ThreePidInviteIO(data));
|
||||
if (updated != 1) {
|
||||
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e); // FIXME do better
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteInvite(String id) {
|
||||
try {
|
||||
int updated = invDao.deleteById(id);
|
||||
if (updated != 1) {
|
||||
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e); // FIXME do better
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.storage.ormlite;
|
||||
|
||||
import io.kamax.mxisd.config.SQLiteStorageConfig;
|
||||
import io.kamax.mxisd.config.StorageConfig;
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.FactoryBeanNotInitializedException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Component
|
||||
public class OrmLiteSqliteStorageBeanFactory implements FactoryBean<IStorage> {
|
||||
|
||||
@Autowired
|
||||
private StorageConfig storagecfg;
|
||||
|
||||
@Autowired
|
||||
private SQLiteStorageConfig cfg;
|
||||
|
||||
private OrmLiteSqliteStorage storage;
|
||||
|
||||
@PostConstruct
|
||||
private void postConstruct() {
|
||||
if (StringUtils.equals("sqlite", storagecfg.getBackend())) {
|
||||
if (StringUtils.isBlank(cfg.getDatabase())) {
|
||||
throw new ConfigurationException("storage.provider.sqlite.database");
|
||||
}
|
||||
|
||||
storage = new OrmLiteSqliteStorage(cfg.getDatabase());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IStorage getObject() throws Exception {
|
||||
if (storage == null) {
|
||||
throw new FactoryBeanNotInitializedException();
|
||||
}
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return OrmLiteSqliteStorage.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSingleton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.storage.ormlite;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.j256.ormlite.field.DatabaseField;
|
||||
import com.j256.ormlite.table.DatabaseTable;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@DatabaseTable(tableName = "invite_3pid")
|
||||
public class ThreePidInviteIO {
|
||||
|
||||
private static Gson gson = new Gson();
|
||||
|
||||
@DatabaseField(id = true)
|
||||
private String id;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String token;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String sender;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String medium;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String address;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String roomId;
|
||||
|
||||
@DatabaseField
|
||||
private String properties;
|
||||
|
||||
public ThreePidInviteIO() {
|
||||
// needed for ORMlite
|
||||
}
|
||||
|
||||
public ThreePidInviteIO(IThreePidInviteReply data) {
|
||||
this.id = data.getId();
|
||||
this.token = data.getToken();
|
||||
this.sender = data.getInvite().getSender().getId();
|
||||
this.medium = data.getInvite().getMedium();
|
||||
this.address = data.getInvite().getAddress();
|
||||
this.roomId = data.getInvite().getRoomId();
|
||||
this.properties = gson.toJson(data.getInvite().getProperties());
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getSender() {
|
||||
return sender;
|
||||
}
|
||||
|
||||
public String getMedium() {
|
||||
return medium;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public String getRoomId() {
|
||||
return roomId;
|
||||
}
|
||||
|
||||
public Map<String, String> getProperties() {
|
||||
if (StringUtils.isBlank(properties)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
return gson.fromJson(properties, new TypeToken<Map<String, String>>() {
|
||||
}.getType());
|
||||
}
|
||||
|
||||
}
|
45
src/main/resources/application.yaml
Normal file
45
src/main/resources/application.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
logging:
|
||||
level:
|
||||
org:
|
||||
springframework: "WARN"
|
||||
apache:
|
||||
catalina: "WARN"
|
||||
directory: "WARN"
|
||||
|
||||
server:
|
||||
port: 8090
|
||||
|
||||
lookup:
|
||||
recursive:
|
||||
enabled: true
|
||||
allowedCidr:
|
||||
- '127.0.0.0/8'
|
||||
- '10.0.0.0/8'
|
||||
- '172.16.0.0/12'
|
||||
- '192.168.0.0/16'
|
||||
- '::1/128'
|
||||
|
||||
bridge:
|
||||
enabled: false
|
||||
recursiveOnly: true
|
||||
|
||||
ldap:
|
||||
enabled: false
|
||||
|
||||
firebase:
|
||||
enabled: false
|
||||
|
||||
forward:
|
||||
servers:
|
||||
- "https://matrix.org"
|
||||
- "https://vector.im"
|
||||
|
||||
invite:
|
||||
sender:
|
||||
email:
|
||||
tls: 1
|
||||
name: "mxisd Identity Server"
|
||||
template: "classpath:email/invite-template.eml"
|
||||
|
||||
storage:
|
||||
backend: 'sqlite'
|
97
src/main/resources/email/invite-template.eml
Normal file
97
src/main/resources/email/invite-template.eml
Normal file
@@ -0,0 +1,97 @@
|
||||
Subject: You have been invited to %DOMAIN%
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ"
|
||||
|
||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Disposition: inline
|
||||
|
||||
Hi,
|
||||
|
||||
%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
||||
Matrix. To join the conversation, register an account on http://%DOMAIN%
|
||||
|
||||
You can also register an account on a public server, like Matrix.org, by going to
|
||||
https://riot.im/app/#/register?%INVITE_MEDIUM%=%INVITE_ADDRESS%
|
||||
|
||||
|
||||
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,
|
||||
|
||||
%DOMAIN_PRETTY% Admins
|
||||
|
||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ
|
||||
Content-Type: multipart/related;
|
||||
boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR";
|
||||
type="text/html"
|
||||
|
||||
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR
|
||||
Content-Type: text/html; charset=UTF-8
|
||||
Content-Disposition: inline
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#page {
|
||||
font-family: 'Open Sans', Helvetica, Arial, Sans-Serif;
|
||||
font-color: #454545;
|
||||
font-size: 12pt;
|
||||
width: 100%%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#inner {
|
||||
width: 640px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table id="page">
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td id="inner">
|
||||
<p>Hi,</p>
|
||||
|
||||
<p>%SENDER_NAME_OR_ID% has invited you into a room [%ROOM_NAME_OR_ID%] on
|
||||
Matrix. To join the conversation, register an account on <a href="http://%DOMAIN%">%DOMAIN%</a>.</p>
|
||||
|
||||
<p>You can also register an account on a public server, like Matrix.org, by following
|
||||
<a href="https://riot.im/app/#/register?%INVITE_MEDIUM%=%INVITE_ADDRESS%">this link</a>.</p>
|
||||
|
||||
<br>
|
||||
<p>About Matrix:</p>
|
||||
|
||||
<p>Matrix is an open standard for interoperable, decentralised, real-time communication
|
||||
over IP, supporting group chat, file transfer, voice and video calling, integrations to
|
||||
other apps, bridges to other communication systems and much more. It can be used to power
|
||||
Instant Messaging, VoIP/WebRTC signalling, Internet of Things communication.</p>
|
||||
|
||||
<p>Thanks,</p>
|
||||
|
||||
<p>%DOMAIN_PRETTY% Admins</p>
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR--
|
||||
|
||||
--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ--
|
Reference in New Issue
Block a user