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 address;
|
||||
|
||||
public ThreePid(ThreePid tpid) {
|
||||
this(tpid.getMedium(), tpid.getAddress());
|
||||
}
|
||||
|
||||
public ThreePid(String medium, String address) {
|
||||
this.medium = medium;
|
||||
this.address = address;
|
||||
|
@@ -20,12 +20,9 @@
|
||||
|
||||
package io.kamax.mxisd.config.invite.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;
|
||||
|
||||
@@ -38,33 +35,8 @@ public class EmailInviteConfig {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(EmailInviteConfig.class);
|
||||
|
||||
private MatrixConfig mxCfg;
|
||||
|
||||
private String from;
|
||||
private String name;
|
||||
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() {
|
||||
return template;
|
||||
}
|
||||
@@ -76,12 +48,6 @@ public class EmailInviteConfig {
|
||||
@PostConstruct
|
||||
public void build() {
|
||||
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.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.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.SessionPhoneTokenRequestJson
|
||||
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.mapping.MappingManager
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.lang.StringUtils
|
||||
import org.apache.http.HttpStatus
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
@@ -62,16 +63,25 @@ class SessionController {
|
||||
|
||||
@RequestMapping(value = "/validate/{medium}/requestToken")
|
||||
String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) {
|
||||
log.info("Requested: {}", request.getRequestURL(), request.getQueryString())
|
||||
|
||||
if (StringUtils.equals("email", medium)) {
|
||||
log.info("Request {}: {}", request.getMethod(), request.getRequestURL(), request.getQueryString())
|
||||
if (ThreePidMedium.Email.is(medium)) {
|
||||
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)
|
||||
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();
|
||||
|
@@ -20,13 +20,11 @@
|
||||
|
||||
package io.kamax.mxisd.controller.v1.io;
|
||||
|
||||
import io.kamax.mxisd.mapping.MappingSession;
|
||||
|
||||
public abstract class GenericTokenRequestJson implements MappingSession {
|
||||
public abstract class GenericTokenRequestJson {
|
||||
|
||||
private String client_secret;
|
||||
private int send_attempt;
|
||||
private String id_server;
|
||||
private String next_link;
|
||||
|
||||
public String getSecret() {
|
||||
return client_secret;
|
||||
@@ -36,8 +34,8 @@ public abstract class GenericTokenRequestJson implements MappingSession {
|
||||
return send_attempt;
|
||||
}
|
||||
|
||||
public String getServer() {
|
||||
return id_server;
|
||||
public String getNextLink() {
|
||||
return next_link;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -24,12 +24,10 @@ public class SessionEmailTokenRequestJson extends GenericTokenRequestJson {
|
||||
|
||||
private String email;
|
||||
|
||||
@Override
|
||||
public String getMedium() {
|
||||
return "email";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return email;
|
||||
}
|
||||
|
@@ -31,12 +31,10 @@ public class SessionPhoneTokenRequestJson extends GenericTokenRequestJson {
|
||||
private String country;
|
||||
private String phone_number;
|
||||
|
||||
@Override
|
||||
public String getMedium() {
|
||||
return "msisdn";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
try {
|
||||
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.mxisd.config.MatrixConfig;
|
||||
import io.kamax.mxisd.config.invite.medium.EmailInviteConfig;
|
||||
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
@@ -38,13 +39,15 @@ import java.nio.charset.StandardCharsets;
|
||||
@Component
|
||||
public class EmailInviteContentGenerator implements IInviteContentGenerator {
|
||||
|
||||
private EmailInviteConfig cfg;
|
||||
private EmailConfig cfg;
|
||||
private EmailInviteConfig invCfg;
|
||||
private MatrixConfig mxCfg;
|
||||
private ApplicationContext app;
|
||||
|
||||
@Autowired
|
||||
public EmailInviteContentGenerator(EmailInviteConfig cfg, MatrixConfig mxCfg, ApplicationContext app) {
|
||||
@Autowired // FIXME ApplicationContext shouldn't be injected, find another way from config (?)
|
||||
public EmailInviteContentGenerator(EmailConfig cfg, EmailInviteConfig invCfg, MatrixConfig mxCfg, ApplicationContext app) {
|
||||
this.cfg = cfg;
|
||||
this.invCfg = invCfg;
|
||||
this.mxCfg = mxCfg;
|
||||
this.app = app;
|
||||
}
|
||||
@@ -68,8 +71,8 @@ public class EmailInviteContentGenerator implements IInviteContentGenerator {
|
||||
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()),
|
||||
StringUtils.startsWith(invCfg.getTemplate(), "classpath:") ?
|
||||
app.getResource(invCfg.getTemplate()).getInputStream() : new FileInputStream(invCfg.getTemplate()),
|
||||
StandardCharsets.UTF_8);
|
||||
templateBody = templateBody.replace("%DOMAIN%", mxCfg.getDomain());
|
||||
templateBody = templateBody.replace("%DOMAIN_PRETTY%", domainPretty);
|
||||
|
@@ -28,8 +28,8 @@ public class ThreePidValidation extends ThreePid {
|
||||
|
||||
private Instant validation;
|
||||
|
||||
public ThreePidValidation(String medium, String address, Instant validation) {
|
||||
super(medium, address);
|
||||
public ThreePidValidation(ThreePid tpid, Instant validation) {
|
||||
super(tpid);
|
||||
this.validation = validation;
|
||||
}
|
||||
|
||||
|
@@ -32,6 +32,10 @@ interface LookupStrategy {
|
||||
|
||||
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> findRecursive(SingleLookupRequest request)
|
||||
|
@@ -118,17 +118,44 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea
|
||||
}).collect(Collectors.toList())
|
||||
}
|
||||
|
||||
List<IThreePidProvider> getRemoteProviders() {
|
||||
return providers.stream().filter(new Predicate<IThreePidProvider>() {
|
||||
@Override
|
||||
Optional<SingleLookupReply> find(String medium, String address, boolean recursive) {
|
||||
boolean test(IThreePidProvider iThreePidProvider) {
|
||||
return iThreePidProvider.isEnabled() && !iThreePidProvider.isLocal()
|
||||
}
|
||||
}).collect(Collectors.toList())
|
||||
}
|
||||
|
||||
private static SingleLookupRequest build(String medium, String address) {
|
||||
SingleLookupRequest req = new SingleLookupRequest();
|
||||
req.setType(medium)
|
||||
req.setThreePid(address)
|
||||
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) {
|
||||
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)
|
||||
if (lookupDataOpt.isPresent()) {
|
||||
return lookupDataOpt
|
||||
|
@@ -20,155 +20,167 @@
|
||||
|
||||
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.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.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Component
|
||||
public class MappingManager {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(MappingManager.class);
|
||||
|
||||
private Map<String, Session> sessions = new HashMap<>();
|
||||
private Timer cleaner;
|
||||
private IStorage storage;
|
||||
private LookupStrategy lookup;
|
||||
|
||||
MappingManager() {
|
||||
cleaner = new Timer();
|
||||
cleaner.schedule(new TimerTask() {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0, 10 * 1000); // TODO config delay
|
||||
@Autowired
|
||||
public MappingManager(IStorage storage, LookupStrategy lookup) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
public String create(MappingSession data) {
|
||||
String 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 {
|
||||
sid = Long.toString(System.currentTimeMillis());
|
||||
} while (sessions.containsKey(sid));
|
||||
sessionId = UUID.randomUUID().toString().replace("-", "");
|
||||
} while (storage.getThreePidSession(sessionId).isPresent());
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
log.info("Created new session {} to validate {} {}", sid, session.medium, session.address);
|
||||
return sid;
|
||||
// TODO send via connector
|
||||
// log.info("Sent validation notification to {}", tpid);
|
||||
|
||||
storage.insertThreePidSession(session.getDao());
|
||||
log.info("Stored session {}", sessionId, tpid, server);
|
||||
|
||||
return sessionId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
public Optional<String> validate(String sid, String secret, String token) {
|
||||
ThreePidSession session = getSession(sid, secret);
|
||||
log.info("Attempting validation for session {} from {}", session.getId(), session.getServer());
|
||||
session.validate(token);
|
||||
storage.updateThreePidSession(session.getDao());
|
||||
log.info("Session {} has been validated", session.getId());
|
||||
return session.getNextLink();
|
||||
}
|
||||
|
||||
// TODO actually check token
|
||||
|
||||
s.isValidated = true;
|
||||
s.validationTimestamp = Instant.now();
|
||||
}
|
||||
|
||||
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 ThreePidValidation(s.medium, s.address, s.validationTimestamp));
|
||||
}
|
||||
|
||||
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) {
|
||||
Session s = sessions.get(sid);
|
||||
if (s == null || !StringUtils.equals(s.secret, secret)) {
|
||||
throw new BadRequestException("sid or secret are not valid");
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
log.info("Attempting bind of {} on session {} from server {}", mxid, session.getId(), session.getServer());
|
||||
|
||||
// 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("Performed bind for mxid {}", mxid);
|
||||
// TODO perform bind, whatever it is
|
||||
log.info("No further action needed for Mapping {} -> {}");
|
||||
return;
|
||||
}
|
||||
|
||||
private class Session {
|
||||
|
||||
private String sid;
|
||||
private String hash;
|
||||
private Instant timestamp;
|
||||
private Instant validationTimestamp;
|
||||
private boolean isValidated;
|
||||
private String secret;
|
||||
private String medium;
|
||||
private String address;
|
||||
|
||||
public Session(String sid, String hash, MappingSession data) {
|
||||
this.sid = sid;
|
||||
this.hash = hash;
|
||||
timestamp = Instant.now();
|
||||
validationTimestamp = Instant.now();
|
||||
secret = data.getSecret();
|
||||
medium = data.getMedium();
|
||||
address = data.getValue();
|
||||
// This might need a configuration by medium type?
|
||||
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
|
||||
}
|
||||
|
||||
public Instant getTimestamp() {
|
||||
return timestamp;
|
||||
if (System.currentTimeMillis() % 2 == 0) {
|
||||
// TODO
|
||||
// 1. Check if configured to publish globally non-local domain. If no, return
|
||||
}
|
||||
|
||||
public void setTimestamp(Instant timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
// TODO
|
||||
// Proxy to configurable IS, by default Matrix.org
|
||||
//
|
||||
// Separate workflow, if user accepts to publish globally
|
||||
// 1. display page to the user that it is waiting for the confirmation
|
||||
// 2. call mxisd-specific endpoint to publish globally
|
||||
// 3. check regularly on client page for a binding
|
||||
// 4. when found, show page "Done globally!"
|
||||
} else {
|
||||
if (isLocalDomain) { // 3PID is not known anywhere but is a local domain
|
||||
// TODO
|
||||
// check if config says this should fail or silently accept.
|
||||
// Required to silently accept if the backend is synapse itself.
|
||||
} else { // 3PID is not known anywhere and is remote
|
||||
// TODO
|
||||
// Proxy to configurable IS, by default Matrix.org
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import io.kamax.mxisd.ThreePid;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||
import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface IStorage {
|
||||
|
||||
@@ -33,4 +36,12 @@ public interface IStorage {
|
||||
|
||||
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/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.mapping;
|
||||
package io.kamax.mxisd.storage.dao;
|
||||
|
||||
public interface MappingSession {
|
||||
public interface IThreePidSessionDao {
|
||||
|
||||
String getId();
|
||||
|
||||
String getServer();
|
||||
|
||||
String getMedium();
|
||||
|
||||
String getAddress();
|
||||
|
||||
String getSecret();
|
||||
|
||||
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.support.ConnectionSource;
|
||||
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.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.IOException;
|
||||
@@ -35,59 +41,141 @@ import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
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<ThreePidSessionDao, String> sessionDao;
|
||||
|
||||
OrmLiteSqliteStorage(String path) {
|
||||
try {
|
||||
withCatcher(() -> {
|
||||
File parent = new File(path).getParentFile();
|
||||
if (!parent.mkdirs() && !parent.isDirectory()) {
|
||||
throw new RuntimeException("Unable to create DB parent directory: " + parent);
|
||||
}
|
||||
|
||||
ConnectionSource connPool = new JdbcConnectionSource("jdbc:sqlite:" + path);
|
||||
invDao = DaoManager.createDao(connPool, ThreePidInviteIO.class);
|
||||
TableUtils.createTableIfNotExists(connPool, ThreePidInviteIO.class);
|
||||
} catch (SQLException e) {
|
||||
invDao = createDaoAndTable(connPool, ThreePidInviteIO.class);
|
||||
sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class);
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
return forIterable(invDao.getWrappedIterable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertInvite(IThreePidInviteReply data) {
|
||||
try {
|
||||
withCatcher(() -> {
|
||||
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 {
|
||||
withCatcher(() -> {
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
@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 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.medium.EmailConfig;
|
||||
import io.kamax.mxisd.exception.ConfigurationException;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
@@ -47,23 +47,21 @@ public class EmailSmtpConnector implements IThreePidConnector {
|
||||
private Logger log = LoggerFactory.getLogger(EmailSmtpConnector.class);
|
||||
|
||||
private EmailSmtpConfig cfg;
|
||||
private EmailInviteConfig invCfg;
|
||||
|
||||
private Session session;
|
||||
private InternetAddress sender;
|
||||
|
||||
@Autowired
|
||||
public EmailSmtpConnector(EmailSmtpConfig cfg, EmailInviteConfig invCfg) {
|
||||
public EmailSmtpConnector(EmailConfig cfg, EmailSmtpConfig smtpCfg) {
|
||||
try {
|
||||
session = Session.getInstance(System.getProperties());
|
||||
sender = new InternetAddress(invCfg.getFrom(), invCfg.getName());
|
||||
sender = new InternetAddress(cfg.getFrom(), cfg.getName());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// What are we supposed to do with this?!
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
|
||||
this.cfg = cfg;
|
||||
this.invCfg = invCfg;
|
||||
this.cfg = smtpCfg;
|
||||
}
|
||||
|
||||
@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