From bc8795e9402976040e85847eca83c451eaa63c52 Mon Sep 17 00:00:00 2001 From: Anatoly Sablin Date: Tue, 1 Oct 2019 23:52:01 +0300 Subject: [PATCH] Add authorization handler. --- src/main/java/io/kamax/mxisd/HttpMxisd.java | 34 ++++----- .../io/kamax/mxisd/auth/AccountManager.java | 29 +++++++- .../handler/AuthorizationHandler.java | 69 +++++++++++++++++++ .../undertow/handler/BasicHttpHandler.java | 19 +++++ .../handler/HomeserverProxyHandler.java | 24 ------- .../auth/v2/AccountGetUserInfoHandler.java | 11 ++- .../handler/auth/v2/AccountLogoutHandler.java | 11 ++- .../auth/v2/AccountRegisterHandler.java | 10 ++- .../java/io/kamax/mxisd/storage/IStorage.java | 2 +- .../storage/ormlite/OrmLiteSqlStorage.java | 4 +- 10 files changed, 154 insertions(+), 59 deletions(-) create mode 100644 src/main/java/io/kamax/mxisd/http/undertow/handler/AuthorizationHandler.java diff --git a/src/main/java/io/kamax/mxisd/HttpMxisd.java b/src/main/java/io/kamax/mxisd/HttpMxisd.java index 416bde1..c7aec3f 100644 --- a/src/main/java/io/kamax/mxisd/HttpMxisd.java +++ b/src/main/java/io/kamax/mxisd/HttpMxisd.java @@ -23,6 +23,7 @@ package io.kamax.mxisd; import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.MxisdConfig; import io.kamax.mxisd.http.undertow.handler.ApiHandler; +import io.kamax.mxisd.http.undertow.handler.AuthorizationHandler; import io.kamax.mxisd.http.undertow.handler.InternalInfoHandler; import io.kamax.mxisd.http.undertow.handler.OptionsHandler; import io.kamax.mxisd.http.undertow.handler.SaneHandler; @@ -87,15 +88,10 @@ public class HttpMxisd { public void start() { m.start(); - HttpHandler helloHandler = SaneHandler.around(new HelloHandler()); - HttpHandler asUserHandler = SaneHandler.around(new AsUserHandler(m.getAs())); HttpHandler asTxnHandler = SaneHandler.around(new AsTransactionHandler(m.getAs())); HttpHandler asNotFoundHandler = SaneHandler.around(new AsNotFoundHandler(m.getAs())); - HttpHandler storeInvHandler = SaneHandler - .around(new StoreInviteHandler(m.getConfig().getServer(), m.getInvite(), m.getKeyManager())); - final RoutingHandler handler = Handlers.routing() .add("OPTIONS", "/**", SaneHandler.around(new OptionsHandler())) @@ -110,8 +106,10 @@ public class HttpMxisd { // Account endpoints .post(AccountRegisterHandler.Path, SaneHandler.around(new AccountRegisterHandler(m.getAccMgr()))) - .get(AccountGetUserInfoHandler.Path, SaneHandler.around(new AccountGetUserInfoHandler(m.getAccMgr()))) - .post(AccountLogoutHandler.Path, SaneHandler.around(new AccountLogoutHandler(m.getAccMgr()))) + .get(AccountGetUserInfoHandler.Path, + SaneHandler.around(AuthorizationHandler.around(m.getAccMgr(), new AccountGetUserInfoHandler(m.getAccMgr())))) + .post(AccountLogoutHandler.Path, + SaneHandler.around(AuthorizationHandler.around(m.getAccMgr(), new AccountLogoutHandler(m.getAccMgr())))) // Directory endpoints .post(UserDirectorySearchHandler.Path, SaneHandler.around(new UserDirectorySearchHandler(m.getDirectory()))) @@ -155,7 +153,7 @@ public class HttpMxisd { } private void keyEndpoints(RoutingHandler routingHandler) { - addEndpoints(routingHandler, Methods.GET, + addEndpoints(routingHandler, Methods.GET, false, new KeyGetHandler(m.getKeyManager()), new RegularKeyIsValidHandler(m.getKeyManager()), new EphemeralKeyIsValidHandler(m.getKeyManager()) @@ -163,14 +161,17 @@ public class HttpMxisd { } private void identityEndpoints(RoutingHandler routingHandler) { + // Legacy v1 routingHandler.get(SingleLookupHandler.Path, sane(new SingleLookupHandler(m.getConfig(), m.getIdentity(), m.getSign()))); routingHandler.post(BulkLookupHandler.Path, sane(new BulkLookupHandler(m.getIdentity()))); - addEndpoints(routingHandler, Methods.GET, - new HelloHandler(), + + addEndpoints(routingHandler, Methods.GET, false, new HelloHandler()); + + addEndpoints(routingHandler, Methods.GET, true, new SessionValidationGetHandler(m.getSession(), m.getConfig()), new SessionTpidGetValidatedHandler(m.getSession()) ); - addEndpoints(routingHandler, Methods.POST, + addEndpoints(routingHandler, Methods.POST, true, new StoreInviteHandler(m.getConfig().getServer(), m.getInvite(), m.getKeyManager()), new SessionStartHandler(m.getSession()), new SessionValidationPostHandler(m.getSession()), @@ -180,19 +181,20 @@ public class HttpMxisd { ); } - private void addEndpoints(RoutingHandler routingHandler, HttpString method, ApiHandler... handlers) { + private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) { for (ApiHandler handler : handlers) { - attachHandler(routingHandler, method, handler, sane(handler)); + attachHandler(routingHandler, method, handler, useAuthorization, sane(handler)); } } - private void attachHandler(RoutingHandler routingHandler, HttpString method, ApiHandler apiHandler, HttpHandler httpHandler) { - final MatrixConfig matrixConfig = m.getConfig().getMatrix(); + private void attachHandler(RoutingHandler routingHandler, HttpString method, ApiHandler apiHandler, boolean useAuthorization, HttpHandler httpHandler) { + MatrixConfig matrixConfig = m.getConfig().getMatrix(); if (matrixConfig.isV1()) { routingHandler.add(method, apiHandler.getPath(IdentityServiceAPI.V1), httpHandler); } if (matrixConfig.isV2()) { - routingHandler.add(method, apiHandler.getPath(IdentityServiceAPI.V2), httpHandler); + HttpHandler wrappedHandler = useAuthorization ? AuthorizationHandler.around(m.getAccMgr(), httpHandler) : httpHandler; + routingHandler.add(method, apiHandler.getPath(IdentityServiceAPI.V2), wrappedHandler); } } diff --git a/src/main/java/io/kamax/mxisd/auth/AccountManager.java b/src/main/java/io/kamax/mxisd/auth/AccountManager.java index d077045..0fc44a0 100644 --- a/src/main/java/io/kamax/mxisd/auth/AccountManager.java +++ b/src/main/java/io/kamax/mxisd/auth/AccountManager.java @@ -8,6 +8,7 @@ import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.InvalidCredentialsException; import io.kamax.mxisd.exception.NotFoundException; +import io.kamax.mxisd.http.undertow.handler.auth.v1.LoginGetHandler; import io.kamax.mxisd.matrix.HomeserverFederationResolver; import io.kamax.mxisd.storage.IStorage; import io.kamax.mxisd.storage.ormlite.dao.AccountDao; @@ -48,7 +49,9 @@ public class AccountManager { Objects.requireNonNull(openIdToken.getTokenType(), "Missing required token type"); Objects.requireNonNull(openIdToken.getMatrixServerName(), "Missing required matrix domain"); + LOGGER.info("Registration from the server: {}", openIdToken.getMatrixServerName()); String userId = getUserId(openIdToken); + LOGGER.info("UserId: {}", userId); String token = UUID.randomUUID().toString(); AccountDao account = new AccountDao(openIdToken.getAccessToken(), openIdToken.getTokenType(), @@ -63,13 +66,16 @@ public class AccountManager { private String getUserId(OpenIdToken openIdToken) { String homeserverURL = resolver.resolve(openIdToken.getMatrixServerName()).toString(); + LOGGER.info("Domain resolved: {} => {}", openIdToken.getMatrixServerName(), homeserverURL); HttpGet getUserInfo = new HttpGet( "https://" + homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken()); String userId; try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == HttpStatus.SC_OK) { - JsonObject body = GsonUtil.parseObj(EntityUtils.toString(response.getEntity())); + String content = EntityUtils.toString(response.getEntity()); + LOGGER.trace("Response: {}", content); + JsonObject body = GsonUtil.parseObj(content); userId = GsonUtil.getStringOrThrow(body, "sub"); } else { LOGGER.error("Wrong response status: {}", statusCode); @@ -107,12 +113,29 @@ public class AccountManager { } public String getUserId(String token) { - return storage.findUserId(token).orElseThrow(NotFoundException::new); + return storage.findAccount(token).orElseThrow(NotFoundException::new).getUserId(); + } + + public AccountDao findAccount(String token) { + AccountDao accountDao = storage.findAccount(token).orElse(null); + + if (LOGGER.isInfoEnabled()) { + if (accountDao != null) { + LOGGER.info("Found account for user: {}", accountDao.getUserId()); + } else { + LOGGER.warn("Account not found."); + } + } + return accountDao; } public void logout(String token) { - String userId = storage.findUserId(token).orElseThrow(InvalidCredentialsException::new); + String userId = storage.findAccount(token).orElseThrow(InvalidCredentialsException::new).getUserId(); LOGGER.info("Logout: {}", userId); + deleteAccount(token); + } + + public void deleteAccount(String token) { storage.deleteToken(token); } diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/AuthorizationHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/AuthorizationHandler.java new file mode 100644 index 0000000..fb198ea --- /dev/null +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/AuthorizationHandler.java @@ -0,0 +1,69 @@ +/* + * 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 . + */ + +package io.kamax.mxisd.http.undertow.handler; + +import io.kamax.mxisd.auth.AccountManager; +import io.kamax.mxisd.exception.InvalidCredentialsException; +import io.kamax.mxisd.storage.ormlite.dao.AccountDao; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AuthorizationHandler extends BasicHttpHandler { + + private static final Logger log = LoggerFactory.getLogger(AuthorizationHandler.class); + + private final AccountManager accountManager; + + private final HttpHandler child; + + public static AuthorizationHandler around(AccountManager accountManager, HttpHandler child) { + return new AuthorizationHandler(accountManager, child); + } + + private AuthorizationHandler(AccountManager accountManager, HttpHandler child) { + this.accountManager = accountManager; + this.child = child; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + String token = findAccessToken(exchange).orElse(null); + if (token == null) { + log.error("Unauthorized request from: {}", exchange.getHostAndPort()); + throw new InvalidCredentialsException(); + } + + AccountDao account = accountManager.findAccount(token); + if (account == null) { + log.error("Account not found from request from: {}", exchange.getHostAndPort()); + throw new InvalidCredentialsException(); + } + if (account.getExpiresIn() < System.currentTimeMillis()) { + log.error("Account for '{}' from: {}", account.getUserId(), exchange.getHostAndPort()); + accountManager.deleteAccount(token); + throw new InvalidCredentialsException(); + } + log.trace("Access for '{}' allowed", account.getUserId()); + child.handleRequest(exchange); + } +} diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/BasicHttpHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/BasicHttpHandler.java index 26e1eeb..46e6c31 100644 --- a/src/main/java/io/kamax/mxisd/http/undertow/handler/BasicHttpHandler.java +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/BasicHttpHandler.java @@ -28,6 +28,7 @@ import io.kamax.mxisd.exception.AccessTokenNotFoundException; import io.kamax.mxisd.exception.HttpMatrixException; import io.kamax.mxisd.exception.InternalServerError; import io.kamax.mxisd.proxy.Response; +import io.kamax.mxisd.util.OptionalUtil; import io.kamax.mxisd.util.RestClientUtils; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; @@ -55,6 +56,24 @@ public abstract class BasicHttpHandler implements HttpHandler { private static final Logger log = LoggerFactory.getLogger(BasicHttpHandler.class); + protected final static String headerName = "Authorization"; + protected final static String headerValuePrefix = "Bearer "; + private final static String parameterName = "access_token"; + + Optional findAccessTokenInHeaders(HttpServerExchange exchange) { + return Optional.ofNullable(exchange.getRequestHeaders().getFirst(headerName)) + .filter(header -> StringUtils.startsWith(header, headerValuePrefix)) + .map(header -> header.substring(headerValuePrefix.length())); + } + + Optional findAccessTokenInQuery(HttpServerExchange exchange) { + return Optional.ofNullable(exchange.getQueryParameters().getOrDefault(parameterName, new LinkedList<>()).peekFirst()); + } + + public Optional findAccessToken(HttpServerExchange exchange) { + return OptionalUtil.findFirst(() -> findAccessTokenInHeaders(exchange), () -> findAccessTokenInQuery(exchange)); + } + protected String getAccessToken(HttpServerExchange exchange) { return Optional.ofNullable(exchange.getRequestHeaders().getFirst("Authorization")) .flatMap(v -> { diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/HomeserverProxyHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/HomeserverProxyHandler.java index 1c33495..cfdd732 100644 --- a/src/main/java/io/kamax/mxisd/http/undertow/handler/HomeserverProxyHandler.java +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/HomeserverProxyHandler.java @@ -21,35 +21,11 @@ package io.kamax.mxisd.http.undertow.handler; import io.kamax.mxisd.exception.AccessTokenNotFoundException; -import io.kamax.mxisd.util.OptionalUtil; import io.undertow.server.HttpServerExchange; -import org.apache.commons.lang3.StringUtils; - -import java.util.LinkedList; -import java.util.Optional; public abstract class HomeserverProxyHandler extends BasicHttpHandler { - protected final static String headerName = "Authorization"; - protected final static String headerValuePrefix = "Bearer "; - private final static String parameterName = "access_token"; - - Optional findAccessTokenInHeaders(HttpServerExchange exchange) { - return Optional.ofNullable(exchange.getRequestHeaders().getFirst(headerName)) - .filter(header -> StringUtils.startsWith(header, headerValuePrefix)) - .map(header -> header.substring(headerValuePrefix.length())); - } - - Optional findAccessTokenInQuery(HttpServerExchange exchange) { - return Optional.ofNullable(exchange.getQueryParameters().getOrDefault(parameterName, new LinkedList<>()).peekFirst()); - } - - public Optional findAccessToken(HttpServerExchange exchange) { - return OptionalUtil.findFirst(() -> findAccessTokenInHeaders(exchange), () -> findAccessTokenInQuery(exchange)); - } - public String getAccessToken(HttpServerExchange exchange) { return findAccessToken(exchange).orElseThrow(AccessTokenNotFoundException::new); } - } diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountGetUserInfoHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountGetUserInfoHandler.java index 274dd43..b3c7f49 100644 --- a/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountGetUserInfoHandler.java +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountGetUserInfoHandler.java @@ -22,6 +22,7 @@ package io.kamax.mxisd.http.undertow.handler.auth.v2; import io.kamax.matrix.json.GsonUtil; import io.kamax.mxisd.auth.AccountManager; +import io.kamax.mxisd.exception.InvalidCredentialsException; import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler; import io.undertow.server.HttpServerExchange; import org.slf4j.Logger; @@ -31,7 +32,7 @@ public class AccountGetUserInfoHandler extends BasicHttpHandler { public static final String Path = "/_matrix/identity/v2/account"; - private static final Logger log = LoggerFactory.getLogger(AccountGetUserInfoHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AccountGetUserInfoHandler.class); private final AccountManager accountManager; @@ -41,13 +42,11 @@ public class AccountGetUserInfoHandler extends BasicHttpHandler { @Override public void handleRequest(HttpServerExchange exchange) { - String token = getQueryParameter(exchange, "access_token"); - if (token == null) { - token = getAccessToken(exchange); - } + LOGGER.info("Get User Info."); + String token = findAccessToken(exchange).orElseThrow(InvalidCredentialsException::new); String userId = accountManager.getUserId(token); - + LOGGER.info("Account found: {}", userId); respond(exchange, GsonUtil.makeObj("user_id", userId)); } } diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountLogoutHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountLogoutHandler.java index 825ec30..436c883 100644 --- a/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountLogoutHandler.java +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountLogoutHandler.java @@ -21,6 +21,7 @@ package io.kamax.mxisd.http.undertow.handler.auth.v2; import io.kamax.mxisd.auth.AccountManager; +import io.kamax.mxisd.exception.InvalidCredentialsException; import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler; import io.undertow.server.HttpServerExchange; import org.slf4j.Logger; @@ -28,9 +29,9 @@ import org.slf4j.LoggerFactory; public class AccountLogoutHandler extends BasicHttpHandler { - public static final String Path = "/_matrix/identity/v2/account"; + public static final String Path = "/_matrix/identity/v2/account/logout"; - private static final Logger log = LoggerFactory.getLogger(AccountLogoutHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AccountLogoutHandler.class); private final AccountManager accountManager; @@ -40,10 +41,8 @@ public class AccountLogoutHandler extends BasicHttpHandler { @Override public void handleRequest(HttpServerExchange exchange) { - String token = getQueryParameter(exchange, "access_token"); - if (token == null) { - token = getAccessToken(exchange); - } + LOGGER.info("Logout."); + String token = findAccessToken(exchange).orElseThrow(InvalidCredentialsException::new); accountManager.logout(token); diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountRegisterHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountRegisterHandler.java index e255bd8..25be560 100644 --- a/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountRegisterHandler.java +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/auth/v2/AccountRegisterHandler.java @@ -28,11 +28,13 @@ import io.undertow.server.HttpServerExchange; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Date; + public class AccountRegisterHandler extends BasicHttpHandler { public static final String Path = "/_matrix/identity/v2/account/register"; - private static final Logger log = LoggerFactory.getLogger(AccountRegisterHandler.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AccountRegisterHandler.class); private final AccountManager accountManager; @@ -43,6 +45,12 @@ public class AccountRegisterHandler extends BasicHttpHandler { @Override public void handleRequest(HttpServerExchange exchange) { OpenIdToken openIdToken = parseJsonTo(exchange, OpenIdToken.class); + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Registration from domain: {}, expired at {}", openIdToken.getMatrixServerName(), + new Date(openIdToken.getExpiredIn())); + } + String token = accountManager.register(openIdToken); respond(exchange, GsonUtil.makeObj("token", token)); } diff --git a/src/main/java/io/kamax/mxisd/storage/IStorage.java b/src/main/java/io/kamax/mxisd/storage/IStorage.java index f5ab95a..eebf9f9 100644 --- a/src/main/java/io/kamax/mxisd/storage/IStorage.java +++ b/src/main/java/io/kamax/mxisd/storage/IStorage.java @@ -55,7 +55,7 @@ public interface IStorage { void insertToken(AccountDao accountDao); - Optional findUserId(String accessToken); + Optional findAccount(String token); void deleteToken(String accessToken); } diff --git a/src/main/java/io/kamax/mxisd/storage/ormlite/OrmLiteSqlStorage.java b/src/main/java/io/kamax/mxisd/storage/ormlite/OrmLiteSqlStorage.java index 4792fc1..421ef28 100644 --- a/src/main/java/io/kamax/mxisd/storage/ormlite/OrmLiteSqlStorage.java +++ b/src/main/java/io/kamax/mxisd/storage/ormlite/OrmLiteSqlStorage.java @@ -255,7 +255,7 @@ public class OrmLiteSqlStorage implements IStorage { } @Override - public Optional findUserId(String token) { + public Optional findAccount(String token) { return withCatcher(() -> { List accounts = accountDao.queryForEq("token", token); if (accounts.isEmpty()) { @@ -264,7 +264,7 @@ public class OrmLiteSqlStorage implements IStorage { if (accounts.size() != 1) { throw new RuntimeException("Unexpected rows for access token: " + accounts.size()); } - return Optional.of(accounts.get(0).getUserId()); + return Optional.of(accounts.get(0)); }); }