diff --git a/src/main/groovy/io/kamax/mxisd/ThreePid.java b/src/main/groovy/io/kamax/mxisd/ThreePid.java
index d51ecd6..3a0b6bf 100644
--- a/src/main/groovy/io/kamax/mxisd/ThreePid.java
+++ b/src/main/groovy/io/kamax/mxisd/ThreePid.java
@@ -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;
diff --git a/src/main/groovy/io/kamax/mxisd/config/invite/medium/EmailInviteConfig.java b/src/main/groovy/io/kamax/mxisd/config/invite/medium/EmailInviteConfig.java
index 76b79c2..a244aff 100644
--- a/src/main/groovy/io/kamax/mxisd/config/invite/medium/EmailInviteConfig.java
+++ b/src/main/groovy/io/kamax/mxisd/config/invite/medium/EmailInviteConfig.java
@@ -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())) {
diff --git a/src/main/groovy/io/kamax/mxisd/config/threepid/medium/EmailConfig.java b/src/main/groovy/io/kamax/mxisd/config/threepid/medium/EmailConfig.java
new file mode 100644
index 0000000..2d47bf3
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/config/threepid/medium/EmailConfig.java
@@ -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 .
+ */
+
+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());
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy
index 2df1de0..93181b4 100644
--- a/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy
+++ b/src/main/groovy/io/kamax/mxisd/controller/v1/SessionController.groovy
@@ -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();
diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java b/src/main/groovy/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java
index f7d1b35..0812505 100644
--- a/src/main/groovy/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java
+++ b/src/main/groovy/io/kamax/mxisd/controller/v1/io/GenericTokenRequestJson.java
@@ -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;
}
}
diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java b/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java
index 3901045..527f1e7 100644
--- a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java
+++ b/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionEmailTokenRequestJson.java
@@ -24,12 +24,10 @@ public class SessionEmailTokenRequestJson extends GenericTokenRequestJson {
private String email;
- @Override
public String getMedium() {
return "email";
}
- @Override
public String getValue() {
return email;
}
diff --git a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java b/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java
index b66e881..e2e82c2 100644
--- a/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java
+++ b/src/main/groovy/io/kamax/mxisd/controller/v1/io/SessionPhoneTokenRequestJson.java
@@ -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);
diff --git a/src/main/groovy/io/kamax/mxisd/exception/InternalServerError.java b/src/main/groovy/io/kamax/mxisd/exception/InternalServerError.java
new file mode 100644
index 0000000..d3ca848
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/exception/InternalServerError.java
@@ -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 .
+ */
+
+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());
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/exception/InvalidCredentialsException.java b/src/main/groovy/io/kamax/mxisd/exception/InvalidCredentialsException.java
new file mode 100644
index 0000000..bbd66df
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/exception/InvalidCredentialsException.java
@@ -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 .
+ */
+
+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");
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/exception/ObjectNotFoundException.java b/src/main/groovy/io/kamax/mxisd/exception/ObjectNotFoundException.java
new file mode 100644
index 0000000..acfb0c3
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/exception/ObjectNotFoundException.java
@@ -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 .
+ */
+
+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");
+ }
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/invitation/generator/EmailInviteContentGenerator.java b/src/main/groovy/io/kamax/mxisd/invitation/generator/EmailInviteContentGenerator.java
index bcd1e44..0bce14d 100644
--- a/src/main/groovy/io/kamax/mxisd/invitation/generator/EmailInviteContentGenerator.java
+++ b/src/main/groovy/io/kamax/mxisd/invitation/generator/EmailInviteContentGenerator.java
@@ -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);
diff --git a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java b/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java
index 8d3b4a0..206d9d4 100644
--- a/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java
+++ b/src/main/groovy/io/kamax/mxisd/lookup/ThreePidValidation.java
@@ -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;
}
diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy
index 37e4327..44b67f0 100644
--- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy
+++ b/src/main/groovy/io/kamax/mxisd/lookup/strategy/LookupStrategy.groovy
@@ -32,6 +32,10 @@ interface LookupStrategy {
Optional find(String medium, String address, boolean recursive)
+ Optional findLocal(String medium, String address);
+
+ Optional findRemote(String medium, String address);
+
Optional find(SingleLookupRequest request)
Optional findRecursive(SingleLookupRequest request)
diff --git a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy
index e4ef776..048dd21 100644
--- a/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy
+++ b/src/main/groovy/io/kamax/mxisd/lookup/strategy/RecursivePriorityLookupStrategy.groovy
@@ -118,17 +118,44 @@ class RecursivePriorityLookupStrategy implements LookupStrategy, InitializingBea
}).collect(Collectors.toList())
}
- @Override
- Optional find(String medium, String address, boolean recursive) {
+ List getRemoteProviders() {
+ return providers.stream().filter(new Predicate() {
+ @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();
req.setType(medium)
req.setThreePid(address)
req.setRequester("Internal")
- return find(req, recursive)
+ return req;
+ }
+
+ @Override
+ Optional find(String medium, String address, boolean recursive) {
+ return find(build(medium, address), recursive)
+ }
+
+ @Override
+ Optional findLocal(String medium, String address) {
+ return find(build(medium, address), getLocalProviders())
+ }
+
+ @Override
+ Optional findRemote(String medium, String address) {
+ return find(build(medium, address), getRemoteProviders())
}
Optional find(SingleLookupRequest request, boolean forceRecursive) {
- for (IThreePidProvider provider : listUsableProviders(request, forceRecursive)) {
+ return find(request, listUsableProviders(request, forceRecursive));
+ }
+
+ Optional find(SingleLookupRequest request, List providers) {
+ for (IThreePidProvider provider : providers) {
Optional lookupDataOpt = provider.find(request)
if (lookupDataOpt.isPresent()) {
return lookupDataOpt
diff --git a/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java b/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java
index 1f8c3ec..42e53b0 100644
--- a/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java
+++ b/src/main/groovy/io/kamax/mxisd/mapping/MappingManager.java
@@ -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 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 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);
+ @Autowired
+ public MappingManager(IStorage storage, LookupStrategy lookup) {
+ this.storage = storage;
+ }
- sessions.remove(s.sid);
- }
+ private ThreePidSession getSession(String sid, String secret) {
+ Optional 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 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 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));
- }
+ public Optional 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();
+ }
- 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 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 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);
- // TODO perform bind, whatever it is
- }
+ // 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
+ }
- 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;
- 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();
- }
-
- 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;
+ // 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
+ }
}
}
diff --git a/src/main/groovy/io/kamax/mxisd/storage/IStorage.java b/src/main/groovy/io/kamax/mxisd/storage/IStorage.java
index b77c50b..329dfd0 100644
--- a/src/main/groovy/io/kamax/mxisd/storage/IStorage.java
+++ b/src/main/groovy/io/kamax/mxisd/storage/IStorage.java
@@ -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 getThreePidSession(String sid);
+
+ Optional findThreePidSession(ThreePid tpid, String secret);
+
+ void insertThreePidSession(IThreePidSessionDao session);
+
+ void updateThreePidSession(IThreePidSessionDao session);
+
}
diff --git a/src/main/groovy/io/kamax/mxisd/mapping/MappingSession.java b/src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java
similarity index 83%
rename from src/main/groovy/io/kamax/mxisd/mapping/MappingSession.java
rename to src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java
index 7d647d3..dd5085e 100644
--- a/src/main/groovy/io/kamax/mxisd/mapping/MappingSession.java
+++ b/src/main/groovy/io/kamax/mxisd/storage/dao/IThreePidSessionDao.java
@@ -18,18 +18,24 @@
* along with this program. If not, see .
*/
-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();
}
diff --git a/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java b/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java
index e76596a..0aac625 100644
--- a/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java
+++ b/src/main/groovy/io/kamax/mxisd/storage/ormlite/OrmLiteSqliteStorage.java
@@ -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 get() throws SQLException, IOException;
+
+ }
+
+ @FunctionalInterface
+ private interface Doer {
+
+ void run() throws SQLException, IOException;
+
+ }
+
private Dao invDao;
+ private Dao 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 Dao createDaoAndTable(ConnectionSource connPool, Class c) throws SQLException {
+ Dao dao = DaoManager.createDao(connPool, c);
+ TableUtils.createTableIfNotExists(connPool, c);
+ return dao;
+ }
+
+ private T withCatcher(Getter 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 List forIterable(CloseableWrappedIterable extends T> t) {
+ return withCatcher(() -> {
+ try {
+ List ioList = new ArrayList<>();
+ t.forEach(ioList::add);
+ return ioList;
+ } finally {
+ t.close();
+ }
+ });
+ }
+
@Override
public Collection getInvites() {
- try (CloseableWrappedIterable t = invDao.getWrappedIterable()) {
- List 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 getThreePidSession(String sid) {
+ return withCatcher(() -> Optional.ofNullable(sessionDao.queryForId(sid)));
+ }
+
+ @Override
+ public Optional findThreePidSession(ThreePid tpid, String secret) {
+ return withCatcher(() -> {
+ List 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);
+ }
+ });
}
}
diff --git a/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java b/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java
new file mode 100644
index 0000000..0b557e1
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/storage/ormlite/dao/ThreePidSessionDao.java
@@ -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 .
+ */
+
+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;
+ }
+}
diff --git a/src/main/groovy/io/kamax/mxisd/threepid/connector/EmailSmtpConnector.java b/src/main/groovy/io/kamax/mxisd/threepid/connector/EmailSmtpConnector.java
index b9d68e0..7bc3ae3 100644
--- a/src/main/groovy/io/kamax/mxisd/threepid/connector/EmailSmtpConnector.java
+++ b/src/main/groovy/io/kamax/mxisd/threepid/connector/EmailSmtpConnector.java
@@ -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
diff --git a/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java b/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java
new file mode 100644
index 0000000..28af430
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/threepid/session/IThreePidSession.java
@@ -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 .
+ */
+
+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 getNextLink();
+
+ void validate(String token);
+
+ boolean isValidated();
+
+ Instant getValidationTime();
+
+}
diff --git a/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java b/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java
new file mode 100644
index 0000000..8d0fcbc
--- /dev/null
+++ b/src/main/groovy/io/kamax/mxisd/threepid/session/ThreePidSession.java
@@ -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 .
+ */
+
+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 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;
+ }
+
+ };
+ }
+
+}