Support access tokens in headers (Fix #65) (#70)

This commit is contained in:
Max Dor
2018-04-02 17:26:03 +02:00
committed by GitHub
parent 91ccb75fa1
commit 5ef145212a
17 changed files with 150 additions and 32 deletions

View File

@@ -84,8 +84,8 @@ public class DefaultExceptionHandler {
return handleGeneric(request, response, e); return handleGeneric(request, response, e);
} }
@ExceptionHandler(MatrixException.class) @ExceptionHandler(HttpMatrixException.class)
public String handleGeneric(HttpServletRequest request, HttpServletResponse response, MatrixException e) { public String handleGeneric(HttpServletRequest request, HttpServletResponse response, HttpMatrixException e) {
response.setStatus(e.getStatus()); response.setStatus(e.getStatus());
return handle(request, e.getErrorCode(), e.getError()); return handle(request, e.getErrorCode(), e.getError());
} }

View File

@@ -0,0 +1,54 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* https://www.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;
import io.kamax.mxisd.exception.AccessTokenNotFoundException;
import io.kamax.mxisd.util.OptionalUtil;
import org.thymeleaf.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
public class ProxyController {
private final static String headerName = "Authorization";
private final static String headerValuePrefix = "Bearer ";
private final static String parameterName = "access_token";
Optional<String> findAccessTokenInHeaders(HttpServletRequest request) {
return Optional.ofNullable(request.getHeader(headerName))
.filter(header -> StringUtils.startsWith(header, headerValuePrefix))
.map(header -> header.substring(headerValuePrefix.length()));
}
Optional<String> findAccessTokenInQuery(HttpServletRequest request) {
return Optional.ofNullable(request.getParameter(parameterName));
}
public Optional<String> findAccessToken(HttpServletRequest request) {
return OptionalUtil.findFirst(() -> findAccessTokenInHeaders(request), () -> findAccessTokenInQuery(request));
}
public String getAccessToken(HttpServletRequest request) {
return findAccessToken(request).orElseThrow(AccessTokenNotFoundException::new);
}
}

View File

