First working prototype to proxy 3PID binds to central Matrix.org IS
This commit is contained in:
@@ -79,6 +79,9 @@ dependencies {
|
||||
// Spring Boot - standalone app
|
||||
compile 'org.springframework.boot:spring-boot-starter-web:1.5.3.RELEASE'
|
||||
|
||||
// Thymeleaf for HTML templates
|
||||
compile "org.springframework.boot:spring-boot-starter-thymeleaf:1.5.3.RELEASE"
|
||||
|
||||
// Matrix Java SDK
|
||||
compile 'io.kamax:matrix-java-sdk:0.0.2'
|
||||
|
||||
|
42
src/main/groovy/io/kamax/mxisd/config/ThymeleafConfig.java
Normal file
42
src/main/groovy/io/kamax/mxisd/config/ThymeleafConfig.java
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.thymeleaf.resourceresolver.FileResourceResolver;
|
||||
import org.thymeleaf.templateresolver.TemplateResolver;
|
||||
|
||||
@Configuration
|
||||
public class ThymeleafConfig {
|
||||
|
||||
@Bean
|
||||
public TemplateResolver getFileSystemResolver() {
|
||||
TemplateResolver resolver = new TemplateResolver();
|
||||
resolver.setPrefix("");
|
||||
resolver.setSuffix("");
|
||||
resolver.setCacheable(false);
|
||||
resolver.setOrder(1);
|
||||
resolver.setResourceResolver(new FileResourceResolver());
|
||||
return resolver;
|
||||
}
|
||||
|
||||
}
|
144
src/main/groovy/io/kamax/mxisd/config/ViewConfig.java
Normal file
144
src/main/groovy/io/kamax/mxisd/config/ViewConfig.java
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties("view")
|
||||
public class ViewConfig {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(ViewConfig.class);
|
||||
|
||||
public static class Session {
|
||||
|
||||
public static class Paths {
|
||||
|
||||
private String failure;
|
||||
private String success;
|
||||
|
||||
public String getFailure() {
|
||||
return failure;
|
||||
}
|
||||
|
||||
public void setFailure(String failure) {
|
||||
this.failure = failure;
|
||||
}
|
||||
|
||||
public String getSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(String success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Local {
|
||||
|
||||
private Paths onTokenSubmit = new Paths();
|
||||
|
||||
public Paths getOnTokenSubmit() {
|
||||
return onTokenSubmit;
|
||||
}
|
||||
|
||||
public void setOnTokenSubmit(Paths onTokenSubmit) {
|
||||
this.onTokenSubmit = onTokenSubmit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Remote {
|
||||
|
||||
private Paths onRequest = new Paths();
|
||||
private Paths onCheck = new Paths();
|
||||
|
||||
public Paths getOnRequest() {
|
||||
return onRequest;
|
||||
}
|
||||
|
||||
public void setOnRequest(Paths onRequest) {
|
||||
this.onRequest = onRequest;
|
||||
}
|
||||
|
||||
public Paths getOnCheck() {
|
||||
return onCheck;
|
||||
}
|
||||
|
||||
public void setOnCheck(Paths onCheck) {
|
||||
this.onCheck = onCheck;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Local local = new Local();
|
||||
private Local localRemote = new Local();
|
||||
private Remote remote = new Remote();
|
||||
|
||||
public Local getLocal() {
|
||||
return local;
|
||||
}
|
||||
|
||||
public void setLocal(Local local) {
|
||||
this.local = local;
|
||||
}
|
||||
|
||||
public Local getLocalRemote() {
|
||||
return localRemote;
|
||||
}
|
||||
|
||||
public void setLocalRemote(Local localRemote) {
|
||||
this.localRemote = localRemote;
|
||||
}
|
||||
|
||||
public Remote getRemote() {
|
||||
return remote;
|
||||
}
|
||||
|
||||
public void setRemote(Remote remote) {
|
||||
this.remote = remote;
|
||||
}
|
||||
}
|
||||
|
||||
private Session session = new Session();
|
||||
|
||||
public Session getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public void setSession(Session session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void build() {
|
||||
log.info("--- View config ---");
|
||||
log.info("Session: {}", new Gson().toJson(session));
|
||||
}
|
||||
|
||||
}
|
@@ -20,146 +20,62 @@
|
||||
|
||||
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
|
||||
import io.kamax.mxisd.exception.SessionNotValidatedException
|
||||
import io.kamax.mxisd.invitation.InvitationManager
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation
|
||||
import io.kamax.mxisd.config.ServerConfig
|
||||
import io.kamax.mxisd.config.ViewConfig
|
||||
import io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1
|
||||
import io.kamax.mxisd.session.SessionMananger
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.http.HttpStatus
|
||||
import io.kamax.mxisd.session.ValidationResult
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import org.springframework.stereotype.Controller
|
||||
import org.springframework.ui.Model
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RequestParam
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.servlet.http.HttpServletResponse
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
@Controller
|
||||
@RequestMapping(path = IdentityAPIv1.BASE)
|
||||
class SessionController {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(SessionController.class)
|
||||
|
||||
@Autowired
|
||||
private ServerConfig srvCfg;
|
||||
|
||||
@Autowired
|
||||
private SessionMananger mgr
|
||||
|
||||
@Autowired
|
||||
private InvitationManager invMgr;
|
||||
|
||||
private Gson gson = new Gson()
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(SessionController.class)
|
||||
|
||||
private <T> T fromJson(HttpServletRequest req, Class<T> obj) {
|
||||
gson.fromJson(new InputStreamReader(req.getInputStream(), StandardCharsets.UTF_8), obj)
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/validate/{medium}/requestToken")
|
||||
String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String 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(
|
||||
request.getRemoteHost(),
|
||||
new ThreePid(req.getMedium(), req.getValue()),
|
||||
req.getSecret(),
|
||||
req.getAttempt(),
|
||||
req.getNextLink())));
|
||||
}
|
||||
|
||||
if (ThreePidMedium.PhoneNumber) {
|
||||
SessionPhoneTokenRequestJson req = fromJson(request, SessionPhoneTokenRequestJson.class)
|
||||
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();
|
||||
obj.addProperty("errcode", "M_INVALID_3PID_TYPE")
|
||||
obj.addProperty("error", medium + " is not supported as a 3PID type")
|
||||
response.setStatus(HttpStatus.SC_BAD_REQUEST)
|
||||
return gson.toJson(obj)
|
||||
}
|
||||
private ViewConfig viewCfg;
|
||||
|
||||
@RequestMapping(value = "/validate/{medium}/submitToken")
|
||||
String validate(HttpServletRequest request,
|
||||
@RequestParam String sid,
|
||||
@RequestParam("client_secret") String secret, @RequestParam String token) {
|
||||
String validate(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@RequestParam String sid,
|
||||
@RequestParam("client_secret") String secret,
|
||||
@RequestParam String token,
|
||||
Model model
|
||||
) {
|
||||
log.info("Requested: {}?{}", request.getRequestURL(), request.getQueryString())
|
||||
|
||||
mgr.validate(sid, secret, token)
|
||||
|
||||
return "{}"
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/3pid/getValidated3pid")
|
||||
String check(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam String sid, @RequestParam("client_secret") String secret) {
|
||||
log.info("Requested: {}", request.getRequestURL(), request.getQueryString())
|
||||
|
||||
try {
|
||||
ThreePidValidation pid = mgr.getValidated(sid, secret)
|
||||
|
||||
JsonObject obj = new JsonObject()
|
||||
obj.addProperty("medium", pid.getMedium())
|
||||
obj.addProperty("address", pid.getAddress())
|
||||
obj.addProperty("validated_at", pid.getValidation().toEpochMilli())
|
||||
|
||||
return gson.toJson(obj);
|
||||
} catch (SessionNotValidatedException e) {
|
||||
log.info("Session {} was requested but has not yet been validated", sid);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/3pid/bind")
|
||||
String bind(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String mxid) {
|
||||
String data = IOUtils.toString(request.getReader())
|
||||
log.info("Requested: {}", request.getRequestURL(), request.getQueryString())
|
||||
try {
|
||||
mgr.bind(sid, secret, mxid)
|
||||
return "{}"
|
||||
} catch (BadRequestException e) {
|
||||
log.info("requested session was not validated")
|
||||
|
||||
JsonObject obj = new JsonObject()
|
||||
obj.addProperty("errcode", "M_SESSION_NOT_VALIDATED")
|
||||
obj.addProperty("error", e.getMessage())
|
||||
response.setStatus(HttpStatus.SC_BAD_REQUEST)
|
||||
return gson.toJson(obj)
|
||||
} finally {
|
||||
// If a user registers, there is no standard login event. Instead, this is the only way to trigger
|
||||
// resolution at an appropriate time. Meh at synapse/Riot!
|
||||
invMgr.lookupMappingsForInvites()
|
||||
}
|
||||
}
|
||||
|
||||
private class Sid {
|
||||
|
||||
private String sid;
|
||||
|
||||
public Sid(String sid) {
|
||||
setSid(sid);
|
||||
}
|
||||
|
||||
String getSid() {
|
||||
return sid
|
||||
}
|
||||
|
||||
void setSid(String sid) {
|
||||
this.sid = sid
|
||||
ValidationResult r = mgr.validate(sid, secret, token)
|
||||
log.info("Session {} was validated", sid)
|
||||
if (r.getNextUrl().isPresent()) {
|
||||
String url = srvCfg.getPublicUrl() + r.getNextUrl().get()
|
||||
log.info("Session {} validation: next URL is present, redirecting to {}", sid, url)
|
||||
response.sendRedirect(url)
|
||||
} else {
|
||||
if (r.isCanRemote()) {
|
||||
String url = srvCfg.getPublicUrl() + RemoteIdentityAPIv1.getRequestToken(r.getSession().getId(), r.getSession().getSecret());
|
||||
model.addAttribute("remoteSessionLink", url)
|
||||
return viewCfg.getSession().getLocalRemote().getOnTokenSubmit().getSuccess()
|
||||
} else {
|
||||
return viewCfg.getSession().getLocal().getOnTokenSubmit().getSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.controller.v1;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.ThreePidMedium;
|
||||
import io.kamax.mxisd.ThreePid;
|
||||
import io.kamax.mxisd.config.ServerConfig;
|
||||
import io.kamax.mxisd.config.ViewConfig;
|
||||
import io.kamax.mxisd.controller.v1.io.SessionEmailTokenRequestJson;
|
||||
import io.kamax.mxisd.controller.v1.io.SessionPhoneTokenRequestJson;
|
||||
import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
||||
import io.kamax.mxisd.invitation.InvitationManager;
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||
import io.kamax.mxisd.session.SessionMananger;
|
||||
import io.kamax.mxisd.util.GsonParser;
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(path = IdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public class SessionRestController {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(SessionRestController.class);
|
||||
|
||||
private class Sid { // FIXME replace with RequestTokenResponse
|
||||
|
||||
private String sid;
|
||||
|
||||
public Sid(String sid) {
|
||||
setSid(sid);
|
||||
}
|
||||
|
||||
String getSid() {
|
||||
return sid;
|
||||
}
|
||||
|
||||
void setSid(String sid) {
|
||||
this.sid = sid;
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private ServerConfig srvCfg;
|
||||
|
||||
@Autowired
|
||||
private SessionMananger mgr;
|
||||
|
||||
@Autowired
|
||||
private InvitationManager invMgr;
|
||||
|
||||
@Autowired
|
||||
private ViewConfig viewCfg;
|
||||
|
||||
private Gson gson = new Gson();
|
||||
private GsonParser parser = new GsonParser(gson);
|
||||
|
||||
@RequestMapping(value = "/validate/{medium}/requestToken")
|
||||
String init(HttpServletRequest request, HttpServletResponse response, @PathVariable String medium) throws IOException {
|
||||
log.info("Request {}: {}", request.getMethod(), request.getRequestURL(), request.getQueryString());
|
||||
if (ThreePidMedium.Email.is(medium)) {
|
||||
SessionEmailTokenRequestJson req = parser.parse(request, SessionEmailTokenRequestJson.class);
|
||||
return gson.toJson(new Sid(mgr.create(
|
||||
request.getRemoteHost(),
|
||||
new ThreePid(req.getMedium(), req.getValue()),
|
||||
req.getSecret(),
|
||||
req.getAttempt(),
|
||||
req.getNextLink())));
|
||||
}
|
||||
|
||||
if (ThreePidMedium.PhoneNumber.is(medium)) {
|
||||
SessionPhoneTokenRequestJson req = parser.parse(request, SessionPhoneTokenRequestJson.class);
|
||||
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();
|
||||
obj.addProperty("errcode", "M_INVALID_3PID_TYPE");
|
||||
obj.addProperty("error", medium + " is not supported as a 3PID type");
|
||||
response.setStatus(HttpStatus.SC_BAD_REQUEST);
|
||||
return gson.toJson(obj);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/3pid/getValidated3pid")
|
||||
String check(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam String sid, @RequestParam("client_secret") String secret) {
|
||||
log.info("Requested: {}", request.getRequestURL(), request.getQueryString());
|
||||
|
||||
try {
|
||||
ThreePidValidation pid = mgr.getValidated(sid, secret);
|
||||
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("medium", pid.getMedium());
|
||||
obj.addProperty("address", pid.getAddress());
|
||||
obj.addProperty("validated_at", pid.getValidation().toEpochMilli());
|
||||
|
||||
return gson.toJson(obj);
|
||||
} catch (SessionNotValidatedException e) {
|
||||
log.info("Session {} was requested but has not yet been validated", sid);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/3pid/bind")
|
||||
String bind(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam String sid, @RequestParam("client_secret") String secret, @RequestParam String mxid) {
|
||||
log.info("Requested: {}", request.getRequestURL(), request.getQueryString());
|
||||
try {
|
||||
mgr.bind(sid, secret, mxid);
|
||||
return "{}";
|
||||
} catch (BadRequestException e) {
|
||||
log.info("requested session was not validated");
|
||||
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.addProperty("errcode", "M_SESSION_NOT_VALIDATED");
|
||||
obj.addProperty("error", e.getMessage());
|
||||
response.setStatus(HttpStatus.SC_BAD_REQUEST);
|
||||
return gson.toJson(obj);
|
||||
} finally {
|
||||
// If a user registers, there is no standard login event. Instead, this is the only way to trigger
|
||||
// resolution at an appropriate time. Meh at synapse/Riot!
|
||||
invMgr.lookupMappingsForInvites();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* mxisd - Matrix Identity Server Daemon
|
||||
* Copyright (C) 2017 Maxime Dor
|
||||
*
|
||||
* https://max.kamax.io/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.kamax.mxisd.controller.v1.io;
|
||||
|
||||
public class RequestTokenResponse {
|
||||
|
||||
private String sid;
|
||||
|
||||
public String getSid() {
|
||||
return sid;
|
||||
}
|
||||
|
||||
}
|
@@ -22,6 +22,16 @@ package io.kamax.mxisd.controller.v1.remote;
|
||||
|
||||
public class RemoteIdentityAPIv1 {
|
||||
|
||||
public static final String BASE = "/_matrix/identity-remote/api/v1";
|
||||
public static final String BASE = "/_matrix/identity/remote/api/v1";
|
||||
public static final String SESSION_REQUEST_TOKEN = BASE + "/validate/requestToken";
|
||||
public static final String SESSION_CHECK = BASE + "/validate/check";
|
||||
|
||||
public static String getRequestToken(String id, String secret) {
|
||||
return SESSION_REQUEST_TOKEN + "?sid=" + id + "&client_secret=" + secret;
|
||||
}
|
||||
|
||||
public static String getSessionCheck(String id, String secret) {
|
||||
return SESSION_CHECK + "?sid=" + id + "&client_secret=" + secret;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,37 +1,59 @@
|
||||
package io.kamax.mxisd.controller.v1.remote;
|
||||
|
||||
import io.kamax.mxisd.config.ViewConfig;
|
||||
import io.kamax.mxisd.exception.SessionNotValidatedException;
|
||||
import io.kamax.mxisd.session.SessionMananger;
|
||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@RestController
|
||||
@CrossOrigin
|
||||
@RequestMapping(path = RemoteIdentityAPIv1.BASE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
import static io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1.SESSION_CHECK;
|
||||
import static io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1.SESSION_REQUEST_TOKEN;
|
||||
|
||||
@Controller
|
||||
public class RemoteSessionController {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(RemoteSessionController.class);
|
||||
|
||||
@Autowired
|
||||
private ViewConfig viewCfg;
|
||||
|
||||
@Autowired
|
||||
private SessionMananger mgr;
|
||||
|
||||
@RequestMapping(path = "/validate/requestToken")
|
||||
@RequestMapping(path = SESSION_REQUEST_TOKEN)
|
||||
public String requestToken(
|
||||
HttpServletRequest request,
|
||||
@RequestParam String sid,
|
||||
@RequestParam("client_secret") String secret,
|
||||
@RequestParam String token) {
|
||||
log.info("Request {}: {}", request.getMethod(), request.getRequestURL(), request.getQueryString());
|
||||
mgr.createRemote(sid, secret, token);
|
||||
Model model
|
||||
) {
|
||||
log.info("Request {}: {}", request.getMethod(), request.getRequestURL());
|
||||
IThreePidSession session = mgr.createRemote(sid, secret);
|
||||
model.addAttribute("checkLink", RemoteIdentityAPIv1.getSessionCheck(session.getId(), session.getSecret()));
|
||||
return viewCfg.getSession().getRemote().getOnRequest().getSuccess();
|
||||
}
|
||||
|
||||
return "{}";
|
||||
@RequestMapping(path = SESSION_CHECK)
|
||||
public String check(
|
||||
HttpServletRequest request,
|
||||
@RequestParam String sid,
|
||||
@RequestParam("client_secret") String secret) {
|
||||
log.info("Request {}: {}", request.getMethod(), request.getRequestURL());
|
||||
|
||||
try {
|
||||
mgr.validateRemote(sid, secret);
|
||||
return viewCfg.getSession().getRemote().getOnCheck().getSuccess();
|
||||
} catch (SessionNotValidatedException e) {
|
||||
return viewCfg.getSession().getRemote().getOnCheck().getFailure();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
|
||||
public class SessionUnknownException extends MatrixException {
|
||||
|
||||
public SessionUnknownException() {
|
||||
this("No valid session was found matching that sid and client secret");
|
||||
}
|
||||
|
||||
public SessionUnknownException(String error) {
|
||||
super(200, "M_NO_VALID_SESSION", error);
|
||||
}
|
||||
|
||||
}
|
@@ -21,10 +21,14 @@
|
||||
package io.kamax.mxisd.session;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.MatrixID;
|
||||
import io.kamax.matrix.ThreePidMedium;
|
||||
import io.kamax.matrix._MatrixID;
|
||||
import io.kamax.mxisd.ThreePid;
|
||||
import io.kamax.mxisd.config.MatrixConfig;
|
||||
import io.kamax.mxisd.config.SessionConfig;
|
||||
import io.kamax.mxisd.controller.v1.io.RequestTokenResponse;
|
||||
import io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1;
|
||||
import io.kamax.mxisd.exception.*;
|
||||
import io.kamax.mxisd.lookup.ThreePidValidation;
|
||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||
@@ -32,20 +36,28 @@ import io.kamax.mxisd.matrix.IdentityServerUtils;
|
||||
import io.kamax.mxisd.notification.NotificationManager;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||
import io.kamax.mxisd.threepid.session.ThreePidSession;
|
||||
import io.kamax.mxisd.util.GsonParser;
|
||||
import io.kamax.mxisd.util.RestClientUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.RandomStringUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -91,7 +103,7 @@ public class SessionMananger {
|
||||
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();
|
||||
throw new SessionUnknownException();
|
||||
}
|
||||
|
||||
return new ThreePidSession(dao.get());
|
||||
@@ -165,13 +177,28 @@ public class SessionMananger {
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<String> validate(String sid, String secret, String token) {
|
||||
public ValidationResult validate(String sid, String secret, String token) {
|
||||
ThreePidSession session = getSession(sid, secret);
|
||||
log.info("Attempting validation for session {} from {}", session.getId(), session.getServer());
|
||||
|
||||
boolean isLocal = isLocal(session.getThreePid());
|
||||
PolicySource policy = cfg.getPolicy().getValidation().forIf(isLocal);
|
||||
if (!policy.isEnabled()) {
|
||||
throw new NotAllowedException("Validating " + (isLocal ? "local" : "remote") + " 3PID is not allowed");
|
||||
}
|
||||
|
||||
session.validate(token);
|
||||
storage.updateThreePidSession(session.getDao());
|
||||
log.info("Session {} has been validated", session.getId());
|
||||
return session.getNextLink();
|
||||
|
||||
// FIXME definitely doable in a nicer way
|
||||
ValidationResult r = new ValidationResult(session, policy.toRemote());
|
||||
if (!policy.toLocal()) {
|
||||
r.setNextUrl(RemoteIdentityAPIv1.getRequestToken(sid, secret));
|
||||
} else {
|
||||
session.getNextLink().ifPresent(r::setNextUrl);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public ThreePidValidation getValidated(String sid, String secret) {
|
||||
@@ -179,34 +206,74 @@ public class SessionMananger {
|
||||
return new ThreePidValidation(session.getThreePid(), session.getValidationTime());
|
||||
}
|
||||
|
||||
public void bind(String sid, String secret, String mxid) {
|
||||
public void bind(String sid, String secret, String mxidRaw) {
|
||||
_MatrixID mxid = new MatrixID(mxidRaw);
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
log.info("Accepting bind of {} on session {} from server {}", mxid, session.getId(), session.getServer());
|
||||
// TODO perform this if request was proxied
|
||||
|
||||
if (!session.isRemote()) {
|
||||
log.info("Session {} for {}: MXID {} was bound locally", sid, session.getThreePid(), mxid);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Session {} for {}: MXID {} bind is remote", sid, session.getThreePid(), mxid);
|
||||
if (!session.isRemoteValidated()) {
|
||||
log.error("Session {} for {}: Not validated remotely", sid, session.getThreePid());
|
||||
throw new SessionNotValidatedException();
|
||||
}
|
||||
|
||||
log.info("Session {} for {}: Performing remote bind", sid, session.getThreePid());
|
||||
|
||||
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(
|
||||
Arrays.asList(
|
||||
new BasicNameValuePair("sid", session.getRemoteId()),
|
||||
new BasicNameValuePair("client_secret", session.getRemoteSecret()),
|
||||
new BasicNameValuePair("mxid", mxid.getId())
|
||||
), StandardCharsets.UTF_8);
|
||||
HttpPost bindReq = new HttpPost(session.getRemoteServer() + "/_matrix/identity/api/v1/3pid/bind");
|
||||
bindReq.setEntity(entity);
|
||||
|
||||
try (CloseableHttpResponse response = client.execute(bindReq)) {
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status < 200 || status >= 300) {
|
||||
String body = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||
log.error("Session {} for {}: Remote IS {} failed when trying to bind {} for remote session {}\n{}",
|
||||
sid, session.getThreePid(), session.getRemoteServer(), mxid, session.getRemoteId(), body);
|
||||
throw new RemoteIdentityServerException(body);
|
||||
}
|
||||
|
||||
log.error("Session {} for {}: MXID {} was bound remotely", sid, session.getThreePid(), mxid);
|
||||
} catch (IOException e) {
|
||||
log.error("Session {} for {}: I/O Error when trying to bind mxid {}", sid, session.getThreePid(), mxid);
|
||||
throw new RemoteIdentityServerException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void createRemote(String sid, String secret, String token) {
|
||||
public IThreePidSession createRemote(String sid, String secret) {
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
log.info("Creating remote 3PID session for {} with local session [{}] to {}", session.getThreePid(), sid);
|
||||
|
||||
boolean isLocal = isLocal(session.getThreePid());
|
||||
PolicySource policy = cfg.getPolicy().getValidation().forIf(isLocal);
|
||||
if (!policy.isEnabled() || !policy.toRemote()) {
|
||||
throw new NotAllowedException("Validating " + (isLocal ? "local" : "remote") + " 3PID is not allowed");
|
||||
}
|
||||
log.info("Remote 3PID is allowed by policy");
|
||||
|
||||
List<String> servers = mxCfg.getIdentity().getServers(policy.getToRemote().getServer());
|
||||
if (servers.isEmpty()) {
|
||||
throw new InternalServerError();
|
||||
}
|
||||
|
||||
String url = IdentityServerUtils.findIsUrlForDomain(servers.get(0)).orElseThrow(InternalServerError::new);
|
||||
log.info("Will use IS endpoint {}", url);
|
||||
|
||||
String remoteSecret = session.isRemote() ? session.getRemoteSecret() : RandomStringUtils.randomAlphanumeric(16);
|
||||
|
||||
JsonObject body = new JsonObject();
|
||||
body.addProperty("client_secret", RandomStringUtils.randomAlphanumeric(16));
|
||||
body.addProperty("client_secret", remoteSecret);
|
||||
body.addProperty(session.getThreePid().getMedium(), session.getThreePid().getAddress());
|
||||
body.addProperty("send_attempt", 1);
|
||||
body.addProperty("send_attempt", session.increaseAndGetRemoteAttempt());
|
||||
|
||||
log.info("Creating remote 3PID session for {} with local session [{}] to {}", session.getThreePid(), sid);
|
||||
log.info("Requesting remote session with attempt {}", session.getRemoteAttempt());
|
||||
HttpPost tokenReq = RestClientUtils.post(url + "/_matrix/identity/api/v1/validate/" + session.getThreePid().getMedium() + "/requestToken", body);
|
||||
try (CloseableHttpResponse response = client.execute(tokenReq)) {
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
@@ -214,13 +281,66 @@ public class SessionMananger {
|
||||
throw new RemoteIdentityServerException("Remote identity server returned with status " + status);
|
||||
}
|
||||
|
||||
// TODO finish
|
||||
RequestTokenResponse data = new GsonParser().parse(response, RequestTokenResponse.class);
|
||||
log.info("Remote Session ID: {}", data.getSid());
|
||||
|
||||
session.setRemoteData(url, data.getSid(), remoteSecret, 1);
|
||||
storage.updateThreePidSession(session.getDao());
|
||||
log.info("Updated Session {} with remote data", sid);
|
||||
|
||||
return session;
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to create remote session with {} for {}: {}", url, session.getThreePid(), e.getMessage());
|
||||
throw new RemoteIdentityServerException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void validateRemote(String sid, String secret) {
|
||||
ThreePidSession session = getSessionIfValidated(sid, secret);
|
||||
if (!session.isRemote()) {
|
||||
throw new NotAllowedException("Cannot remotely validate a local session");
|
||||
}
|
||||
|
||||
log.info("Session {} for {}: Validating remote 3PID session {} on {}", sid, session.getThreePid(), session.getRemoteId(), session.getRemoteServer());
|
||||
if (session.isRemoteValidated()) {
|
||||
log.info("Session {} for {}: Already remotely validated", sid, session.getThreePid());
|
||||
return;
|
||||
}
|
||||
|
||||
HttpGet validateReq = new HttpGet(session.getRemoteServer() + "/_matrix/identity/api/v1/3pid/getValidated3pid?sid=" + session.getRemoteId() + "&client_secret=" + session.getRemoteSecret());
|
||||
try (CloseableHttpResponse response = client.execute(validateReq)) {
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status < 200 || status >= 300) {
|
||||
throw new RemoteIdentityServerException("Remote identity server returned with status " + status);
|
||||
}
|
||||
|
||||
JsonObject o = new GsonParser().parse(response.getEntity().getContent());
|
||||
if (o.has("errcode")) {
|
||||
String errcode = o.get("errcode").getAsString();
|
||||
if (StringUtils.equals("M_SESSION_NOT_VALIDATED", errcode)) {
|
||||
throw new SessionNotValidatedException();
|
||||
} else if (StringUtils.equals("M_NO_VALID_SESSION", errcode)) {
|
||||
throw new SessionUnknownException();
|
||||
} else {
|
||||
throw new RemoteIdentityServerException("Unknown error while validating Remote 3PID session: " + errcode + " - " + o.get("error").getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
if (o.has("validated_at")) {
|
||||
ThreePid remoteThreePid = new ThreePid(o.get("medium").getAsString(), o.get("address").getAsString());
|
||||
if (session.getThreePid().equals(remoteThreePid)) { // sanity check
|
||||
throw new InternalServerError("Local 3PID " + session.getThreePid() + " and remote 3PID " + remoteThreePid + " do not match for session " + session.getId());
|
||||
}
|
||||
|
||||
log.info("Session {} for {}: Remotely validated successfully", sid, session.getThreePid());
|
||||
session.validateRemote();
|
||||
storage.updateThreePidSession(session.getDao());
|
||||
log.info("Session {} was updated in storage", sid);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("Session {} for {}: Failed to validated remotely on {}: {}", sid, session.getThreePid(), session.getRemoteServer(), e.getMessage());
|
||||
throw new RemoteIdentityServerException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
54
src/main/groovy/io/kamax/mxisd/session/ValidationResult.java
Normal file
54
src/main/groovy/io/kamax/mxisd/session/ValidationResult.java
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ValidationResult {
|
||||
|
||||
private IThreePidSession session;
|
||||
private boolean canRemote;
|
||||
private String nextUrl;
|
||||
|
||||
public ValidationResult(IThreePidSession session, boolean canRemote) {
|
||||
this.session = session;
|
||||
this.canRemote = canRemote;
|
||||
}
|
||||
|
||||
public IThreePidSession getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public boolean isCanRemote() {
|
||||
return canRemote;
|
||||
}
|
||||
|
||||
public Optional<String> getNextUrl() {
|
||||
return Optional.ofNullable(nextUrl);
|
||||
}
|
||||
|
||||
public void setNextUrl(String nextUrl) {
|
||||
this.nextUrl = nextUrl;
|
||||
}
|
||||
|
||||
}
|
@@ -44,4 +44,16 @@ public interface IThreePidSessionDao {
|
||||
|
||||
long getValidationTime();
|
||||
|
||||
boolean isRemote();
|
||||
|
||||
String getRemoteServer();
|
||||
|
||||
String getRemoteId();
|
||||
|
||||
String getRemoteSecret();
|
||||
|
||||
int getRemoteAttempt();
|
||||
|
||||
boolean isRemoteValidated();
|
||||
|
||||
}
|
||||
|
@@ -61,6 +61,24 @@ public class ThreePidSessionDao implements IThreePidSessionDao {
|
||||
@DatabaseField
|
||||
private long validationTime;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private boolean isRemote;
|
||||
|
||||
@DatabaseField
|
||||
private String remoteServer;
|
||||
|
||||
@DatabaseField
|
||||
private String remoteId;
|
||||
|
||||
@DatabaseField
|
||||
private String remoteSecret;
|
||||
|
||||
@DatabaseField
|
||||
private Integer remoteAttempt;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
private boolean isRemoteValidated;
|
||||
|
||||
public ThreePidSessionDao() {
|
||||
// stub for ORMLite
|
||||
}
|
||||
@@ -77,7 +95,12 @@ public class ThreePidSessionDao implements IThreePidSessionDao {
|
||||
setToken(session.getToken());
|
||||
setValidated(session.getValidated());
|
||||
setValidationTime(session.getValidationTime());
|
||||
|
||||
setRemote(session.isRemote());
|
||||
setRemoteServer(session.getRemoteServer());
|
||||
setRemoteId(session.getRemoteId());
|
||||
setRemoteSecret(session.getRemoteSecret());
|
||||
setRemoteAttempt(session.getRemoteAttempt());
|
||||
setRemoteValidated(session.isRemoteValidated());
|
||||
}
|
||||
|
||||
public ThreePidSessionDao(ThreePid tpid, String secret) {
|
||||
@@ -180,6 +203,60 @@ public class ThreePidSessionDao implements IThreePidSessionDao {
|
||||
return validationTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRemote() {
|
||||
return isRemote;
|
||||
}
|
||||
|
||||
public void setRemote(boolean remote) {
|
||||
isRemote = remote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteServer() {
|
||||
return remoteServer;
|
||||
}
|
||||
|
||||
public void setRemoteServer(String remoteServer) {
|
||||
this.remoteServer = remoteServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteId() {
|
||||
return remoteId;
|
||||
}
|
||||
|
||||
public void setRemoteId(String remoteId) {
|
||||
this.remoteId = remoteId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteSecret() {
|
||||
return remoteSecret;
|
||||
}
|
||||
|
||||
public void setRemoteSecret(String remoteSecret) {
|
||||
this.remoteSecret = remoteSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemoteAttempt() {
|
||||
return remoteAttempt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRemoteValidated() {
|
||||
return isRemoteValidated;
|
||||
}
|
||||
|
||||
public void setRemoteValidated(boolean remoteValidated) {
|
||||
isRemoteValidated = remoteValidated;
|
||||
}
|
||||
|
||||
public void setRemoteAttempt(int remoteAttempt) {
|
||||
this.remoteAttempt = remoteAttempt;
|
||||
}
|
||||
|
||||
public void setValidationTime(long validationTime) {
|
||||
this.validationTime = validationTime;
|
||||
}
|
||||
|
@@ -26,7 +26,6 @@ import io.kamax.mxisd.config.ServerConfig;
|
||||
import io.kamax.mxisd.config.threepid.medium.EmailConfig;
|
||||
import io.kamax.mxisd.config.threepid.medium.EmailTemplateConfig;
|
||||
import io.kamax.mxisd.controller.v1.IdentityAPIv1;
|
||||
import io.kamax.mxisd.controller.v1.remote.RemoteIdentityAPIv1;
|
||||
import io.kamax.mxisd.exception.InternalServerError;
|
||||
import io.kamax.mxisd.invitation.IThreePidInviteReply;
|
||||
import io.kamax.mxisd.threepid.session.IThreePidSession;
|
||||
@@ -140,16 +139,14 @@ public class EmailNotificationGenerator implements IEmailNotificationGenerator {
|
||||
log.info("Generating notification content for remote-only 3PID session");
|
||||
String templateBody = getTemplateAndPopulate(templateCfg.getSession().getValidation().getRemote(), session.getThreePid());
|
||||
|
||||
// FIXME should have a global link builder, specific to mxisd
|
||||
String nextStepLink = srvCfg.getPublicUrl() + RemoteIdentityAPIv1.BASE +
|
||||
"/validate/requestToken?sid=" + session.getId() +
|
||||
"&client_secret=" + session.getSecret() +
|
||||
// FIXME should have a global link builder, most likely in the SDK?
|
||||
String validationLink = srvCfg.getPublicUrl() + IdentityAPIv1.BASE +
|
||||
"/validate/" + session.getThreePid().getMedium() +
|
||||
"/submitToken?sid=" + session.getId() + "&client_secret=" + session.getSecret() +
|
||||
"&token=" + session.getToken();
|
||||
|
||||
templateBody = templateBody.replace("%SESSION_ID%", session.getId());
|
||||
templateBody = templateBody.replace("%SESSION_SECRET%", session.getSecret());
|
||||
templateBody = templateBody.replace("%SESSION_TOKEN%", session.getToken());
|
||||
templateBody = templateBody.replace("%NEXT_STEP_LINK%", nextStepLink);
|
||||
templateBody = templateBody.replace("%VALIDATION_LINK%", validationLink);
|
||||
templateBody = templateBody.replace("%VALIDATION_TOKEN%", session.getToken());
|
||||
|
||||
return templateBody;
|
||||
}
|
||||
|
@@ -51,4 +51,16 @@ public interface IThreePidSession {
|
||||
|
||||
Instant getValidationTime();
|
||||
|
||||
boolean isRemote();
|
||||
|
||||
String getRemoteServer();
|
||||
|
||||
String getRemoteId();
|
||||
|
||||
String getRemoteSecret();
|
||||
|
||||
int getRemoteAttempt();
|
||||
|
||||
void setRemoteData(String server, String id, String secret, int attempt);
|
||||
|
||||
}
|
||||
|
@@ -42,6 +42,12 @@ public class ThreePidSession implements IThreePidSession {
|
||||
private int attempt;
|
||||
private Instant validationTimestamp;
|
||||
private boolean isValidated;
|
||||
private boolean isRemote;
|
||||
private String remoteServer;
|
||||
private String remoteId;
|
||||
private String remoteSecret;
|
||||
private int remoteAttempt;
|
||||
private boolean isRemoteValidated;
|
||||
|
||||
public ThreePidSession(IThreePidSessionDao dao) {
|
||||
this(
|
||||
@@ -58,6 +64,13 @@ public class ThreePidSession implements IThreePidSession {
|
||||
if (isValidated) {
|
||||
validationTimestamp = Instant.ofEpochMilli(dao.getValidationTime());
|
||||
}
|
||||
|
||||
isRemote = dao.isRemote();
|
||||
remoteServer = dao.getRemoteServer();
|
||||
remoteId = dao.getRemoteId();
|
||||
remoteSecret = dao.getRemoteSecret();
|
||||
remoteAttempt = dao.getRemoteAttempt();
|
||||
isRemoteValidated = dao.isRemoteValidated();
|
||||
}
|
||||
|
||||
public ThreePidSession(String id, String server, ThreePid tPid, String secret, int attempt, String nextLink, String token) {
|
||||
@@ -129,6 +142,44 @@ public class ThreePidSession implements IThreePidSession {
|
||||
return validationTimestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRemote() {
|
||||
return isRemote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteServer() {
|
||||
return remoteServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteId() {
|
||||
return remoteId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteSecret() {
|
||||
return remoteSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemoteAttempt() {
|
||||
return remoteAttempt;
|
||||
}
|
||||
|
||||
public int increaseAndGetRemoteAttempt() {
|
||||
return ++remoteAttempt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRemoteData(String server, String id, String secret, int attempt) {
|
||||
this.remoteServer = server;
|
||||
this.remoteId = id;
|
||||
this.remoteSecret = secret;
|
||||
this.attempt = attempt;
|
||||
this.isRemote = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidated() {
|
||||
return isValidated;
|
||||
@@ -151,6 +202,14 @@ public class ThreePidSession implements IThreePidSession {
|
||||
isValidated = true;
|
||||
}
|
||||
|
||||
public boolean isRemoteValidated() {
|
||||
return isRemoteValidated;
|
||||
}
|
||||
|
||||
public void validateRemote() {
|
||||
this.isRemoteValidated = true;
|
||||
}
|
||||
|
||||
public IThreePidSessionDao getDao() {
|
||||
return new IThreePidSessionDao() {
|
||||
|
||||
@@ -209,6 +268,36 @@ public class ThreePidSession implements IThreePidSession {
|
||||
return isValidated ? validationTimestamp.toEpochMilli() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRemote() {
|
||||
return isRemote;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteServer() {
|
||||
return remoteServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteId() {
|
||||
return remoteId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRemoteSecret() {
|
||||
return remoteSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemoteAttempt() {
|
||||
return remoteAttempt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRemoteValidated() {
|
||||
return isRemoteValidated;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -26,6 +26,7 @@ import io.kamax.mxisd.exception.JsonMemberNotFoundException;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -53,6 +54,10 @@ public class GsonParser {
|
||||
return el.getAsJsonObject();
|
||||
}
|
||||
|
||||
public <T> T parse(HttpServletRequest req, Class<T> type) throws IOException {
|
||||
return gson.fromJson(parse(req.getInputStream()), type);
|
||||
}
|
||||
|
||||
public <T> T parse(HttpResponse res, Class<T> type) throws IOException {
|
||||
return gson.fromJson(parse(res.getEntity().getContent()), type);
|
||||
}
|
||||
|
@@ -106,6 +106,24 @@ session.policy.validation:
|
||||
enabled: true
|
||||
server: 'root'
|
||||
|
||||
view:
|
||||
session:
|
||||
local:
|
||||
onTokenSubmit:
|
||||
success: 'session/local/tokenSubmitSuccess'
|
||||
failure: 'session/local/tokenSubmitFailure'
|
||||
localRemote:
|
||||
onTokenSubmit:
|
||||
success: 'session/localRemote/tokenSubmitSuccess'
|
||||
failure: 'session/local/tokenSubmitFailure'
|
||||
remote:
|
||||
onRequest:
|
||||
success: 'session/remote/requestSuccess'
|
||||
failure: 'session/remote/requestFailure'
|
||||
onCheck:
|
||||
success: 'session/remote/checkSuccess'
|
||||
failure: 'session/remote/checkFailure'
|
||||
|
||||
storage:
|
||||
backend: 'sqlite'
|
||||
|
||||
|
@@ -17,7 +17,7 @@ If you continue, your e-mail address and Matrix ID association will be made publ
|
||||
If you would still like to continue, you will need to:
|
||||
1. Go to your private Public registration process page:
|
||||
|
||||
%NEXT_STEP_LINK%
|
||||
%VALIDATION_LINK%
|
||||
|
||||
2. Follow the registration process of the central Identity Server, usually another email with similar content
|
||||
3. Once your email address validated with the central Identity Server, click on "Continue" on page of step #1
|
||||
@@ -81,7 +81,7 @@ pre, code {
|
||||
|
||||
<p>If you would still like to continue, you will need to:
|
||||
<ol>
|
||||
<li>Go to your private <a href="%NEXT_STEP_LINK%">Public registration process page</a></li>
|
||||
<li>Go to your private <a href="%VALIDATION_LINK%">Public registration process page</a></li>
|
||||
<li>Follow the registration process of the central Identity Server, usually another email with similar content</li>
|
||||
<li>Once your email address validated with the central Identity Server, click on "Continue" on page of step #1</li>
|
||||
<li>If your public association is found by our Identity server, the next step will be given to you.</li>
|
||||
|
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Matrix Token Verification</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
||||
font-size: 12pt;
|
||||
margin: 1em;
|
||||
}
|
||||
#message {
|
||||
width: 1200px;
|
||||
text-align: left;
|
||||
padding: 1em;
|
||||
margin-bottom: 40px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 50px;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
|
||||
background-color: #f8f8f8;
|
||||
border: 1px #ccc solid;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="message">
|
||||
<p>Verification failed: you may need to request another verification email</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Matrix Token Verification</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
||||
font-size: 12pt;
|
||||
margin: 1em;
|
||||
}
|
||||
#message {
|
||||
width: 1200px;
|
||||
text-align: left;
|
||||
padding: 1em;
|
||||
margin-bottom: 40px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 50px;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
|
||||
background-color: #f8f8f8;
|
||||
border: 1px #ccc solid;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="message">
|
||||
<p>Verification successful: return to your Matrix client to complete the process.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Matrix Token Verification</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
||||
font-size: 12pt;
|
||||
margin: 1em;
|
||||
}
|
||||
#message {
|
||||
width: 1200px;
|
||||
text-align: left;
|
||||
padding: 1em;
|
||||
margin-bottom: 40px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 50px;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
|
||||
background-color: #f8f8f8;
|
||||
border: 1px #ccc solid;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="message">
|
||||
<p>Verification successful!</p>
|
||||
<p>Your email will remain private and you will only be discoverable with it on your own server, or any related
|
||||
servers configured by your system admin.<br/>
|
||||
If you would like to be globally discoverable, start the process <a th:href="${remoteSessionLink}">here</a>.
|
||||
<br/>If you chose to start the global publication process, wait until it is done before returning to your
|
||||
client.</p>
|
||||
<p>If the remote process is finished, or if you do not wish to start it at this time, you can now return to your
|
||||
Matrix client to complete the process.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Matrix global token verification</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
||||
font-size: 12pt;
|
||||
margin: 1em;
|
||||
}
|
||||
#message {
|
||||
width: 1200px;
|
||||
text-align: left;
|
||||
padding: 1em;
|
||||
margin-bottom: 40px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 50px;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
|
||||
background-color: #f8f8f8;
|
||||
border: 1px #ccc solid;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="message">
|
||||
<p>You do not seem to have validated your session with the global server. Please check your messages for one similar
|
||||
to the one you received initially.<br/>
|
||||
Once this is done, <a href="#">click here to continue</a></p>
|
||||
<p>If this problem persists, contact your system administrator with the following info: Reference #ABC</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Matrix global token verification</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
||||
font-size: 12pt;
|
||||
margin: 1em;
|
||||
}
|
||||
#message {
|
||||
width: 1200px;
|
||||
text-align: left;
|
||||
padding: 1em;
|
||||
margin-bottom: 40px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 50px;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
|
||||
background-color: #f8f8f8;
|
||||
border: 1px #ccc solid;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="message">
|
||||
<p>Verification successful!</p>
|
||||
<p>Return to your Matrix client to complete the process and make yourself globally discoverable.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Matrix global token verification</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
||||
font-size: 12pt;
|
||||
margin: 1em;
|
||||
}
|
||||
#message {
|
||||
width: 1200px;
|
||||
text-align: left;
|
||||
padding: 1em;
|
||||
margin-bottom: 40px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 50px;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
|
||||
background-color: #f8f8f8;
|
||||
border: 1px #ccc solid;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="message">
|
||||
<p>The process to be globally discoverable has failed!<br/>You can try to refresh this page in a few seconds or
|
||||
minutes.</p>
|
||||
<p>If this problem persists, contact your system administrator with the following info: Reference #ABC</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Matrix global token verification</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Myriad Pro", "Myriad", Helvetica, Arial, sans-serif;
|
||||
font-size: 12pt;
|
||||
margin: 1em;
|
||||
}
|
||||
#message {
|
||||
width: 1200px;
|
||||
text-align: left;
|
||||
padding: 1em;
|
||||
margin-bottom: 40px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 50px;
|
||||
|
||||
-webkit-border-radius: 10px;
|
||||
-moz-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
|
||||
-webkit-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
-moz-box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
box-shadow: 0px 0px 20px 0px rgba(0,0,0,0.15);
|
||||
|
||||
background-color: #f8f8f8;
|
||||
border: 1px #ccc solid;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="message">
|
||||
<p>The process to be globally discoverable has started. A verification token has been requested on your behalf.</p>
|
||||
<p>You will receive a similar communication as the first verification message.<br/>
|
||||
Follow the instructions and come back to this page once you are told to return to your Matrix client or that the
|
||||
verification was successful.</p>
|
||||
<p>Once the validation was successful with the global server, please follow <a th:href="${checkLink}">this link</a>
|
||||
to validate it with us.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user