Prepare structure to handle 3PID sessions and bindings validation/proxy
This commit is contained in:
@@ -26,6 +26,10 @@ public class ThreePid {
|
|||||||
private String medium;
|
private String medium;
|
||||||
private String address;
|
private String address;
|
||||||
|
|
||||||
|
public ThreePid(ThreePid tpid) {
|
||||||
|
this(tpid.getMedium(), tpid.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
public ThreePid(String medium, String address) {
|
public ThreePid(String medium, String address) {
|
||||||
this.medium = medium;
|
this.medium = medium;
|
||||||
this.address = address;
|
this.address = address;
|
||||||
|
@@ -20,12 +20,9 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.config.invite.medium;
|
package io.kamax.mxisd.config.invite.medium;
|
||||||
|
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.apache.commons.lang.WordUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
@@ -38,33 +35,8 @@ public class EmailInviteConfig {
|
|||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(EmailInviteConfig.class);
|
private Logger log = LoggerFactory.getLogger(EmailInviteConfig.class);
|
||||||
|
|
||||||
private MatrixConfig mxCfg;
|
|
||||||
|
|
||||||
private String from;
|
|
||||||
private String name;
|
|
||||||
private String template;
|
private String template;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public EmailInviteConfig(MatrixConfig mxCfg) {
|
|
||||||
this.mxCfg = mxCfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFrom() {
|
|
||||||
return from;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFrom(String from) {
|
|
||||||
this.from = from;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTemplate() {
|
public String getTemplate() {
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
@@ -76,12 +48,6 @@ public class EmailInviteConfig {
|
|||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void build() {
|
public void build() {
|
||||||
log.info("--- E-mail invites config ---");
|
log.info("--- E-mail invites config ---");
|
||||||
log.info("From: {}", getFrom());
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(getName())) {
|
|
||||||
setName(WordUtils.capitalize(mxCfg.getDomain()) + " Identity Server");
|
|
||||||
}
|
|
||||||
log.info("Name: {}", getName());
|
|
||||||
|
|
||||||
if (!StringUtils.startsWith(getTemplate(), "classpath:")) {
|
if (!StringUtils.startsWith(getTemplate(), "classpath:")) {
|
||||||
if (StringUtils.isBlank(getTemplate())) {
|
if (StringUtils.isBlank(getTemplate())) {
|
||||||
|
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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.threepid.medium;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
|
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.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConfigurationProperties("threepid.medium.email")
|
||||||
|
public class EmailConfig {
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(EmailConfig.class);
|
||||||
|
|
||||||
|
private MatrixConfig mxCfg;
|
||||||
|
|
||||||
|
private String from;
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public EmailConfig(MatrixConfig mxCfg) {
|
||||||
|
this.mxCfg = mxCfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFrom() {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrom(String from) {
|
||||||
|
this.from = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void build() {
|
||||||
|
log.info("--- E-mail config ---");
|
||||||
|
log.info("From: {}", getFrom());
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(getName())) {
|
||||||
|
setName(WordUtils.capitalize(mxCfg.getDomain()) + " Identity Server");
|
||||||
|
}
|
||||||
|
log.info("Name: {}", getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -22,6 +22,8 @@ package io.kamax.mxisd.controller.v1
|
|||||||
|
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
|
import io.kamax.matrix.ThreePidMedium
|
||||||
|
import io.kamax.mxisd.ThreePid
|
||||||
import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson
|
import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson
|
||||||
import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson
|
import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson
|
||||||
import io.kamax.mxisd.exception.BadRequestException
|
import io.kamax.mxisd.exception.BadRequestException
|
||||||
@@ -29,7 +31,6 @@ import io.kamax.mxisd.invitation.InvitationManager
|
|||||||
import io.kamax.mxisd.lookup.ThreePidValidation
|
import io.kamax.mxisd.lookup.ThreePidValidation
|
||||||
import io.kamax.mxisd.mapping.MappingManager
|
import io.kamax.mxisd.mapping.MappingManager
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
import org.apache.commons.lang.StringUtils
|
|
||||||
import org.apache.http.HttpStatus
|
import org.apache.http.HttpStatus
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@@ -62,16 +63,25 @@ class SessionController {
|
|||||||
|
|
||||||
@RequestMapping(value = "/validate/{medium}/requestToken")
|
@RequestMapping(value = "/validate/{medium}/requestToken")
|
||||||
String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) {
|
String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) {
|
||||||
log.info("Requested: {}", request.getRequestURL(), request.getQueryString())
|
log.info("Request {}: {}", request.getMethod(), request.getRequestURL(), request.getQueryString())
|
||||||
|
if (ThreePidMedium.Email.is(medium)) {
|
||||||
if (StringUtils.equals("email", medium)) {
|
|
||||||
SessionEmailTokenRequestJson req = fromJson(request, SessionEmailTokenRequestJson.class)
|
SessionEmailTokenRequestJson req = fromJson(request, SessionEmailTokenRequestJson.class)
|
||||||
return gson.toJson(new Sid(mgr.create(req)))
|
return gson.toJson(new Sid(mgr.create(
|
||||||
|
request.getRemoteHost(),
|
||||||
|
new ThreePid(req.getMedium(), req.getValue()),
|
||||||
|
req.getSecret(),
|
||||||
|
req.getAttempt(),
|
||||||
|
req.getNextLink())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtils.equals("msisdn", medium)) {
|
if (ThreePidMedium.PhoneNumber) {
|
||||||
SessionPhoneTokenRequestJson req = fromJson(request, SessionPhoneTokenRequestJson.class)
|
SessionPhoneTokenRequestJson req = fromJson(request, SessionPhoneTokenRequestJson.class)
|
||||||
return gson.toJson(new Sid(mgr.create(req)))
|
return gson.toJson(new Sid(mgr.create(
|
||||||
|
request.getRemoteHost(),
|
||||||
|
new ThreePid(req.getMedium(), req.getValue()),
|
||||||
|
req.getSecret(),
|
||||||
|
req.getAttempt(),
|
||||||
|
req.getNextLink())));
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject obj = new JsonObject();
|
JsonObject obj = new JsonObject();
|
||||||
|
@@ -20,13 +20,11 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.controller.v1.io;
|
package io.kamax.mxisd.controller.v1.io;
|
||||||
|
|
||||||
import io.kamax.mxisd.mapping.MappingSession;
|
public abstract class GenericTokenRequestJson {
|
||||||
|
|
||||||
public abstract class GenericTokenRequestJson implements MappingSession {
|
|
||||||
|
|
||||||
private String client_secret;
|
private String client_secret;
|
||||||
private int send_attempt;
|
private int send_attempt;
|
||||||
private String id_server;
|
private String next_link;
|
||||||
|
|
||||||
public String getSecret() {
|
public String getSecret() {
|
||||||
return client_secret;
|
return client_secret;
|
||||||
@@ -36,8 +34,8 @@ public abstract class GenericTokenRequestJson implements MappingSession {
|
|||||||
return send_attempt;
|
return send_attempt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getServer() {
|
public String getNextLink() {
|
||||||
return id_server;
|
return next_link;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -24,12 +24,10 @@ public class SessionEmailTokenRequestJson extends GenericTokenRequestJson {
|
|||||||
|
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMedium() {
|
public String getMedium() {
|
||||||
return "email";
|
return "email";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getValue() {
|
public String getValue() {
|
||||||
return email;
|
return email;
|
||||||
}
|
}
|
||||||
|
@@ -31,12 +31,10 @@ public class SessionPhoneTokenRequestJson extends GenericTokenRequestJson {
|
|||||||
private String country;
|
private String country;
|
||||||
private String phone_number;
|
private String phone_number;
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMedium() {
|
public String getMedium() {
|
||||||
return "msisdn";
|
return "msisdn";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getValue() {
|
public String getValue() {
|
||||||
try {
|
try {
|
||||||
Phonenumber.PhoneNumber num = phoneUtil.parse(phone_number, country);
|
Phonenumber.PhoneNumber num = phoneUtil.parse(phone_number, country);
|
||||||
|
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public class InternalServerError extends RuntimeException {
|
||||||
|
|
||||||
|
public InternalServerError() {
|
||||||
|
super("An internal server error occured. If this error persists, please contact support with reference #" + Instant.now().toEpochMilli());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(value = HttpStatus.FORBIDDEN)
|
||||||
|
public class InvalidCredentialsException extends RuntimeException {
|
||||||
|
|
||||||
|
public InvalidCredentialsException() {
|
||||||
|
super("Supplied credentials are invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(value = HttpStatus.NOT_FOUND)
|
||||||
|
public class ObjectNotFoundException extends RuntimeException {
|
||||||
|
|
||||||
|
public ObjectNotFoundException(String type, String id) {
|
||||||
|
super(type + " with ID " + id + " does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -23,6 +23,7 @@ package io.kamax.mxisd.invitation.generator;
|
|||||||
import io.kamax.matrix.ThreePidMedium;
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
import io.kamax.mxisd.config.MatrixConfig;
|
import io.kamax.mxisd.config.MatrixConfig;
|
||||||
import io.kamax.mxisd.config.invite.medium.EmailInviteConfig;
|
import io.kamax.mxisd.config.invite.medium.EmailInviteConfig;
|
||||||
|
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
@@ -38,13 +39,15 @@ import java.nio.charset.StandardCharsets;
|
|||||||
@Component
|
@Component
|
||||||
public class EmailInviteContentGenerator implements IInviteContentGenerator {
|
public class EmailInviteContentGenerator implements IInviteContentGenerator {
|
||||||
|
|
||||||
private EmailInviteConfig cfg;
|
private EmailConfig cfg;
|
||||||
|
private EmailInviteConfig invCfg;
|
||||||
private MatrixConfig mxCfg;
|
private MatrixConfig mxCfg;
|
||||||
private ApplicationContext app;
|
private ApplicationContext app;
|
||||||
|
|
||||||
@Autowired
|
@Autowired // FIXME ApplicationContext shouldn't be injected, find another way from config (?)
|
||||||
public EmailInviteContentGenerator(EmailInviteConfig cfg, MatrixConfig mxCfg, ApplicationContext app) {
|
public EmailInviteContentGenerator(EmailConfig cfg, EmailInviteConfig invCfg, MatrixConfig mxCfg, ApplicationContext app) {
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
|
this.invCfg = invCfg;
|
||||||
this.mxCfg = mxCfg;
|
this.mxCfg = mxCfg;
|
||||||
this.app = app;
|
this.app = app;
|
||||||
}
|
}
|
||||||
@@ -68,8 +71,8 @@ public class EmailInviteContentGenerator implements IInviteContentGenerator {
|
|||||||
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
|
String roomNameOrId = StringUtils.defaultIfBlank(roomName, invite.getInvite().getRoomId());
|
||||||
|
|
||||||
String templateBody = IOUtils.toString(
|
String templateBody = IOUtils.toString(
|
||||||
StringUtils.startsWith(cfg.getTemplate(), "classpath:") ?
|
StringUtils.startsWith(invCfg.getTemplate(), "classpath:") ?
|
||||||
app.getResource(cfg.getTemplate()).getInputStream() : new FileInputStream(cfg.getTemplate()),
|
app.getResource(invCfg.getTemplate()).getInputStream() : new FileInputStream(invCfg.getTemplate()),
|
||||||
StandardCharsets.UTF_8);
|
StandardCharsets.UTF_8);
|
||||||
templateBody = templateBody.replace("%DOMAIN%", mxCfg.getDomain());
|
templateBody = templateBody.replace("%DOMAIN%", mxCfg.getDomain());
|
||||||
templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty);
|
templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty);
|
||||||
|
@@ -28,8 +28,8 @@ public class ThreePidValidation extends ThreePid {
|
|||||||
|
|
||||||
private Instant validation;
|
private Instant validation;
|
||||||
|
|
||||||
public ThreePidValidation(String medium, String address, Instant validation) {
|
public ThreePidValidation(ThreePid tpid, Instant validation) {
|
||||||
super(medium, address);
|
super(tpid);
|
||||||
this.validation = validation;
|
this.validation = validation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -32,6 +32,10 @@ interface LookupStrategy {
|
|||||||
|
|
||||||
Optional<SingleLookupReply> find(String medium, String address, boolean recursive)
|
Optional<SingleLookupReply> find(String medium, String address, boolean recursive)
|
||||||
|
|
||||||
|
Optional<SingleLookupReply> findLocal(String medium, String address);
|
||||||
|
|
||||||
|
Optional<SingleLookupReply> findRemote(String medium, String address);
|
||||||
|
|
||||||
Optional<SingleLookupReply> find(SingleLookupRequest request)
|
Optional<SingleLookupReply> find(SingleLookupRequest request)
|
||||||
|
|
||||||
Optional<SingleLookupReply> findRecursive(SingleLookupRequest request)
|
Optional<SingleLookupReply> findRecursive(SingleLookupRequest request)
|
||||||
|
@@ -118,17 +118,44 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea
|
|||||||
}).collect(Collectors.toList())
|
}).collect(Collectors.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
List<IThreePidProvider> getRemoteProviders() {
|
||||||
Optional<SingleLookupReply> find(String medium, String address, boolean recursive) {
|
return providers.stream().filter(new Predicate<IThreePidProvider>() {
|
||||||
|
@Override
|
||||||
|
boolean test(IThreePidProvider iThreePidProvider) {
|
||||||
|
return iThreePidProvider.isEnabled() && !iThreePidProvider.isLocal()
|
||||||
|
}
|
||||||
|
}).collect(Collectors.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SingleLookupRequest build(String medium, String address) {
|
||||||
SingleLookupRequest req = new SingleLookupRequest();
|
SingleLookupRequest req = new SingleLookupRequest();
|
||||||
req.setType(medium)
|
req.setType(medium)
|
||||||
req.setThreePid(address)
|
req.setThreePid(address)
|
||||||
req.setRequester("Internal")
|
req.setRequester("Internal")
|
||||||
return find(req, recursive)
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Optional<SingleLookupReply> find(String medium, String address, boolean recursive) {
|
||||||
|
return find(build(medium, address), recursive)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Optional<SingleLookupReply> findLocal(String medium, String address) {
|
||||||
|
return find(build(medium, address), getLocalProviders())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Optional<SingleLookupReply> findRemote(String medium, String address) {
|
||||||
|
return find(build(medium, address), getRemoteProviders())
|
||||||
}
|
}
|
||||||
|
|
||||||
Optional<SingleLookupReply> find(SingleLookupRequest request, boolean forceRecursive) {
|
Optional<SingleLookupReply> find(SingleLookupRequest request, boolean forceRecursive) {
|
||||||
for (IThreePidProvider provider : listUsableProviders(request, forceRecursive)) {
|
return find(request, listUsableProviders(request, forceRecursive));
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<SingleLookupReply> find(SingleLookupRequest request, List<IThreePidProvider> providers) {
|
||||||
|
for (IThreePidProvider provider : providers) {
|
||||||
Optional<SingleLookupReply> lookupDataOpt = provider.find(request)
|
Optional<SingleLookupReply> lookupDataOpt = provider.find(request)
|
||||||
if (lookupDataOpt.isPresent()) {
|
if (lookupDataOpt.isPresent()) {
|
||||||
return lookupDataOpt
|
return lookupDataOpt
|
||||||
|
@@ -20,155 +20,167 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.mapping;
|
package io.kamax.mxisd.mapping;
|
||||||
|
|
||||||
import io.kamax.mxisd.exception.BadRequestException;
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
|
import io.kamax.mxisd.ThreePid;
|
||||||
|
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||||
|
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||||
|
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||||
|
import io.kamax.mxisd.storage.IStorage;
|
||||||
|
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||||
|
import io.kamax.mxisd.threepid.session.ThreePidSession;
|
||||||
|
import org.apache.commons.lang.RandomStringUtils;
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.util.Optional;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.util.UUID;
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class MappingManager {
|
public class MappingManager {
|
||||||
|
|
||||||
private Logger log = LoggerFactory.getLogger(MappingManager.class);
|
private Logger log = LoggerFactory.getLogger(MappingManager.class);
|
||||||
|
|
||||||
private Map<String, Session> sessions = new HashMap<>();
|
private IStorage storage;
|
||||||
private Timer cleaner;
|
private LookupStrategy lookup;
|
||||||
|
|
||||||
MappingManager() {
|
@Autowired
|
||||||
cleaner = new Timer();
|
public MappingManager(IStorage storage, LookupStrategy lookup) {
|
||||||
cleaner.schedule(new TimerTask() {
|
this.storage = storage;
|
||||||
@Override
|
}
|
||||||
public void run() {
|
|
||||||
List<Session> sList = new ArrayList<>(sessions.values());
|
|
||||||
for (Session s : sList) {
|
|
||||||
if (s.timestamp.plus(24, ChronoUnit.HOURS).isBefore(Instant.now())) { // TODO config timeout
|
|
||||||
log.info("Session {} is obsolete, removing", s.sid);
|
|
||||||
|
|
||||||
sessions.remove(s.sid);
|
private ThreePidSession getSession(String sid, String secret) {
|
||||||
}
|
Optional<IThreePidSessionDao> dao = storage.getThreePidSession(sid);
|
||||||
|
if (!dao.isPresent() || !StringUtils.equals(dao.get().getSecret(), secret)) {
|
||||||
|
throw new InvalidCredentialsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ThreePidSession(dao.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThreePidSession getSessionIfValidated(String sid, String secret) {
|
||||||
|
ThreePidSession session = getSession(sid, secret);
|
||||||
|
if (!session.isValidated()) {
|
||||||
|
throw new IllegalStateException("Session " + sid + " has not been validated");
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String create(String server, ThreePid tpid, String secret, int attempt, String nextLink) {
|
||||||
|
synchronized (this) {
|
||||||
|
log.info("Server {} is asking to create session for {} (Attempt #{}) - Next link: {}", server, tpid, attempt, nextLink);
|
||||||
|
Optional<IThreePidSessionDao> dao = storage.findThreePidSession(tpid, secret);
|
||||||
|
if (dao.isPresent()) {
|
||||||
|
ThreePidSession session = new ThreePidSession(dao.get());
|
||||||
|
log.info("We already have a session for {}: {}", tpid, session.getId());
|
||||||
|
if (session.getAttempt() < attempt) {
|
||||||
|
log.info("Received attempt {} is greater than stored attempt {}, sending validation communication", attempt, session.getAttempt());
|
||||||
|
// TODO send via connector
|
||||||
|
session.increaseAttempt();
|
||||||
|
storage.updateThreePidSession(session.getDao());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return session.getId();
|
||||||
|
} else {
|
||||||
|
log.info("No existing session for {}", tpid);
|
||||||
|
String sessionId;
|
||||||
|
do {
|
||||||
|
sessionId = UUID.randomUUID().toString().replace("-", "");
|
||||||
|
} while (storage.getThreePidSession(sessionId).isPresent());
|
||||||
|
|
||||||
|
String token = RandomStringUtils.randomNumeric(6);
|
||||||
|
ThreePidSession session = new ThreePidSession(sessionId, server, tpid, secret, attempt, nextLink, token);
|
||||||
|
log.info("Generated new session {} to validate {} from server {}", sessionId, tpid, server);
|
||||||
|
|
||||||
|
// TODO send via connector
|
||||||
|
// log.info("Sent validation notification to {}", tpid);
|
||||||
|
|
||||||
|
storage.insertThreePidSession(session.getDao());
|
||||||
|
log.info("Stored session {}", sessionId, tpid, server);
|
||||||
|
|
||||||
|
return sessionId;
|
||||||
}
|
}
|
||||||
}, 0, 10 * 1000); // TODO config delay
|
|
||||||
}
|
|
||||||
|
|
||||||
public String create(MappingSession data) {
|
|
||||||
String sid;
|
|
||||||
do {
|
|
||||||
sid = Long.toString(System.currentTimeMillis());
|
|
||||||
} while (sessions.containsKey(sid));
|
|
||||||
|
|
||||||
String threePidHash = data.getMedium() + data.getValue();
|
|
||||||
// TODO think how to handle different requests for the same e-mail
|
|
||||||
Session session = new Session(sid, threePidHash, data);
|
|
||||||
sessions.put(sid, session);
|
|
||||||
|
|
||||||
log.info("Created new session {} to validate {} {}", sid, session.medium, session.address);
|
|
||||||
return sid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void validate(String sid, String secret, String token) {
|
|
||||||
Session s = sessions.get(sid);
|
|
||||||
if (s == null || !StringUtils.equals(s.secret, secret)) {
|
|
||||||
throw new BadRequestException("sid or secret are not valid");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO actually check token
|
|
||||||
|
|
||||||
s.isValidated = true;
|
|
||||||
s.validationTimestamp = Instant.now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ThreePidValidation> getValidated(String sid, String secret) {
|
public Optional<String> validate(String sid, String secret, String token) {
|
||||||
Session s = sessions.get(sid);
|
ThreePidSession session = getSession(sid, secret);
|
||||||
if (s != null && StringUtils.equals(s.secret, secret)) {
|
log.info("Attempting validation for session {} from {}", session.getId(), session.getServer());
|
||||||
return Optional.of(new ThreePidValidation(s.medium, s.address, s.validationTimestamp));
|
session.validate(token);
|
||||||
}
|
storage.updateThreePidSession(session.getDao());
|
||||||
|
log.info("Session {} has been validated", session.getId());
|
||||||
|
return session.getNextLink();
|
||||||
|
}
|
||||||
|
|
||||||
return Optional.empty();
|
public ThreePidValidation getValidated(String sid, String secret) {
|
||||||
|
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||||
|
return new ThreePidValidation(session.getThreePid(), session.getValidationTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(String sid, String secret, String mxid) {
|
public void bind(String sid, String secret, String mxid) {
|
||||||
Session s = sessions.get(sid);
|
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||||
if (s == null || !StringUtils.equals(s.secret, secret)) {
|
log.info("Attempting bind of {} on session {} from server {}", mxid, session.getId(), session.getServer());
|
||||||
throw new BadRequestException("sid or secret are not valid");
|
|
||||||
|
// We lookup if the 3PID is already known locally.
|
||||||
|
// If it is, we do not need to process any further as it is already bound.
|
||||||
|
Optional<SingleLookupReply> rLocal = lookup.findLocal(session.getThreePid().getMedium(), session.getThreePid().getAddress());
|
||||||
|
boolean knownLocal = rLocal.isPresent() && StringUtils.equals(rLocal.get().getMxid().getId(), mxid);
|
||||||
|
log.info("Mapping {} -> {} is " + (knownLocal ? "already" : "not") + " known locally", mxid, session.getThreePid());
|
||||||
|
|
||||||
|
// MXID is not known locally, checking remotely
|
||||||
|
Optional<SingleLookupReply> rRemote = lookup.findRemote(session.getThreePid().getMedium(), session.getThreePid().getAddress());
|
||||||
|
boolean knownRemote = rRemote.isPresent() && StringUtils.equals(rRemote.get().getMxid().getId(), mxid);
|
||||||
|
log.info("Mapping {} -> {} is " + (knownRemote ? "already" : "not") + " known remotely", mxid, session.getThreePid());
|
||||||
|
|
||||||
|
boolean isLocalDomain = false;
|
||||||
|
if (ThreePidMedium.Email.is(session.getThreePid().getMedium())) {
|
||||||
|
// TODO
|
||||||
|
// 1. Extract domain from email
|
||||||
|
// 2. set isLocalDomain
|
||||||
|
isLocalDomain = session.getThreePid().getAddress().isEmpty(); // FIXME only for testing
|
||||||
|
}
|
||||||
|
if (knownRemote) {
|
||||||
|
if (isLocalDomain && !knownLocal) {
|
||||||
|
log.warn("Mapping {} -> {} is not known locally but is about a local domain!");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("No further action needed for Mapping {} -> {}");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Performed bind for mxid {}", mxid);
|
// This might need a configuration by medium type?
|
||||||
// TODO perform bind, whatever it is
|
if (knownLocal) { // 3PID is ony known local
|
||||||
}
|
if (isLocalDomain) {
|
||||||
|
// TODO
|
||||||
|
// 1. Check if global publishing is enabled, allowed and offered. If one is no, return.
|
||||||
|
// 2. Publish globally
|
||||||
|
}
|
||||||
|
|
||||||
private class Session {
|
if (System.currentTimeMillis() % 2 == 0) {
|
||||||
|
// TODO
|
||||||
|
// 1. Check if configured to publish globally non-local domain. If no, return
|
||||||
|
}
|
||||||
|
|
||||||
private String sid;
|
// TODO
|
||||||
private String hash;
|
// Proxy to configurable IS, by default Matrix.org
|
||||||
private Instant timestamp;
|
//
|
||||||
private Instant validationTimestamp;
|
// Separate workflow, if user accepts to publish globally
|
||||||
private boolean isValidated;
|
// 1. display page to the user that it is waiting for the confirmation
|
||||||
private String secret;
|
// 2. call mxisd-specific endpoint to publish globally
|
||||||
private String medium;
|
// 3. check regularly on client page for a binding
|
||||||
private String address;
|
// 4. when found, show page "Done globally!"
|
||||||
|
} else {
|
||||||
public Session(String sid, String hash, MappingSession data) {
|
if (isLocalDomain) { // 3PID is not known anywhere but is a local domain
|
||||||
this.sid = sid;
|
// TODO
|
||||||
this.hash = hash;
|
// check if config says this should fail or silently accept.
|
||||||
timestamp = Instant.now();
|
// Required to silently accept if the backend is synapse itself.
|
||||||
validationTimestamp = Instant.now();
|
} else { // 3PID is not known anywhere and is remote
|
||||||
secret = data.getSecret();
|
// TODO
|
||||||
medium = data.getMedium();
|
// Proxy to configurable IS, by default Matrix.org
|
||||||
address = data.getValue();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTimestamp(Instant timestamp) {
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getValidationTimestamp() {
|
|
||||||
return validationTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValidationTimestamp(Instant validationTimestamp) {
|
|
||||||
this.validationTimestamp = validationTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValidated() {
|
|
||||||
return isValidated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValidated(boolean validated) {
|
|
||||||
isValidated = validated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSecret() {
|
|
||||||
return secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSecret(String secret) {
|
|
||||||
this.secret = secret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getMedium() {
|
|
||||||
return medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMedium(String medium) {
|
|
||||||
this.medium = medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAddress(String address) {
|
|
||||||
this.address = address;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,10 +20,13 @@
|
|||||||
|
|
||||||
package io.kamax.mxisd.storage;
|
package io.kamax.mxisd.storage;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.ThreePid;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
|
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||||
import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO;
|
import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface IStorage {
|
public interface IStorage {
|
||||||
|
|
||||||
@@ -33,4 +36,12 @@ public interface IStorage {
|
|||||||
|
|
||||||
void deleteInvite(String id);
|
void deleteInvite(String id);
|
||||||
|
|
||||||
|
Optional<IThreePidSessionDao> getThreePidSession(String sid);
|
||||||
|
|
||||||
|
Optional<IThreePidSessionDao> findThreePidSession(ThreePid tpid, String secret);
|
||||||
|
|
||||||
|
void insertThreePidSession(IThreePidSessionDao session);
|
||||||
|
|
||||||
|
void updateThreePidSession(IThreePidSessionDao session);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -18,18 +18,24 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.kamax.mxisd.mapping;
|
package io.kamax.mxisd.storage.dao;
|
||||||
|
|
||||||
public interface MappingSession {
|
public interface IThreePidSessionDao {
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
String getServer();
|
String getServer();
|
||||||
|
|
||||||
|
String getMedium();
|
||||||
|
|
||||||
|
String getAddress();
|
||||||
|
|
||||||
String getSecret();
|
String getSecret();
|
||||||
|
|
||||||
int getAttempt();
|
int getAttempt();
|
||||||
|
|
||||||
String getMedium();
|
String getNextLink();
|
||||||
|
|
||||||
String getValue();
|
String getToken();
|
||||||
|
|
||||||
}
|
}
|
@@ -26,8 +26,14 @@ import com.j256.ormlite.dao.DaoManager;
|
|||||||
import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
import com.j256.ormlite.jdbc.JdbcConnectionSource;
|
||||||
import com.j256.ormlite.support.ConnectionSource;
|
import com.j256.ormlite.support.ConnectionSource;
|
||||||
import com.j256.ormlite.table.TableUtils;
|
import com.j256.ormlite.table.TableUtils;
|
||||||
|
import io.kamax.mxisd.ThreePid;
|
||||||
|
import io.kamax.mxisd.exception.InternalServerError;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
import io.kamax.mxisd.storage.IStorage;
|
import io.kamax.mxisd.storage.IStorage;
|
||||||
|
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||||
|
import io.kamax.mxisd.storage.ormlite.dao.ThreePidSessionDao;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -35,59 +41,141 @@ import java.sql.SQLException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class OrmLiteSqliteStorage implements IStorage {
|
public class OrmLiteSqliteStorage implements IStorage {
|
||||||
|
|
||||||
|
private Logger log = LoggerFactory.getLogger(OrmLiteSqliteStorage.class);
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface Getter<T> {
|
||||||
|
|
||||||
|
T get() throws SQLException, IOException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
private interface Doer {
|
||||||
|
|
||||||
|
void run() throws SQLException, IOException;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private Dao<ThreePidInviteIO, String> invDao;
|
private Dao<ThreePidInviteIO, String> invDao;
|
||||||
|
private Dao<ThreePidSessionDao, String> sessionDao;
|
||||||
|
|
||||||
OrmLiteSqliteStorage(String path) {
|
OrmLiteSqliteStorage(String path) {
|
||||||
try {
|
withCatcher(() -> {
|
||||||
File parent = new File(path).getParentFile();
|
File parent = new File(path).getParentFile();
|
||||||
if (!parent.mkdirs() && !parent.isDirectory()) {
|
if (!parent.mkdirs() && !parent.isDirectory()) {
|
||||||
throw new RuntimeException("Unable to create DB parent directory: " + parent);
|
throw new RuntimeException("Unable to create DB parent directory: " + parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectionSource connPool = new JdbcConnectionSource("jdbc:sqlite:" + path);
|
ConnectionSource connPool = new JdbcConnectionSource("jdbc:sqlite:" + path);
|
||||||
invDao = DaoManager.createDao(connPool, ThreePidInviteIO.class);
|
invDao = createDaoAndTable(connPool, ThreePidInviteIO.class);
|
||||||
TableUtils.createTableIfNotExists(connPool, ThreePidInviteIO.class);
|
sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class);
|
||||||
} catch (SQLException e) {
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private <V, K> Dao<V, K> createDaoAndTable(ConnectionSource connPool, Class<V> c) throws SQLException {
|
||||||
|
Dao<V, K> dao = DaoManager.createDao(connPool, c);
|
||||||
|
TableUtils.createTableIfNotExists(connPool, c);
|
||||||
|
return dao;
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T withCatcher(Getter<T> g) {
|
||||||
|
try {
|
||||||
|
return g.get();
|
||||||
|
} catch (SQLException | IOException e) {
|
||||||
throw new RuntimeException(e); // FIXME do better
|
throw new RuntimeException(e); // FIXME do better
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void withCatcher(Doer d) {
|
||||||
|
try {
|
||||||
|
d.run();
|
||||||
|
} catch (SQLException | IOException e) {
|
||||||
|
throw new RuntimeException(e); // FIXME do better
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> List<T> forIterable(CloseableWrappedIterable<? extends T> t) {
|
||||||
|
return withCatcher(() -> {
|
||||||
|
try {
|
||||||
|
List<T> ioList = new ArrayList<>();
|
||||||
|
t.forEach(ioList::add);
|
||||||
|
return ioList;
|
||||||
|
} finally {
|
||||||
|
t.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<ThreePidInviteIO> getInvites() {
|
public Collection<ThreePidInviteIO> getInvites() {
|
||||||
try (CloseableWrappedIterable<ThreePidInviteIO> t = invDao.getWrappedIterable()) {
|
return forIterable(invDao.getWrappedIterable());
|
||||||
List<ThreePidInviteIO> ioList = new ArrayList<>();
|
|
||||||
t.forEach(ioList::add);
|
|
||||||
return ioList;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e); // FIXME do better
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void insertInvite(IThreePidInviteReply data) {
|
public void insertInvite(IThreePidInviteReply data) {
|
||||||
try {
|
withCatcher(() -> {
|
||||||
int updated = invDao.create(new ThreePidInviteIO(data));
|
int updated = invDao.create(new ThreePidInviteIO(data));
|
||||||
if (updated != 1) {
|
if (updated != 1) {
|
||||||
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
});
|
||||||
throw new RuntimeException(e); // FIXME do better
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteInvite(String id) {
|
public void deleteInvite(String id) {
|
||||||
try {
|
withCatcher(() -> {
|
||||||
int updated = invDao.deleteById(id);
|
int updated = invDao.deleteById(id);
|
||||||
if (updated != 1) {
|
if (updated != 1) {
|
||||||
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
});
|
||||||
throw new RuntimeException(e); // FIXME do better
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public Optional<IThreePidSessionDao> getThreePidSession(String sid) {
|
||||||
|
return withCatcher(() -> Optional.ofNullable(sessionDao.queryForId(sid)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<IThreePidSessionDao> findThreePidSession(ThreePid tpid, String secret) {
|
||||||
|
return withCatcher(() -> {
|
||||||
|
List<ThreePidSessionDao> daoList = sessionDao.queryForMatchingArgs(new ThreePidSessionDao(tpid, secret));
|
||||||
|
if (daoList.size() > 1) {
|
||||||
|
log.error("Lookup for 3PID Session {}:{} returned more than one result");
|
||||||
|
throw new InternalServerError();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (daoList.isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.of(daoList.get(0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insertThreePidSession(IThreePidSessionDao session) {
|
||||||
|
withCatcher(() -> {
|
||||||
|
int updated = sessionDao.create(new ThreePidSessionDao(session));
|
||||||
|
if (updated != 1) {
|
||||||
|
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateThreePidSession(IThreePidSessionDao session) {
|
||||||
|
withCatcher(() -> {
|
||||||
|
int updated = sessionDao.update(new ThreePidSessionDao(session));
|
||||||
|
if (updated != 1) {
|
||||||
|
throw new RuntimeException("Unexpected row count after DB action: " + updated);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* 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.dao;
|
||||||
|
|
||||||
|
import com.j256.ormlite.field.DatabaseField;
|
||||||
|
import com.j256.ormlite.table.DatabaseTable;
|
||||||
|
import io.kamax.mxisd.ThreePid;
|
||||||
|
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||||
|
|
||||||
|
@DatabaseTable(tableName = "session_3pid")
|
||||||
|
public class ThreePidSessionDao implements IThreePidSessionDao {
|
||||||
|
|
||||||
|
@DatabaseField(id = true)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false)
|
||||||
|
private String server;
|
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false)
|
||||||
|
private String medium;
|
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false)
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false)
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false)
|
||||||
|
private int attempt;
|
||||||
|
|
||||||
|
@DatabaseField
|
||||||
|
private String nextLink;
|
||||||
|
|
||||||
|
@DatabaseField(canBeNull = false)
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
public ThreePidSessionDao() {
|
||||||
|
// stub for ORMLite
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThreePidSessionDao(IThreePidSessionDao session) {
|
||||||
|
setId(session.getId());
|
||||||
|
setServer(session.getServer());
|
||||||
|
setMedium(session.getMedium());
|
||||||
|
setAddress(session.getAddress());
|
||||||
|
setSecret(session.getSecret());
|
||||||
|
setAttempt(session.getAttempt());
|
||||||
|
setNextLink(session.getNextLink());
|
||||||
|
setToken(session.getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThreePidSessionDao(ThreePid tpid, String secret) {
|
||||||
|
setMedium(tpid.getMedium());
|
||||||
|
setAddress(tpid.getAddress());
|
||||||
|
setSecret(secret);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServer() {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServer(String server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSecret() {
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSecret(String secret) {
|
||||||
|
this.secret = secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAttempt() {
|
||||||
|
return attempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAttempt(int attempt) {
|
||||||
|
this.attempt = attempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMedium() {
|
||||||
|
return medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMedium(String medium) {
|
||||||
|
this.medium = medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAddress() {
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddress(String address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNextLink() {
|
||||||
|
return nextLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNextLink(String nextLink) {
|
||||||
|
this.nextLink = nextLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
}
|
@@ -22,8 +22,8 @@ package io.kamax.mxisd.threepid.connector;
|
|||||||
|
|
||||||
import com.sun.mail.smtp.SMTPTransport;
|
import com.sun.mail.smtp.SMTPTransport;
|
||||||
import io.kamax.matrix.ThreePidMedium;
|
import io.kamax.matrix.ThreePidMedium;
|
||||||
import io.kamax.mxisd.config.invite.medium.EmailInviteConfig;
|
|
||||||
import io.kamax.mxisd.config.threepid.connector.EmailSmtpConfig;
|
import io.kamax.mxisd.config.threepid.connector.EmailSmtpConfig;
|
||||||
|
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
||||||
import io.kamax.mxisd.exception.ConfigurationException;
|
import io.kamax.mxisd.exception.ConfigurationException;
|
||||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
@@ -47,23 +47,21 @@ public class EmailSmtpConnector implements IThreePidConnector {
|
|||||||
private Logger log = LoggerFactory.getLogger(EmailSmtpConnector.class);
|
private Logger log = LoggerFactory.getLogger(EmailSmtpConnector.class);
|
||||||
|
|
||||||
private EmailSmtpConfig cfg;
|
private EmailSmtpConfig cfg;
|
||||||
private EmailInviteConfig invCfg;
|
|
||||||
|
|
||||||
private Session session;
|
private Session session;
|
||||||
private InternetAddress sender;
|
private InternetAddress sender;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public EmailSmtpConnector(EmailSmtpConfig cfg, EmailInviteConfig invCfg) {
|
public EmailSmtpConnector(EmailConfig cfg, EmailSmtpConfig smtpCfg) {
|
||||||
try {
|
try {
|
||||||
session = Session.getInstance(System.getProperties());
|
session = Session.getInstance(System.getProperties());
|
||||||
sender = new InternetAddress(invCfg.getFrom(), invCfg.getName());
|
sender = new InternetAddress(cfg.getFrom(), cfg.getName());
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
// What are we supposed to do with this?!
|
// What are we supposed to do with this?!
|
||||||
throw new ConfigurationException(e);
|
throw new ConfigurationException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cfg = cfg;
|
this.cfg = smtpCfg;
|
||||||
this.invCfg = invCfg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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.threepid.session;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.ThreePid;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface IThreePidSession {
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
String getHash();
|
||||||
|
|
||||||
|
Instant getCreationTime();
|
||||||
|
|
||||||
|
String getServer();
|
||||||
|
|
||||||
|
ThreePid getThreePid();
|
||||||
|
|
||||||
|
int getAttempt();
|
||||||
|
|
||||||
|
void increaseAttempt();
|
||||||
|
|
||||||
|
Optional<String> getNextLink();
|
||||||
|
|
||||||
|
void validate(String token);
|
||||||
|
|
||||||
|
boolean isValidated();
|
||||||
|
|
||||||
|
Instant getValidationTime();
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* 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.threepid.session;
|
||||||
|
|
||||||
|
import io.kamax.mxisd.ThreePid;
|
||||||
|
import io.kamax.mxisd.exception.BadRequestException;
|
||||||
|
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||||
|
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||||
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class ThreePidSession implements IThreePidSession {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private Instant timestamp;
|
||||||
|
private String hash;
|
||||||
|
private String server;
|
||||||
|
private ThreePid tPid;
|
||||||
|
private String secret;
|
||||||
|
private String nextLink;
|
||||||
|
private String token;
|
||||||
|
private int attempt;
|
||||||
|
private Instant validationTimestamp;
|
||||||
|
private boolean isValidated;
|
||||||
|
|
||||||
|
public ThreePidSession(IThreePidSessionDao dao) {
|
||||||
|
this(
|
||||||
|
dao.getId(),
|
||||||
|
dao.getServer(),
|
||||||
|
new ThreePid(dao.getMedium(), dao.getAddress()),
|
||||||
|
dao.getSecret(),
|
||||||
|
dao.getAttempt(),
|
||||||
|
dao.getNextLink(),
|
||||||
|
dao.getToken()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThreePidSession(String id, String server, ThreePid tPid, String secret, int attempt, String nextLink, String token) {
|
||||||
|
this.id = id;
|
||||||
|
this.server = server;
|
||||||
|
this.tPid = new ThreePid(tPid);
|
||||||
|
this.secret = secret;
|
||||||
|
this.attempt = attempt;
|
||||||
|
this.nextLink = nextLink;
|
||||||
|
this.token = token;
|
||||||
|
|
||||||
|
this.timestamp = Instant.now();
|
||||||
|
this.hash = server.toLowerCase() + tPid.getMedium().toLowerCase() + tPid.getAddress().toLowerCase() + secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHash() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant getCreationTime() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServer() {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ThreePid getThreePid() {
|
||||||
|
return tPid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSecret() {
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAttempt() {
|
||||||
|
return attempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void increaseAttempt() {
|
||||||
|
attempt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<String> getNextLink() {
|
||||||
|
return Optional.ofNullable(nextLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void setAttempt(int attempt) {
|
||||||
|
if (isValidated()) {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.attempt = attempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant getValidationTime() {
|
||||||
|
return validationTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidated() {
|
||||||
|
return isValidated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void validate(String token) {
|
||||||
|
if (Instant.now().minus(24, ChronoUnit.HOURS).isAfter(getCreationTime())) {
|
||||||
|
throw new BadRequestException("Session " + getId() + " has expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.equals(this.token, token)) {
|
||||||
|
throw new InvalidCredentialsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidated()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validationTimestamp = Instant.now();
|
||||||
|
isValidated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IThreePidSessionDao getDao() {
|
||||||
|
return new IThreePidSessionDao() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServer() {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMedium() {
|
||||||
|
return tPid.getMedium();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAddress() {
|
||||||
|
return tPid.getAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSecret() {
|
||||||
|
return secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getAttempt() {
|
||||||
|
return attempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getNextLink() {
|
||||||
|
return nextLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user