@@ -21,6 +21,7 @@
package io.kamax.mxisd.controller.directory.v1; package io.kamax.mxisd.controller.directory.v1;
import com.google.gson.Gson; import com.google.gson.Gson;
import io.kamax.mxisd.controller.ProxyController;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest; import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult; import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.directory.DirectoryManager; import io.kamax.mxisd.directory.DirectoryManager;
@@ -28,7 +29,10 @@ import io.kamax.mxisd.util.GsonParser;
import io.kamax.mxisd.util.GsonUtil; import io.kamax.mxisd.util.GsonUtil;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
@@ -37,7 +41,7 @@ import java.net.URI;
@RestController @RestController
@CrossOrigin @CrossOrigin
@RequestMapping(path = "/_matrix/client/r0/user_directory", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @RequestMapping(path = "/_matrix/client/r0/user_directory", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class UserDirectoryController { public class UserDirectoryController extends ProxyController {
private Gson gson = GsonUtil.build(); private Gson gson = GsonUtil.build();
private GsonParser parser = new GsonParser(gson); private GsonParser parser = new GsonParser(gson);
@@ -46,7 +50,8 @@ public class UserDirectoryController {
private DirectoryManager mgr; private DirectoryManager mgr;
@RequestMapping(path = "/search", method = RequestMethod.POST) @RequestMapping(path = "/search", method = RequestMethod.POST)
public String search(HttpServletRequest request, @RequestParam("access_token") String accessToken) throws IOException { public String search(HttpServletRequest request) throws IOException {
String accessToken = getAccessToken(request);
UserDirectorySearchRequest searchQuery = parser.parse(request, UserDirectorySearchRequest.class); UserDirectorySearchRequest searchQuery = parser.parse(request, UserDirectorySearchRequest.class);
URI target = URI.create(request.getRequestURL().toString()); URI target = URI.create(request.getRequestURL().toString());
UserDirectorySearchResult result = mgr.search(target, accessToken, searchQuery.getSearchTerm()); UserDirectorySearchResult result = mgr.search(target, accessToken, searchQuery.getSearchTerm());

View File

@@ -26,6 +26,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import io.kamax.matrix.MatrixID; import io.kamax.matrix.MatrixID;
import io.kamax.matrix._ThreePid; import io.kamax.matrix._ThreePid;
import io.kamax.mxisd.controller.ProxyController;
import io.kamax.mxisd.dns.ClientDnsOverwrite; import io.kamax.mxisd.dns.ClientDnsOverwrite;
import io.kamax.mxisd.profile.ProfileManager; import io.kamax.mxisd.profile.ProfileManager;
import io.kamax.mxisd.util.GsonUtil; import io.kamax.mxisd.util.GsonUtil;
@@ -49,11 +50,12 @@ import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
@RestController @RestController
@CrossOrigin @CrossOrigin
@RequestMapping(path = "/_matrix/client/r0/profile", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) @RequestMapping(path = "/_matrix/client/r0/profile", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class ProfileController { public class ProfileController extends ProxyController {
private final Logger log = LoggerFactory.getLogger(ProfileController.class); private final Logger log = LoggerFactory.getLogger(ProfileController.class);
private final ProfileManager mgr; private final ProfileManager mgr;
@@ -82,7 +84,11 @@ public class ProfileController {
@RequestMapping("/{userId:.+}") @RequestMapping("/{userId:.+}")
public String getProfile(HttpServletRequest req, HttpServletResponse res, @PathVariable String userId) { public String getProfile(HttpServletRequest req, HttpServletResponse res, @PathVariable String userId) {
try (CloseableHttpResponse hsResponse = client.execute(new HttpGet(resolveProxyUrl(req)))) { Optional<String> accessTokenOpt = findAccessToken(req);
HttpGet reqOut = new HttpGet(resolveProxyUrl(req));
accessTokenOpt.ifPresent(accessToken -> reqOut.addHeader("Authorization", "Bearer " + accessToken));
try (CloseableHttpResponse hsResponse = client.execute(reqOut)) {
res.setStatus(hsResponse.getStatusLine().getStatusCode()); res.setStatus(hsResponse.getStatusLine().getStatusCode());
JsonElement el = parser.parse(EntityUtils.toString(hsResponse.getEntity())); JsonElement el = parser.parse(EntityUtils.toString(hsResponse.getEntity()));
List<_ThreePid> list = mgr.getThreepids(MatrixID.asAcceptable(userId)); List<_ThreePid> list = mgr.getThreepids(MatrixID.asAcceptable(userId));

View File

@@ -27,8 +27,8 @@ import io.kamax.mxisd.config.DirectoryConfig;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest; import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchRequest;
import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult; import io.kamax.mxisd.controller.directory.v1.io.UserDirectorySearchResult;
import io.kamax.mxisd.dns.ClientDnsOverwrite; import io.kamax.mxisd.dns.ClientDnsOverwrite;
import io.kamax.mxisd.exception.HttpMatrixException;
import io.kamax.mxisd.exception.InternalServerError; import io.kamax.mxisd.exception.InternalServerError;
import io.kamax.mxisd.exception.MatrixException;
import io.kamax.mxisd.util.GsonUtil; import io.kamax.mxisd.util.GsonUtil;
import io.kamax.mxisd.util.RestClientUtils; import io.kamax.mxisd.util.RestClientUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@@ -99,7 +99,7 @@ public class DirectoryManager {
log.warn("Homeserver does not support Directory feature, skipping"); log.warn("Homeserver does not support Directory feature, skipping");
} else { } else {
log.error("Homeserver returned an error while performing directory search"); log.error("Homeserver returned an error while performing directory search");
throw new MatrixException(status, info.getErrcode(), info.getError()); throw new HttpMatrixException(status, info.getErrcode(), info.getError());
} }
} }

View File

@@ -0,0 +1,29 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* https://www.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 AccessTokenNotFoundException extends HttpMatrixException {
public AccessTokenNotFoundException() {
super(401, "M_UNKNOWN_TOKEN", "An access token is required to access this resource");
}
}

View File

@@ -22,7 +22,7 @@ package io.kamax.mxisd.exception;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
public class FeatureNotAvailable extends MatrixException { public class FeatureNotAvailable extends HttpMatrixException {
private String internalReason; private String internalReason;

View File

@@ -20,28 +20,19 @@
package io.kamax.mxisd.exception; package io.kamax.mxisd.exception;
public class MatrixException extends MxisdException { import io.kamax.matrix.MatrixException;
public class HttpMatrixException extends MatrixException {
private int status; private int status;
private String errorCode;
private String error;
public MatrixException(int status, String errorCode, String error) { public HttpMatrixException(int status, String errorCode, String error) {
super(errorCode, error);
this.status = status; this.status = status;
this.errorCode = errorCode;
this.error = error;
} }
public int getStatus() { public int getStatus() {
return status; return status;
} }
public String getErrorCode() {
return errorCode;
}
public String getError() {
return error;
}
} }

View File

@@ -24,7 +24,7 @@ import org.apache.http.HttpStatus;
import java.time.Instant; import java.time.Instant;
public class InternalServerError extends MatrixException { public class InternalServerError extends HttpMatrixException {
private String reference = Long.toString(Instant.now().toEpochMilli()); private String reference = Long.toString(Instant.now().toEpochMilli());
private String internalReason; private String internalReason;

View File

@@ -22,7 +22,7 @@ package io.kamax.mxisd.exception;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
public class MessageForClientException extends MatrixException { public class MessageForClientException extends HttpMatrixException {
public MessageForClientException(String error) { public MessageForClientException(String error) {
super(HttpStatus.SC_OK, "M_MESSAGE_FOR_CLIENT", error); super(HttpStatus.SC_OK, "M_MESSAGE_FOR_CLIENT", error);

View File

@@ -23,7 +23,7 @@ package io.kamax.mxisd.exception;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
public class NotAllowedException extends MatrixException { public class NotAllowedException extends HttpMatrixException {
public NotAllowedException(String s) { public NotAllowedException(String s) {
super(HttpStatus.SC_FORBIDDEN, "M_FORBIDDEN", s); super(HttpStatus.SC_FORBIDDEN, "M_FORBIDDEN", s);

View File

@@ -2,7 +2,7 @@ package io.kamax.mxisd.exception;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
public class RemoteHomeServerException extends MatrixException { public class RemoteHomeServerException extends HttpMatrixException {
public RemoteHomeServerException(String error) { public RemoteHomeServerException(String error) {
super(HttpStatus.SC_SERVICE_UNAVAILABLE, "M_REMOTE_HS_ERROR", "Error from remote server: " + error); super(HttpStatus.SC_SERVICE_UNAVAILABLE, "M_REMOTE_HS_ERROR", "Error from remote server: " + error);

View File

@@ -22,7 +22,7 @@ package io.kamax.mxisd.exception;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
public class RemoteIdentityServerException extends MatrixException { public class RemoteIdentityServerException extends HttpMatrixException {
public RemoteIdentityServerException(String error) { public RemoteIdentityServerException(String error) {
super(HttpStatus.SC_SERVICE_UNAVAILABLE, "M_REMOTE_IS_ERROR", "Error from remote server: " + error); super(HttpStatus.SC_SERVICE_UNAVAILABLE, "M_REMOTE_IS_ERROR", "Error from remote server: " + error);

View File

@@ -23,7 +23,7 @@ package io.kamax.mxisd.exception;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
public class RemoteLoginException extends MatrixException { public class RemoteLoginException extends HttpMatrixException {
private JsonObject errorBodyMsgResp; private JsonObject errorBodyMsgResp;

View File

@@ -22,7 +22,7 @@ package io.kamax.mxisd.exception;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
public class SessionNotValidatedException extends MatrixException { public class SessionNotValidatedException extends HttpMatrixException {
public SessionNotValidatedException() { public SessionNotValidatedException() {
super(HttpStatus.SC_OK, "M_SESSION_NOT_VALIDATED", "This validation session has not yet been completed"); super(HttpStatus.SC_OK, "M_SESSION_NOT_VALIDATED", "This validation session has not yet been completed");

View File

@@ -20,7 +20,7 @@
package io.kamax.mxisd.exception; package io.kamax.mxisd.exception;
public class SessionUnknownException extends MatrixException { public class SessionUnknownException extends HttpMatrixException {
public SessionUnknownException() { public SessionUnknownException() {
this("No valid session was found matching that sid and client secret"); this("No valid session was found matching that sid and client secret");

View File

@@ -0,0 +1,33 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2018 Kamax Sarl
*
* https://www.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.util;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class OptionalUtil {
public static <T> Optional<T> findFirst(Supplier<Optional<T>>... suppliers) {
return Stream.of(suppliers).map(Supplier::get).filter(Optional::isPresent).map(Optional::get).findFirst();
}
}